Торговые инструменты на языке MQL5 (Часть 8): Улучшенная информационная панель с возможностью перетаскивания и сворачивания
Введение
В своей предыдущей статье (Часть 7) мы разработали информационную панель на MetaQuotes Language 5 (MQL5) для мониторинга позиций на нескольких символах и показателей счета, таких как "Balance", "Equity" и "Free Margin", с возможностью сортировки столбцов и экспорта данных в формате CSV (Comma Separated Values). В Части 8 мы улучшаем эту панель, добавив функции перетаскивания и сворачивания, интерактивные кнопки для закрытия, переключения и экспорта, а также эффекты наведения курсора для более динамичного взаимодействия с пользователем. Это усовершенствование сохраняет отслеживание позиций в режиме реального времени и эффект свечения заголовка. В статье рассмотрим следующие темы:
- Понимание архитектуры улучшенной панели
- Реализация средствами MQL5
- Тестирование на истории
- Заключение
В итоге у вас будет универсальная, удобная в использовании панель в MQL5, предназначенная для эффективного контроля за торговлей. Давайте начнем!
Понимание архитектуры улучшенной панели
Мы усовершенствуем информационную панель из Части 7, добавляя функции перетаскивания и сворачивания, а также интерактивные кнопки и эффекты наведения курсора, чтобы сделать ее более гибкой и удобной для управления несколькими позициями. Эти обновления важны, поскольку позволят перемещать панель в любое место на графике, сводя к минимуму помехи во время анализа, в то время как опция "Свернуть" сэкономит место на экране, а интерактивные элементы обеспечат мгновенную визуальную обратную связь, улучшая общий опыт торговли в условиях быстрого рынка.
Мы реализуем это, включив обработку событий мыши для перетаскивания и нажатия кнопок, гарантируя, что панель остается отзывчивой и адаптируемой без потери своих основных возможностей отслеживания позиций. Также добавим значок в заголовок для функции экспорта, чтобы ее было легко найти, но при этом сохраним ключевую функцию клавиатуры. Мы сохраняем сортируемую таблицу и обновления в реальном времени. Эти улучшения делают инструмент более интуитивным и удобным для ежедневной работы. Взгляните ниже на то, к чему мы стремимся, а затем мы сможем приступить к реализации!

