Трейдинг с экономическим календарем MQL5 (Часть 10): Перетаскиваемая панель и hover-эффекты на кнопках
Введение
В данной статье мы продолжаем серию, посвященную экономическому календарю MQL5. Сегодня мы добавим возможность перетаскивать панель, а также эффекты при наведении мыши на кнопки для повышения удобства работы с новостями, обеспечивая интуитивную навигацию и интуитивно понятную работу. За основу возьмем версию из Части 9, в которой мы добавили динамический скроллбар, и улучшим адаптивность пользовательского интерфейса, добавим возможность перемещать календарь на графике, а также улучшим функционал кнопок, чтобы было еще удобнее пользоваться экономическим календарем как в режиме реальной торговли, так и при работе в тестере стратегий. Структура статьи включает следующие темы:
- Добавление функциональности для перетаскивания панели на графике
- Реализация средствами MQL5
- Тестирование и проверка
- Заключение
Давайте рассмотрим изменения.
Добавление функциональности для перетаскивания панели на графике
Для повышения удобства работы с экономическим календарем MQL5 мы реализуем перетаскиваемую панель, позволяющую изменять положение интерфейса на графике, а также динамически позиционируемый скроллбар для навигации по новостям. Наша цель — создать гибкий инструмент, который адаптируется к различным компоновкам графика. Мы избавимся от фиксированного позиционирования, которое было в предыдущих версиях и добавим возможность перемещать панель, события и скроллбар. Вот как мы это будем делать:
- Проектирование перетаскиваемой панели: мы реализуем систему отслеживания кликов мыши в области заголовка, позволяющую перетаскивать всю панель. Все элементы интерфейса будут обновлять свои координаты в реальном времени, чтобы вся панель полностью перемещалась плавно.
- Динамическое позиционирование срколлбара: для этого переведем полосу прокрутки на относительные координаты, привязанные к позиции панели, чтобы она оставалась функциональной и корректно расположенной во время перетаскивания.
- Ограничения границ графика: введем ограничения, которые будут удерживать панель в пределах видимой области графика, предотвращая ее выход за пределы экрана и обеспечивая постоянную доступность.
- Согласованное перемещение элементов: новости, кнопки фильтрации и торговые метки будут перемещаться синхронно с панелью, формируя единый профессиональный интерфейс.
Наш стратегический подход превратит панель в гибкий инструмент, который можно размещать в удобном месте, чтобы улучшать обзор графика и взаимодействие с ним. Вкратце, именно этого мы стремимся достичь.

Реализация средствами MQL5
Для внедрения улучшений в MQL5 нам сначала необходимо определить дополнительные функции, которые позволят добавить динамическую интерактивность. Предлагаю двигаться от простого к сложному — от состояний наведения мыши к состояниям перетаскивания. Начнем с определения функции void, которая будет определять состояние наведения кнопки в зависимости от положения курсора на графике и обновлять ее визуальное состояние, как правило, затемняя цвет (при желании можно задать собственные цвета для состояния hover). Сначала реализуем функцию, отвечающую за затемнение цвета.
//+------------------------------------------------------------------+ //| Helper function to darken a color for hover effect | //+------------------------------------------------------------------+ color ColorToDarken(color clr) { int r = (clr & 0xFF); int g = ((clr >> 8) & 0xFF); int b = ((clr >> 16) & 0xFF); r = MathMax(0, r - 50); g = MathMax(0, g - 50); b = MathMax(0, b - 50); return (color)((b << 16) | (g << 8) | r); }
Мы определяем функцию ColorToDarken для генерации более темной версии исходного цвета. ЕЕ мы будем использовать для усиления визуального эффекта при наведении курсора на кнопки. Входной параметр clr представляет собой одно числовое значение, объединяющее компоненты красного, зеленого и синего (каждая в диапазоне от 0 до 255), они формируют конкретный цвет. Для разделения этих компонентов используем побитовые операции, позволяющие работать с отдельными битами внутри clr.
Для извлечения красной компоненты r используется оператор & (побитовое AND) с 0xFF — шестнадцатеричным значением, равным 255 в десятичной системе, которое выступает в роли фильтра и выделяет младшие 8 бит clr, соответствующие интенсивности красного. Для получения зеленой компоненты g значение clr сдвигается вправо на 8 бит с помощью оператора >>, после чего снова применяется & с 0xFF для изоляции компоненты. Аналогично синяя компонента b извлекается путем сдвига clr вправо на 16 бит и применения & с 0xFF. Вот как записываются значения в шестнадцатеричной системе:

