English
preview
Handel mit dem MQL5 Wirtschaftskalender (Teil 10): Bewegliches Dashboard und interaktive Hover-Effekte für eine reibungslose Nachrichten-Navigation

Handel mit dem MQL5 Wirtschaftskalender (Teil 10): Bewegliches Dashboard und interaktive Hover-Effekte für eine reibungslose Nachrichten-Navigation

MetaTrader 5Handel |
32 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

In diesem Artikel entwickeln wir die Serie über den MQL5-Wirtschaftskalender weiter, indem wir ein bewegliches Dashboard und interaktive Hover-Effekte einführen, um die Kontrolle und Navigation von Händlern bei Nachrichtenereignissen zu verbessern und eine flexible und intuitive Nutzererfahrung zu gewährleisten. Aufbauend auf der dynamischen Bildlaufleiste und der ausgefeilten Darstellung von Teil 9 konzentrieren wir uns nun auf eine reaktionsfähige Schnittstelle (UI), die eine Neupositionierung des Kalenders im Chart ermöglicht und visuelles Feedback für Schaltflächeninteraktionen bietet, um den Zugang zu Wirtschaftsnachrichten in Live- und Backtesting-Umgebungen zu optimieren. Wir gliedern den Artikel in die folgenden Themen:

  1. Hinzufügen eines beweglichen Dashboards für erweiterte Flexibilität auf dem Chat
  2. Implementierung in MQL5
  3. Tests und Validierung
  4. Schlussfolgerung

Lassen Sie uns in diese Verbesserungen eintauchen!


Hinzufügen eines beweglichen Dashboards für erweiterte Flexibilität auf dem Chat

Um die Nutzerfreundlichkeit des MQL5-Wirtschaftskalenders zu verbessern, werden wir ein bewegliches Dashboard einführen, das es uns ermöglicht, die Schnittstelle auf dem Chart neu zu positionieren, gepaart mit einer dynamisch positionierten Bildlaufleiste, um eine nahtlose Nachrichtennavigation zu gewährleisten. Unser Ziel ist es, ein auf den Händler zugeschnittenes Tool zu schaffen, das sich an verschiedene Chart-Layouts anpasst, indem wir die feste Positionierung der vorherigen Versionen beseitigen und dafür sorgen, dass das Dashboard, die News-Ereignisse und die Bildlaufleiste mühelos zusammenlaufen. Wie wir das erreichen wollen, ist folgendermaßen:

  • Design des bewegliches Dashboards: Wir werden ein System zur Erkennung von Mausklicks im Kopfbereich implementieren, das es dem Nutzer ermöglicht, das gesamte Dashboard zu verschieben. Alle Elemente der Nutzeroberfläche aktualisieren ihre Positionen in Echtzeit, um die Ausrichtung beizubehalten.
  • Dynamische Positionierung der Bildlaufleiste: Wir werden die Bildlaufleiste so anpassen, dass sie relative Koordinaten verwendet, die an die Position des Armaturenbretts gebunden sind, um sicherzustellen, dass sie beim Ziehen funktional und korrekt platziert bleibt.
  • Chart Boundary Constraints: Wir setzen Grenzen, um das Dashboard innerhalb des sichtbaren Bereichs des Charts zu halten, sodass es nicht aus dem Bildschirm verschwinden kann und die Zugänglichkeit gewährleistet ist.
  • ‚Klebrige‘ Bewegung der Elemente: Wir sorgen dafür, dass Nachrichten, Filterschaltflächen und Handelsetiketten mit dem Dashboard synchronisiert werden und eine einheitliche und professionelle Oberfläche bieten.

Dieser strategische Ansatz wird das Dashboard in ein flexibles Instrument verwandeln, das wir je nach Bedarf positionieren können, um die Sichtbarkeit des Charts und die Interaktion zu verbessern. Kurz gesagt, das ist es, was wir erreichen wollen.

STRATEGIEPLAN


Implementierung in MQL5