Реализация средствами MQL5
Чтобы усовершенствовать программу в MQL5, нам придется определить новые компоненты панели, обычно их четыре.
//--- 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
Начинаем с добавления новых директив defines для поддержки расширенных функций информационной панели, вводя идентификаторы для интерактивных элементов пользовательского интерфейса (UI). Определяем "HEADER_PANEL_TEXT" как "HEADER_PANEL_TEXT" для метки заголовка панели, обеспечивая понятный визуальный заголовок. "CLOSE_BUTTON" определяется как "BUTTON_CLOSE", что создает идентификатор для кнопки закрытия панели, позволяя нам удалить ее с графика. "EXPORT_BUTTON" определяется как "BUTTON_EXPORT" и настраивает кнопку для запуска экспорта в формате CSV, повышая доступность данных. "TOGGLE_BUTTON" определяется как "BUTTON_TOGGLE", позволяя кнопке сворачивать или разворачивать панель, улучшая управление пространством экрана.
Эти определения обеспечивают упорядоченное присвоение имен новым интерактивным компонентам, поддерживая обновления, касающиеся возможности перетаскивания и сворачивания. Следующее, что мы изменим, - это цвет оттенков заголовка, заменив их четко определенными константами MQL5.
// 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'} */
Здесь мы улучшаем структуру "DashboardSettings", обновляя массив "header_shades", чтобы улучшить эффект свечения заголовка и сделать его более визуально привлекательным. Ранее "header_shades" использовал сочетание основных цветов RGB (например, чисто черный, красный, зеленый, синий, желтый, голубой, пурпурный, белый) для цикла свечения. Теперь мы определяем "header_shades" с помощью тщательно подобранного набора из 12 цветов: "clrBlack", "clrRed", "clrBlue", "clrGreen", "clrMagenta", "clrDarkOrchid", "clrDeepPink", "clrSkyBlue", "clrDodgerBlue", "clrDarkViolet", "clrOrange" и "clrCrimson".
Это обновление позволит получить более богатую и разнообразную палитру с чередованием ярких и тонких оттенков, улучшая эстетику панели и сохраняя функциональность эффекта свечения для подсветки заголовков. Наконец, мы добавили больше глобальных переменных, которые помогут нам создать динамическую панель, отвечающую за состояния наведения курсора мыши и перетаскивания.
//--- 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
Наконец, мы вводим дополнительные глобальные переменные для поддержки расширенной интерактивности и функций перетаскивания. Определяем "prev_num_symbols" как 0, чтобы отслеживать предыдущее количество активных символов для динамического изменения размера, "panel_is_visible" как true для управления видимостью панели, а "panel_minimized" как false для управления свернутым состоянием. Чтобы включить перетаскивание, устанавливаем значение параметра "panel_dragging" равным false для отслеживания состояния перетаскивания, значения параметров "panel_drag_x" и "panel_drag_y" равными 0 для координат мыши в начале перетаскивания, а также значения параметров "panel_start_x" и "panel_start_y" равными 0 для координат панели в начале перетаскивания.
Указываем "prev_mouse_state" как 0 для отслеживания состояний щелчка мыши, а для эффектов при наведении курсора мыши определяем "header_hovered", "toggle_hovered", "close_hovered" и "export_hovered" как false для отслеживания состояний при наведении курсора мыши на заголовок и кнопки, а "last_mouse_x" и "last_mouse_y" как 0 для сохранения последнего положения мыши, а "prev_header_hovered", "prev_toggle_hovered", "prev_close_hovered" и "prev_export_hovered" как значение false для обнаружения изменений состояния при наведении курсора.
Эти переменные позволят осуществлять динамические взаимодействия с пользовательским интерфейсом, такие как перетаскивание, сворачивание и обратная связь при наведении курсора мыши. Поскольку теперь у нас есть обновленные переменные, давайте обновим и функции, чтобы стандартизировать создание объектов, так как это передовой подход к модульной организации. Начнём с функции создания метки и добавления функции всплывающей подсказки.
//+------------------------------------------------------------------+ //| 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 }
Для функции "createLABEL" мы улучшили логику всплывающих подсказок, чтобы она была более гибкой и пригодной для повторного использования в различных элементах пользовательского интерфейса. Ранее подсказка была жестко запрограммирована с помощью ObjectSetString, устанавливающего OBJPROP_TOOLTIP, либо на "Нажмите для сортировки" для выбираемых меток (labels), либо на "Данные о позиции" для невыбираемых меток, что ограничивало настройку. Теперь мы это изменяем, добавляя параметр "tooltip" с пустой строкой по умолчанию и используем троичное условие в "ObjectSetString" для "OBJPROP_TOOLTIP": если "tooltip" пуст, то по умолчанию используется "Нажмите для сортировки" для выбираемых меток или "Данные о позиции" для других; в противном случае используется предоставленное значение "tooltip". Это изменение позволит использовать специальные подсказки для таких элементов, как кнопки (например, "Свернуть панель" или "Закрыть панель"), сохраняя при этом значения по умолчанию для заголовков и меток данных, улучшая руководство пользователя и упрощая взаимодействие.
Затем, чтобы стандартизировать создание панели, заменим вызовы встроенного создания объектов в обработчике OnInit функцией, упрощающей обслуживание. Для этого мы применили следующую логику.
//+------------------------------------------------------------------+ //| 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 }
Здесь мы просто создаем логическую функцию "createRectangle" и используем аналогичную структуру для определения, как мы делали с метками. Это для вас не новость, поэтому мы просто сэкономим время и перейдем к следующему обновлению, которое представляет собой незначительное исправление функций подсчета для изменения направления цикла.
//+------------------------------------------------------------------+ //| 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 } }
Чтобы усилить логику функций подсчета, мы изменили направление цикла с инкрементального на декрементальное, что обычно безопаснее в MQL5, но вы можете сохранить исходное. Однако мы добавили проверку тикетов перед выбором ордеров и позиций, чтобы предотвратить возможные ошибки с недействительными ордерами или внесением изменений в списки во время итераций. Поскольку нам нужна полная динамичность, давайте перенесем первоначальное создание панели в функцию и разделим ее на две части: одну для создания развернутого, а другую для создания свернутого состояния.
//+------------------------------------------------------------------+ //| 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 }
Реализуем функции "createFullDashboard", "createMinimizedDashboard" и "deleteAllObjects" для управления пользовательским интерфейсом, поддерживающим развернутый и свернутый виды с интерактивными элементами. В "createFullDashboard" вызываем "CollectActiveSymbols" для заполнения "symbol_data", рассчитываем "num_rows" и "num_columns" с помощью ArraySize и вычисляем "panel_width", используя "column_widths" и "settings.header_x_distances". Создаем основную панель с помощью "createRectangle" для "PREFIX + PANEL", панель заголовка с помощью "PREFIX + HEADER_PANEL" и название заголовка с помощью "createLABEL" для "PREFIX + HEADER_PANEL_TEXT" как "Торговая панель".
Добавляем кнопки с помощью "createLABEL" для "PREFIX + EXPORT_BUTTON" (Wingdings 60), "PREFIX + TOGGLE_BUTTON" (Wingdings 'r' для сворачивания) и "PREFIX + CLOSE_BUTTON" (Webdings 'r'), все с конкретными всплывающими подсказками и возможностью выбора значения true. Выбор стилей иконок зависит от вас. Вот краткое описание того, что вы могли бы использовать для шрифтов. Просто используйте точный символ и тип знаков.