Для затемнения цвета мы вычитаем 50 из каждой компоненты r, g и b, чтобы уменьшить их интенсивность, и используем функцию MathMax, чтобы ни одно значение не стало меньше 0, поскольку цвет не может быть отрицательным. Затем объединяем затемненные компоненты обратно в единое цветовое значение: сдвигаем b влево на 16 бит с помощью оператора <<, g — на 8 бит и объединяем их с r при помощи оператора | (побитовое OR). Возвращаемое значение color применяется к кнопкам, создавая затемненный hover-эффект и делая интерфейс более интуитивным. Теперь эту функцию можно использовать для динамического форматирования hover-состояний, которые будут использоваться при наведении мыши.
//+------------------------------------------------------------------+ //| Update hover states for header and buttons | //+------------------------------------------------------------------+ void updateHoverStates(int mouse_x, int mouse_y) { // Header hover int header_x = (int)ObjectGetInteger(0, HEADER_LABEL, OBJPROP_XDISTANCE); int header_y = (int)ObjectGetInteger(0, HEADER_LABEL, OBJPROP_YDISTANCE); int header_width = 740; int header_height = 30; bool is_header_hovered = (mouse_x >= header_x && mouse_x <= header_x + header_width && mouse_y >= header_y && mouse_y <= header_y + header_height); if (is_header_hovered && !header_hovered) { ObjectSetInteger(0, MAIN_REC, OBJPROP_BGCOLOR, clrDarkGreen); header_hovered = true; } else if (!is_header_hovered && header_hovered) { ObjectSetInteger(0, MAIN_REC, OBJPROP_BGCOLOR, clrSeaGreen); header_hovered = false; } }
Далее реализуем функцию updateHoverStates для добавления hover-эффекта к заголовку. Мы получаем позицию заголовка HEADER_LABEL с помощью функции ObjectGetInteger, извлекаем координаты header_x и header_y с помощью функций OBJPROP_XDISTANCE и OBJPROP_YDISTANCE, которые образуют область размером 740x30 пикселей. Проверяем, находятся ли mouse_x и mouse_y внутри этой области, и устанавливаем флаг is_header_hovered при необходимости.
Если флаг is_header_hovered равен true, а header_hovered равно false, мы используем функцию ObjectSetInteger для установки цвета фона MAIN_REC (OBJPROP_BGCOLOR) в значение clrDarkGreen и обновляем header_hovered в true. Если же курсор покинул область и header_hovered было true, возвращаем цвет clrSeaGreen и устанавливаем header_hovered в false. Аналогичную логику применяем к другим кнопкам.
// FILTER_CURR_BTN hover int curr_btn_x = (int)ObjectGetInteger(0, FILTER_CURR_BTN, OBJPROP_XDISTANCE); int curr_btn_y = (int)ObjectGetInteger(0, FILTER_CURR_BTN, OBJPROP_YDISTANCE); int curr_btn_width = 110; int curr_btn_height = 26; bool is_curr_btn_hovered = (mouse_x >= curr_btn_x && mouse_x <= curr_btn_x + curr_btn_width && mouse_y >= curr_btn_y && mouse_y <= curr_btn_y + curr_btn_height); if (is_curr_btn_hovered && !filter_curr_hovered) { ObjectSetInteger(0, FILTER_CURR_BTN, OBJPROP_BGCOLOR, clrDarkGray); filter_curr_hovered = true; } else if (!is_curr_btn_hovered && filter_curr_hovered) { ObjectSetInteger(0, FILTER_CURR_BTN, OBJPROP_BGCOLOR, clrBlack); filter_curr_hovered = false; } // FILTER_IMP_BTN hover int imp_btn_x = (int)ObjectGetInteger(0, FILTER_IMP_BTN, OBJPROP_XDISTANCE); int imp_btn_y = (int)ObjectGetInteger(0, FILTER_IMP_BTN, OBJPROP_YDISTANCE); int imp_btn_width = 120; int imp_btn_height = 26; bool is_imp_btn_hovered = (mouse_x >= imp_btn_x && mouse_x <= imp_btn_x + imp_btn_width && mouse_y >= imp_btn_y && mouse_y <= imp_btn_y + imp_btn_height); if (is_imp_btn_hovered && !filter_imp_hovered) { ObjectSetInteger(0, FILTER_IMP_BTN, OBJPROP_BGCOLOR, clrDarkGray); filter_imp_hovered = true; } else if (!is_imp_btn_hovered && filter_imp_hovered) { ObjectSetInteger(0, FILTER_IMP_BTN, OBJPROP_BGCOLOR, clrBlack); filter_imp_hovered = false; } // FILTER_TIME_BTN hover int time_btn_x = (int)ObjectGetInteger(0, FILTER_TIME_BTN, OBJPROP_XDISTANCE); int time_btn_y = (int)ObjectGetInteger(0, FILTER_TIME_BTN, OBJPROP_YDISTANCE); int time_btn_width = 70; int time_btn_height = 26; bool is_time_btn_hovered = (mouse_x >= time_btn_x && mouse_x <= time_btn_x + time_btn_width && mouse_y >= time_btn_y && mouse_y <= time_btn_y + time_btn_height); if (is_time_btn_hovered && !filter_time_hovered) { ObjectSetInteger(0, FILTER_TIME_BTN, OBJPROP_BGCOLOR, clrDarkGray); filter_time_hovered = true; } else if (!is_time_btn_hovered && filter_time_hovered) { ObjectSetInteger(0, FILTER_TIME_BTN, OBJPROP_BGCOLOR, clrBlack); filter_time_hovered = false; } // CANCEL_BTN hover int cancel_btn_x = (int)ObjectGetInteger(0, CANCEL_BTN, OBJPROP_XDISTANCE); int cancel_btn_y = (int)ObjectGetInteger(0, CANCEL_BTN, OBJPROP_YDISTANCE); int cancel_btn_width = 50; int cancel_btn_height = 30; bool is_cancel_btn_hovered = (mouse_x >= cancel_btn_x && mouse_x <= cancel_btn_x + cancel_btn_width && mouse_y >= cancel_btn_y && mouse_y <= cancel_btn_y + cancel_btn_height); if (is_cancel_btn_hovered && !cancel_hovered) { ObjectSetInteger(0, CANCEL_BTN, OBJPROP_BGCOLOR, clrDarkRed); cancel_hovered = true; } else if (!is_cancel_btn_hovered && cancel_hovered) { ObjectSetInteger(0, CANCEL_BTN, OBJPROP_BGCOLOR, clrRed); cancel_hovered = false; } // CURRENCY_BTNS hover int curr_size = 51, button_height = 22, spacing_x = 0, spacing_y = 3, max_columns = 4; for (int i = 0; i < ArraySize(curr_filter); i++) { int row = i / max_columns; int col = i % max_columns; int x_pos = panel_x + 525 + col * (curr_size + spacing_x); int y_pos = panel_y + 33 + row * (button_height + spacing_y); bool is_curr_hovered = (mouse_x >= x_pos && mouse_x <= x_pos + curr_size && mouse_y >= y_pos && mouse_y <= y_pos + button_height); string btn_name = CURRENCY_BTNS+IntegerToString(i); if (is_curr_hovered && !currency_btns_hovered[i]) { ObjectSetInteger(0, btn_name, OBJPROP_BGCOLOR, clrLightGray); currency_btns_hovered[i] = true; } else if (!is_curr_hovered && currency_btns_hovered[i]) { ObjectSetInteger(0, btn_name, OBJPROP_BGCOLOR, clrNONE); currency_btns_hovered[i] = false; } } // IMPACT_LABEL buttons hover int impact_size = 100; for (int i = 0; i < ArraySize(impact_labels); i++) { int x_pos = panel_x + 90 + impact_size * i; int y_pos = panel_y + 55; bool is_impact_hovered = (mouse_x >= x_pos && mouse_x <= x_pos + impact_size && mouse_y >= y_pos && mouse_y <= y_pos + 25); string btn_name = IMPACT_LABEL+string(i); color normal_color = clrBlack; if (impact_labels[i] == "None") normal_color = clrBlack; else if (impact_labels[i] == "Low") normal_color = clrYellow; else if (impact_labels[i] == "Medium") normal_color = clrOrange; else if (impact_labels[i] == "High") normal_color = clrRed; color hover_color = normal_color == clrBlack ? clrDarkGray : ColorToDarken(normal_color); if (is_impact_hovered && !impact_btns_hovered[i]) { ObjectSetInteger(0, btn_name, OBJPROP_BGCOLOR, hover_color); impact_btns_hovered[i] = true; } else if (!is_impact_hovered && impact_btns_hovered[i]) { ObjectSetInteger(0, btn_name, OBJPROP_BGCOLOR, normal_color); impact_btns_hovered[i] = false; } }
Мы расширяем функцию updateHoverStates для добавления hover-эффектов к нескольким кнопкам, которые дают визуальную обратную связь при взаимодействии пользователя. Для кнопки FILTER_CURR_BTN с помощью ObjectGetInteger получаем координаты curr_btn_x и curr_btn_y через OBJPROP_XDISTANCE и OBJPROP_YDISTANCE, получается область 110x26 пикселей. Проверяем, находятся ли значения mouse_x и mouse_y внутри нее, чтобы установить флаг is_curr_btn_hovered.
Если is_curr_btn_hovered равно true, а filter_curr_hovered равно false, устанавливаем цвет фона FILTER_CURR_BTN (OBJPROP_BGCOLOR) в clrDarkGray через функцию ObjectSetInteger и обновляем состояние filter_curr_hovered в true. Иначе возвращаем цвет clrBlack и устанавливаем флаг filter_curr_hovered в false.
Аналогичная логика применяется к кнопкам FILTER_IMP_BTN (120x26 пикселей) и FILTER_TIME_BTN (70x26 пикселей) - для них обновляются состояния filter_imp_hovered и filter_time_hovered соответственно. Для кнопки отмены CANCEL_BTN (50x30 пикселей) цвет переключается между clrDarkRed и clrRed в зависимости от состояний is_cancel_btn_hovered и cancel_hovered. Для кнопок валют CURRENCY_BTNS выполняется цикл по каждой кнопке, вычисляются позиции с использованием panel_x и panel_y, определяется состояние is_curr_hovered и осуществляется переключение между clrLightGray и clrNONE для currency_btns_hovered[i].
Для кнопок важности IMPACT_LABEL задается цвет normal_color в зависимости от уровня важности (например, clrYellow для Low), вычисляется цвет наведения hover_color через ColorToDarken и выполняется переключение цвета на основе состояний is_impact_hovered и impact_btns_hovered[i]. Чтобы изменения вступили в силу, функция вызывается в обработчике события OnChartEvent.
//+------------------------------------------------------------------+ //| Chart event handler | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) { int mouse_x = (int)lparam; int mouse_y = (int)dparam; int mouse_state = (int)sparam; // Update hover states if (id == CHARTEVENT_MOUSE_MOVE) { updateHoverStates(mouse_x, mouse_y); } }
В обработчике OnChartEvent мы сначала получаем координаты мыши mouse_x и mouse_y из параметров lparam и dparam, а также состояние мыши mouse_state из sparam, определяющее нажатие или отпускание кнопки. Когда id равно CHARTEVENT_MOUSE_MOVE, вызывается функция updateHoverStates с передачей значений mouse_x и mouse_y для проверки наведения на заголовок или кнопки и обновления их визуального состояния. Так мы получаем удобную обратную связь при наведении для интерактивных элементов панели. Далее необходимо обрабатывать клики для обновления элементов и определять состояние перемещения элементов.
if (id == CHARTEVENT_OBJECT_CLICK) { UpdateFilterInfo(); CheckForNewsTrade(); if (sparam == CANCEL_BTN) { isDashboardUpdate = false; destroy_Dashboard(); } if (sparam == FILTER_CURR_BTN) { bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); enableCurrencyFilter = btn_state; if (debugLogging) Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableCurrencyFilter); string filter_curr_text = enableCurrencyFilter ? ShortToString(0x2714)+"Currency" : ShortToString(0x274C)+"Currency"; color filter_curr_txt_color = enableCurrencyFilter ? clrLime : clrRed; ObjectSetString(0,FILTER_CURR_BTN,OBJPROP_TEXT,filter_curr_text); ObjectSetInteger(0,FILTER_CURR_BTN,OBJPROP_COLOR,filter_curr_txt_color); if (MQLInfoInteger(MQL_TESTER)) filters_changed = true; update_dashboard_values(curr_filter_selected,imp_filter_selected); // Recalculate scrollbar ObjectDelete(0, SCROLL_LEADER); ObjectDelete(0, SCROLL_UP_REC); ObjectDelete(0, SCROLL_UP_LABEL); ObjectDelete(0, SCROLL_DOWN_REC); ObjectDelete(0, SCROLL_DOWN_LABEL); ObjectDelete(0, SCROLL_SLIDER); scroll_visible = totalEvents_Filtered > VISIBLE_ITEMS; if (debugLogging) Print("Scrollbar visibility: ", scroll_visible ? "Visible" : "Hidden"); if (scroll_visible) { createRecLabel(SCROLL_LEADER, panel_x + SCROLLBAR_X_OFFSET, panel_y + SCROLLBAR_Y_OFFSET, SCROLLBAR_WIDTH, SCROLLBAR_HEIGHT, clrSilver, 1, clrNONE); int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS); color up_color = (scroll_pos == 0) ? clrLightGray : clrBlack; color down_color = (scroll_pos >= max_scroll) ? clrLightGray : clrBlack; createRecLabel(SCROLL_UP_REC, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, panel_y + SCROLLBAR_Y_OFFSET, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray); createLabel(SCROLL_UP_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, panel_y + SCROLLBAR_Y_OFFSET-5, CharToString(0x35), up_color, 15, "Webdings"); int down_y = panel_y + SCROLLBAR_Y_OFFSET + SCROLLBAR_HEIGHT - BUTTON_SIZE; createRecLabel(SCROLL_DOWN_REC, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, down_y, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray); createLabel(SCROLL_DOWN_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, down_y-5, CharToString(0x36), down_color, 15, "Webdings"); slider_height = calculateSliderHeight(); int slider_y = panel_y + SCROLLBAR_Y_OFFSET + BUTTON_SIZE; createButton(SCROLL_SLIDER, panel_x + SCROLLBAR_X_OFFSET + SLIDER_OFFSET_X, slider_y, SLIDER_WIDTH, slider_height, "", clrWhite, 12, clrLightSlateGray, clrDarkGray, "Arial Bold"); ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_WIDTH, 2); if (debugLogging) Print("Scrollbar created: totalEvents_Filtered=", totalEvents_Filtered, ", slider_height=", slider_height); updateSliderPosition(); updateButtonColors(); } if (debugLogging) Print("Success. Changes updated! State: "+(string)enableCurrencyFilter); ChartRedraw(0); } if (sparam == FILTER_IMP_BTN) { bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); enableImportanceFilter = btn_state; if (debugLogging) Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableImportanceFilter); string filter_imp_text = enableImportanceFilter ? ShortToString(0x2714)+"Importance" : ShortToString(0x274C)+"Importance"; color filter_imp_txt_color = enableImportanceFilter ? clrLime : clrRed; ObjectSetString(0,FILTER_IMP_BTN,OBJPROP_TEXT,filter_imp_text); ObjectSetInteger(0,FILTER_IMP_BTN,OBJPROP_COLOR,filter_imp_txt_color); if (MQLInfoInteger(MQL_TESTER)) filters_changed = true; update_dashboard_values(curr_filter_selected,imp_filter_selected); // Recalculate scrollbar ObjectDelete(0, SCROLL_LEADER); ObjectDelete(0, SCROLL_UP_REC); ObjectDelete(0, SCROLL_UP_LABEL); ObjectDelete(0, SCROLL_DOWN_REC); ObjectDelete(0, SCROLL_DOWN_LABEL); ObjectDelete(0, SCROLL_SLIDER); scroll_visible = totalEvents_Filtered > VISIBLE_ITEMS; if (debugLogging) Print("Scrollbar visibility: ", scroll_visible ? "Visible" : "Hidden"); if (scroll_visible) { createRecLabel(SCROLL_LEADER, panel_x + SCROLLBAR_X_OFFSET, panel_y + SCROLLBAR_Y_OFFSET, SCROLLBAR_WIDTH, SCROLLBAR_HEIGHT, clrSilver, 1, clrNONE); int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS); color up_color = (scroll_pos == 0) ? clrLightGray : clrBlack; color down_color = (scroll_pos >= max_scroll) ? clrLightGray : clrBlack; createRecLabel(SCROLL_UP_REC, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, panel_y + SCROLLBAR_Y_OFFSET, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray); createLabel(SCROLL_UP_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, panel_y + SCROLLBAR_Y_OFFSET-5, CharToString(0x35), up_color, 15, "Webdings"); int down_y = panel_y + SCROLLBAR_Y_OFFSET + SCROLLBAR_HEIGHT - BUTTON_SIZE; createRecLabel(SCROLL_DOWN_REC, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, down_y, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray); createLabel(SCROLL_DOWN_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, down_y-5, CharToString(0x36), down_color, 15, "Webdings"); slider_height = calculateSliderHeight(); int slider_y = panel_y + SCROLLBAR_Y_OFFSET + BUTTON_SIZE; createButton(SCROLL_SLIDER, panel_x + SCROLLBAR_X_OFFSET + SLIDER_OFFSET_X, slider_y, SLIDER_WIDTH, slider_height, "", clrWhite, 12, clrLightSlateGray, clrDarkGray, "Arial Bold"); ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_WIDTH, 2); if (debugLogging) Print("Scrollbar created: totalEvents_Filtered=", totalEvents_Filtered, ", slider_height=", slider_height); updateSliderPosition(); updateButtonColors(); } if (debugLogging) Print("Success. Changes updated! State: "+(string)enableImportanceFilter); ChartRedraw(0); } if (sparam == FILTER_TIME_BTN) { bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); enableTimeFilter = btn_state; if (debugLogging) Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableTimeFilter); string filter_time_text = enableTimeFilter ? ShortToString(0x2714)+"Time" : ShortToString(0x274C)+"Time"; color filter_time_txt_color = enableTimeFilter ? clrLime : clrRed; ObjectSetString(0,FILTER_TIME_BTN,OBJPROP_TEXT,filter_time_text); ObjectSetInteger(0,FILTER_TIME_BTN,OBJPROP_COLOR,filter_time_txt_color); if (MQLInfoInteger(MQL_TESTER)) filters_changed = true; update_dashboard_values(curr_filter_selected,imp_filter_selected); // Recalculate scrollbar ObjectDelete(0, SCROLL_LEADER); ObjectDelete(0, SCROLL_UP_REC); ObjectDelete(0, SCROLL_UP_LABEL); ObjectDelete(0, SCROLL_DOWN_REC); ObjectDelete(0, SCROLL_DOWN_LABEL); ObjectDelete(0, SCROLL_SLIDER); scroll_visible = totalEvents_Filtered > VISIBLE_ITEMS; if (debugLogging) Print("Scrollbar visibility: ", scroll_visible ? "Visible" : "Hidden"); if (scroll_visible) { createRecLabel(SCROLL_LEADER, panel_x + SCROLLBAR_X_OFFSET, panel_y + SCROLLBAR_Y_OFFSET, SCROLLBAR_WIDTH, SCROLLBAR_HEIGHT, clrSilver, 1, clrNONE); int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS); color up_color = (scroll_pos == 0) ? clrLightGray : clrBlack; color down_color = (scroll_pos >= max_scroll) ? clrLightGray : clrBlack; createRecLabel(SCROLL_UP_REC, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, panel_y + SCROLLBAR_Y_OFFSET, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray); createLabel(SCROLL_UP_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, panel_y + SCROLLBAR_Y_OFFSET-5, CharToString(0x35), up_color, 15, "Webdings"); int down_y = panel_y + SCROLLBAR_Y_OFFSET + SCROLLBAR_HEIGHT - BUTTON_SIZE; createRecLabel(SCROLL_DOWN_REC, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, down_y, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray); createLabel(SCROLL_DOWN_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, down_y-5, CharToString(0x36), down_color, 15, "Webdings"); slider_height = calculateSliderHeight(); int slider_y = panel_y + SCROLLBAR_Y_OFFSET + BUTTON_SIZE; createButton(SCROLL_SLIDER, panel_x + SCROLLBAR_X_OFFSET + SLIDER_OFFSET_X, slider_y, SLIDER_WIDTH, slider_height, "", clrWhite, 12, clrLightSlateGray, clrDarkGray, "Arial Bold"); ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_WIDTH, 2); if (debugLogging) Print("Scrollbar created: totalEvents_Filtered=", totalEvents_Filtered, ", slider_height=", slider_height); updateSliderPosition(); updateButtonColors(); } if (debugLogging) Print("Success. Changes updated! State: "+(string)enableTimeFilter); ChartRedraw(0); } if (StringFind(sparam,CURRENCY_BTNS) >= 0) { string selected_curr = ObjectGetString(0,sparam,OBJPROP_TEXT); if (debugLogging) Print("BTN NAME = ",sparam,", CURRENCY = ",selected_curr); bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); if (btn_state == false) { if (debugLogging) Print("BUTTON IS IN UN-SELECTED MODE."); for (int i = 0; i < ArraySize(curr_filter_selected); i++) { if (curr_filter_selected[i] == selected_curr) { for (int j = i; j < ArraySize(curr_filter_selected) - 1; j++) { curr_filter_selected[j] = curr_filter_selected[j + 1]; } ArrayResize(curr_filter_selected, ArraySize(curr_filter_selected) - 1); if (debugLogging) Print("Removed from selected filters: ", selected_curr); break; } } } else { if (debugLogging) Print("BUTTON IS IN SELECTED MODE. TAKE ACTION"); bool already_selected = false; for (int j = 0; j < ArraySize(curr_filter_selected); j++) { if (curr_filter_selected[j] == selected_curr) { already_selected = true; break; } } if (!already_selected) { ArrayResize(curr_filter_selected, ArraySize(curr_filter_selected) + 1); curr_filter_selected[ArraySize(curr_filter_selected) - 1] = selected_curr; if (debugLogging) Print("Added to selected filters: ", selected_curr); } else { if (debugLogging) Print("Currency already selected: ", selected_curr); } } if (debugLogging) Print("SELECTED ARRAY SIZE = ",ArraySize(curr_filter_selected)); if (debugLogging) ArrayPrint(curr_filter_selected); if (MQLInfoInteger(MQL_TESTER)) filters_changed = true; update_dashboard_values(curr_filter_selected,imp_filter_selected); // Recalculate scrollbar ObjectDelete(0, SCROLL_LEADER); ObjectDelete(0, SCROLL_UP_REC); ObjectDelete(0, SCROLL_UP_LABEL); ObjectDelete(0, SCROLL_DOWN_REC); ObjectDelete(0, SCROLL_DOWN_LABEL); ObjectDelete(0, SCROLL_SLIDER); scroll_visible = totalEvents_Filtered > VISIBLE_ITEMS; if (debugLogging) Print("Scrollbar visibility: ", scroll_visible ? "Visible" : "Hidden"); if (scroll_visible) { createRecLabel(SCROLL_LEADER, panel_x + SCROLLBAR_X_OFFSET, panel_y + SCROLLBAR_Y_OFFSET, SCROLLBAR_WIDTH, SCROLLBAR_HEIGHT, clrSilver, 1, clrNONE); int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS); color up_color = (scroll_pos == 0) ? clrLightGray : clrBlack; color down_color = (scroll_pos >= max_scroll) ? clrLightGray : clrBlack; createRecLabel(SCROLL_UP_REC, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, panel_y + SCROLLBAR_Y_OFFSET, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray); createLabel(SCROLL_UP_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, panel_y + SCROLLBAR_Y_OFFSET-5, CharToString(0x35), up_color, 15, "Webdings"); int down_y = panel_y + SCROLLBAR_Y_OFFSET + SCROLLBAR_HEIGHT - BUTTON_SIZE; createRecLabel(SCROLL_DOWN_REC, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, down_y, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray); createLabel(SCROLL_DOWN_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, down_y-5, CharToString(0x36), down_color, 15, "Webdings"); slider_height = calculateSliderHeight(); int slider_y = panel_y + SCROLLBAR_Y_OFFSET + BUTTON_SIZE; createButton(SCROLL_SLIDER, panel_x + SCROLLBAR_X_OFFSET + SLIDER_OFFSET_X, slider_y, SLIDER_WIDTH, slider_height, "", clrWhite, 12, clrLightSlateGray, clrDarkGray, "Arial Bold"); ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_WIDTH, 2); if (debugLogging) Print("Scrollbar created: totalEvents_Filtered=", totalEvents_Filtered, ", slider_height=", slider_height); updateSliderPosition(); updateButtonColors(); } if (debugLogging) Print("SUCCESS. DASHBOARD UPDATED"); ChartRedraw(0); } if (StringFind(sparam, IMPACT_LABEL) >= 0) { string selected_imp = ObjectGetString(0, sparam, OBJPROP_TEXT); ENUM_CALENDAR_EVENT_IMPORTANCE selected_importance_lvl = get_importance_level(impact_labels,allowed_importance_levels,selected_imp); if (debugLogging) Print("BTN NAME = ", sparam, ", IMPORTANCE LEVEL = ", selected_imp,"(",selected_importance_lvl,")"); bool btn_state = ObjectGetInteger(0, sparam, OBJPROP_STATE); color color_border = btn_state ? clrNONE : clrBlack; if (btn_state == false) { if (debugLogging) Print("BUTTON IS IN UN-SELECTED MODE."); for (int i = 0; i < ArraySize(imp_filter_selected); i++) { if (impact_filter_selected[i] == selected_imp) { for (int j = i; j < ArraySize(imp_filter_selected) - 1; j++) { imp_filter_selected[j] = imp_filter_selected[j + 1]; impact_filter_selected[j] = impact_filter_selected[j + 1]; } ArrayResize(imp_filter_selected, ArraySize(imp_filter_selected) - 1); ArrayResize(impact_filter_selected, ArraySize(impact_filter_selected) - 1); if (debugLogging) Print("Removed from selected importance filters: ", selected_imp,"(",selected_importance_lvl,")"); break; } } } else { if (debugLogging) Print("BUTTON IS IN SELECTED MODE. TAKE ACTION"); bool already_selected = false; for (int j = 0; j < ArraySize(imp_filter_selected); j++) { if (impact_filter_selected[j] == selected_imp) { already_selected = true; break; } } if (!already_selected) { ArrayResize(imp_filter_selected, ArraySize(imp_filter_selected) + 1); ArrayResize(impact_filter_selected, ArraySize(impact_filter_selected) + 1); imp_filter_selected[ArraySize(imp_filter_selected) - 1] = selected_importance_lvl; impact_filter_selected[ArraySize(impact_filter_selected) - 1] = selected_imp; if (debugLogging) Print("Added to selected importance filters: ", selected_imp,"(",selected_importance_lvl,")"); } else { if (debugLogging) Print("Importance level already selected: ", selected_imp,"(",selected_importance_lvl,")"); } } if (debugLogging) Print("SELECTED ARRAY SIZE = ", ArraySize(imp_filter_selected)," >< ",ArraySize(impact_filter_selected)); if (debugLogging) ArrayPrint(imp_filter_selected); if (debugLogging) ArrayPrint(impact_filter_selected); if (MQLInfoInteger(MQL_TESTER)) filters_changed = true; update_dashboard_values(curr_filter_selected,imp_filter_selected); ObjectSetInteger(0,sparam,OBJPROP_BORDER_COLOR,color_border); // Recalculate scrollbar ObjectDelete(0, SCROLL_LEADER); ObjectDelete(0, SCROLL_UP_REC); ObjectDelete(0, SCROLL_UP_LABEL); ObjectDelete(0, SCROLL_DOWN_REC); ObjectDelete(0, SCROLL_DOWN_LABEL); ObjectDelete(0, SCROLL_SLIDER); scroll_visible = totalEvents_Filtered > VISIBLE_ITEMS; if (debugLogging) Print("Scrollbar visibility: ", scroll_visible ? "Visible" : "Hidden"); if (scroll_visible) { createRecLabel(SCROLL_LEADER, panel_x + SCROLLBAR_X_OFFSET, panel_y + SCROLLBAR_Y_OFFSET, SCROLLBAR_WIDTH, SCROLLBAR_HEIGHT, clrSilver, 1, clrNONE); int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS); color up_color = (scroll_pos == 0) ? clrLightGray : clrBlack; color down_color = (scroll_pos >= max_scroll) ? clrLightGray : clrBlack; createRecLabel(SCROLL_UP_REC, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, panel_y + SCROLLBAR_Y_OFFSET, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray); createLabel(SCROLL_UP_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, panel_y + SCROLLBAR_Y_OFFSET-5, CharToString(0x35), up_color, 15, "Webdings"); int down_y = panel_y + SCROLLBAR_Y_OFFSET + SCROLLBAR_HEIGHT - BUTTON_SIZE; createRecLabel(SCROLL_DOWN_REC, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, down_y, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray); createLabel(SCROLL_DOWN_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, down_y-5, CharToString(0x36), down_color, 15, "Webdings"); slider_height = calculateSliderHeight(); int slider_y = panel_y + SCROLLBAR_Y_OFFSET + BUTTON_SIZE; createButton(SCROLL_SLIDER, panel_x + SCROLLBAR_X_OFFSET + SLIDER_OFFSET_X, slider_y, SLIDER_WIDTH, slider_height, "", clrWhite, 12, clrLightSlateGray, clrDarkGray, "Arial Bold"); ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_WIDTH, 2); if (debugLogging) Print("Scrollbar created: totalEvents_Filtered=", totalEvents_Filtered, ", slider_height=", slider_height); updateSliderPosition(); updateButtonColors(); } if (debugLogging) Print("SUCCESS. DASHBOARD UPDATED"); ChartRedraw(0); } // Scrollbar button clicks if (scroll_visible && (sparam == SCROLL_UP_REC || sparam == SCROLL_UP_LABEL)) { scrollUp(); updateButtonColors(); if (debugLogging) Print("Up button clicked (", sparam, "). CurrPos: ", scroll_pos); ChartRedraw(0); } if (scroll_visible && (sparam == SCROLL_DOWN_REC || sparam == SCROLL_DOWN_LABEL)) { scrollDown(); updateButtonColors(); if (debugLogging) Print("Down button clicked (", sparam, "). CurrPos: ", scroll_pos); ChartRedraw(0); } }
Для обработки кликов объектов при id равном CHARTEVENT_OBJECT_CLICK сначала вызываются функции UpdateFilterInfo и CheckForNewsTrade для обновления состояний фильтров и проверки торговых условий. Если sparam равно значению CANCEL_BTN, устанавливаем isDashboardUpdate в значение false и вызываем функцию destroy_Dashboard для закрытия панели. Для кнопок FILTER_CURR_BTN, FILTER_IMP_BTN или FILTER_TIME_BTN получаем состояние кнопки btn_state с помощью функции ObjectGetInteger и OBJPROP_STATE, обновляем значение enableCurrencyFilter, enableImportanceFilter или enableTimeFilter и корректируем текст и цвет кнопки с помощью ObjectSetString и ObjectSetInteger (OBJPROP_TEXT и OBJPROP_COLOR), например clrLime для включенного состояния и clrRed для отключенного. Затем вызываем функцию update_dashboard_values и пересоздаем скроллбар с помощью функций createRecLabel, createLabel и createButton для элементов SCROLL_LEADER и SCROLL_SLIDER, позиционируя их с использованием panel_x и panel_y, и обновляем состояние с помощью функций через updateSliderPosition и updateButtonColors.
Для кнопок валют CURRENCY_BTNS получаем выбранную валюту selected_curr с помощью ObjectGetString, добавляем или удаляем ее из массива curr_filter_selected при помощи ArrayResize и обновляем панель. Для кнопок важности события IMPACT_LABEL вызываем функцию get_importance_level для сопоставления selected_imp с selected_importance_lvl, обновляем массивы imp_filter_selected и impact_filter_selected, устанавливаем цвет color_border с помощью функции ObjectSetInteger и выполняем обновление панели. Если sparam равно SCROLL_UP_REC или SCROLL_UP_LABEL, вызывается scrollUp; если SCROLL_DOWN_REC или SCROLL_DOWN_LABEL — scrollDown. После этого обновляются цвета через updateButtonColors и выполняется перерисовка графика с помощью функции ChartRedraw. После компиляции и запуска программы получаем следующий результат.