Um die Verbesserungen in MQL5 vorzunehmen, müssen wir zunächst einige zusätzliche Funktionen definieren, um die dynamische Interaktivität zu verbessern. Wir beginnen mit den einfachsten bis hin zu den komplexesten, d. h. den Schwebe- und Ziehzuständen. Lassen Sie uns zunächst eine ungültige Funktion definieren, die den Zustand der Hover-Schaltfläche in Abhängigkeit von der Cursorposition auf dem Chart bestimmt und die Schaltflächenzustände aktualisiert, normalerweise durch Abdunkeln, aber Sie können Ihre eigenen farbigen Hover-Zustände definieren. Definieren wir also zunächst die Funktion, die für die Verdunkelung der Farbe verantwortlich ist.

//+------------------------------------------------------------------+
//| 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);
}

Zunächst definieren wir die Funktion „ColorToDarken“, um eine dunklere Version einer Eingabefarbe zu erzeugen, die wir verwenden werden, um das visuelle Feedback für Schaltflächen zu verbessern, wenn wir mit dem Mauszeiger über sie fahren. Wir beginnen mit der Eingabefarbe „clr“, einer einzelnen Zahl, die rote, grüne und blaue Komponenten kombiniert, die jeweils von 0 bis 255 reichen, um eine bestimmte Farbe darzustellen. Um diese Komponenten zu trennen, verwenden wir bitweise Operationen, mit denen wir die Bits in „clr“ manipulieren können.

Für die rote Komponente „r“ verwenden wir den Operator „&“ (bitweises UND) mit „0xFF“, einem hexadecimalen Wert, der dezimal 255 entspricht und wie ein Filter wirkt, um nur die untersten 8 Bits von „clr“ zu extrahieren, wodurch wir die rote Intensität erhalten. Für die grüne Komponente „g“ verschieben wir „clr“ mit dem Operator „>>“ um 8 Bits nach rechts, um die grünen Daten in die untersten 8 Bits zu verschieben, und wenden dann „&“ mit „0xFF“ an, um sie zu isolieren. In ähnlicher Weise extrahieren wir die blaue Komponente „b“, indem wir „clr“ um 16 Bit nach rechts verschieben und „&“ mit „0xFF“ verwenden. Die Buchstaben in hexadezimaler Darstellung sind wie folgt.

255 IN HEXADEZIMAL 0xFF

Um die Farbe abzudunkeln, subtrahieren wir von jeder Komponente - „r“, „g“ und „b“ - 50, um ihre Intensität zu verringern, und wir verwenden die Funktion „MathMax“, um sicherzustellen, dass kein Wert unter 0 fällt, da negative Farbwerte ungültig sind. Schließlich werden die abgedunkelten Komponenten wieder zu einem einzigen Farbwert kombiniert, indem „b“ mit dem Operator „<<“ um 16 Bits nach links verschoben wird, „g“ um 8 Bits nach links verschoben wird und sie mit dem Operator „|“ (bitweises ODER) mit „r“ zusammengeführt werden. Wir geben diesen neuen „Farb“-Wert zurück, den wir auf die Schaltflächen anwenden, um einen dunkleren Hover-Effekt zu erzeugen, der die Schnittstelle interaktiver und intuitiver macht. Mit dieser Funktion können wir nun die Hovered-Farbzustände dynamisch formatieren.

//+------------------------------------------------------------------+
//| 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;
   }
}

Hier implementieren wir die Funktion „updateHoverStates“, um einen Hover-Effekt in der Kopfzeile zu erzeugen. Wir rufen die Position „HEADER_LABEL“ mit ObjectGetInteger ab, um „header_x“ und „header_y“ mit OBJPROP_XDISTANCE und OBJPROP_YDISTANCE zu erhalten, die einen Bereich von 740x30 Pixel definieren. Wir prüfen, ob „mouse_x“ und „mouse_y“ innerhalb dieses Bereichs liegen und setzen „is_header_hovered“.

Wenn „is_header_hovered“ true und „header_hovered“ false ist, setzen wir mit ObjectSetInteger die OBJPROP_BGCOLOR „MAIN_REC“ auf „clrDarkGreen“ und aktualisieren „header_hovered“ auf „true“; wenn „false“ und „header_hovered“ „true“ ist, wird clrSeaGreen wiederhergestellt und „header_hovered“ auf „false“ gesetzt, um eine visuelle Rückmeldung für das Schweben zu erhalten. Wir können nun das Gleiche mit den anderen Schaltflächen tun.

// 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;
   }
}

Wir erweitern die Funktion „updateHoverStates“, um Hover-Effekte für mehrere Schaltflächen hinzuzufügen, die visuelles Feedback für Nutzerinteraktionen liefern. Für „FILTER_CURR_BTN“ verwenden wir die Funktion ObjectGetInteger, um die Koordinaten „curr_btn_x“ und „curr_btn_y“ mit OBJPROP_XDISTANCE und „OBJPROP_YDISTANCE“, die einen Bereich von 110x26 Pixeln definieren, und prüfen, ob „mouse_x“ und „mouse_y“ innerhalb dieses Bereichs liegen, um „is_curr_btn_hovered“ zu setzen.

Wenn „is_curr_btn_hovered“ true und „filter_curr_hovered“ false ist, setzen wir „FILTER_CURR_BTN“ und OBJPROP_BGCOLOR mit ObjectSetInteger auf clrDarkGray und aktualisieren „filter_curr_hovered“ auf true; andernfalls wird der Wert „clrBlack“ wiederhergestellt und „filter_curr_hovered“ auf false gesetzt.

Wir wenden eine ähnliche Logik für „FILTER_IMP_BTN“ (120x26 Pixel) und „FILTER_TIME_BTN“ (70x26 Pixel) an und aktualisieren „filter_imp_hovered“ bzw. „filter_time_hovered“. Bei „CANCEL_BTN“ (50x30 Pixel) wird zwischen „clrDarkRed“ und „clrRed“ umgeschaltet, je nach „is_cancel_btn_hovered“ und „cancel_hovered“. Für „CURRENCY_BTNS“ durchlaufen wir eine Schleife durch jede Schaltfläche, berechnen die Positionen mit „panel_x“ und „panel_y“, prüfen „is_curr_hovered“ und schalten „clrLightGray“ und „clrNONE“ für „currency_btns_hovered[i]“ um.

Für die Schaltflächen „IMPACT_LABEL“ weisen wir „normal_color“ auf der Grundlage der Wirkungsstufe zu (z. B., „clrYellow“ für „Low“), berechnen „hover_color“ mit „ColorToDarken“ und schalten die Farben auf der Grundlage von „is_impact_hovered“ und „impact_btns_hovered[i]“ um, um dynamische Hover-Effekte zu gewährleisten. Damit die Änderungen wirksam werden, rufen wir die Funktion in der Ereignishandlung von OnChartEvent auf.

//+------------------------------------------------------------------+
//| 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);
   }
}

In OnChartEvent werden zunächst die Mauskoordinaten „mouse_x“ und „mouse_y“ aus den Parametern „lparam“ und „dparam“ sowie der Mausstatus „mouse_state“ aus „sparam“ erfasst, die die Position der Maus angeben und ob sie angeklickt oder losgelassen wurde. Wenn die „id“ des Ereignisses CHARTEVENT_MOUSE_MOVE ist, rufen wir die Funktion „updateHoverStates“ auf und übergeben „mouse_x“ und „mouse_y“, um zu prüfen, ob sich die Maus über der Kopfzeile oder den Schaltflächen befindet, und deren visuelles Erscheinungsbild entsprechend zu aktualisieren, um ein ansprechendes Hover-Feedback für die interaktiven Elemente des Dashboards zu gewährleisten. Anschließend müssen wir die Aktualisierungen der anderen Elemente beim Klicken erfassen und ihren Bewegungsstatus bestimmen.

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);
   }
}

Um die dynamischen Objektklick-Ereignisse zu verbessern, wenn die „id“ des Ereignisses CHARTEVENT_OBJECT_CLICK ist, rufen wir zunächst „UpdateFilterInfo“ und „CheckForNewsTrade“ auf, um den Filterstatus zu aktualisieren und die Handelsbedingungen zu prüfen. Wenn „sparam“ „CANCEL_BTN“ ist, setzen wir „isDashboardUpdate“ auf false und rufen „destroy_Dashboard“ auf, um das Dashboard zu schließen. Für „FILTER_CURR_BTN“, „FILTER_IMP_BTN“ oder „FILTER_TIME_BTN“ verwenden wir ObjectGetInteger, um „btn_state“ mit OBJPROP_STATE zu erhalten, aktualisieren „enableCurrencyFilter“, „enableImportanceFilter“ oder „enableTimeFilter“, und passen Sie den Text und die Farbe der Schaltfläche mit „ObjectSetString“ und ObjectSetInteger mit „OBJPROP_TEXT“ und „OBJPROP_COLOR“ an (z.g., „clrLime“ für aktiviert, „clrRed“ für deaktiviert); dann rufen wir „update_dashboard_values“ auf und bauen die Bildlaufleiste mit „createRecLabel“, „createLabel“ und „createButton“ neu auf für Elemente wie „SCROLL_LEADER“ und „SCROLL_SLIDER“, die mit „panel_x“ und „panel_y“ positioniert werden, und aktualisieren ihren Zustand mit „updateSliderPosition“ und „updateButtonColors“.

Für „CURRENCY_BTNS“ verwenden wir ObjectGetString, um „selected_curr“ zu erhalten, fügen es mit ArrayResize zu „curr_filter_selected“ hinzu oder entfernen es daraus, und aktualisieren das Dashboard auf ähnliche Weise. Für „IMPACT_LABEL“-Schaltflächen rufen wir „get_importance_level“ auf, um „selected_imp“ auf „selected_importance_lvl“ abzubilden, verwalten „imp_filter_selected“ und „impact_filter_selected“ Arrays, setzen „color_border“ mit „ObjectSetInteger“ und aktualisieren das Dashboard. Wenn „sparam“ „SCROLL_UP_REC“ oder „SCROLL_UP_LABEL“ ist, rufen wir „scrollUp“ auf, und für „SCROLL_DOWN_REC“ oder „SCROLL_DOWN_LABEL“ rufen wir „scrollDown“ auf, aktualisieren die Farben mit „updateButtonColors“ und zeichnen das Chart mit ChartRedraw neu, um die Änderungen zu berücksichtigen. Wenn wir das Programm kompilieren und ausführen, erhalten wir die folgenden Ergebnisse.

HOVER-SCHALTFLÄCHEN

Anhand der Visualisierung können wir sehen, dass wir visuelles Hover-Feedback für definierte Schaltflächen erhalten können. Jetzt müssen wir uns dem komplexen Teil zuwenden, nämlich der Bewegung des gesamten Dashboards. Das erreichen wir durch die folgende Logik.

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);
      }
   }

}

Hier erweitern wir die Funktion OnChartEvent, um eine Funktion zum Ziehen des Panels zu implementieren, die es uns ermöglicht, die Schnittstelle neu zu positionieren, wenn „isDashboardUpdate“ wahr ist und die „id“ des Ereignisses CHARTEVENT_MOUSE_MOVE ist. Wir rufen die Position der Kopfzeile mit ObjectGetInteger ab, um „header_x“ und „header_y“ aus „HEADER_LABEL“ mit „OBJPROP_XDISTANCE“ und „OBJPROP_YDISTANCE“ zu erhalten, die einen Bereich von 740x30 Pixel definieren. Wenn „prev_mouse_state“ gleich 0 und „mouse_state“ gleich 1 (Maustaste gedrückt) ist, prüfen wir, ob „mouse_x“ und „mouse_y“ innerhalb der Kopfzeile liegen, setzen „panel_dragging“ auf true, speichern „panel_drag_x“, „panel_drag_y“, „panel_start_x“ und „panel_start_y“ und deaktivieren das Scrollen des Charts mit ChartSetInteger und CHART_MOUSE_SCROLL.

Wenn „panel_dragging“ wahr und „mouse_state“ gleich 1 ist, berechnen wir den Verschiebe-Offset „dx“ und „dy“, aktualisieren „panel_x“ und „panel_y“und verwenden MathMax und MathMin, um sie innerhalb der „chart_width“ und „chart_height“ des Charts einzuschränken (erhalten über ChartGetInteger mit „CHART_WIDTH_IN_PIXELS“ und CHART_HEIGHT_IN_PIXELS). Wir positionieren dann alle UI-Elemente neu, indem wir ObjectSetInteger mit „OBJPROP_XDISTANCE“ und „OBJPROP_YDISTANCE“ für Objekte wie „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“ und „CURRENCY_BTNS“ sowie die Aktualisierung der Bildlaufleistenelemente („SCROLL_LEADER“, „SCROLL_UP_REC“, „SCROLL_UP_LABEL“, „SCROLL_DOWN_REC“, „SCROLL_DOWN_LABEL“, „SCROLL_SLIDER“), wenn „scroll_visible“ wahr ist, unter Verwendung von „panel_x“ und „panel_y“ mit Offsets.

Wir rufen „update_dashboard_values“ auf, um die Nachrichtenereignisse zu aktualisieren, die Handelsetiketten „NewsCountdown“ und „NewsTradeInfo“, falls vorhanden, mit ObjectFind neu zu positionieren und das Chart mit ChartRedraw neu zu zeichnen. Wenn „mouse_state“ gleich 0 ist (losgelassene Maus), setzen wir „panel_dragging“ auf false, aktivieren das Scrolling mit ChartSetInteger wieder und zeichnen das Chart neu, protokollieren Drag-Events mit Print, wenn „debugLogging“ aktiviert ist. Dann können wir die Bildlaufleistenfunktion handhaben, vorausgesetzt, das Panel befindet sich nicht im Ziehmodus, um Ereigniskonflikte zu vermeiden.

// 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;

Hier implementieren wir eine Funktion zum Ziehen der Bildlaufleiste, die es uns ermöglicht, durch News-Ereignisse zu blättern, wenn „scroll_visible“ true und „panel_dragging“ false ist, sodass kein Konflikt mit dem Ziehen von Panels entsteht. Wenn „prev_mouse_state“ gleich 0 und „mouse_state“ gleich 1 (Maustaste gedrückt) ist, wenden wir die Bildlauflogik an, die wir bereits ausgeführt haben, sowie die anderen Schieberegler und Freigabezustände. Nach dem Kompilieren erhalten wir die folgende Ausgabe.

BEWEGUNGSINTERAKTION

Anhand der Visualisierung können wir sehen, dass wir den Mauszeiger über die Schaltflächen bewegen und das Panel in die Nähe der Chartextrema ziehen können. Was jetzt noch bleibt, sind gründliche Backtests des Systems, und das wird im nächsten Abschnitt behandelt.


Tests und Validierung

Wir haben das verbesserte Dashboard getestet, um zu bestätigen, dass die dynamische Bewegung, die Hover-Schaltflächen und die Bildlaufleiste wie vorgesehen funktionieren und eine nahtlose Navigation durch die Nachrichtenereignisse ermöglichen. Unser Test konzentrierte sich auf das visuelle Feedback der Schaltflächen des Dashboards beim Hovern, die Bewegung der gesamten Komponenten des Panels in der Nähe des Charts und die Integrierbarkeit der Bildlaufleiste im Live-Modus. Wir haben diese Tests in einem prägnanten Graphics Interchange Format (GIF) festgehalten, um die Leistung des Dashboards visuell zu demonstrieren (siehe unten).

BACKTEST GIF

Anhand der Visualisierung können wir sehen, dass das Dashboard nahtlos funktioniert.


Schlussfolgerung

Abschließend haben wir die Serie über den MQL5-Wirtschaftskalender durch die Einführung eines beweglichen Dashboards und interaktiver Hover-Effekte aufgewertet, die uns eine flexible Positionierung und intuitive Navigation von Nachrichtenereignissen ermöglichen. Diese Verbesserungen, die auf der dynamischen Bildlaufleiste und der ausgefeilten Anzeige von Teil 9 aufbauen, gewährleisten eine nahtlose Interaktion sowohl im Live- als auch im Strategietester-Modus und bieten eine robuste und anpassungsfähige Plattform für nachrichtengesteuerte Handelsstrategien. Sie können nun dieses vielseitige Dashboard als Grundlage nutzen und es an Ihre individuellen Chart- und Handelsanforderungen anpassen.

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/18241

Beigefügte Dateien |
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 66): Verwendung von FrAMA-Mustern und des Force Index mit dem Punktprodukt-Kernel MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 66): Verwendung von FrAMA-Mustern und des Force Index mit dem Punktprodukt-Kernel
Der FrAMA-Indikator und der Force Index Oscillator sind Trend- und Volumeninstrumente, die bei der Entwicklung eines Expert Advisors kombiniert werden können. Wir knüpfen an unseren letzten Artikel an, in dem dieses Paar vorgestellt wurde, und betrachten die Anwendbarkeit des maschinellen Lernens auf dieses Paar. Wir verwenden ein neuronales Faltungsnetzwerk, das den Punkt-Produkt-Kernel bei der Erstellung von Prognosen mit den Eingaben dieser Indikatoren verwendet. Dies geschieht in einer nutzerdefinierten Signalklassendatei, die mit dem MQL5-Assistenten arbeitet, um einen Expert Advisor zusammenzustellen.
Datenwissenschaft und ML (Teil 40): Verwendung von Fibonacci-Retracements in Daten des maschinellen Lernens Datenwissenschaft und ML (Teil 40): Verwendung von Fibonacci-Retracements in Daten des maschinellen Lernens
Fibonacci-Retracements sind ein beliebtes Instrument der technischen Analyse, das Händlern hilft, potenzielle Umkehrzonen zu identifizieren. In diesem Artikel werden wir untersuchen, wie diese Retracement-Levels in Zielvariablen für maschinelle Lernmodelle umgewandelt werden können, damit diese den Markt mit Hilfe dieses leistungsstarken Tools besser verstehen können.
Entwicklung des Price Action Analysis Toolkit (Teil 26): Pin Bar, Engulfing Patterns und RSI Divergence (Multi-Pattern) Tool Entwicklung des Price Action Analysis Toolkit (Teil 26): Pin Bar, Engulfing Patterns und RSI Divergence (Multi-Pattern) Tool
Im Einklang mit unserem Ziel, praktische Tools zu Preis-Aktionen zu entwickeln, untersucht dieser Artikel die Erstellung eines EA, der die Muster von Pin-Bars und Engulfing erkennt und die RSI-Divergenz als Bestätigungsauslöser verwendet, bevor er Handelssignale erzeugt.
Nutzerdefinierte Debugging- und Profiling-Tools für die MQL5-Entwicklung (Teil I): Erweiterte Protokollierung Nutzerdefinierte Debugging- und Profiling-Tools für die MQL5-Entwicklung (Teil I): Erweiterte Protokollierung
Lernen Sie, wie Sie ein leistungsfähiges, nutzerdefiniertes Logging-Framework für MQL5 implementieren, das über einfache Print()-Anweisungen hinausgeht, indem es Schweregrade, mehrere Output-Handler und eine automatische Dateirotation unterstützt - alles on-the-fly konfigurierbar. Integrieren Sie das Singleton CLogger mit ConsoleLogHandler und FileLogHandler, um kontextbezogene Protokolle mit Zeitstempel sowohl in der Registerkarte Experten als auch in persistenten Dateien zu erfassen. Optimieren Sie Debugging und Performance-Tracing in Ihren Expert Advisors mit klaren, anpassbaren Protokollformaten und zentraler Steuerung.