Затем создаем метки заголовков для "headers" в вычисляемых позициях, метки символов для "symbol_data[i].name", метки данных с начальными значениями, нижнюю панель с помощью "PREFIX + FOOTER_PANEL", текст нижней панели "Total:", метки данных нижней панели, панель информации о счёте с помощью "PREFIX + ACCOUNT_PANEL" и метки счетов для "account_items", все с использованием "createRectangle" и "createLABEL" с соответствующими координатами и цветами, за которыми следует функция ChartRedraw.
В "createMinimizedDashboard" создаем компактный пользовательский интерфейс, содержащий только панель заголовка, используя "createRectangle" для "PREFIX + HEADER_PANEL", название заголовка с помощью "createLABEL" для "PREFIX + HEADER_PANEL_TEXT" и кнопки для экспорта (Wingdings 60), переключения (Wingdings 'o' для разворачивания) и закрытия (Webdings 'r'), обеспечивающий минимальное использование экрана, и перерисовываем.
Функция "deleteAllObjects" удаляет все объекты панели с помощью ObjectsDeleteAll, используя "PREFIX" для всех графиков и типов, обеспечивая чистый лист для обновлений пользовательского интерфейса или закрытия. Эти функции позволят создать гибкую панель с развернутым и свернутым состояниями, поддерживающую такие взаимодействия с пользователем, как перетаскивание и переключение. Теперь перейдем к обновлению функции панели, используя эти динамические функции.
//+------------------------------------------------------------------+ //| 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 } }
К функции "UpdateDashboard" добавляем вызовы "CollectActiveSymbols" в начале, поэтому мы всегда обновляем поля итоговых значений и баланса. Затем, когда количество символов изменяется, вызываем функцию "deleteAllObjects", чтобы уничтожить панель и воссоздать ее с помощью функции "createFullDashboard" или "createMinimizedDashboard". В цикле расчета данных мы добавили цветовую логику для прибыли/свопа/комиссии, которая ранее была частичной. Мы выделили области изменений для облегчения идентификации и внесения ясности. Наконец, теперь мы можем вызвать нашу логику при инициализации, чтобы увидеть достижение важного этапа.
//+------------------------------------------------------------------+ //| 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 } }
Здесь мы реализуем обработчики событий OnInit, "OnDeinit" и "OnTimer" для управления жизненным циклом и обновлениями панели, обеспечивая ее интерактивную и динамическую функциональность. В функции "OnInit" вызываем "createFullDashboard", чтобы создать полный пользовательский интерфейс, устанавливаем для "prev_num_symbols" размер "symbol_data", используя ArraySize для отслеживания начальных символов, включаем события перемещения мыши с помощью ChartSetInteger, устанавливая для CHART_EVENT_MOUSE_MOVE значение true для эффектов перетаскивания и наведения курсора мыши, устанавливаем таймер с помощью EventSetMillisecondTimer, используя максимальное значение из "UpdateIntervalMs" и 10 мс для периодических обновлений и вызываем "UpdateDashboard" для заполнения исходных данных, возвращая "INIT_SUCCEEDED" для успешной инициализации.
Функция OnDeinit выполняет очистку посредством вызова "deleteAllObjects", чтобы удалить все объекты панели с "PREFIX", отключая события перемещения мыши с помощью "ChartSetInteger", устанавливая для "CHART_EVENT_MOUSE_MOVE" значение false и останавливая таймер с помощью EventKillTimer для освобождения ресурсов.
В функции OnTimer проверяем, имеет ли "panel_is_visible" значение true, а "panel_minimized" значение false, затем вызываем "UpdateDashboard" для обновления данных только тогда, когда панель полностью видна, обеспечивая эффективное обновление без обработки в свернутом или скрытом состояниях. После компиляции получаем следующий результат.