НА анимации видно, что реализована корректная визуальная обратная связь при наведении на заданные кнопки. Следующий шаг — реализация более сложной части, а именно перемещения всей панели. Здесь у нас следующая логика.
else if (id == CHARTEVENT_MOUSE_MOVE && isDashboardUpdate) { // Handle panel dragging int header_x = (int)ObjectGetInteger(0, HEADER_LABEL, OBJPROP_XDISTANCE); int header_y = (int)ObjectGetInteger(0, HEADER_LABEL, OBJPROP_YDISTANCE); int header_width = 740; int header_height = 30; if (prev_mouse_state == 0 && mouse_state == 1) { // Mouse button down if (mouse_x >= header_x && mouse_x <= header_x + header_width && mouse_y >= header_y && mouse_y <= header_y + header_height) { panel_dragging = true; panel_drag_x = mouse_x; panel_drag_y = mouse_y; panel_start_x = panel_x; panel_start_y = panel_y; ChartSetInteger(0, CHART_MOUSE_SCROLL, false); if (debugLogging) Print("Panel drag started at x=", mouse_x, ", y=", mouse_y); } } if (panel_dragging && mouse_state == 1) { // Dragging panel int dx = mouse_x - panel_drag_x; int dy = mouse_y - panel_drag_y; panel_x = panel_start_x + dx; panel_y = panel_start_y + dy; // Ensure panel stays within chart boundaries int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); int chart_height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); panel_x = MathMax(0, MathMin(panel_x, chart_width - 753)); // 753 = panel width panel_y = MathMax(0, MathMin(panel_y, chart_height - 410)); // 410 = panel height // Update positions of all panel objects ObjectSetInteger(0, MAIN_REC, OBJPROP_XDISTANCE, panel_x); ObjectSetInteger(0, MAIN_REC, OBJPROP_YDISTANCE, panel_y); ObjectSetInteger(0, SUB_REC1, OBJPROP_XDISTANCE, panel_x + 3); ObjectSetInteger(0, SUB_REC1, OBJPROP_YDISTANCE, panel_y + 30); ObjectSetInteger(0, SUB_REC2, OBJPROP_XDISTANCE, panel_x + 3 + 5); ObjectSetInteger(0, SUB_REC2, OBJPROP_YDISTANCE, panel_y + 30 + 50 + 27); ObjectSetInteger(0, HEADER_LABEL, OBJPROP_XDISTANCE, panel_x + 3 + 5); ObjectSetInteger(0, HEADER_LABEL, OBJPROP_YDISTANCE, panel_y + 5); ObjectSetInteger(0, TIME_LABEL, OBJPROP_XDISTANCE, panel_x + 20); ObjectSetInteger(0, TIME_LABEL, OBJPROP_YDISTANCE, panel_y + 35); ObjectSetInteger(0, IMPACT_LABEL, OBJPROP_XDISTANCE, panel_x + 20); ObjectSetInteger(0, IMPACT_LABEL, OBJPROP_YDISTANCE, panel_y + 55); ObjectSetInteger(0, FILTER_LABEL, OBJPROP_XDISTANCE, panel_x + 320); ObjectSetInteger(0, FILTER_LABEL, OBJPROP_YDISTANCE, panel_y + 5); ObjectSetInteger(0, FILTER_CURR_BTN, OBJPROP_XDISTANCE, panel_x + 380); ObjectSetInteger(0, FILTER_CURR_BTN, OBJPROP_YDISTANCE, panel_y + 5); ObjectSetInteger(0, FILTER_IMP_BTN, OBJPROP_XDISTANCE, panel_x + 490); ObjectSetInteger(0, FILTER_IMP_BTN, OBJPROP_YDISTANCE, panel_y + 5); ObjectSetInteger(0, FILTER_TIME_BTN, OBJPROP_XDISTANCE, panel_x + 610); ObjectSetInteger(0, FILTER_TIME_BTN, OBJPROP_YDISTANCE, panel_y + 5); ObjectSetInteger(0, CANCEL_BTN, OBJPROP_XDISTANCE, panel_x + 692+10); ObjectSetInteger(0, CANCEL_BTN, OBJPROP_YDISTANCE, panel_y + 1); // Update calendar buttons int startX = panel_x + 9; for (int i = 0; i < ArraySize(array_calendar); i++) { ObjectSetInteger(0, ARRAY_CALENDAR+IntegerToString(i), OBJPROP_XDISTANCE, startX); ObjectSetInteger(0, ARRAY_CALENDAR+IntegerToString(i), OBJPROP_YDISTANCE, panel_y + 82); startX += buttons[i] + 3; } // Update impact buttons for (int i = 0; i < ArraySize(impact_labels); i++) { ObjectSetInteger(0, IMPACT_LABEL+string(i), OBJPROP_XDISTANCE, panel_x + 90 + 100 * i); ObjectSetInteger(0, IMPACT_LABEL+string(i), OBJPROP_YDISTANCE, panel_y + 55); } // Update currency buttons int curr_size = 51, button_height = 22, spacing_x = 0, spacing_y = 3, max_columns = 4; for (int i = 0; i < ArraySize(curr_filter); i++) { int row = i / max_columns; int col = i % max_columns; int x_pos = panel_x + 525 + col * (curr_size + spacing_x); int y_pos = panel_y + 33 + row * (button_height + spacing_y); ObjectSetInteger(0, CURRENCY_BTNS+IntegerToString(i), OBJPROP_XDISTANCE, x_pos); ObjectSetInteger(0, CURRENCY_BTNS+IntegerToString(i), OBJPROP_YDISTANCE, y_pos); } // Update scrollbar if (scroll_visible) { ObjectSetInteger(0, SCROLL_LEADER, OBJPROP_XDISTANCE, panel_x + SCROLLBAR_X_OFFSET); ObjectSetInteger(0, SCROLL_LEADER, OBJPROP_YDISTANCE, panel_y + SCROLLBAR_Y_OFFSET); ObjectSetInteger(0, SCROLL_UP_REC, OBJPROP_XDISTANCE, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X); ObjectSetInteger(0, SCROLL_UP_REC, OBJPROP_YDISTANCE, panel_y + SCROLLBAR_Y_OFFSET); ObjectSetInteger(0, SCROLL_UP_LABEL, OBJPROP_XDISTANCE, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X); ObjectSetInteger(0, SCROLL_UP_LABEL, OBJPROP_YDISTANCE, panel_y + SCROLLBAR_Y_OFFSET - 5); ObjectSetInteger(0, SCROLL_DOWN_REC, OBJPROP_XDISTANCE, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X); ObjectSetInteger(0, SCROLL_DOWN_REC, OBJPROP_YDISTANCE, panel_y + SCROLLBAR_Y_OFFSET + SCROLLBAR_HEIGHT - BUTTON_SIZE); ObjectSetInteger(0, SCROLL_DOWN_LABEL, OBJPROP_XDISTANCE, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X); ObjectSetInteger(0, SCROLL_DOWN_LABEL, OBJPROP_YDISTANCE, panel_y + SCROLLBAR_Y_OFFSET + SCROLLBAR_HEIGHT - BUTTON_SIZE - 5); ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_XDISTANCE, panel_x + SCROLLBAR_X_OFFSET + SLIDER_OFFSET_X); // Y-position of slider is managed by updateSliderPosition() } // Update event display update_dashboard_values(curr_filter_selected, imp_filter_selected); // Update trade labels if they exist if (ObjectFind(0, "NewsCountdown") >= 0) { ObjectSetInteger(0, "NewsCountdown", OBJPROP_XDISTANCE, panel_x); ObjectSetInteger(0, "NewsCountdown", OBJPROP_YDISTANCE, panel_y - 33); } if (ObjectFind(0, "NewsTradeInfo") >= 0) { ObjectSetInteger(0, "NewsTradeInfo", OBJPROP_XDISTANCE, panel_x + 305); ObjectSetInteger(0, "NewsTradeInfo", OBJPROP_YDISTANCE, panel_y - 28); } ChartRedraw(0); if (debugLogging) Print("Panel moved to x=", panel_x, ", y=", panel_y); } if (mouse_state == 0) { // Mouse button released if (panel_dragging) { panel_dragging = false; ChartSetInteger(0, CHART_MOUSE_SCROLL, true); if (debugLogging) Print("Panel drag stopped."); ChartRedraw(0); } } }
Мы расширяем функцию OnChartEvent, чтобы реализовать возможность перетаскивания панели, когда isDashboardUpdate равно true и id равно CHARTEVENT_MOUSE_MOVE. Позицию заголовка получаем с помощью функции ObjectGetInteger, чтобы получить параметры "header_x" и "header_y" из HEADER_LABEL с использованием OBJPROP_XDISTANCE и OBJPROP_YDISTANCE, задавая область 740x30 пикселей. Когда предыдущее состояние мыши prev_mouse_state равно 0, а новое mouse_state равно 1 (нажатие кнопки мыши), проверяем, находятся ли mouse_x и mouse_y в пределах заголовка. Если да, устанавливаем параметр panel_dragging в значение true, сохраняем значения координат panel_drag_x, panel_drag_y, panel_start_x и panel_start_y и отключаем прокрутку графика через ChartSetInteger и CHART_MOUSE_SCROLL.
Если состояние перетаскивания panel_dragging равно true и текущее состояние mouse_state равно 1, вычисляем смещение dx и dy, обновляем расположение панели panel_x и panel_y и ограничиваем их с помощью MathMax и MathMin в пределах размеров графика chart_width и chart_height, полученных функцией ChartGetInteger с использованием CHART_WIDTH_IN_PIXELS и CHART_HEIGHT_IN_PIXELS. Затем перепозиционируем все элементы интерфейса, используя функцию ObjectSetInteger с параметрами OBJPROP_XDISTANCE и OBJPROP_YDISTANCE для всех объектов: MAIN_REC, SUB_REC1, SUB_REC2, HEADER_LABEL, TIME_LABEL, IMPACT_LABEL, FILTER_LABEL, FILTER_CURR_BTN, FILTER_IMP_BTN, FILTER_TIME_BTN, CANCEL_BTN, ARRAY_CALENDAR, IMPACT_LABEL и CURRENCY_BTNS, а также обновляем элементы полосы прокрутки (SCROLL_LEADER, SCROLL_UP_REC, SCROLL_UP_LABEL, SCROLL_DOWN_REC, SCROLL_DOWN_LABEL, SCROLL_SLIDER), если scroll_visible равно true — для этого используем panel_x и panel_y с соответствующими смещениями.
Вызываем функцию update_dashboard_values для обновления новостей на панели, перепозиционируем торговые метки NewsCountdown и NewsTradeInfo (при их наличии), используя функцию ObjectFind и выполняем ChartRedraw. Когда mouse_state становится 0 (кнопка мыши отпущена), устанавливаем флаг состояние panel_dragging в false, повторно включаем прокрутку графика через ChartSetInteger и выполняем перерисовку, при необходимости выводя сообщения через Print (если включен параметр debugLogging). После этого можно обрабатывать логику скроллбара, если панель не находится в режиме перетаскивания, чтобы избежать конфликтов событий.
// Scrollbar handling (only if not dragging panel) if (scroll_visible && !panel_dragging) { if (prev_mouse_state == 0 && mouse_state == 1) { int xd = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XDISTANCE); int yd = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE); int xs = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XSIZE); int ys = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE); // Skip if mouse is over header to prioritize panel dragging if (mouse_x >= (panel_x + 3 + 5) && mouse_x <= (panel_x + 3 + 5 + 740) && mouse_y >= (panel_y + 5) && mouse_y <= (panel_y + 5 + 30)) { return; } if (mouse_x >= xd && mouse_x <= xd + xs && mouse_y >= yd && mouse_y <= yd + ys) { moving_state_slider = true; mlb_down_x = mouse_x; mlb_down_y = mouse_y; mlb_down_yd_slider = yd; ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_BGCOLOR, clrDodgerBlue); ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE, slider_height + 2); ChartSetInteger(0, CHART_MOUSE_SCROLL, false); if (debugLogging) Print("Slider drag started at y=", mouse_y); } } if (moving_state_slider && mouse_state == 1) { int delta_y = mouse_y - mlb_down_y; int new_y = mlb_down_yd_slider + delta_y; int scroll_area_y_min = panel_y + SCROLLBAR_Y_OFFSET + BUTTON_SIZE; int scroll_area_y_max = scroll_area_y_min + SCROLL_AREA_HEIGHT - slider_height; new_y = MathMax(scroll_area_y_min, MathMin(new_y, scroll_area_y_max)); ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE, new_y); int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS); double scroll_ratio = (double)(new_y - scroll_area_y_min) / (scroll_area_y_max - scroll_area_y_min); int new_scroll_pos = (int)MathRound(scroll_ratio * max_scroll); if (new_scroll_pos != scroll_pos) { scroll_pos = new_scroll_pos; update_dashboard_values(curr_filter_selected, imp_filter_selected); updateButtonColors(); if (debugLogging) Print("Slider dragged. CurrPos: ", scroll_pos, ", Total steps: ", max_scroll, ", Slider y=", new_y); } ChartRedraw(0); } if (mouse_state == 0) { if (moving_state_slider) { moving_state_slider = false; ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_BGCOLOR, clrLightSlateGray); ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE, slider_height); ChartSetInteger(0, CHART_MOUSE_SCROLL, true); if (debugLogging) Print("Slider drag stopped."); ChartRedraw(0); } } } prev_mouse_state = mouse_state;
Здесь мы реализуем перетаскивание скроллбара, оно выполняется при значении scroll_visible равном true и panel_dragging равном false, что исключает конфликт с перетаскиванием панели. Когда prev_mouse_state равно 0, а mouse_state равно 1, применяется ранее реализованная логика прокрутки и обработки состояний ползунка. После компиляции мы получаем следующий результат.

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

Как видим, панель функционирует без сбоев.
Заключение
В данной части серии об экономическом календаре MQL5 мы реализовали перетаскиваемую панель и интерактивные эффекты при наведении мыши на кнопки. Надеюсь, навигация по событиям стала удобнее. За основу улучшений мы брали разработки из Части 9 с динамическим скроллбаром и улучшенным дизайном, а добавленный функционал работает как в режиме реальной торговли, так и в тестере, предоставляя надежную платформу для торговых стратегий по новостям. Вы можете использовать эту доработанную панель в качестве основы и адаптировать ее под свои торговые требования.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/18241
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
Моделирование рынка (Часть 19): Первые шаги на SQL (II)
Возможности Мастера MQL5, которые вам нужно знать (Часть 67): Использование паттернов TRIX и процентного диапазона Уильямса
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования