
MQL5 Trading Tools (Part 8): Enhanced Informational Dashboard with Draggable and Minimizable Features
Introduction
In our previous article (Part 7), we developed an informational dashboard in MetaQuotes Language 5 (MQL5) to monitor multi-symbol positions and account metrics, such as "Balance", "Equity", and "Free Margin", with sortable columns and Comma Separated Values (CSV) export capabilities. In Part 8, we enhance this dashboard by adding draggable and minimizable features, interactive buttons for closing, toggling, and exporting, as well as hover effects for a more dynamic user experience. This enhancement retains real-time position tracking and a header glow effect. We will cover the following topics:
By the end, you’ll have a versatile, user-friendly MQL5 dashboard tailored for efficient trading oversight—let’s get started!
Understanding the Enhanced Dashboard Architecture
We're upgrading the Informational Dashboard from Part 7 by adding draggable and minimizable features, along with interactive buttons and hover effects, to make it more flexible and user-friendly for managing multiple positions. These upgrades are relevant because they will allow the dashboard to be moved anywhere on the chart, minimizing clutter during analysis, while the minimize option will save screen space, and the interactive elements will provide immediate visual feedback, improving the overall trading experience in fast-paced environments.
We will achieve this by incorporating mouse event handling for dragging and button clicks, ensuring the dashboard remains responsive and adaptable without losing its core position tracking capabilities. We will also add an icon in the header for the export feature, so it is easy to find, but still keep the keyboard key feature. We plan to maintain the sortable grid and real-time updates while adding these enhancements, creating a tool that feels intuitive and efficient for daily use. Have a look below at what we aim to achieve, and then we can proceed to the implementation!
Implementation in MQL5
To enhance the program in MQL5, we will need to define the new dashboard components, typically 4 of them.
//--- existing components #define HEADER_PANEL_TEXT "HEADER_PANEL_TEXT"//--- Header title label #define CLOSE_BUTTON "BUTTON_CLOSE" //--- Close button identifier #define EXPORT_BUTTON "BUTTON_EXPORT" //--- Export button identifier #define TOGGLE_BUTTON "BUTTON_TOGGLE" //--- Toggle (minimize/maximize) button //--- the rest of the components
We start by adding new defines to support the enhanced features of the informational dashboard, introducing identifiers for interactive User Interface (UI) elements. We define "HEADER_PANEL_TEXT" as "HEADER_PANEL_TEXT" for the dashboard’s title label, providing a clear visual header. The "CLOSE_BUTTON" is defined as "BUTTON_CLOSE", which creates an identifier for a button to close the dashboard, allowing us to remove it from the chart. The "EXPORT_BUTTON" is defined as "BUTTON_EXPORT" and sets up a button for triggering CSV export, enhancing data accessibility. The "TOGGLE_BUTTON" is defined as "BUTTON_TOGGLE", enabling a button to minimize or maximize the dashboard, improving screen space management.
These definitions ensure organized naming for the new interactive components, supporting the draggable and minimizable upgrades. The next thing we change is the color of the header shades by replacing them with clearly defined MQL5 constants.
// Dashboard settings struct DashboardSettings { //--- Structure for dashboard settings int panel_x; //--- X-coordinate of panel int panel_y; //--- Y-coordinate of panel int row_height; //--- Height of each row int font_size; //--- Font size for labels string font; //--- Font type for labels color bg_color; //--- Background color of main panel color border_color; //--- Border color of panels color header_color; //--- Default color for header text color text_color; //--- Default color for text color section_bg_color; //--- Background color for header/footer panels int zorder_panel; //--- Z-order for main panel int zorder_subpanel; //--- Z-order for sub-panels int zorder_labels; //--- Z-order for labels int label_y_offset; //--- Y-offset for label positioning int label_x_offset; //--- X-offset for label positioning int header_x_distances[9]; //--- X-distances for header labels (9 columns) color header_shades[12]; //--- Array of header color shades for glow effect } settings = { //--- Initialize settings with default values 20, //--- panel_x 20, //--- panel_y 24, //--- row_height 11, //--- font_size "Calibri Bold", //--- font C'240,240,240', //--- bg_color (light gray) clrBlack, //--- border_color C'0,50,70', //--- header_color (dark teal) clrBlack, //--- text_color C'200,220,230', //--- section_bg_color (light blue-gray) 100, //--- zorder_panel 101, //--- zorder_subpanel 102, //--- zorder_labels 3, //--- label_y_offset 25, //--- label_x_offset {10, 120, 170, 220, 280, 330, 400, 470, 530}, //--- header_x_distances {clrBlack, clrRed, clrBlue, clrGreen, clrMagenta, clrDarkOrchid, clrDeepPink, clrSkyBlue, clrDodgerBlue, clrDarkViolet, clrOrange, clrCrimson} //--- header_shades }; //--- the previous one was as below /* //--- {C'0,0,0', C'255,0,0', C'0,255,0', C'0,0,255', C'255,255,0', C'0,255,255', C'255,0,255', C'255,255,255', C'255,0,255', C'0,255,255', C'255,255,0', C'0,0,255'} */
Here, we enhance the "DashboardSettings" structure by updating the "header_shades" array to improve the header glow effect for a more visually appealing experience. Previously, "header_shades" used a mix of basic RGB colors (e.g., pure black, red, green, blue, yellow, cyan, magenta, white) for the glow cycle. We now define "header_shades" with a curated set of 12 colors: "clrBlack", "clrRed", "clrBlue", "clrGreen", "clrMagenta", "clrDarkOrchid", "clrDeepPink", "clrSkyBlue", "clrDodgerBlue", "clrDarkViolet", "clrOrange", and "clrCrimson".
This upgrade will provide a richer, more varied palette that cycles through vibrant and nuanced shades, enhancing the dashboard’s aesthetic while maintaining the glow effect’s functionality for highlighting headers. Finally, we add more global variables to help us have a dynamic dashboard, to take care of the hover and drag states.
//--- added global variables int prev_num_symbols = 0; //--- Previous number of active symbols bool panel_is_visible = true; //--- Flag to control panel visibility bool panel_minimized = false; //--- Flag to control minimized state bool panel_dragging = false; //--- Flag to track if panel is being dragged int panel_drag_x = 0; //--- Mouse x-coordinate when drag starts int panel_drag_y = 0; //--- Mouse y-coordinate when drag starts int panel_start_x = 0; //--- Panel x-coordinate when drag starts int panel_start_y = 0; //--- Panel y-coordinate when drag starts int prev_mouse_state = 0; //--- Previous mouse state bool header_hovered = false; //--- Header hover state bool toggle_hovered = false; //--- Toggle button hover state bool close_hovered = false; //--- Close button hover state bool export_hovered = false; //--- Export button hover state int last_mouse_x = 0; //--- Last mouse x position int last_mouse_y = 0; //--- Last mouse y position bool prev_header_hovered = false; //--- Previous header hover state bool prev_toggle_hovered = false; //--- Previous toggle hover state bool prev_close_hovered = false; //--- Previous close button hover state bool prev_export_hovered = false; //--- Previous export button hover state
Finally, we introduce additional global variables to support the enhanced interactivity and draggable features. We define "prev_num_symbols" as 0 to track the previous number of active symbols for dynamic resizing, "panel_is_visible" as true to control dashboard visibility, and "panel_minimized" as false to manage the minimized state. To enable dragging, we add "panel_dragging" as false to track drag status, "panel_drag_x" and "panel_drag_y" as 0 for mouse coordinates at drag start, and "panel_start_x" and "panel_start_y" as 0 for panel coordinates at drag start.
We include "prev_mouse_state" as 0 to monitor mouse click states, and for hover effects, we define "header_hovered", "toggle_hovered", "close_hovered", and "export_hovered" as false to track hover states for the header and buttons, with "last_mouse_x" and "last_mouse_y" as 0 to store the last mouse position, and "prev_header_hovered", "prev_toggle_hovered", "prev_close_hovered", and "prev_export_hovered" as false to detect hover state changes.
These variables will enable dynamic UI interactions like dragging, minimizing, and hover feedback. Since we now have the updated variables, let us update the functions as well to standardize object creation, since this is taking an advanced way for modularization. Let us start with the function to create a label and add a tooltip feature.
//+------------------------------------------------------------------+ //| Creating label object | //+------------------------------------------------------------------+ bool createLABEL(string objName, string txt, int xD, int yD, color clrTxt, int fontSize, string font, int anchor, string tooltip = "", bool selectable = false) { if(!ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0)) { //--- Creating label object Print(__FUNCTION__, ": Failed to create label '", objName, "'. Error code = ", GetLastError()); //--- Logging creation failure return(false); //--- Returning failure } ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); //--- Setting x-coordinate ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); //--- Setting y-coordinate ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Setting corner alignment ObjectSetString(0, objName, OBJPROP_TEXT, txt); //--- Setting text content ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); //--- Setting font size ObjectSetString(0, objName, OBJPROP_FONT, font); //--- Setting font type ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); //--- Setting text color ObjectSetInteger(0, objName, OBJPROP_BACK, false); //--- Setting to foreground ObjectSetInteger(0, objName, OBJPROP_STATE, selectable); //--- Setting selectable state ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, selectable); //--- Setting selectability ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); //--- Setting not selected ObjectSetInteger(0, objName, OBJPROP_ANCHOR, anchor); //--- Setting anchor point ObjectSetInteger(0, objName, OBJPROP_ZORDER, settings.zorder_labels); //--- Setting z-order //--- added this tooltip feature ObjectSetString(0, objName, OBJPROP_TOOLTIP, tooltip == "" ? (selectable ? "Click to sort" : "Position data") : tooltip); //--- Setting tooltip text //--- //--- the existing was a hardcoded to this // ObjectSetString(0, objName, OBJPROP_TOOLTIP, selectable ? "Click to sort" : "Position data"); //--- Set tooltip //--- ChartRedraw(0); //--- Redrawing chart return(true); //--- Returning success }
For the "createLABEL" function, we improve the tooltip logic to make it more flexible and reusable for various UI elements. Previously, the tooltip was hardcoded with ObjectSetString setting OBJPROP_TOOLTIP to either "Click to sort" for selectable labels or "Position data" for non-selectable ones, limiting customization. We now modify this by adding a "tooltip" parameter with a default empty string, and use a ternary condition in "ObjectSetString" for "OBJPROP_TOOLTIP": if "tooltip" is empty, it defaults to "Click to sort" for selectable labels or "Position data" for others; otherwise, it uses the provided "tooltip" value. This change will allow specific tooltips for elements like buttons (e.g., "Minimize dashboard" or "Close dashboard") while maintaining defaults for headers and data labels, improving user guidance and interaction clarity.
Then, to standardize panel creation, we will replace the inline object creation calls in the OnInit event handler with a function for easier maintenance. Here is the logic we adopt for that.
//+------------------------------------------------------------------+ //| Creating rectangle object | //+------------------------------------------------------------------+ bool createRectangle(string object_name, int x_distance, int y_distance, int x_size, int y_size, color background_color, color border_color = clrBlack) { if(!ObjectCreate(0, object_name, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { //--- Creating rectangle object Print(__FUNCTION__, ": Failed to create Rectangle: '", object_name, "'. Error code = ", GetLastError()); //--- Logging creation failure return(false); //--- Returning failure } ObjectSetInteger(0, object_name, OBJPROP_XDISTANCE, x_distance); //--- Setting x-coordinate ObjectSetInteger(0, object_name, OBJPROP_YDISTANCE, y_distance); //--- Setting y-coordinate ObjectSetInteger(0, object_name, OBJPROP_XSIZE, x_size); //--- Setting width ObjectSetInteger(0, object_name, OBJPROP_YSIZE, y_size); //--- Setting height ObjectSetInteger(0, object_name, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Setting corner alignment ObjectSetInteger(0, object_name, OBJPROP_BGCOLOR, background_color); //--- Setting background color ObjectSetInteger(0, object_name, OBJPROP_BORDER_COLOR, border_color); //--- Setting border color ObjectSetInteger(0, object_name, OBJPROP_BORDER_TYPE, BORDER_FLAT); //--- Setting border type ObjectSetInteger(0, object_name, OBJPROP_BACK, false); //--- Setting to foreground ObjectSetInteger(0, object_name, OBJPROP_ZORDER, settings.zorder_subpanel); //--- Setting z-order return(true); //--- Returning success }
Here, we just create a boolean "createRectangle" function and use a similar structure for definition as we did with the labels. This is not new to you, so we will just save time and move on to the next update, which is a minor fix in the counting functions to change the loop direction.
//+------------------------------------------------------------------+ //| Counting total positions for a symbol | //+------------------------------------------------------------------+ string countPositionsTotal(string symbol) { int totalPositions = 0; //--- Initializing position counter int count_Total_Pos = PositionsTotal(); //--- Getting total positions for(int i = count_Total_Pos - 1; i >= 0; i--) { //--- Iterating through positions ulong ticket = PositionGetTicket(i); //--- Getting position ticket if(ticket > 0 && PositionSelectByTicket(ticket)) { //--- Checking if position selected if(PositionGetString(POSITION_SYMBOL) == symbol && (MagicNumber < 0 || PositionGetInteger(POSITION_MAGIC) == MagicNumber)) totalPositions++; //--- Checking symbol and magic } } return IntegerToString(totalPositions); //--- Returning total as string } //+------------------------------------------------------------------+ //| Counting buy or sell positions for a symbol | //+------------------------------------------------------------------+ string countPositions(string symbol, ENUM_POSITION_TYPE pos_type) { int totalPositions = 0; //--- Initializing position counter int count_Total_Pos = PositionsTotal(); //--- Getting total positions for(int i = count_Total_Pos - 1; i >= 0; i--) { //--- Iterating through positions ulong ticket = PositionGetTicket(i); //--- Getting position ticket if(ticket > 0 && PositionSelectByTicket(ticket)) { //--- Checking if position selected if(PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_TYPE) == pos_type && (MagicNumber < 0 || PositionGetInteger(POSITION_MAGIC) == MagicNumber)) { //--- Checking symbol, type, magic totalPositions++; //--- Incrementing counter } } } return IntegerToString(totalPositions); //--- Returning total as string } //+------------------------------------------------------------------+ //| Counting pending orders for a symbol | //+------------------------------------------------------------------+ string countOrders(string symbol) { int total = 0; //--- Initializing counter int tot = OrdersTotal(); //--- Getting total orders for(int i = tot - 1; i >= 0; i--) { //--- Iterating through orders ulong ticket = OrderGetTicket(i); //--- Getting order ticket if(ticket > 0 && OrderSelect(ticket)) { //--- Checking if order selected if(OrderGetString(ORDER_SYMBOL) == symbol && (MagicNumber < 0 || OrderGetInteger(ORDER_MAGIC) == MagicNumber)) total++; //--- Checking symbol and magic } } return IntegerToString(total); //--- Returning total as string } //+------------------------------------------------------------------+ //| Summing double property for positions of a symbol | //+------------------------------------------------------------------+ string sumPositionDouble(string symbol, ENUM_POSITION_PROPERTY_DOUBLE prop) { double total = 0.0; //--- Initializing total int count_Total_Pos = PositionsTotal(); //--- Getting total positions for(int i = count_Total_Pos - 1; i >= 0; i--) { //--- Iterating through positions ulong ticket = PositionGetTicket(i); //--- Getting position ticket if(ticket > 0 && PositionSelectByTicket(ticket)) { //--- Checking if position selected if(PositionGetString(POSITION_SYMBOL) == symbol && (MagicNumber < 0 || PositionGetInteger(POSITION_MAGIC) == MagicNumber)) { //--- Checking symbol and magic total += PositionGetDouble(prop); //--- Adding property value } } } return DoubleToString(total, 2); //--- Returning total as string } //+------------------------------------------------------------------+ //| Summing commission for positions of a symbol from history | //+------------------------------------------------------------------+ double sumPositionCommission(string symbol) { double total_comm = 0.0; //--- Initializing total commission int pos_total = PositionsTotal(); //--- Getting total positions for(int p = 0; p < pos_total; p++) { //--- Iterating through positions ulong ticket = PositionGetTicket(p); //--- Getting position ticket if(ticket > 0 && PositionSelectByTicket(ticket)) { //--- Checking if selected if(PositionGetString(POSITION_SYMBOL) == symbol && (MagicNumber < 0 || PositionGetInteger(POSITION_MAGIC) == MagicNumber)) { //--- Checking symbol and magic long pos_id = PositionGetInteger(POSITION_IDENTIFIER); //--- Getting position ID if(HistorySelectByPosition(pos_id)) { //--- Selecting history by position int deals_total = HistoryDealsTotal(); //--- Getting total deals for(int d = 0; d < deals_total; d++) { //--- Iterating through deals ulong deal_ticket = HistoryDealGetTicket(d); //--- Getting deal ticket if(deal_ticket > 0) { //--- Checking valid total_comm += HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION); //--- Adding commission } } } } } } return total_comm; //--- Returning total commission } //+------------------------------------------------------------------+ //| Collecting active symbols with positions or orders | //+------------------------------------------------------------------+ void CollectActiveSymbols() { string symbols_temp[]; //--- Temporary array for symbols int added = 0; //--- Counter for added symbols // Collecting from positions int pos_total = PositionsTotal(); //--- Getting total positions for(int i = pos_total - 1; i >= 0; i--) { //--- Iterating through positions ulong ticket = PositionGetTicket(i); //--- Getting position ticket if(ticket > 0 && PositionSelectByTicket(ticket)) { //--- Checking if position selected if(MagicNumber < 0 || PositionGetInteger(POSITION_MAGIC) == MagicNumber) { //--- Checking magic number string sym = PositionGetString(POSITION_SYMBOL); //--- Getting symbol bool found = false; //--- Flag for symbol found for(int k = 0; k < added; k++) { //--- Checking existing symbols if(symbols_temp[k] == sym) { //--- Symbol already added found = true; //--- Setting found flag break; //--- Exiting loop } } if(!found) { //--- If not found ArrayResize(symbols_temp, added + 1); //--- Resizing array symbols_temp[added] = sym; //--- Adding symbol added++; //--- Incrementing counter } } } } // Collecting from orders int ord_total = OrdersTotal(); //--- Getting total orders for(int i = ord_total - 1; i >= 0; i--) { //--- Iterating through orders ulong ticket = OrderGetTicket(i); //--- Getting order ticket if(ticket > 0 && OrderSelect(ticket)) { //--- Checking if order selected if(MagicNumber < 0 || OrderGetInteger(ORDER_MAGIC) == MagicNumber) { //--- Checking magic number string sym = OrderGetString(ORDER_SYMBOL); //--- Getting symbol bool found = false; //--- Flag for symbol found for(int k = 0; k < added; k++) { //--- Checking existing symbols if(symbols_temp[k] == sym) { //--- Symbol already added found = true; //--- Setting found flag break; //--- Exiting loop } } if(!found) { //--- If not found ArrayResize(symbols_temp, added + 1); //--- Resizing array symbols_temp[added] = sym; //--- Adding symbol added++; //--- Incrementing counter } } } } // Setting symbol_data ArrayResize(symbol_data, added); //--- Resizing symbol data array for(int i = 0; i < added; i++) { //--- Iterating through added symbols symbol_data[i].name = symbols_temp[i]; //--- Setting symbol name symbol_data[i].buys = 0; //--- Initializing buys symbol_data[i].sells = 0; //--- Initializing sells symbol_data[i].trades = 0; //--- Initializing trades symbol_data[i].lots = 0.0; //--- Initializing lots symbol_data[i].profit = 0.0; //--- Initializing profit symbol_data[i].pending = 0; //--- Initializing pending symbol_data[i].swaps = 0.0; //--- Initializing swaps symbol_data[i].comm = 0.0; //--- Initializing commission symbol_data[i].buys_str = "0"; //--- Initializing buys string symbol_data[i].sells_str = "0"; //--- Initializing sells string symbol_data[i].trades_str = "0"; //--- Initializing trades string symbol_data[i].lots_str = "0.00"; //--- Initializing lots string symbol_data[i].profit_str = "0.00"; //--- Initializing profit string symbol_data[i].pending_str = "0"; //--- Initializing pending string symbol_data[i].swaps_str = "0.00"; //--- Initializing swaps string symbol_data[i].comm_str = "0.00"; //--- Initializing commission string } }
To strengthen the counting functions' logic, we change the loop direction from an incremental one to a decremental one, which is typically safer in MQL5, but you can choose to keep the original. However, we added ticket checks before selecting orders and positions to prevent potential errors with invalid tickets or list modifications during iterations. Since we want full dynamicity, let us move the initial dashboard creation into a function and subdivide it into two, one for maximized and the other for minimized creation.
//+------------------------------------------------------------------+ //| Creating full dashboard UI | //+------------------------------------------------------------------+ void createFullDashboard() { CollectActiveSymbols(); //--- Collecting active symbols int num_rows = ArraySize(symbol_data); //--- Getting number of rows int num_columns = ArraySize(headers); //--- Getting number of columns int column_width_sum = 0; //--- Initializing column width sum for(int i = 0; i < num_columns; i++) //--- Iterating through columns column_width_sum += column_widths[i]; //--- Adding column width int panel_width = MathMax(settings.header_x_distances[num_columns - 1] + column_widths[num_columns - 1], column_width_sum) + 20 + settings.label_x_offset; //--- Calculating panel width // Creating main panel string panel_name = PREFIX + PANEL; //--- Defining main panel name createRectangle(panel_name, settings.panel_x, settings.panel_y, panel_width, (num_rows + 3) * settings.row_height, settings.bg_color, settings.border_color); //--- Creating main panel // Creating header panel string header_panel = PREFIX + HEADER_PANEL; //--- Defining header panel name createRectangle(header_panel, settings.panel_x, settings.panel_y, panel_width, settings.row_height, settings.section_bg_color, settings.border_color); //--- Creating header panel // Creating header title createLABEL(PREFIX + HEADER_PANEL_TEXT, "Trading Dashboard", settings.panel_x + 10, settings.panel_y + 8 + settings.label_y_offset, clrBlack, 14, settings.font, ANCHOR_LEFT, "Dashboard Title"); //--- Creating header title // Creating export button createLABEL(PREFIX + EXPORT_BUTTON, CharToString(60), settings.panel_x + panel_width - 90, settings.panel_y + 12, clrBlack, 18, "Wingdings", ANCHOR_CENTER, "Click to export or press 'E' key to export", true); //--- Creating export button // Creating toggle button createLABEL(PREFIX + TOGGLE_BUTTON, CharToString('r'), settings.panel_x + panel_width - 60, settings.panel_y + 12, clrBlack, 18, "Wingdings", ANCHOR_CENTER, "Minimize dashboard", true); //--- Creating toggle button // Creating close button createLABEL(PREFIX + CLOSE_BUTTON, CharToString('r'), settings.panel_x + panel_width - 30, settings.panel_y + 12, clrBlack, 18, "Webdings", ANCHOR_CENTER, "Close dashboard", true); //--- Creating close button // Creating headers int header_y = settings.panel_y + settings.row_height + 8 + settings.label_y_offset; //--- Calculating header y-coordinate for(int i = 0; i < num_columns; i++) { //--- Iterating through headers string header_name = PREFIX + HEADER + IntegerToString(i); //--- Defining header label name int header_x = settings.panel_x + settings.header_x_distances[i] + settings.label_x_offset; //--- Calculating header x-coordinate createLABEL(header_name, headers[i], header_x, header_y, settings.header_color, 12, settings.font, ANCHOR_LEFT, "Click to sort", true); //--- Creating header label } // Creating symbol and data labels int first_row_y = header_y + settings.row_height; //--- Calculating first row y-coordinate int symbol_x = settings.panel_x + 10 + settings.label_x_offset; //--- Setting symbol x-coordinate for(int i = 0; i < num_rows; i++) { //--- Iterating through rows string symbol_name = PREFIX + SYMB + IntegerToString(i); //--- Defining symbol label name createLABEL(symbol_name, symbol_data[i].name, symbol_x, first_row_y + i * settings.row_height + settings.label_y_offset, settings.text_color, settings.font_size, settings.font, ANCHOR_LEFT, "Symbol name"); //--- Creating symbol label int x_offset = settings.panel_x + 10 + column_widths[0] + settings.label_x_offset; //--- Setting data x-offset for(int j = 0; j < num_columns - 1; j++) { //--- Iterating through data columns string data_name = PREFIX + DATA + IntegerToString(i) + "_" + IntegerToString(j); //--- Defining data label name color initial_color = data_default_colors[j]; //--- Setting initial color string initial_txt = (j <= 2 || j == 5) ? "0" : "0.00"; //--- Setting initial text createLABEL(data_name, initial_txt, x_offset, first_row_y + i * settings.row_height + settings.label_y_offset, initial_color, settings.font_size, settings.font, ANCHOR_RIGHT, "Data value"); //--- Creating data label x_offset += column_widths[j + 1]; //--- Updating x-offset } } // Creating footer panel int footer_y = settings.panel_y + (num_rows + 3) * settings.row_height - settings.row_height; //--- Calculating footer y-coordinate string footer_panel = PREFIX + FOOTER_PANEL; //--- Defining footer panel name createRectangle(footer_panel, settings.panel_x, footer_y, panel_width, settings.row_height, settings.section_bg_color, settings.border_color); //--- Creating footer panel // Creating footer text and data int footer_text_x = settings.panel_x + 10 + settings.label_x_offset; //--- Setting footer text x-coordinate createLABEL(PREFIX + FOOTER_TEXT, "Total:", footer_text_x, footer_y + 8 + settings.label_y_offset, settings.text_color, settings.font_size, settings.font, ANCHOR_LEFT, "Totals"); //--- Creating footer text label int x_offset = settings.panel_x + 10 + column_widths[0] + settings.label_x_offset; //--- Setting footer data x-offset for(int j = 0; j < num_columns - 1; j++) { //--- Iterating through footer data string footer_data_name = PREFIX + FOOTER_DATA + IntegerToString(j); //--- Defining footer data label name color footer_color = data_default_colors[j]; //--- Setting footer data color string initial_txt = (j <= 2 || j == 5) ? "0" : "0.00"; //--- Setting initial text createLABEL(footer_data_name, initial_txt, x_offset, footer_y + 8 + settings.label_y_offset, footer_color, settings.font_size, settings.font, ANCHOR_RIGHT, "Total value"); //--- Creating footer data label x_offset += column_widths[j + 1]; //--- Updating x-offset } // Creating account panel int account_panel_y = footer_y + settings.row_height + 5; //--- Calculating account panel y-coordinate string account_panel_name = PREFIX + ACCOUNT_PANEL; //--- Defining account panel name createRectangle(account_panel_name, settings.panel_x, account_panel_y, panel_width, settings.row_height, settings.section_bg_color, settings.border_color); //--- Creating account panel // Creating account text and data labels int acc_x = settings.panel_x + 10 + settings.label_x_offset; //--- Setting account label x-coordinate int acc_data_offset = 160; //--- Setting data offset int acc_spacing = (panel_width - 45) / ArraySize(account_items); //--- Calculating spacing for(int k = 0; k < ArraySize(account_items); k++) { //--- Iterating through account items string acc_text_name = PREFIX + ACC_TEXT + IntegerToString(k); //--- Defining account text label name int text_x = acc_x + k * acc_spacing; //--- Calculating text x-coordinate createLABEL(acc_text_name, account_items[k] + ":", text_x, account_panel_y + 8 + settings.label_y_offset, settings.text_color, settings.font_size, settings.font, ANCHOR_LEFT, "Account info"); //--- Creating account text label string acc_data_name = PREFIX + ACC_DATA + IntegerToString(k); //--- Defining account data label name int data_x = text_x + acc_data_offset; //--- Calculating data x-coordinate createLABEL(acc_data_name, "0.00", data_x, account_panel_y + 8 + settings.label_y_offset, settings.text_color, settings.font_size, settings.font, ANCHOR_RIGHT, "Account value"); //--- Creating account data label } ChartRedraw(0); //--- Redrawing chart } //+------------------------------------------------------------------+ //| Creating minimized dashboard UI | //+------------------------------------------------------------------+ void createMinimizedDashboard() { int num_columns = ArraySize(headers); //--- Getting number of columns int column_width_sum = 0; //--- Initializing column width sum for(int i = 0; i < num_columns; i++) //--- Iterating through columns column_width_sum += column_widths[i]; //--- Adding column width int panel_width = MathMax(settings.header_x_distances[num_columns - 1] + column_widths[num_columns - 1], column_width_sum) + 20 + settings.label_x_offset; //--- Calculating panel width // Creating header panel createRectangle(PREFIX + HEADER_PANEL, settings.panel_x, settings.panel_y, panel_width, settings.row_height, settings.section_bg_color, settings.border_color); //--- Creating header panel // Creating header title createLABEL(PREFIX + HEADER_PANEL_TEXT, "Trading Dashboard", settings.panel_x + 10, settings.panel_y + 8 + settings.label_y_offset, clrBlack, 14, settings.font, ANCHOR_LEFT, "Dashboard Title"); //--- Creating header title // Creating export button createLABEL(PREFIX + EXPORT_BUTTON, CharToString(60), settings.panel_x + panel_width - 90, settings.panel_y + 12, clrBlack, 18, "Wingdings", ANCHOR_CENTER, "Click to export or press 'E' key to export", true); //--- Creating export button // Creating toggle button (maximize) createLABEL(PREFIX + TOGGLE_BUTTON, CharToString('o'), settings.panel_x + panel_width - 60, settings.panel_y + 12, clrBlack, 18, "Wingdings", ANCHOR_CENTER, "Maximize dashboard", true); //--- Creating toggle button // Creating close button createLABEL(PREFIX + CLOSE_BUTTON, CharToString('r'), settings.panel_x + panel_width - 30, settings.panel_y + 12, clrBlack, 18, "Webdings", ANCHOR_CENTER, "Close dashboard", true); //--- Creating close button ChartRedraw(0); //--- Redrawing chart } //+------------------------------------------------------------------+ //| Deleting all dashboard objects | //+------------------------------------------------------------------+ void deleteAllObjects() { ObjectsDeleteAll(0, PREFIX, -1, -1); //--- Deleting all objects with prefix }
We implement the "createFullDashboard", "createMinimizedDashboard", and "deleteAllObjects" functions to manage the UI supporting full and minimized views with interactive elements. In "createFullDashboard", we call "CollectActiveSymbols" to populate "symbol_data", calculate "num_rows" and "num_columns" with ArraySize, and compute "panel_width" using "column_widths" and "settings.header_x_distances". We create the main panel with "createRectangle" for "PREFIX + PANEL", header panel with "PREFIX + HEADER_PANEL", and header title with "createLABEL" for "PREFIX + HEADER_PANEL_TEXT" as "Trading Dashboard".
We add buttons with "createLABEL" for "PREFIX + EXPORT_BUTTON" (Wingdings 60), "PREFIX + TOGGLE_BUTTON" (Wingdings 'r' for minimize), and "PREFIX + CLOSE_BUTTON" (Webdings 'r'), all with specific tooltips and selectable true. The choice of the icon styles depends on you. Here is a compact view of what you could use for the fonts. Just use the accurate symbol and character type.
Then, we create header labels for "headers" at calculated positions, symbol labels for "symbol_data[i].name", data labels with initial values, footer panel with "PREFIX + FOOTER_PANEL", footer text "Total:", footer data labels, account panel with "PREFIX + ACCOUNT_PANEL", and account labels for "account_items", all using "createRectangle" and "createLABEL" with appropriate coordinates and colors, followed by the ChartRedraw function.
In "createMinimizedDashboard", we create a compact UI with only the header panel using "createRectangle" for "PREFIX + HEADER_PANEL", header title with "createLABEL" for "PREFIX + HEADER_PANEL_TEXT", and buttons for export (Wingdings 60), toggle (Wingdings 'o' for maximize), and close (Webdings 'r'), ensuring minimal screen usage, and redraw.
The "deleteAllObjects" function removes all dashboard objects with ObjectsDeleteAll using "PREFIX" for all charts and types, ensuring a clean slate for UI updates or closure. These functions will enable a flexible dashboard with full and minimized states, supporting user interactions like dragging and toggling. We will now proceed to update the dashboard function using these dynamic functions.
//+------------------------------------------------------------------+ //| Updating dashboard data and visuals | //+------------------------------------------------------------------+ void UpdateDashboard() { bool needs_redraw = false; //--- Initializing redraw flag CollectActiveSymbols(); //--- Collecting active symbols int current_num = ArraySize(symbol_data); //--- Getting current number of symbols if(current_num != prev_num_symbols) { //--- Checking if symbol count changed deleteAllObjects(); //--- Deleting all objects if(panel_minimized) { //--- Checking if minimized createMinimizedDashboard(); //--- Creating minimized dashboard } else { createFullDashboard(); //--- Creating full dashboard } prev_num_symbols = current_num; //--- Updating previous symbol count needs_redraw = true; //--- Setting redraw flag } if(!panel_is_visible || panel_minimized) return; //--- Exiting if not visible or minimized // Resetting totals totalBuys = 0; //--- Resetting total buys totalSells = 0; //--- Resetting total sells totalTrades = 0; //--- Resetting total trades totalLots = 0.0; //--- Resetting total lots totalProfit = 0.0; //--- Resetting total profit totalPending = 0; //--- Resetting total pending totalSwap = 0.0; //--- Resetting total swap totalComm = 0.0; //--- Resetting total commission // Calculating symbol data and totals for(int i = 0; i < current_num; i++) { //--- Iterating through symbols string symbol = symbol_data[i].name; //--- Getting symbol name for(int j = 0; j < 8; j++) { //--- Iterating through data columns string value = ""; //--- Initializing value color data_color = data_default_colors[j]; //--- Setting default color double dval = 0.0; //--- Initializing double value int ival = 0; //--- Initializing integer value switch(j) { //--- Handling data type case 0: // Buy positions value = countPositions(symbol, POSITION_TYPE_BUY); //--- Getting buy positions ival = (int)StringToInteger(value); //--- Converting to integer if(value != symbol_data[i].buys_str) { //--- Checking if changed symbol_data[i].buys_str = value; //--- Updating buys string symbol_data[i].buys = ival; //--- Updating buys count } totalBuys += ival; //--- Adding to total buys break; case 1: // Sell positions value = countPositions(symbol, POSITION_TYPE_SELL); //--- Getting sell positions ival = (int)StringToInteger(value); //--- Converting to integer if(value != symbol_data[i].sells_str) { //--- Checking if changed symbol_data[i].sells_str = value; //--- Updating sells string symbol_data[i].sells = ival; //--- Updating sells count } totalSells += ival; //--- Adding to total sells break; case 2: // Total trades value = countPositionsTotal(symbol); //--- Getting total trades ival = (int)StringToInteger(value); //--- Converting to integer if(value != symbol_data[i].trades_str) { //--- Checking if changed symbol_data[i].trades_str = value; //--- Updating trades string symbol_data[i].trades = ival; //--- Updating trades count } totalTrades += ival; //--- Adding to total trades break; case 3: // Lots value = sumPositionDouble(symbol, POSITION_VOLUME); //--- Getting total lots dval = StringToDouble(value); //--- Converting to double if(value != symbol_data[i].lots_str) { //--- Checking if changed symbol_data[i].lots_str = value; //--- Updating lots string symbol_data[i].lots = dval; //--- Updating lots value } totalLots += dval; //--- Adding to total lots break; case 4: // Profit value = sumPositionDouble(symbol, POSITION_PROFIT); //--- Getting total profit dval = StringToDouble(value); //--- Converting to double data_color = (dval > 0) ? clrGreen : (dval < 0) ? clrRed : clrGray; //--- Setting color based on value if(value != symbol_data[i].profit_str) { //--- Checking if changed symbol_data[i].profit_str = value; //--- Updating profit string symbol_data[i].profit = dval; //--- Updating profit value } totalProfit += dval; //--- Adding to total profit break; case 5: // Pending value = countOrders(symbol); //--- Getting pending orders ival = (int)StringToInteger(value); //--- Converting to integer if(value != symbol_data[i].pending_str) { //--- Checking if changed symbol_data[i].pending_str = value; //--- Updating pending string symbol_data[i].pending = ival; //--- Updating pending count } totalPending += ival; //--- Adding to total pending break; case 6: // Swap value = sumPositionDouble(symbol, POSITION_SWAP); //--- Getting total swap dval = StringToDouble(value); //--- Converting to double data_color = (dval > 0) ? clrGreen : (dval < 0) ? clrRed : clrPurple; //--- Setting color based on value if(value != symbol_data[i].swaps_str) { //--- Checking if changed symbol_data[i].swaps_str = value; //--- Updating swap string symbol_data[i].swaps = dval; //--- Updating swap value } totalSwap += dval; //--- Adding to total swap break; case 7: // Comm dval = sumPositionCommission(symbol); //--- Getting total commission value = DoubleToString(dval, 2); //--- Formatting commission data_color = (dval > 0) ? clrGreen : (dval < 0) ? clrRed : clrBrown; //--- Setting color based on value if(value != symbol_data[i].comm_str) { //--- Checking if changed symbol_data[i].comm_str = value; //--- Updating commission string symbol_data[i].comm = dval; //--- Updating commission value } totalComm += dval; //--- Adding to total commission break; } } } // Sort after calculating values SortDashboard(); //--- Sorting dashboard data // Update header breathing effect glow_counter += MathMax(UpdateIntervalMs, 10); //--- Incrementing glow counter if(glow_counter >= GLOW_INTERVAL_MS) { //--- Checking if glow interval reached if(glow_direction) { //--- Checking if glowing forward glow_index++; //--- Incrementing glow index if(glow_index >= ArraySize(settings.header_shades) - 1) //--- Checking if at end glow_direction = false; //--- Reversing glow direction } else { //--- Glow backward glow_index--; //--- Decrementing glow index if(glow_index <= 0) //--- Checking if at start glow_direction = true; //--- Reversing glow direction } glow_counter = 0; //--- Resetting glow counter } color header_shade = settings.header_shades[glow_index]; //--- Getting current header shade for(int i = 0; i < ArraySize(headers); i++) { //--- Iterating through headers string header_name = PREFIX + HEADER + IntegerToString(i); //--- Defining header name ObjectSetInteger(0, header_name, OBJPROP_COLOR, header_shade); //--- Updating header color needs_redraw = true; //--- Setting redraw flag } // Update symbol and data labels bool labels_updated = false; //--- Initializing label update flag for(int i = 0; i < current_num; i++) { //--- Iterating through symbols string symbol = symbol_data[i].name; //--- Getting symbol name string symb_name = PREFIX + SYMB + IntegerToString(i); //--- Defining symbol label name string current_symb_txt = ObjectGetString(0, symb_name, OBJPROP_TEXT); //--- Getting current symbol text if(current_symb_txt != symbol) { //--- Checking if symbol changed ObjectSetString(0, symb_name, OBJPROP_TEXT, symbol); //--- Updating symbol text labels_updated = true; //--- Setting label updated flag } for(int j = 0; j < 8; j++) { //--- Iterating through data columns string data_name = PREFIX + DATA + IntegerToString(i) + "_" + IntegerToString(j); //--- Defining data label name string value; //--- Initializing value color data_color = data_default_colors[j]; //--- Setting default color switch(j) { //--- Handling data type case 0: //--- Buy positions value = symbol_data[i].buys_str; //--- Getting buys string data_color = clrRed; //--- Setting color to red break; case 1: //--- Sell positions value = symbol_data[i].sells_str; //--- Getting sells string data_color = clrGreen; //--- Setting color to green break; case 2: // Total trades value = symbol_data[i].trades_str; data_color = clrDarkGray; break; case 3: //--- Lots value = symbol_data[i].lots_str; //--- Getting lots string data_color = clrOrange; //--- Setting color to orange break; case 4: //--- Profit value = symbol_data[i].profit_str; //--- Getting profit string data_color = (symbol_data[i].profit > 0) ? clrGreen : (symbol_data[i].profit < 0) ? clrRed : clrGray; //--- Setting color based on profit break; case 5: //--- Pending value = symbol_data[i].pending_str; //--- Getting pending string data_color = clrBlue; //--- Setting color to blue break; case 6: //--- Swap value = symbol_data[i].swaps_str; //--- Getting swap string data_color = (symbol_data[i].swaps > 0) ? clrGreen : (symbol_data[i].swaps < 0) ? clrRed : clrPurple; //--- Setting color based on swap break; case 7: //--- Comm value = symbol_data[i].comm_str; //--- Getting commission string data_color = (symbol_data[i].comm > 0) ? clrGreen : (symbol_data[i].comm < 0) ? clrRed : clrBrown; //--- Setting color based on commission break; } if(updateLABEL(data_name, value, data_color)) labels_updated = true; //--- Updating label if changed } } if(labels_updated) needs_redraw = true; //--- Setting redraw flag if labels updated // Updating totals string new_total_buys = IntegerToString(totalBuys); //--- Formatting total buys if(new_total_buys != total_buys_str) { //--- Checking if changed total_buys_str = new_total_buys; //--- Updating buys string if(updateLABEL(PREFIX + FOOTER_DATA + "0", new_total_buys, clrRed)) needs_redraw = true; //--- Updating label } string new_total_sells = IntegerToString(totalSells); //--- Formatting total sells if(new_total_sells != total_sells_str) { //--- Checking if changed total_sells_str = new_total_sells; //--- Updating sells string if(updateLABEL(PREFIX + FOOTER_DATA + "1", new_total_sells, clrGreen)) needs_redraw = true; //--- Updating label } string new_total_trades = IntegerToString(totalTrades); //--- Formatting total trades if(new_total_trades != total_trades_str) { //--- Checking if changed total_trades_str = new_total_trades; //--- Updating trades string if(updateLABEL(PREFIX + FOOTER_DATA + "2", new_total_trades, clrDarkGray)) needs_redraw = true; //--- Updating label } string new_total_lots = DoubleToString(totalLots, 2); //--- Formatting total lots if(new_total_lots != total_lots_str) { //--- Checking if changed total_lots_str = new_total_lots; //--- Updating lots string if(updateLABEL(PREFIX + FOOTER_DATA + "3", new_total_lots, clrOrange)) needs_redraw = true; //--- Updating label } string new_total_profit = DoubleToString(totalProfit, 2); //--- Formatting total profit color total_profit_color = (totalProfit > 0) ? clrGreen : (totalProfit < 0) ? clrRed : clrGray; //--- Setting color based on profit if(new_total_profit != total_profit_str) { //--- Checking if changed total_profit_str = new_total_profit; //--- Updating profit string if(updateLABEL(PREFIX + FOOTER_DATA + "4", new_total_profit, total_profit_color)) needs_redraw = true; //--- Updating label } string new_total_pending = IntegerToString(totalPending); //--- Formatting total pending if(new_total_pending != total_pending_str) { //--- Checking if changed total_pending_str = new_total_pending; //--- Updating pending string if(updateLABEL(PREFIX + FOOTER_DATA + "5", new_total_pending, clrBlue)) needs_redraw = true; //--- Updating label } string new_total_swap = DoubleToString(totalSwap, 2); //--- Formatting total swap color total_swap_color = (totalSwap > 0) ? clrGreen : (totalSwap < 0) ? clrRed : clrPurple; //--- Setting color based on swap if(new_total_swap != total_swap_str) { //--- Checking if changed total_swap_str = new_total_swap; //--- Updating swap string if(updateLABEL(PREFIX + FOOTER_DATA + "6", new_total_swap, total_swap_color)) needs_redraw = true; //--- Updating label } string new_total_comm = DoubleToString(totalComm, 2); //--- Formatting total commission color total_comm_color = (totalComm > 0) ? clrGreen : (totalComm < 0) ? clrRed : clrBrown; //--- Setting color based on commission if(new_total_comm != total_comm_str) { //--- Checking if changed total_comm_str = new_total_comm; //--- Updating commission string if(updateLABEL(PREFIX + FOOTER_DATA + "7", new_total_comm, total_comm_color)) needs_redraw = true; //--- Updating label } // Updating account info double balance = AccountInfoDouble(ACCOUNT_BALANCE); //--- Getting account balance double equity = AccountInfoDouble(ACCOUNT_EQUITY); //--- Getting account equity double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); //--- Getting free margin string new_bal = DoubleToString(balance, 2); //--- Formatting balance if(new_bal != acc_bal_str) { //--- Checking if changed acc_bal_str = new_bal; //--- Updating balance string if(updateLABEL(PREFIX + ACC_DATA + "0", new_bal, clrBlack)) needs_redraw = true; //--- Updating label } string new_eq = DoubleToString(equity, 2); //--- Formatting equity color eq_color = (equity > balance) ? clrGreen : (equity < balance) ? clrRed : clrBlack; //--- Setting color based on equity if(new_eq != acc_eq_str) { //--- Checking if changed acc_eq_str = new_eq; //--- Updating equity string if(updateLABEL(PREFIX + ACC_DATA + "1", new_eq, eq_color)) needs_redraw = true; //--- Updating label } string new_free = DoubleToString(free_margin, 2); //--- Formatting free margin if(new_free != acc_free_str) { //--- Checking if changed acc_free_str = new_free; //--- Updating free margin string if(updateLABEL(PREFIX + ACC_DATA + "2", new_free, clrBlack)) needs_redraw = true; //--- Updating label } if(needs_redraw) { //--- Checking if redraw needed ChartRedraw(0); //--- Redrawing chart } }
To the "UpdateDashboard" function, we add calls to the "CollectActiveSymbols" at the start, so we always update the totals and balance fields. Then, when the symbol count changes, we call the "deleteAllObjects" function to destroy the dashboard and recreate it via the "createFullDashboard" or "createMinimizedDashboard" function. In the data calculation loop, we added color logic for profits/swap/commission, which was previously partial. We have highlighted the areas of change for easier identification and clarity. Finally, we can now call our logic on initialization to see the milestone achievement.
//+------------------------------------------------------------------+ //| Initializing expert | //+------------------------------------------------------------------+ int OnInit() { createFullDashboard(); //--- Creating full dashboard prev_num_symbols = ArraySize(symbol_data); //--- Setting initial symbol count ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); //--- Enabling mouse move events EventSetMillisecondTimer(MathMax(UpdateIntervalMs, 10)); //--- Setting timer with minimum 10ms UpdateDashboard(); //--- Updating dashboard return(INIT_SUCCEEDED); //--- Returning success } //+------------------------------------------------------------------+ //| Deinitializing expert | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { deleteAllObjects(); //--- Deleting all objects ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); //--- Disabling mouse move events EventKillTimer(); //--- Stopping timer } //+------------------------------------------------------------------+ //| Handling timer for millisecond-based updates | //+------------------------------------------------------------------+ void OnTimer() { if(panel_is_visible && !panel_minimized) { //--- Checking if visible and not minimized UpdateDashboard(); //--- Updating dashboard } }
Here, we implement the OnInit, "OnDeinit", and "OnTimer" event handlers to manage the lifecycle and updates of the dashboard, enabling its interactive and dynamic functionality. In the "OnInit" function, we call "createFullDashboard" to build the complete UI, set "prev_num_symbols" to the size of "symbol_data" using ArraySize to track initial symbols, enable mouse move events with ChartSetInteger setting CHART_EVENT_MOUSE_MOVE to true for dragging and hover effects, set a timer with EventSetMillisecondTimer using the maximum of "UpdateIntervalMs" and 10ms for periodic updates, and call "UpdateDashboard" to populate initial data, returning "INIT_SUCCEEDED" for successful initialization.
The OnDeinit function cleans up by calling "deleteAllObjects" to remove all dashboard objects with "PREFIX", disabling mouse move events with "ChartSetInteger" setting "CHART_EVENT_MOUSE_MOVE" to false, and stopping the timer with EventKillTimer to free resources.
In the OnTimer function, we check if "panel_is_visible" is true and "panel_minimized" is false, then call "UpdateDashboard" to refresh data only when the dashboard is fully visible, ensuring efficient updates without processing in minimized or hidden states. Upon compilation, we have the following outcome.
From the image, we can see that the new features pop up successfully. We will now move on to creating a function for updating the panel positions when we are dragging to avoid recreating the objects.
//+------------------------------------------------------------------+ //| Updating panel object positions | //+------------------------------------------------------------------+ void updatePanelPositions() { int num_rows = ArraySize(symbol_data); //--- Getting number of rows int num_columns = ArraySize(headers); //--- Getting number of columns int column_width_sum = 0; //--- Initializing column width sum for(int i = 0; i < num_columns; i++) //--- Iterating through columns column_width_sum += column_widths[i]; //--- Adding column width int panel_width = MathMax(settings.header_x_distances[num_columns - 1] + column_widths[num_columns - 1], column_width_sum) + 20 + settings.label_x_offset; //--- Calculating panel width // Updating header panel and buttons ObjectSetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_XDISTANCE, settings.panel_x); //--- Updating header panel x-coordinate ObjectSetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_YDISTANCE, settings.panel_y); //--- Updating header panel y-coordinate ObjectSetInteger(0, PREFIX + HEADER_PANEL_TEXT, OBJPROP_XDISTANCE, settings.panel_x + 10); //--- Updating header text x-coordinate ObjectSetInteger(0, PREFIX + HEADER_PANEL_TEXT, OBJPROP_YDISTANCE, settings.panel_y + 8 + settings.label_y_offset); //--- Updating header text y-coordinate ObjectSetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_XDISTANCE, settings.panel_x + panel_width - 90); //--- Updating export button x-coordinate ObjectSetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_YDISTANCE, settings.panel_y + 12); //--- Updating export button y-coordinate ObjectSetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_XDISTANCE, settings.panel_x + panel_width - 60); //--- Updating toggle button x-coordinate ObjectSetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_YDISTANCE, settings.panel_y + 12); //--- Updating toggle button y-coordinate ObjectSetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_XDISTANCE, settings.panel_x + panel_width - 30); //--- Updating close button x-coordinate ObjectSetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_YDISTANCE, settings.panel_y + 12); //--- Updating close button y-coordinate if(!panel_minimized) { //--- Checking if not minimized // Updating main panel ObjectSetInteger(0, PREFIX + PANEL, OBJPROP_XDISTANCE, settings.panel_x); //--- Updating main panel x-coordinate ObjectSetInteger(0, PREFIX + PANEL, OBJPROP_YDISTANCE, settings.panel_y); //--- Updating main panel y-coordinate // Updating headers int header_y = settings.panel_y + settings.row_height + 8 + settings.label_y_offset; //--- Calculating header y-coordinate for(int i = 0; i < num_columns; i++) { //--- Iterating through headers string header_name = PREFIX + HEADER + IntegerToString(i); //--- Defining header name int header_x = settings.panel_x + settings.header_x_distances[i] + settings.label_x_offset; //--- Calculating header x-coordinate ObjectSetInteger(0, header_name, OBJPROP_XDISTANCE, header_x); //--- Updating header x-coordinate ObjectSetInteger(0, header_name, OBJPROP_YDISTANCE, header_y); //--- Updating header y-coordinate } // Updating symbol and data labels int first_row_y = header_y + settings.row_height; //--- Calculating first row y-coordinate int symbol_x = settings.panel_x + 10 + settings.label_x_offset; //--- Setting symbol x-coordinate for(int i = 0; i < num_rows; i++) { //--- Iterating through rows string symbol_name = PREFIX + SYMB + IntegerToString(i); //--- Defining symbol label name ObjectSetInteger(0, symbol_name, OBJPROP_XDISTANCE, symbol_x); //--- Updating symbol x-coordinate ObjectSetInteger(0, symbol_name, OBJPROP_YDISTANCE, first_row_y + i * settings.row_height + settings.label_y_offset); //--- Updating symbol y-coordinate int x_offset = settings.panel_x + 10 + column_widths[0] + settings.label_x_offset; //--- Setting data x-offset for(int j = 0; j < num_columns - 1; j++) { //--- Iterating through data columns string data_name = PREFIX + DATA + IntegerToString(i) + "_" + IntegerToString(j); //--- Defining data label name ObjectSetInteger(0, data_name, OBJPROP_XDISTANCE, x_offset); //--- Updating data x-coordinate ObjectSetInteger(0, data_name, OBJPROP_YDISTANCE, first_row_y + i * settings.row_height + settings.label_y_offset); //--- Updating data y-coordinate x_offset += column_widths[j + 1]; //--- Updating x-offset } } // Updating footer panel and labels int footer_y = settings.panel_y + (num_rows + 3) * settings.row_height - settings.row_height; //--- Calculating footer y-coordinate ObjectSetInteger(0, PREFIX + FOOTER_PANEL, OBJPROP_XDISTANCE, settings.panel_x); //--- Updating footer panel x-coordinate ObjectSetInteger(0, PREFIX + FOOTER_PANEL, OBJPROP_YDISTANCE, footer_y); //--- Updating footer panel y-coordinate ObjectSetInteger(0, PREFIX + FOOTER_TEXT, OBJPROP_XDISTANCE, settings.panel_x + 10 + settings.label_x_offset); //--- Updating footer text x-coordinate ObjectSetInteger(0, PREFIX + FOOTER_TEXT, OBJPROP_YDISTANCE, footer_y + 8 + settings.label_y_offset); //--- Updating footer text y-coordinate int x_offset = settings.panel_x + 10 + column_widths[0] + settings.label_x_offset; //--- Setting footer data x-offset for(int j = 0; j < num_columns - 1; j++) { //--- Iterating through footer data string footer_data_name = PREFIX + FOOTER_DATA + IntegerToString(j); //--- Defining footer data name ObjectSetInteger(0, footer_data_name, OBJPROP_XDISTANCE, x_offset); //--- Updating footer data x-coordinate ObjectSetInteger(0, footer_data_name, OBJPROP_YDISTANCE, footer_y + 8 + settings.label_y_offset); //--- Updating footer data y-coordinate x_offset += column_widths[j + 1]; //--- Updating x-offset } // Updating account panel and labels int account_panel_y = footer_y + settings.row_height + 5; //--- Calculating account panel y-coordinate ObjectSetInteger(0, PREFIX + ACCOUNT_PANEL, OBJPROP_XDISTANCE, settings.panel_x); //--- Updating account panel x-coordinate ObjectSetInteger(0, PREFIX + ACCOUNT_PANEL, OBJPROP_YDISTANCE, account_panel_y); //--- Updating account panel y-coordinate int acc_x = settings.panel_x + 10 + settings.label_x_offset; //--- Setting account label x-coordinate int acc_data_offset = 160; //--- Setting data offset int acc_spacing = (panel_width - 45) / ArraySize(account_items); //--- Calculating spacing for(int k = 0; k < ArraySize(account_items); k++) { //--- Iterating through account items string acc_text_name = PREFIX + ACC_TEXT + IntegerToString(k); //--- Defining account text name int text_x = acc_x + k * acc_spacing; //--- Calculating text x-coordinate ObjectSetInteger(0, acc_text_name, OBJPROP_XDISTANCE, text_x); //--- Updating account text x-coordinate ObjectSetInteger(0, acc_text_name, OBJPROP_YDISTANCE, account_panel_y + 8 + settings.label_y_offset); //--- Updating account text y-coordinate string acc_data_name = PREFIX + ACC_DATA + IntegerToString(k); //--- Defining account data name int data_x = text_x + acc_data_offset; //--- Calculating data x-coordinate ObjectSetInteger(0, acc_data_name, OBJPROP_XDISTANCE, data_x); //--- Updating account data x-coordinate ObjectSetInteger(0, acc_data_name, OBJPROP_YDISTANCE, account_panel_y + 8 + settings.label_y_offset); //--- Updating account data y-coordinate } } ChartRedraw(0); //--- Redrawing chart }
Here, we implement the "updatePanelPositions" function to enable the draggable feature of the enhanced dashboard, ensuring all UI elements move cohesively when the dashboard is dragged. We calculate "num_rows" and "num_columns" using "ArraySize" on "symbol_data" and "headers", and compute "panel_width" by summing "column_widths" and using MathMax with "settings.header_x_distances" plus padding. We update the header panel and buttons by setting OBJPROP_XDISTANCE and "OBJPROP_YDISTANCE" for "PREFIX + HEADER_PANEL", "PREFIX + HEADER_PANEL_TEXT", "PREFIX + EXPORT_BUTTON", "PREFIX + TOGGLE_BUTTON", and "PREFIX + CLOSE_BUTTON" using ObjectSetInteger with "settings.panel_x" and "settings.panel_y" coordinates.
If "panel_minimized" is false, we update the main panel’s position with "PREFIX + PANEL", headers at "header_y" calculated from "settings.panel_y + settings.row_height + 8 + settings.label_y_offset", symbol and data labels at "first_row_y" with "symbol_x" and "x_offset" adjusted by "column_widths", footer panel and labels at "footer_y" calculated for "num_rows + 3", and account panel and labels at "account_panel_y" with "acc_x" and "acc_spacing" for alignment, all using the ObjectSetInteger function. We call ChartRedraw to refresh the display. This will ensure the entire dashboard moves seamlessly during dragging, maintaining layout integrity. We will need to define a logic to track the position of the cursor over the header or buttons for hover considerations. Here is the logic we used to implement that.
//+------------------------------------------------------------------+ //| Checking if cursor is inside header or buttons | //+------------------------------------------------------------------+ bool isCursorInHeaderOrButtons(int mouse_x, int mouse_y) { int header_x = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_XDISTANCE); //--- Getting header x-coordinate int header_y = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_YDISTANCE); //--- Getting header y-coordinate int header_width = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_XSIZE); //--- Getting header width int header_height = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_YSIZE); //--- Getting header height bool in_header = (mouse_x >= header_x && mouse_x <= header_x + header_width && mouse_y >= header_y && mouse_y <= header_y + header_height); //--- Checking if in header int close_x = (int)ObjectGetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_XDISTANCE); //--- Getting close button x-coordinate int close_y = (int)ObjectGetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_YDISTANCE); //--- Getting close button y-coordinate int close_width = 20; //--- Setting close button width int close_height = 20; //--- Setting close button height bool in_close = (mouse_x >= close_x && mouse_x <= close_x + close_width && mouse_y >= close_y && mouse_y <= close_y + close_height); //--- Checking if in close button int export_x = (int)ObjectGetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_XDISTANCE); //--- Getting export button x-coordinate int export_y = (int)ObjectGetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_YDISTANCE); //--- Getting export button y-coordinate int export_width = 20; //--- Setting export button width int export_height = 20; //--- Setting export button height bool in_export = (mouse_x >= export_x && mouse_x <= export_x + export_width && mouse_y >= export_y && mouse_y <= export_y + export_height); //--- Checking if in export button int toggle_x = (int)ObjectGetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_XDISTANCE); //--- Getting toggle button x-coordinate int toggle_y = (int)ObjectGetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_YDISTANCE); //--- Getting toggle button y-coordinate int toggle_width = 20; //--- Setting toggle button width int toggle_height = 20; //--- Setting toggle button height bool in_toggle = (mouse_x >= toggle_x && mouse_x <= toggle_x + toggle_width && mouse_y >= toggle_y && mouse_y <= toggle_y + toggle_height); //--- Checking if in toggle button return in_header || in_close || in_export || in_toggle; //--- Returning combined check }
We implement the "isCursorInHeaderOrButtons" function to detect mouse cursor presence over interactive elements, enabling dragging and button interactions. We retrieve coordinates and dimensions for the header panel using ObjectGetInteger for "PREFIX + HEADER_PANEL" with "OBJPROP_XDISTANCE", "OBJPROP_YDISTANCE", "OBJPROP_XSIZE", and "OBJPROP_YSIZE", storing them in "header_x", "header_y", "header_width", and "header_height", and check if the cursor ("mouse_x", "mouse_y") is within the header bounds with "in_header".
Similarly, we get coordinates for "PREFIX + CLOSE_BUTTON", "PREFIX + EXPORT_BUTTON", and "PREFIX + TOGGLE_BUTTON" using "OBJPROP_XDISTANCE" and OBJPROP_YDISTANCE, setting "close_width", "close_height", "export_width", "export_height", "toggle_width", and "toggle_height" to 20, and verify if the cursor is within each button’s bounds with "in_close", "in_export", and "in_toggle". We return true if the cursor is in the header or any button, combining conditions with the OR operator. After hover detection, we will need to update the detected header or buttons for visual feedback. Here is the logic we implement to achieve that.
//+------------------------------------------------------------------+ //| Updating button hover states | //+------------------------------------------------------------------+ void updateButtonHoverStates(int mouse_x, int mouse_y) { int close_x = (int)ObjectGetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_XDISTANCE); //--- Getting close button x-coordinate int close_y = (int)ObjectGetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_YDISTANCE); //--- Getting close button y-coordinate int close_width = 20; //--- Setting close button width int close_height = 20; //--- Setting close button height bool is_close_hovered = (mouse_x >= close_x && mouse_x <= close_x + close_width && mouse_y >= close_y && mouse_y <= close_y + close_height); //--- Checking if close button hovered if(is_close_hovered != prev_close_hovered) { //--- Checking hover change ObjectSetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_COLOR, is_close_hovered ? clrRed : clrBlack); //--- Updating close button color ObjectSetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_BGCOLOR, is_close_hovered ? clrDodgerBlue : clrNONE); //--- Updating close button background prev_close_hovered = is_close_hovered; //--- Updating previous hover state ChartRedraw(0); //--- Redrawing chart } int export_x = (int)ObjectGetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_XDISTANCE); //--- Getting export button x-coordinate int export_y = (int)ObjectGetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_YDISTANCE); //--- Getting export button y-coordinate int export_width = 20; //--- Setting export button width int export_height = 20; //--- Setting export button height bool is_export_hovered = (mouse_x >= export_x && mouse_x <= export_x + export_width && mouse_y >= export_y && mouse_y <= export_y + export_height); //--- Checking if export button hovered if(is_export_hovered != prev_export_hovered) { //--- Checking hover change ObjectSetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_COLOR, is_export_hovered ? clrOrange : clrBlack); //--- Updating export button color ObjectSetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_BGCOLOR, is_export_hovered ? clrDodgerBlue : clrNONE); //--- Updating export button background prev_export_hovered = is_export_hovered; //--- Updating previous hover state ChartRedraw(0); //--- Redrawing chart } int toggle_x = (int)ObjectGetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_XDISTANCE); //--- Getting toggle button x-coordinate int toggle_y = (int)ObjectGetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_YDISTANCE); //--- Getting toggle button y-coordinate int toggle_width = 20; //--- Setting toggle button width int toggle_height = 20; //--- Setting toggle button height bool is_toggle_hovered = (mouse_x >= toggle_x && mouse_x <= toggle_x + toggle_width && mouse_y >= toggle_y && mouse_y <= toggle_y + toggle_height); //--- Checking if toggle button hovered if(is_toggle_hovered != prev_toggle_hovered) { //--- Checking hover change ObjectSetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_COLOR, is_toggle_hovered ? clrBlue : clrBlack); //--- Updating toggle button color ObjectSetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_BGCOLOR, is_toggle_hovered ? clrDodgerBlue : clrNONE); //--- Updating toggle button background prev_toggle_hovered = is_toggle_hovered; //--- Updating previous hover state ChartRedraw(0); //--- Redrawing chart } } //+------------------------------------------------------------------+ //| Updating header hover state | //+------------------------------------------------------------------+ void updateHeaderHoverState(int mouse_x, int mouse_y) { int header_x = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_XDISTANCE); //--- Getting header x-coordinate int header_y = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_YDISTANCE); //--- Getting header y-coordinate int header_width = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_XSIZE); //--- Getting header width int header_height = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_YSIZE); //--- Getting header height int close_x = (int)ObjectGetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_XDISTANCE); //--- Getting close button x-coordinate int close_y = (int)ObjectGetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_YDISTANCE); //--- Getting close button y-coordinate int close_width = 20; //--- Setting close button width int close_height = 20; //--- Setting close button height int export_x = (int)ObjectGetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_XDISTANCE); //--- Getting export button x-coordinate int export_y = (int)ObjectGetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_YDISTANCE); //--- Getting export button y-coordinate int export_width = 20; //--- Setting export button width int export_height = 20; //--- Setting export button height int toggle_x = (int)ObjectGetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_XDISTANCE); //--- Getting toggle button x-coordinate int toggle_y = (int)ObjectGetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_YDISTANCE); //--- Getting toggle button y-coordinate int toggle_width = 20; //--- Setting toggle button width int toggle_height = 20; //--- Setting toggle button height bool is_header_hovered = (mouse_x >= header_x && mouse_x <= header_x + header_width && mouse_y >= header_y && mouse_y <= header_y + header_height && !(mouse_x >= close_x && mouse_x <= close_x + close_width && mouse_y >= close_y && mouse_y <= close_y + close_height) && !(mouse_x >= export_x && mouse_x <= export_x + export_width && mouse_y >= export_y && mouse_y <= export_y + export_height) && !(mouse_x >= toggle_x && mouse_x <= toggle_x + toggle_width && mouse_y >= toggle_y && mouse_y <= toggle_y + toggle_height)); //--- Checking if header hovered if(is_header_hovered != prev_header_hovered && !panel_dragging) { //--- Checking hover change ObjectSetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_BGCOLOR, is_header_hovered ? clrRed : settings.section_bg_color); //--- Updating header background prev_header_hovered = is_header_hovered; //--- Updating previous hover state ChartRedraw(0); //--- Redrawing chart } updateButtonHoverStates(mouse_x, mouse_y); //--- Updating button hover states }
Finally, we implement the "updateButtonHoverStates" and "updateHeaderHoverState" functions to add visual feedback for user interactions, enhancing button and header responsiveness. In "updateButtonHoverStates", we check hover states for buttons by retrieving coordinates for "PREFIX + CLOSE_BUTTON", "PREFIX + EXPORT_BUTTON", and "PREFIX + TOGGLE_BUTTON" using ObjectGetInteger with "OBJPROP_XDISTANCE" and "OBJPROP_YDISTANCE", setting "close_width", "close_height", "export_width", "export_height", "toggle_width", and "toggle_height" to 20.
For the close button, we set "is_close_hovered" if "mouse_x" and "mouse_y" are within its bounds, and if different from "prev_close_hovered", update "OBJPROP_COLOR" to "clrRed" or "clrBlack" and "OBJPROP_BGCOLOR" to "clrDodgerBlue" or "clrNONE" with "ObjectSetInteger", update "prev_close_hovered", and call the ChartRedraw function. Similarly, for the export button, we set "is_export_hovered" to "clrOrange" or "clrBlack" and "clrDodgerBlue" or "clrNONE", update "prev_export_hovered", and redraw; for the toggle button, we use "clrBlue" or "clrBlack", update "prev_toggle_hovered", and redraw.
In "updateHeaderHoverState", we get "header_x", "header_y", "header_width", and "header_height" for "PREFIX + HEADER_PANEL", and button coordinates, checking "is_header_hovered" if the cursor is within the header but outside button bounds. If "is_header_hovered" differs from "prev_header_hovered" and "panel_dragging" is false, we update "OBJPROP_BGCOLOR" of "PREFIX + HEADER_PANEL" to "clrRed" or "settings.section_bg_color" with ObjectSetInteger, update "prev_header_hovered", call "ChartRedraw", and invoke "updateButtonHoverStates". These functions will provide dynamic hover effects for intuitive user interaction. To make use of the functions, we will expand the OnChartEvent function to house the visual feedback logic.
//+------------------------------------------------------------------+ //| Handling chart events for sorting, export, and UI interactions | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id == CHARTEVENT_OBJECT_CLICK) { //--- Handling object click if(sparam == PREFIX + CLOSE_BUTTON) { //--- Checking close button click Print("Closing the dashboard"); //--- Logging closing PlaySound("alert.wav"); //--- Playing alert sound panel_is_visible = false; //--- Setting panel invisible deleteAllObjects(); //--- Deleting all objects ChartRedraw(0); //--- Redrawing chart } else if(sparam == PREFIX + EXPORT_BUTTON) { //--- Checking export button click Print("Exporting dashboard to CSV"); //--- Logging exporting ExportToCSV(); //--- Exporting to CSV ChartRedraw(0); //--- Redrawing chart } else if(sparam == PREFIX + TOGGLE_BUTTON) { //--- Checking toggle button click deleteAllObjects(); //--- Deleting all objects panel_minimized = !panel_minimized; //--- Toggling minimized state if(panel_minimized) { //--- Checking if minimized Print("Minimizing the dashboard"); //--- Logging minimizing createMinimizedDashboard(); //--- Creating minimized dashboard } else { Print("Maximizing the dashboard"); //--- Logging maximizing createFullDashboard(); //--- Creating full dashboard // Resetting string variables to force update total_buys_str = ""; //--- Resetting buys string total_sells_str = ""; //--- Resetting sells string total_trades_str = ""; //--- Resetting trades string total_lots_str = ""; //--- Resetting lots string total_profit_str = ""; //--- Resetting profit string total_pending_str = ""; //--- Resetting pending string total_swap_str = ""; //--- Resetting swap string total_comm_str = ""; //--- Resetting commission string acc_bal_str = ""; //--- Resetting balance string acc_eq_str = ""; //--- Resetting equity string acc_free_str = ""; //--- Resetting free margin string UpdateDashboard(); //--- Updating dashboard } prev_header_hovered = false; //--- Resetting header hover prev_close_hovered = false; //--- Resetting close hover prev_export_hovered = false; //--- Resetting export hover prev_toggle_hovered = false; //--- Resetting toggle hover ObjectSetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_BGCOLOR, settings.section_bg_color); //--- Resetting header background ObjectSetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_COLOR, clrBlack); //--- Resetting close button color ObjectSetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_BGCOLOR, clrNONE); //--- Resetting close button background ObjectSetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_COLOR, clrBlack); //--- Resetting export button color ObjectSetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_BGCOLOR, clrNONE); //--- Resetting export button background ObjectSetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_COLOR, clrBlack); //--- Resetting toggle button color ObjectSetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_BGCOLOR, clrNONE); //--- Resetting toggle button background ChartRedraw(0); //--- Redrawing chart } else { for(int i = 0; i < ArraySize(headers); i++) { //--- Iterating through headers if(sparam == PREFIX + HEADER + IntegerToString(i)) { //--- Checking header click if(sort_column == i) //--- Checking if same column sort_ascending = !sort_ascending; //--- Toggling sort direction else { sort_column = i; //--- Setting new sort column sort_ascending = true; //--- Setting to ascending } UpdateDashboard(); //--- Updating dashboard break; //--- Exiting loop } } } } else if(id == CHARTEVENT_KEYDOWN && lparam == 'E') { //--- Handling 'E' key press ExportToCSV(); //--- Exporting to CSV } else if(id == CHARTEVENT_MOUSE_MOVE && panel_is_visible) { //--- Handling mouse move int mouse_x = (int)lparam; //--- Getting mouse x-coordinate int mouse_y = (int)dparam; //--- Getting mouse y-coordinate int mouse_state = (int)sparam; //--- Getting mouse state if(mouse_x == last_mouse_x && mouse_y == last_mouse_y && !panel_dragging) { //--- Checking if mouse moved return; //--- Exiting if no movement } last_mouse_x = mouse_x; //--- Updating last mouse x last_mouse_y = mouse_y; //--- Updating last mouse y updateHeaderHoverState(mouse_x, mouse_y); //--- Updating header hover state int header_x = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_XDISTANCE); //--- Getting header x-coordinate int header_y = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_YDISTANCE); //--- Getting header y-coordinate int header_width = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_XSIZE); //--- Getting header width int header_height = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_YSIZE);//--- Getting header height int close_x = (int)ObjectGetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_XDISTANCE); //--- Getting close button x-coordinate int close_width = 20; //--- Setting close button width int export_x = (int)ObjectGetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_XDISTANCE);//--- Getting export button x-coordinate int export_width = 20; //--- Setting export button width int toggle_x = (int)ObjectGetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_XDISTANCE);//--- Getting toggle button x-coordinate int toggle_width = 20; //--- Setting toggle button width if(prev_mouse_state == 0 && mouse_state == 1) { //--- Checking mouse click start if(mouse_x >= header_x && mouse_x <= header_x + header_width && mouse_y >= header_y && mouse_y <= header_y + header_height && !(mouse_x >= close_x && mouse_x <= close_x + close_width) && !(mouse_x >= export_x && mouse_x <= export_x + export_width) && !(mouse_x >= toggle_x && mouse_x <= toggle_x + toggle_width)) { //--- Checking if in draggable area panel_dragging = true; //--- Starting dragging panel_drag_x = mouse_x; //--- Setting drag start x panel_drag_y = mouse_y; //--- Setting drag start y panel_start_x = header_x; //--- Setting panel start x panel_start_y = header_y; //--- Setting panel start y ObjectSetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_BGCOLOR, clrMediumBlue); //--- Setting dragging color ChartSetInteger(0, CHART_MOUSE_SCROLL, false); //--- Disabling chart scroll } } if(panel_dragging && mouse_state == 1) { //--- Handling dragging int dx = mouse_x - panel_drag_x; //--- Calculating x change int dy = mouse_y - panel_drag_y; //--- Calculating y change settings.panel_x = panel_start_x + dx; //--- Updating panel x settings.panel_y = panel_start_y + dy; //--- Updating panel y int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Getting chart width int chart_height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Getting chart height int num_columns = ArraySize(headers); //--- Getting number of columns int column_width_sum = 0; //--- Initializing column width sum for(int i = 0; i < num_columns; i++) column_width_sum += column_widths[i]; //--- Adding column width int panel_width = MathMax(settings.header_x_distances[num_columns - 1] + column_widths[num_columns - 1], column_width_sum) + 20 + settings.label_x_offset; //--- Calculating panel width int panel_height = panel_minimized ? settings.row_height : (ArraySize(symbol_data) + 3) * settings.row_height; //--- Calculating panel height settings.panel_x = MathMax(0, MathMin(chart_width - panel_width, settings.panel_x)); //--- Constraining x settings.panel_y = MathMax(0, MathMin(chart_height - panel_height, settings.panel_y)); //--- Constraining y updatePanelPositions(); //--- Updating object positions ChartRedraw(0); //--- Redrawing chart } if(mouse_state == 0 && prev_mouse_state == 1) { //--- Handling mouse release if(panel_dragging) { //--- Checking if was dragging panel_dragging = false; //--- Stopping dragging updateHeaderHoverState(mouse_x, mouse_y); //--- Updating hover state ChartSetInteger(0, CHART_MOUSE_SCROLL, true); //--- Enabling chart scroll ChartRedraw(0); //--- Redrawing chart } } prev_mouse_state = mouse_state; //--- Updating previous mouse state } }
Here, we expand the OnChartEvent function to handle interactive events, managing clicks for closing, exporting, toggling, sorting, and mouse movements for dragging. For CHARTEVENT_OBJECT_CLICK, we check if "sparam" is "PREFIX + CLOSE_BUTTON", logging with "Print", playing "alert.wav" with PlaySound, setting "panel_is_visible" to false, calling "deleteAllObjects", and redrawing with "ChartRedraw". If "sparam" is "PREFIX + EXPORT_BUTTON", we log and call "ExportToCSV".
For "PREFIX + TOGGLE_BUTTON", we delete objects, toggle "panel_minimized", log "Minimizing" or "Maximizing" with "Print", call "createMinimizedDashboard" or "createFullDashboard", reset string variables like "total_buys_str" and "acc_bal_str", call "UpdateDashboard", reset hover states ("prev_header_hovered", "prev_close_hovered", etc.), and reset colors for "PREFIX + HEADER_PANEL", "PREFIX + CLOSE_BUTTON", "PREFIX + EXPORT_BUTTON", and "PREFIX + TOGGLE_BUTTON" using the ObjectSetInteger function. That will give you something as follows.
For header clicks, we loop through "headers", toggle "sort_ascending" if "sort_column" matches, or set new "sort_column" and "sort_ascending" to true, then call "UpdateDashboard". For CHARTEVENT_KEYDOWN with 'E', we call "ExportToCSV". For CHARTEVENT_MOUSE_MOVE when "panel_is_visible", we get "mouse_x", "mouse_y", and "mouse_state", exit if unchanged and not dragging, update "last_mouse_x" and "last_mouse_y", and call "updateHeaderHoverState".
If "prev_mouse_state" is 0 and "mouse_state" is 1, we check for draggable area clicks (excluding buttons), set "panel_dragging" to true, store coordinates, set header color to "clrMediumBlue", and disable scroll with the ChartSetInteger function. If dragging and "mouse_state" is 1, we calculate "dx" and "dy", update "settings.panel_x" and "settings.panel_y" within chart bounds, call "updatePanelPositions", and redraw. On mouse release, we stop dragging, update hover, re-enable scroll, and redraw. This enables dynamic UI interactions for a user-friendly dashboard. Upon compilation, we get the following button hover state outcome.
The outcome for a maximized and drag state is as follows.
From the image, we can see that we have added the dashboard components for the hover, drag, and minimization logic, hence achieving our objectives. What now remains is testing the workability of the project, and that is handled in the preceding section.
Backtesting
We did the testing, and below is the compiled visualization in a single Graphics Interchange Format (GIF) bitmap image format.
Conclusion
In conclusion, we’ve enhanced the Informational Dashboard in MQL5 for Part 8, adding draggable and minimizable features, interactive buttons like "CLOSE_BUTTON" and "TOGGLE_BUTTON", and hover effects to improve user experience while maintaining robust multi-symbol position and account monitoring. We’ve detailed the architecture and implementation, using functions like "createFullDashboard", "updatePanelPositions", and OnChartEvent to deliver a flexible, visually responsive tool with real-time updates and Excel CSV export. You can customize this dashboard to optimize your trading workflow, making position analysis more intuitive and efficient.
Warning: All rights to these materials are reserved by MetaQuotes Ltd. Copying or reprinting of these materials in whole or in part is prohibited.
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use