На изображении видно, что новые функции успешно реализованы. Теперь перейдем к созданию функции для обновления положения панели при перетаскивании, чтобы избежать повторного создания объектов.
//+------------------------------------------------------------------+ //| 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 }
Здесь мы реализуем функцию "updatePanelPositions", чтобы включить функцию перетаскивания улучшенной панели, гарантирующую согласованное перемещение всех элементов пользовательского интерфейса при перетаскивании панели. Рассчитываем "num_rows" и "num_columns", используя "ArraySize" для "symbol_data" и "headers", и вычисляем "panel_width", суммируя "column_widths" и используя MathMax с "settings.header_x_distances" плюс заполнение. Обновляем панель заголовка и кнопки, установив OBJPROP_XDISTANCE и "OBJPROP_YDISTANCE" для "PREFIX + HEADER_PANEL", "PREFIX + HEADER_PANEL_TEXT", "PREFIX + EXPORT_BUTTON", "PREFIX + TOGGLE_BUTTON" и "PREFIX + CLOSE_BUTTON", используя ObjectSetInteger с координатами "settings.panel_x" и "settings.panel_y".
Если значение параметра "panel_minimized" равно false, обновляем положение основной панели с помощью параметра "PREFIX + PANEL", заголовки по координате "header_y", рассчитанные на основе "settings.panel_y + settings.row_height + 8 + settings.label_y_offset", метки символов и данных по координате "first_row_y" с параметрами "symbol_x" и "x_offset", скорректированными на "column_widths", нижнюю панель и метки по координате "footer_y", рассчитанные на основе "num_rows + 3", а панель счёта и метки по координате "account_panel_y" с параметрами "acc_x" и "acc_spacing" для выравнивания, используя функцию ObjectSetInteger. Для обновления изображения вызываем ChartRedraw. Это обеспечит плавное перемещение всей панели при перетаскивании, сохраняя целостность структуры. Нам нужно будет определить логику для отслеживания положения курсора над заголовком или кнопками при наведении курсора. Для реализации этого мы использовали следующую логику.
//+------------------------------------------------------------------+ //| 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 }
Реализуем функцию "isCursorInHeaderOrButtons" для определения присутствия курсора мыши над интерактивными элементами, позволяя перетаскивать их и нажимать кнопки. Извлекаем координаты и размеры для панели заголовка, используя ObjectGetInteger для "PREFIX + HEADER_PANEL" с "OBJPROP_XDISTANCE", "OBJPROP_YDISTANCE", "OBJPROP_XSIZE" и "OBJPROP_YSIZE", сохраняя их в "header_x", "header_y", "header_width" и "header_height", и проверяем, находится ли курсор ("mouse_x", "mouse_y") в пределах заголовка с "in_header".
Аналогично, получаем координаты для "PREFIX + CLOSE_BUTTON", "PREFIX + EXPORT_BUTTON" и "PREFIX + TOGGLE_BUTTON", используя "OBJPROP_XDISTANCE" и OBJPROP_YDISTANCE, установив для "close_width", "close_height", "export_width", "export_height", "toggle_width" и "toggle_height" значение 20, и проверяем, находится ли курсор в пределах каждой кнопки с помощью "in_close", "in_export" и "in_toggle". Возвращаем значение true, если курсор находится в заголовке или на любой кнопке, комбинируя условия с оператором OR. После обнаружения наведения курсора потребуется обновить обнаруженный заголовок или кнопки для визуальной обратной связи. Вот логика, которую мы реализуем для достижения этой цели.
//+------------------------------------------------------------------+ //| 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 }
Наконец, реализуем функции "updateButtonHoverStates" и "updateHeaderHoverState", чтобы добавить визуальную обратную связь для взаимодействия с пользователем, повышая отзывчивость кнопок и заголовка. В методе "updateButtonHoverStates" проверяем состояния наведения курсора на кнопки, получая координаты для "PREFIX + CLOSE_BUTTON", "PREFIX + EXPORT_BUTTON" и "PREFIX + TOGGLE_BUTTON" с помощью ObjectGetInteger с параметрами "OBJPROP_XDISTANCE" и "OBJPROP_YDISTANCE", устанавливая значения "close_width", "close_height", "export_width", "export_height", "toggle_width" и "toggle_height" равными 20.
Для кнопки закрытия устанавливаем значение "is_close_hovered", если значения "mouse_x" и "mouse_y" находятся в пределах её границ, а если они отличаются от "prev_close_hovered", обновляем "OBJPROP_COLOR" на "clrRed" или "clrBlack" и "OBJPROP_BGCOLOR" на "clrDodgerBlue" или "clrNONE" с помощью "ObjectSetInteger", обновляем "prev_close_hovered" и вызываем функцию ChartRedraw. Аналогично, для кнопки экспорта устанавливаем значение параметра "is_export_hovered" равным "clrOrange" или "clrBlack" и "clrDodgerBlue" или "clrNONE", обновляем "prev_export_hovered" и перерисовываем; для кнопки переключения используем значение параметра "clrBlue" или "clrBlack", обновляем значение "prev_toggle_hovered" и перерисовываем.
В "updateHeaderHoverState" получаем значения "header_x", "header_y", "header_width" и "header_height" для "PREFIX + HEADER_PANEL", а также координаты кнопки, проверяя "is_header_hovered", находится ли курсор внутри заголовка, но за пределами кнопки. Если значение "is_header_hovered" отличается от "prev_header_hovered", а "panel_dragging" равно false, обновляем "OBJPROP_BGCOLOR" объекта "PREFIX + HEADER_PANEL" до значения "clrRed" или "settings.section_bg_color" с помощью ObjectSetInteger, обновляем "prev_header_hovered", вызываем "ChartRedraw" и вызываем "updateButtonHoverStates". Эти функции обеспечивают динамические эффекты при наведении курсора мыши для интуитивно понятного взаимодействия с пользователем. Чтобы использовать эти функции, расширим функцию OnChartEvent, включив в нее логику визуальной обратной связи.
//+------------------------------------------------------------------+ //| 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 } }
Здесь мы расширяем функцию OnChartEvent для обработки интерактивных событий, управляя щелчками на закрытие, экспорт, переключение, сортировку и перемещениями мыши для перетаскивания. Для CHARTEVENT_OBJECT_CLICK проверяем, является ли "sparam" значением "PREFIX + CLOSE_BUTTON", выводим сообщение в лог с помощью функции "Print", воспроизводим "alert.wav" с помощью функции PlaySound, устанавливаем "panel_is_visible" в значение false, вызываем "deleteAllObjects" и перерисовываем график с помощью "ChartRedraw". Если "sparam" равно "PREFIX + EXPORT_BUTTON", регистрируем это в логе и вызываем функцию "ExportToCSV".
Для "PREFIX + TOGGLE_BUTTON" удаляем объекты, переключаем "panel_minimized", выводим в лог "Minimizing" или "Maximizing" с помощью функции "Print", вызываем "createMinimizedDashboard" или "createFullDashboard", сбрасываем строковые переменные, такие как "total_buys_str" и "acc_bal_str", вызываем "UpdateDashboard", сбрасываем состояния при наведении курсора ("prev_header_hovered", "prev_close_hovered" и т. д.), а также сбрасываем цвета для "PREFIX + HEADER_PANEL", "PREFIX + CLOSE_BUTTON", "PREFIX + EXPORT_BUTTON" и "PREFIX + TOGGLE_BUTTON" с помощью функции ObjectSetInteger. В результате вы получите примерно следующий результат.

При кликах по заголовкам мы перебираем в цикле "headers", переключаем значение "sort_ascending", если совпадает с "sort_column", либо устанавливаем новые значения "sort_column" и "sort_ascending" в true, а затем вызываем функцию "UpdateDashboard". Для CHARTEVENT_KEYDOWN с 'E', вызываем "ExportToCSV". Для CHARTEVENT_MOUSE_MOVE, когда "panel_is_visible", получаем значения "mouse_x", "mouse_y" и "mouse_state", выходим, если значение не изменилось и перетаскивание не происходит, обновляем "last_mouse_x" и "last_mouse_y" и вызываем "updateHeaderHoverState".
Если "prev_mouse_state" равно 0, а "mouse_state" равно 1, проверяем наличие щелчков по перетаскиваемой области (исключая кнопки), устанавливаем "panel_dragging" в значение true, сохраняем координаты, устанавливаем цвет заголовка на "clrMediumBlue" и отключаем прокрутку с помощью функции ChartSetInteger. Если выполняется перетаскивание и значение параметра "mouse_state" равно 1, рассчитываем значения "dx" и "dy", обновляем значения параметров "settings.panel_x" и "settings.panel_y" в пределах границ графика, вызываем метод "updatePanelPositions" и выполняем перерисовку. Отпустив мышь, прекращаем перетаскивание, обновляем состояние при наведении курсора, снова включаем прокрутку и перерисовываем. Это обеспечивает динамическое взаимодействие с пользовательским интерфейсом для создания удобной панели. После компиляции получаем следующий результат состояния при наведении курсора на кнопку.

Результат для развернутого состояния и состояния перетаскивания выглядит следующим образом.

На изображении видно, что мы добавили компоненты панели для управления логикой наведения курсора мыши, перетаскивания и сворачивания, что позволило достичь наших целей. Теперь осталось протестировать работоспособность проекта, и это рассматривается в предыдущем разделе.
Тестирование на истории
Мы провели тестирование, а ниже представлена скомпилированная визуализация в едином формате растрового изображения Graphics Interchange Format (GIF).

Заключение
В заключение, мы усовершенствовали информационную панель в MQL5 для части 8, добавив функции перетаскивания и сворачивания, интерактивные кнопки, такие как "CLOSE_BUTTON" и "TOGGLE_BUTTON", а также эффекты при наведении курсора для улучшения взаимодействия с пользователем, поддерживая надежное положение нескольких символов и мониторинг счёта. Мы подробно описали архитектуру и реализацию, используя такие функции, как "createFullDashboard", "updatePanelPositions" и OnChartEvent, чтобы создать гибкий, визуально адаптивный инструмент с обновлениями в реальном времени и экспортом в Excel в формате CSV. Вы можете настроить эту панель для оптимизации своего торгового процесса, сделав анализ позиций более интуитивно понятным и эффективным.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/19059
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Создание торговой панели администратора на MQL5 (Часть XII): Интеграция форекс-калькулятора
Автоматизация торговых стратегий на MQL5 (Часть 19): Envelopes Trend Bounce Scalping — Исполнение сделок и управление рисками (Часть II)
Знакомство с языком MQL5 (Часть 35): Освоение API и функции WebRequest в языке MQL5 (IX)
Моделирование рынка (Часть 20): Первые шаги на SQL (III)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования