English 日本語
preview
MQL5-Handelswerkzeuge (Teil 9): Entwicklung eines Ersteinrichtungsassistenten für Expert Advisors mit scrollbarem Leitfaden

MQL5-Handelswerkzeuge (Teil 9): Entwicklung eines Ersteinrichtungsassistenten für Expert Advisors mit scrollbarem Leitfaden

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

Einführung

In unserem letzten Artikel (Teil 8) haben wir ein Informations-Dashboard in MetaQuotes Language 5 (MQL5) entwickelt, um Positionen und Kontometrien für mehrere Symbole zu überwachen. In Teil 9 erstellen wir einen dynamischen Assistenten für die erste Ausführung, um neue Nutzer bei der ersten Ausführung eines Programms zu unterstützen. Die Assistenten für die Ersteinrichtung sind wichtige Werkzeuge zur Vereinfachung der Konfiguration komplexer Systeme, wie z. B. Expert Advisors (EAs) in MetaTrader 5. Sie führen neue Nutzer durch die Ersteinrichtung und bieten ein Orientierungsmodell, um eine optimale Leistung sicherzustellen. In diesem Artikel entwickeln wir einen MQL5-Erstanwender-Setup-Assistenten für Expert Advisors mit einem scrollbaren Dashboard mit dynamischem Text, interaktiven Schaltflächen und einem Kontrollkästchen für eine rationalisierte Konfiguration, die nur einmal beim ersten Start des Programms ausgeführt wird. Wir werden die folgenden Themen behandeln:

  1. Verstehen der Rolle und des Wertes eines Leitfadens für die Ersteinrichtung von Handelsprogrammen
  2. Implementation in MQL5
  3. Testen des Einrichtungsassistenten
  4. Schlussfolgerung

Am Ende verfügen Sie über einen interaktiven MQL5-Assistenten zur Verbesserung der EA-Initialisierung, den Sie an Ihre Handelsanforderungen anpassen können - legen wir los!


Verstehen der Rolle und des Wertes eines Leitfadens für die Ersteinrichtung von Handelsprogrammen

Ein Leitfaden für die Ersteinrichtung ist eine wichtige Funktion für Handelsprogramme wie Expert Advisors (EAs) in MetaTrader 5. Er bietet eine Schritt-für-Schritt-Führung für die Konfiguration wichtiger Einstellungen wie Losgrößen, Risikoniveaus und Handelsfilter und hilft Händlern, Fehler zu vermeiden, die zu Verlusten führen könnten, wie z. B. die Einstellung einer zu großen Losgröße, die zu einem übermäßigen Drawdown führt. Sagen wir, es ist mehr wie eine Orientierung, die neue Nutzer in die Programmschemata und -möglichkeiten einführt. Sein Wert liegt in der Vereinfachung des Einführungsprozesses für Händler aller Erfahrungsstufen, in der Gewährleistung einer ordnungsgemäßen Programmeinrichtung von Anfang an und in der Verwendung eines Mechanismus, der sich merkt, ob der Leitfaden angezeigt wurde, wodurch unnötige Aufforderungen bei zukünftigen Initialisierungen vermieden werden, um die Nutzererfahrung zu optimieren, insbesondere für Händler, die wiederholt Programme an Charts anhängen.

Unser Ansatz besteht darin, ein intuitives, scrollbares Dashboard zu entwerfen, das eine übersichtliche Einrichtungsanleitung mit visuell deutlichem Text (z. B. hervorgehobene Überschriften und anklickbare Links für den Support), interaktive Schaltflächen für Nutzeraktionen und ein Kontrollkästchen anzeigt, mit dem der Händler wählen kann, ob er die Anleitung bei künftigen Durchläufen überspringen möchte. Wir werden die globale Variablenfunktion von MQL5 nutzen, um die Auswahl des Nutzers in einer Variablen zu speichern, die die Build-Nummer des Handelsterminals (die Softwareversion) und das Betriebssystem (OS), auf dem das Programm zum ersten Mal ausgeführt wird, speichert und abruft. Wir erstellen eine zentrierte Nutzeroberfläche mit einer Kopf-, einer Haupt- und einer Fußzeile, die dynamische Textformatierungen für eine bessere Lesbarkeit, scrollbare Inhalte für umfassende Anweisungen und eine anpassungsfähige Größe für unterschiedliche Bildschirmauflösungen enthält. Dadurch wird sichergestellt, dass Händler Schritte wie die Einstellung von Risikoparametern oder die Aktivierung von AutoTrading leicht nachvollziehen können, was den Einrichtungsprozess nahtlos und effizient macht. Wir folgen der Anleitung der integrierten Struktur des Ein-Klick-Handels wie folgt.

ANSATZ SYSTEMVERGLEICH

Auf dem Bild können Sie sehen, wie wir vorgehen werden. Wir werden ein optionales, skaliertes Programmbild hinzufügen, um es von anderen Programmen zu unterscheiden. Kurz gesagt was wir erreichen wollen, ist die Visualisierung.

AUSGANG DES ASSISTENTEN-SYSTEMS


Implementation in MQL5

Um das Programm in MQL5 zu erstellen, öffnen Sie den MetaEditor, gehen zum Navigator, suchen den Ordner Experts, klicken auf die Registerkarte „Neu“ und folgen den Anweisungen, um die Datei zu erstellen. Sobald es erstellt ist, müssen wir in der Programmierumgebung einige globale Variablen deklarieren, die wir im gesamten Programm verwenden werden.

//+------------------------------------------------------------------+
//|                                      EA Initialization Setup.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link "https://t.me/Forex_Algo_Trader"
#property version "1.00"
#property strict
#property icon "1. Forex Algo-Trader.ico"

//+------------------------------------------------------------------+
//| Global variables for setup                                       |
//+------------------------------------------------------------------+
string GV_SETUP = "";                             //--- Store global variable name with build and OS
string g_scaled_image_resource = "";              //--- Store name of scaled image resource
int g_mainX = 0;                                  //--- Store calculated x-coordinate of main container
int g_mainY = 50;                                 //--- Set starting y-coordinate 50px below chart top
int g_mainWidth = 500;                            //--- Set main container width
int g_headerHeight = 50;                          //--- Set header height
int g_footerHeight = 40;                          //--- Set footer height
int g_padding = 10;                               //--- Set general padding
int g_textPadding = 10;                           //--- Set text padding
int g_spacing = 0;                                //--- Set spacing between header/body/footer
int g_lineSpacing = 3;                            //--- Set spacing between text lines
int g_minBodyHeight = 200;                        //--- Set minimum body height
int g_maxBodyHeight = 400;                        //--- Set maximum body height
int g_bottomMargin = 50;                          //--- Set bottom margin
int g_displayHeight = 0;                          //--- Store calculated display height
int g_mainHeight = 0;                             //--- Store calculated main container height
int g_adjustedLineHeight = 0;                     //--- Store adjusted line height for scrolling
int g_max_scroll = 0;                             //--- Store maximum scroll to prevent overflow
bool scroll_visible = false;                      //--- Track scrollbar visibility
bool mouse_in_body = false;                       //--- Track if mouse is in body
int scroll_pos = 0;                               //--- Store current scroll position
int prev_scroll_pos = -1;                         //--- Store previous scroll position
int slider_height = 20;                           //--- Set default slider height
bool movingStateSlider = false;                   //--- Track slider drag state
int mlbDownX_Slider = 0;                          //--- Store mouse x on slider click
int mlbDownY_Slider = 0;                          //--- Store mouse y on slider click
int mlbDown_YD_Slider = 0;                        //--- Store slider y on click
int g_total_height = 0;                           //--- Store total text height
int g_visible_height = 0;                         //--- Store visible text height
bool checkbox_checked = false;                    //--- Track checkbox state
bool ok_button_hovered = false;                   //--- Track OK button hover
bool cancel_button_hovered = false;               //--- Track Cancel button hover
bool checkbox_hovered = false;                    //--- Track checkbox hover
bool header_cancel_hovered = false;               //--- Track header cancel hover
bool scroll_up_hovered = false;                   //--- Track scroll up button hover
bool scroll_down_hovered = false;                 //--- Track scroll down button hover
bool scroll_slider_hovered = false;               //--- Track scroll slider hover
string ea_name = "Expert Advisor Setup Wizard";   //--- Set EA name
const int MAX_LINES = 100;                        //--- Set maximum text lines

//+------------------------------------------------------------------+
//| Enum for scrollbar mode                                          |
//+------------------------------------------------------------------+
enum ENUM_SCROLLBAR_MODE {                         // Define scrollbar visibility modes
   SCROLL_ALWAYS,                                  // Show scrollbar if needed
   SCROLL_ON_HOVER,                                // Show scrollbar on hover if needed
   SCROLL_NEVER                                    // Never show scrollbar, wheel only
};
ENUM_SCROLLBAR_MODE ScrollbarMode = SCROLL_ALWAYS; // Scrollbar shows when needed and remains

//+------------------------------------------------------------------+
//| Scrollbar object names                                           |
//+------------------------------------------------------------------+
#define SCROLL_LEADER "Setup_Scroll_Leader"        //--- Define scroll leader name
#define SCROLL_UP_REC "Setup_Scroll_Up_Rec"        //--- Define scroll up rectangle name
#define SCROLL_UP_LABEL "Setup_Scroll_Up_Label"    //--- Define scroll up label name
#define SCROLL_DOWN_REC "Setup_Scroll_Down_Rec"    //--- Define scroll down rectangle name
#define SCROLL_DOWN_LABEL "Setup_Scroll_Down_Label" //--- Define scroll down label name
#define SCROLL_SLIDER "Setup_Scroll_Slider"        //--- Define scroll slider name

//+------------------------------------------------------------------+
//| Dashboard object names                                           |
//+------------------------------------------------------------------+
#define SETUP_MAIN "Setup_MainContainer"           //--- Define main container name
#define SETUP_HEADER_BG "Setup_HeaderBg"           //--- Define header background name
#define SETUP_HEADER_IMAGE "Setup_HeaderImage"     //--- Define header image name
#define SETUP_HEADER_TITLE "Setup_HeaderTitle"     //--- Define header title name
#define SETUP_HEADER_SUBTITLE "Setup_HeaderSubtitle" //--- Define header subtitle name
#define SETUP_HEADER_CANCEL "Setup_HeaderCancel"   //--- Define header cancel button name
#define SETUP_BODY_BG "Setup_BodyBg"               //--- Define body background name
#define SETUP_FOOTER_BG "Setup_FooterBg"           //--- Define footer background name
#define SETUP_CHECKBOX_BG "Setup_CheckboxBg"       //--- Define checkbox background name
#define SETUP_CHECKBOX_LABEL "Setup_CheckboxLabel" //--- Define checkbox label name
#define SETUP_CHECKBOX_TEXT "Setup_CheckboxText"   //--- Define checkbox text name
#define SETUP_OK_BUTTON "Setup_OkButton"           //--- Define OK button name
#define SETUP_CANCEL_BUTTON "Setup_CancelButton"   //--- Define Cancel button name

//+------------------------------------------------------------------+
//| Enhanced setup text                                              |
//+------------------------------------------------------------------+
string setup_text =                                //--- Define setup guide text
"\nExpert Advisor Initialization Guide\n\n"
"Welcome to the Expert Advisor Setup Wizard – Your Gateway to Automated Trading in MetaTrader 5!\n\n"
"Unlock the power of algorithmic trading with this comprehensive setup guide. Designed for seamless integration, this wizard ensures your EA is configured optimally for performance, risk management, and reliability across diverse market conditions.\n\n"
"Key Features:\n"
"- Versatile Configuration: Tailor parameters for lot sizing, magic numbers, stop losses, and take profits to suit your trading style and broker requirements.\n"
"- Risk Controls: Implement drawdown limits, position sizing rules, and equity protection mechanisms to safeguard your capital.\n"
"- Filter Integration: Apply time-based, spread, and news filters to avoid unfavorable trading environments and enhance entry precision.\n"
"- Monitoring Tools: Access real-time panels for trade tracking, performance metrics, and alert notifications.\n"
"- Backtesting Support: Optimize settings with historical data, ensuring robust strategies before live deployment.\n"
"- Broker Adaptability: Supports netting and hedging modes, with customizable slippage and execution tolerances.\n\n"
"Initial Setup Instructions:\n"
"1. Attach the EA to a new chart of your selected symbol (e.g., EURUSD) on an appropriate timeframe (e.g., M15 for intraday strategies).\n"
"2. Adjust core inputs: Define risk parameters, enable/disable filters, and set notification preferences to align with your objectives.\n"
"3. Activate AutoTrading: Ensure MT5's AutoTrading is enabled, and verify EA permissions for secure operation.\n"
"4. Customize Interfaces: Toggle visibility of info panels, trade managers, and alerts for an intuitive user experience.\n"
"5. Validate Setup: Run a forward test on demo to confirm functionality and fine-tune based on observed behavior.\n\n"
"Important Notes:\n"
"- Risk Disclaimer: Automated trading carries inherent risks. Always use appropriate leverage and start with conservative settings on a demo account.\n"
"- Compatibility Check: Confirm broker supports required features like hedging; monitor spreads during volatile periods.\n"
"- Optimization Tips: Regularly review performance logs and adjust filters to adapt to evolving market dynamics.\n"
"- Security Measures: Use unique magic numbers and enable two-factor authentication for account protection.\n"
"- Legal Notice: No guarantees of profitability. Trade responsibly and consult professionals as needed.\n\n"
"Contact Methods:\n"
"NB:\n"
"********************************************\n"
" >*** FOR SUPPORT, QUERIES, OR CUSTOMIZATIONS, REACH OUT IMMEDIATELY: ***<\n"
" __________________________________________\n\n"
" 1. Email: mutiiriallan.forex@gmail.com (Primary Support Channel)\n"
" 2. Telegram Channel: @ForexAlgo-Trader (Updates & Community)\n"
" 3. Telegram Group: https://t.me/Forex_Algo_Trader (Direct Assistance & Discussions)\n\n"
"********************************************\n\n"
"Thank you for choosing our Expert Advisor solutions. Configure wisely, trade confidently, and elevate your trading journey! 🚀\n";

Um die Grundlage des Assistenten einzurichten, verwenden wir globale Variablen, um das Layout des Dashboards zu verwalten. Wir richten Koordinaten („g_mainX“, „g_mainY“ auf 50), Abmessungen („g_mainWidth“ auf 500, „g_headerHeight“ auf 50, „g_footerHeight“ auf 40) und Auffüllungen („g_padding“, „g_textPadding“ auf 10) ein. Wir definieren auch Abstände („g_spacing“ bei 0, „g_lineSpacing“ bei 3), Höhenbegrenzungen („g_minBodyHeight“ bei 200, „g_maxBodyHeight“ bei 400) und Ränder („g_bottomMargin“ bei 50). Für den Bildlauf setzen wir Variablen wie „scroll_visible“, „scroll_pos“ und „slider_height“ auf 20. Mausinteraktionszustände umfassen „movingStateSlider“ und „mlbDownX_Slider“. Wir fügen Hover-Flags für Schaltflächen und Kontrollkästchen hinzu. Wir setzen „ea_name“ auf „Expert Advisor Setup Wizard“ und „MAX_LINES“ auf 100.

Die Enumeration „ENUM_SCROLLBAR_MODE“ definiert das Verhalten der Bildlaufleiste („SCROLL_ALWAYS“, „SCROLL_ON_HOVER“, „SCROLL_NEVER“), wobei der Standardwert „SCROLL_ALWAYS“ ist. Wir definieren Konstanten für Objektnamen wie „SETUP_MAIN“, „SETUP_HEADER_BG“, „SCROLL_LEADER“ und andere für eine einheitliche Benennung von Dashboard- und Scrollbar-Elementen. Schließlich erstellen wir die Zeichenfolge „setup_text“, einen umfassenden Leitfaden mit Abschnitten zu Funktionen, Einrichtungsanweisungen, Hinweisen und Kontaktmethoden, der mit Überschriften und nummerierten Schritten formatiert ist und ein System zur Organisation der Nutzeroberfläche und des Inhalts des Assistenten für die Nutzerinteraktion bietet. Sie können die Positionierung oder den Inhalt ändern; wir haben einfach beliebige Werte verwendet. Als Nächstes müssen wir das Bild konfigurieren, das als Kopfzeilensymbol verwendet werden soll. Sie können diesen Schritt auslassen, wenn Sie ihn nicht wünschen. Wir müssen unsere Bilddatei in eine Bitmap-Datei (BMP) umwandeln. Nach der Konvertierung sollte das Bild die folgenden Eigenschaften aufweisen.

BITMAP-DATEI BILD

Anhand des Bildes können Sie erkennen, dass es sich bei unserem Bild um eine Bitmap-Datei handelt. Sie müssen sich nicht um die Größe oder die Abmessungen kümmern, da wir später bei Bedarf in jede beliebige Richtung skalieren können. Denken Sie daran, die Datei im gleichen Ordner wie die Programmdatei abzulegen. Jetzt müssen wir die Datei als Ressource in das Programm aufnehmen.

#resource "1. Forex Algo-Trader SQ.bmp"
#define resourceImg "::1. Forex Algo-Trader SQ.bmp"

Wir verwenden die Direktive „#resource“, um die Bilddatei mit dem Namen „1. Forex Algo-Trader SQ.bmp“ einzubinden, und definieren eine Konstante „resourceImg“ als „::1.Forex Algo-Trader SQ.bmp“, um das Bild im Programm zu referenzieren. Dadurch erhält das Dashboard des Assistenten ein professionelles und markengerechtes Erscheinungsbild. Wir werden nun mit der Erstellung der Schnittstelle beginnen und benötigen dazu einige Hilfsfunktionen. Lassen Sie uns Funktionen definieren, um die erforderlichen Rechteckbeschriftungen, Texte, Bilder und Schaltflächen zu erstellen.

//+------------------------------------------------------------------+
//| Create rectangle label                                           |
//+------------------------------------------------------------------+
bool createRecLabel(string objName, int xD, int yD, int xS, int yS,
                    color clrBg, int widthBorder, color clrBorder = clrNONE,
                    ENUM_BORDER_TYPE borderType = BORDER_FLAT,
                    ENUM_LINE_STYLE borderStyle = STYLE_SOLID,
                    ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER) {
   ResetLastError();                                              //--- Reset error code
   if (!ObjectCreate(0, objName, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { //--- Create rectangle label
      Print(__FUNCTION__, ": failed to create rec label! Error code = ", GetLastError()); //--- Log failure
      return false;                                               //--- Return failure
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD);           //--- Set x distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD);           //--- Set y distance
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS);               //--- Set width
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS);               //--- Set height
   ObjectSetInteger(0, objName, OBJPROP_CORNER, corner);          //--- Set corner
   ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg);          //--- Set background color
   ObjectSetInteger(0, objName, OBJPROP_BORDER_TYPE, borderType); //--- Set border type
   ObjectSetInteger(0, objName, OBJPROP_STYLE, borderStyle);      //--- Set border style
   ObjectSetInteger(0, objName, OBJPROP_WIDTH, widthBorder);      //--- Set border width
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clrBorder);        //--- Set border color
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);             //--- Set to foreground
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);            //--- Disable state
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);       //--- Disable selectability
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);         //--- Disable selection
   return true;                                                   //--- Return success
}

//+------------------------------------------------------------------+
//| Create button                                                    |
//+------------------------------------------------------------------+
bool createButton(string objName, int xD, int yD, int xS, int yS,
                  string txt = "", color clrTxt = clrBlack, int fontSize = 12,
                  color clrBg = clrNONE, color clrBorder = clrNONE,
                  string font = "Arial",
                  ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER, bool isBack = false) {
   ResetLastError();                                              //--- Reset error code
   if (!ObjectCreate(0, objName, OBJ_BUTTON, 0, 0, 0)) {          //--- Create button
      Print(__FUNCTION__, ": failed to create the button! Error code = ", GetLastError()); //--- Log failure
      return false;                                               //--- Return failure
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD);           //--- Set x distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD);           //--- Set y distance
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS);               //--- Set width
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS);               //--- Set height
   ObjectSetInteger(0, objName, OBJPROP_CORNER, corner);          //--- Set corner
   ObjectSetString(0, objName, OBJPROP_TEXT, txt);                //--- Set button text
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt);           //--- Set text color
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize);      //--- Set font size
   ObjectSetString(0, objName, OBJPROP_FONT, font);               //--- Set font
   ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg);          //--- Set background color
   ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, clrBorder); //--- Set border color
   ObjectSetInteger(0, objName, OBJPROP_BACK, isBack);            //--- Set background/foreground
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);            //--- Disable state
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);       //--- Disable selectability
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);         //--- Disable selection
   return true;                                                   //--- Return success
}

//+------------------------------------------------------------------+
//| Create text label                                                |
//+------------------------------------------------------------------+
bool createLabel(string objName, int xD, int yD,
                 string txt, color clrTxt = clrBlack, int fontSize = 12,
                 string font = "Arial",
                 ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER, ENUM_ANCHOR_POINT anchor = ANCHOR_LEFT_UPPER) {
   ResetLastError();                                              //--- Reset error code
   if (!ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0)) {           //--- Create label
      Print(__FUNCTION__, ": failed to create the label! Error code = ", GetLastError()); //--- Log failure
      return false;                                               //--- Return failure
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD);           //--- Set x distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD);           //--- Set y distance
   ObjectSetInteger(0, objName, OBJPROP_CORNER, corner);          //--- Set corner
   ObjectSetString(0, objName, OBJPROP_TEXT, txt);                //--- Set label text
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt);           //--- Set text color
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize);      //--- Set font size
   ObjectSetString(0, objName, OBJPROP_FONT, font);               //--- Set font
   ObjectSetInteger(0, objName, OBJPROP_ANCHOR, anchor);          //--- Set anchor
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);             //--- Set to foreground
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);            //--- Disable state
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);       //--- Disable selectability
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);         //--- Disable selection
   return true;                                                   //--- Return success
}

//+------------------------------------------------------------------+
//| Create bitmap label                                              |
//+------------------------------------------------------------------+
bool createBitmapLabel(string objName, int xD, int yD, int xS, int yS,
                       string bitmapPath, color clr, ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER) {
   ResetLastError();                                              //--- Reset error code
   if (!ObjectCreate(0, objName, OBJ_BITMAP_LABEL, 0, 0, 0)) {    //--- Create bitmap label
      Print(__FUNCTION__, ": failed to create bitmap label! Error code = ", GetLastError()); //--- Log failure
      return false;                                               //--- Return failure
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD);           //--- Set x distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD);           //--- Set y distance
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS);               //--- Set width
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS);               //--- Set height
   ObjectSetInteger(0, objName, OBJPROP_CORNER, corner);          //--- Set corner
   ObjectSetString(0, objName, OBJPROP_BMPFILE, bitmapPath);      //--- Set bitmap path
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);              //--- Set color
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);             //--- Set to foreground
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);            //--- Disable state
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);       //--- Disable selectability
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);         //--- Disable selection
   return true;                                                   //--- Return success
}

Hier implementieren wir die wichtigsten grafischen Komponenten für das interaktive Dashboard. Zunächst entwickeln wir die Funktion „createRecLabel“, die ein Rechteck-Kennzeichen (OBJ_RECTANGLE_LABEL) mit den angegebenen Koordinaten, der Größe, der Hintergrundfarbe, der Breite des Rahmens, dem Typ (BORDER_FLAT), dem Stil (STYLE_SOLID), und der Ecke (CORNER_LEFT_UPPER) unter Verwendung der Funktionen ObjectCreate und ObjectSetInteger erstellt, wobei Fehler mit Print protokolliert werden, wenn die Erstellung fehlschlägt, und es in den nicht auswählbaren Vordergrund gestellt wird. Dann implementieren wir die Funktion „createButton“, die eine Schaltfläche (OBJ_BUTTON) mit Text, Farbe, Schriftgröße, Schriftart (standardmäßig „Arial“), Hintergrund, Rahmen und Ecke im gleichen Format erstellt.

Als Nächstes erstellen wir die Funktion „createLabel“, die eine Textbeschriftung (OBJ_LABEL) mit Text, Farbe, Schriftgröße, Schriftart, Ecke und Anker (ANCHOR_LEFT_UPPER) unter Verwendung ähnlicher Aufrufe zur Objekterstellung und Eigenschaftseinstellung erzeugt und bei Bedarf Fehler protokolliert. Schließlich erstellen wir die Funktion „createBitmapLabel“, die ein Bitmap-Etikett (OBJ_BITMAP_LABEL) für Bilder mit Koordinaten, Größe, Bitmap-Pfad, Farbe und Ecke erstellt, indem wir „ObjectCreate“ verwenden und Eigenschaften festlegen, um eine nicht auswählbare Vordergrundanzeige zu gewährleisten, und alle Fehler protokollieren. Dadurch wird sichergestellt, dass wir ein System zum Rendern der visuellen Elemente des Assistenten, wie Container, Schaltflächen, Text und Bilder, erstellen.

Wir können nun einige Hilfsfunktionen erstellen, die uns bei der Berechnung der Höhe des Dashboards helfen, denn wir wollen es dynamisch zentrieren, die Textschriftarten dynamisch entsprechend der Bildschirmauflösung abrufen, sodass einige Texte auf verschiedenen Geräten nicht zu klein oder zu groß erscheinen, und den Text abschneiden, wenn er lang ist, um einen möglichen Überlauf zu vermeiden.

//+------------------------------------------------------------------+
//| Calculate font size based on screen DPI                          |
//+------------------------------------------------------------------+
int getFontSizeByDPI(int baseFontSize, int baseDPI = 96) {
   int currentDPI = (int)TerminalInfoInteger(TERMINAL_SCREEN_DPI); //--- Retrieve current screen DPI
   int scaledFontSize = (int)(baseFontSize * (double)baseDPI / currentDPI); //--- Calculate scaled font size
   return scaledFontSize;                                          //--- Return scaled font size
}

//+------------------------------------------------------------------+
//| Calculate dashboard dimensions                                   |
//+------------------------------------------------------------------+
void CalculateDashboardDimensions() {
   long chart_width = ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);   //--- Get chart width
   long chart_height = ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Get chart height
   g_mainX = (int)((chart_width - g_mainWidth) / 2); //--- Center main container horizontally
   int available_height = (int)(chart_height - g_mainY - g_bottomMargin - g_headerHeight - g_footerHeight - 2 * g_spacing); //--- Calculate available height
   g_displayHeight = MathMin(g_maxBodyHeight, MathMax(g_minBodyHeight, available_height)); //--- Set display height
   g_mainHeight = g_headerHeight + g_displayHeight + g_footerHeight + 2 * g_spacing; //--- Calculate main container height
}

//+------------------------------------------------------------------+
//| Truncate string                                                  |
//+------------------------------------------------------------------+
string truncateString(string valueStr, int startPos, int lengthStr = -1, int threshHold = 0, bool isEllipsis = false) {
   string result = valueStr;                                        //--- Initialize result
   if (StringLen(valueStr) > threshHold && threshHold > 0) {        //--- Check if truncation needed
      result = StringSubstr(valueStr, startPos, lengthStr);         //--- Extract substring
      if (isEllipsis) result += "...";                              //--- Add ellipsis if needed
   }
   return result;                                                   //--- Return truncated string
}

Hier implementieren wir Utility-Funktionen, die eine adaptive Größenanpassung und Textformatierung gewährleisten. Wir entwickeln die Funktion „getFontSizeByDPI“, die DPI (Dots Per Inch) des Bildschirms mit Hilfe von TerminalInfoInteger mit TERMINAL_SCREEN_DPI abruft, eine skalierte Schriftgröße berechnet, indem sie die Basisschriftgröße relativ zu einem Standard-DPI (96) mit Hilfe eines einfachen Verhältnisses anpasst, und das Ergebnis für eine konsistente Textanzeige auf allen Geräten zurückgibt.

Dann erstellen wir die Funktion „CalculateDashboardDimensions“, die die Breite und Höhe des Charts über ChartGetInteger mit CHART_WIDTH_IN_PIXELS und „CHART_HEIGHT_IN_PIXELS“ abruft, den Hauptcontainer horizontal zentriert, indem sie „g_mainX“ auf die Hälfte der Differenz zwischen der Breite des Charts und „g_mainWidth“ setzt, die verfügbare Höhe berechnet, indem sie „g_mainY“, „g_bottomMargin“, „g_headerHeight“, „g_footerHeight“ und zweimal „g_spacing“ von der Charthöhe subtrahiert wird, „g_displayHeight“ auf „g_minBodyHeight“ und „g_maxBodyHeight“ mit Hilfe von MathMin und MathMax ein und „g_mainHeight“ als Summe der Kopf-, Körper- und Fußzeilenhöhen sowie des Abstands berechnet.

Schließlich implementieren wir die Funktion „truncateString“, die die eingegebene Zeichenkette unverändert zurückgibt, wenn ihre Länge unter einem Schwellenwert oder Null liegt, andernfalls extrahiert sie eine Teilzeichenkette mit StringSubstr aus „startPos“ für „lengthStr“ Zeichen und fügt eine Ellipse hinzu, falls angegeben, um Textüberlauf zu verwalten. Mit diesen Funktionen können wir die Implementierung beginnen, indem wir das Haupt-Dashboard erstellen. Wir werden seine Logik zur Modularisierung in einer Funktion unterbringen.

//+------------------------------------------------------------------+
//| Show the setup dashboard                                         |
//+------------------------------------------------------------------+
void ShowDashboard() {
   checkbox_checked = false;                         //--- Reset checkbox state
   createRecLabel(SETUP_MAIN, g_mainX, g_mainY, g_mainWidth, g_mainHeight, C'20,20,20', 1, C'40,40,40', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create main container
   createRecLabel(SETUP_HEADER_BG, g_mainX, g_mainY, g_mainWidth, g_headerHeight, C'45,45,45', 1, C'60,60,60', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create header background

}

Wir implementieren die Funktion „ShowDashboard“ und setzen zunächst den Status der Checkbox auf false zurück, um einen sauberen Start für die Nutzerinteraktion zu gewährleisten. Dann rufen wir „createRecLabel“ auf, um den Hauptcontainer („SETUP_MAIN“) an den Koordinaten „g_mainX“ und „g_mainY“ mit den Abmessungen „g_mainWidth“ und „g_mainHeight“ unter Verwendung eines dunkelgrauen Hintergrunds (C'20,20,20'), eines 1-Pixel-Rahmens (C'40,40,40'), eines flachen Rahmentyps, eines einfarbigen Stils und einer Ausrichtung der linken oberen Ecke.

Als Nächstes verwenden wir „createRecLabel“, um den Kopfzeilenhintergrund („SETUP_HEADER_BG“) an der gleichen x-Koordinate und „g_mainY“ zu erstellen, der sich über „g_mainWidth“ und „g_headerHeight“ erstreckt mit einem etwas helleren grauen Hintergrund (C'45,45,45') und Rahmen (C'60,60,60'), wobei das einheitliche Styling beibehalten und die grundlegende visuelle Struktur des Dashboards des Assistenten wiedergegeben wird. Wir müssen diese Funktion nun in der Ereignisbehandlung von OnInit aufrufen.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   int build = (int)TerminalInfoInteger(TERMINAL_BUILD); //--- Get terminal build number
   string os = TerminalInfoString(TERMINAL_OS_VERSION);  //--- Get operating system version
   StringReplace(os, " ", "_");                          //--- Replace spaces with underscores
   StringReplace(os, ".", "_");                          //--- Replace dots with underscores
   StringReplace(os, ",", "_");                          //--- Replace commas with underscores
   GV_SETUP = "EA_Setup_" + IntegerToString(build) + "_" + os; //--- Set global variable name
   CalculateDashboardDimensions();                       //--- Calculate dashboard dimensions
   if (!GlobalVariableCheck(GV_SETUP)) {                 //--- Check if global variable exists
      Print("Global variable '" + GV_SETUP + "' not found. Creating new one with value FALSE (0.0)."); //--- Log variable creation
      GlobalVariableSet(GV_SETUP, 0.0);                  //--- Set variable to false
      ShowDashboard();                                   //--- Display dashboard
   } else {                                              //--- Variable exists
      double val = GlobalVariableGet(GV_SETUP);          //--- Get variable value
      if (val == 1.0) {                                  //--- Check if set to never show
         // No dashboard
      } else {                                           //--- Show dashboard
         ShowDashboard();                                //--- Display dashboard
      }
   }
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);     //--- Enable mouse move events
   ChartSetInteger(0, CHART_EVENT_MOUSE_WHEEL, true);    //--- Enable mouse wheel events
   ChartSetInteger(0, CHART_MOUSE_SCROLL, true);         //--- Enable chart scrolling
   return(INIT_SUCCEEDED);                               //--- Return initialization success
}

Wir fahren fort, die Initialisierungslogik zu implementieren, um das Anzeigeverhalten in OnInit zu verwalten, wo wir die MetaTrader 5 Terminal-Build-Nummer mit TerminalInfoInteger und TERMINAL_BUILD und die Betriebssystemversion mit TerminalInfoString und TERMINAL_OS_VERSION abrufen, Leerzeichen, Punkte und Kommas im OS-String mit Unterstrichen über StringReplace ersetzen, um einen sauberen globalen Variablennamen zu erstellen („GV_SETUP“) zu erstellen, der als „EA_Setup_<build>_<OS>“ formatiert ist. Sie könnten auch eine andere magische Zahl oder einen Programmnamen und eine Versionsnummer verwenden, aber diese Kombination war einfach genial und einzigartig. Wir rufen „CalculateDashboardDimensions“ auf, um das Layout des Dashboards auf der Grundlage der Chartabmessungen einzurichten.

Dann prüfen wir mit GlobalVariableCheck, ob die globale Variable existiert; wenn nicht, protokollieren wir ihre Erstellung mit „Print“, setzen sie mit GlobalVariableSet auf 0,0 (false) und zeigen das Dashboard mit „ShowDashboard“ an. Wenn die Variable existiert, rufen wir ihren Wert mit GlobalVariableGet ab und zeigen das Dashboard nur dann an, wenn der Wert nicht 1,0 ist (was bedeutet, dass der Nutzer die Variable nicht mehr anzeigen möchte). Schließlich aktivieren wir die Mausbewegungs- und Mausrad-Ereignisse mit ChartSetInteger unter Verwendung von CHART_EVENT_MOUSE_MOVE, „CHART_EVENT_MOUSE_WHEEL“ und „CHART_MOUSE_SCROLL“, um interaktive Funktionen zu unterstützen, die im OnChartEvent-Ereignishandler hilfreich sein werden, und geben INIT_SUCCEEDED für eine erfolgreiche Initialisierung zurück. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

BASIS-DASHBOARD MIT KOPFZEILE

Da wir nun einen Kopfbereich haben, fügen wir die Bilddatei hinzu. Wir müssen das Bild skalieren, also definieren wir die Funktionen, die diese Aufgabe übernehmen, und rufen sie dann auf, um unsere Bilddatei zu skalieren.

//+------------------------------------------------------------------+
//| Scale image using bicubic interpolation                          |
//+------------------------------------------------------------------+
void ScaleImage(uint &pixels[], int original_width, int original_height, int new_width, int new_height) {
   uint scaled_pixels[];                                               //--- Declare scaled pixel array
   ArrayResize(scaled_pixels, new_width * new_height);                 //--- Resize scaled pixel array
   for (int y = 0; y < new_height; y++) {                              //--- Iterate through new height
      for (int x = 0; x < new_width; x++) {                            //--- Iterate through new width
         double original_x = (double)x * original_width / new_width;   //--- Calculate original x
         double original_y = (double)y * original_height / new_height; //--- Calculate original y
         uint pixel = BicubicInterpolate(pixels, original_width, original_height, original_x, original_y); //--- Interpolate pixel
         scaled_pixels[y * new_width + x] = pixel;                     //--- Store scaled pixel
      }
   }
   ArrayResize(pixels, new_width * new_height);                        //--- Resize original pixel array
   ArrayCopy(pixels, scaled_pixels);                                   //--- Copy scaled pixels
}

//+------------------------------------------------------------------+
//| Perform bicubic interpolation for a single pixel                 |
//+------------------------------------------------------------------+
uint BicubicInterpolate(uint &pixels[], int width, int height, double x, double y) {
   int x0 = (int)x;                                                    //--- Get integer x coordinate
   int y0 = (int)y;                                                    //--- Get integer y coordinate
   double fractional_x = x - x0;                                       //--- Calculate fractional x
   double fractional_y = y - y0;                                       //--- Calculate fractional y
   int x_indices[4], y_indices[4];                                     //--- Declare index arrays
   for (int i = -1; i <= 2; i++) {                                     //--- Calculate indices
      x_indices[i + 1] = MathMin(MathMax(x0 + i, 0), width - 1);       //--- Clamp x indices
      y_indices[i + 1] = MathMin(MathMax(y0 + i, 0), height - 1);      //--- Clamp y indices
   }
   uint neighborhood_pixels[16];                                       //--- Declare neighborhood pixels
   for (int j = 0; j < 4; j++) {                                       //--- Iterate y indices
      for (int i = 0; i < 4; i++) {                                    //--- Iterate x indices
         neighborhood_pixels[j * 4 + i] = pixels[y_indices[j] * width + x_indices[i]]; //--- Store pixel
      }
   }
   uchar alpha_components[16], red_components[16], green_components[16], blue_components[16]; //--- Declare color components
   for (int i = 0; i < 16; i++) {                                      //--- Extract components
      GetArgb(neighborhood_pixels[i], alpha_components[i], red_components[i], green_components[i], blue_components[i]); //--- Get ARGB components
   }
   uchar alpha_out = (uchar)BicubicInterpolateComponent(alpha_components, fractional_x, fractional_y); //--- Interpolate alpha
   uchar red_out = (uchar)BicubicInterpolateComponent(red_components, fractional_x, fractional_y); //--- Interpolate red
   uchar green_out = (uchar)BicubicInterpolateComponent(green_components, fractional_x, fractional_y); //--- Interpolate green
   uchar blue_out = (uchar)BicubicInterpolateComponent(blue_components, fractional_x, fractional_y); //--- Interpolate blue
   return (alpha_out << 24) | (red_out << 16) | (green_out << 8) | blue_out; //--- Combine components
}

//+------------------------------------------------------------------+
//| Perform bicubic interpolation for a single color component       |
//+------------------------------------------------------------------+
double BicubicInterpolateComponent(uchar &components[], double fractional_x, double fractional_y) {
   double weights_x[4];                                                 //--- Declare x weights
   double t = fractional_x;                                             //--- Set x fraction
   weights_x[0] = (-0.5 * t * t * t + t * t - 0.5 * t);                 //--- Calculate x weight 0
   weights_x[1] = (1.5 * t * t * t - 2.5 * t * t + 1);                  //--- Calculate x weight 1
   weights_x[2] = (-1.5 * t * t * t + 2 * t * t + 0.5 * t);             //--- Calculate x weight 2
   weights_x[3] = (0.5 * t * t * t - 0.5 * t * t);                      //--- Calculate x weight 3
   double y_values[4];                                                  //--- Declare y values
   for (int j = 0; j < 4; j++) {                                        //--- Iterate y indices
      y_values[j] = weights_x[0] * components[j * 4 + 0] + weights_x[1] * components[j * 4 + 1] +
                    weights_x[2] * components[j * 4 + 2] + weights_x[3] * components[j * 4 + 3]; //--- Calculate y value
   }
   double weights_y[4];                                                 //--- Declare y weights
   t = fractional_y;                                                    //--- Set y fraction
   weights_y[0] = (-0.5 * t * t * t + t * t - 0.5 * t);                 //--- Calculate y weight 0
   weights_y[1] = (1.5 * t * t * t - 2.5 * t * t + 1);                  //--- Calculate y weight 1
   weights_y[2] = (-1.5 * t * t * t + 2 * t * t + 0.5 * t);             //--- Calculate y weight 2
   weights_y[3] = (0.5 * t * t * t - 0.5 * t * t);                      //--- Calculate y weight 3
   double result = weights_y[0] * y_values[0] + weights_y[1] * y_values[1] +
                   weights_y[2] * y_values[2] + weights_y[3] * y_values[3]; //--- Calculate final value
   return MathMax(0, MathMin(255, result));        //--- Clamp result to valid range
}

//+------------------------------------------------------------------+
//| Extract ARGB components from a pixel                             |
//+------------------------------------------------------------------+
void GetArgb(uint pixel, uchar &alpha, uchar &red, uchar &green, uchar &blue) {
   alpha = (uchar)((pixel >> 24) & 0xFF);                               //--- Extract alpha component
   red = (uchar)((pixel >> 16) & 0xFF);                                 //--- Extract red component
   green = (uchar)((pixel >> 8) & 0xFF);                                //--- Extract green component
   blue = (uchar)(pixel & 0xFF);                                        //--- Extract blue component
}

Hier implementieren wir Bildverarbeitungsfunktionen, um die visuelle Qualität zu verbessern. Zunächst entwickeln wir die Funktion „ScaleImage“, die die Größe eines Bildes ändert, indem sie ein neues Pixel-Array mit ArrayResize für die Zieldimensionen („new_width“ x „new_height“) ein neues Pixel-Array mit ArrayRize für die Zieldimensionen („new_width x „new_ight“) erstellt, jedes Pixel durchläuft, die Koordinaten mithilfe der proportionalen Skalierung auf das ursprüngliche Bild abbildet und „BicubicInterpolate“ aufruft, um den interpolierten Pixelwert zu berechnen, und diesen im skalierten Array speichert, bevor er mit der Funktion ArrayCopy zurück in das ursprüngliche Array kopiert wird.

Dann erstellen wir die Funktion „BicubicInterpolate“, die die Farbe eines Pixels an nicht-ganzzahligen Koordinaten („x“, „y“) berechnet, indem sie eine 4x4-Nachbarschaft von Pixeln auswählt, die Indizes mit MathMin und MathMax beschränkt, um innerhalb der Bildgrenzen zu bleiben, und die ARGB-Komponenten mit „GetArgb“ extrahieren, jede Komponente mit „BicubicInterpolateComponent“ interpolieren und sie bitweisen zu einem endgültigen Pixelwert kombinieren. Als Nächstes implementieren wir „BicubicInterpolateComponent“, das die bikubische Interpolation auf eine einzelne Farbkomponente anwendet, indem es kubische Gewichte für x- und y-Fraktionskoordinaten berechnet, y-Zwischenwerte aus einem 4x4-Raster berechnet und sie mit y-Gewichten kombiniert und das Ergebnis zwischen 0 und 255 beschränkt. Schließlich extrahiert die Funktion „GetArgb“ die Alpha-, Rot-, Grün- und Blaukomponenten eines Pixels unter Verwendung von bitweisen Verschiebungen und Masken. Dadurch wird die Skalierung unserer Bilder an das Armaturenbrett oder den vorgesehenen Bereich angepasst. Wir können nun diese Funktion aufrufen, um unser Ressourcenbild zu skalieren und anzuzeigen.

uint img_pixels[];                                       //--- Declare pixel array for image
uint orig_width = 0, orig_height = 0;                    //--- Initialize image dimensions
bool image_loaded = ResourceReadImage(resourceImg, img_pixels, orig_width, orig_height); //--- Load image resource
if (image_loaded && orig_width > 0 && orig_height > 0) { //--- Check image load success
   ScaleImage(img_pixels, (int)orig_width, (int)orig_height, 40, 40); //--- Scale image to 40x40
   g_scaled_image_resource = "::SetupHeaderImageScaled"; //--- Set scaled image resource name
   if (ResourceCreate(g_scaled_image_resource, img_pixels, 40, 40, 0, 0, 40, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create scaled resource
      createBitmapLabel(SETUP_HEADER_IMAGE, g_mainX + 5, g_mainY + (g_headerHeight - 40)/2, 40, 40, g_scaled_image_resource, clrWhite, CORNER_LEFT_UPPER); //--- Create scaled image label
   } else {                                              //--- Handle resource creation failure
      Print("Failed to create scaled image resource");   //--- Log failure
      createBitmapLabel(SETUP_HEADER_IMAGE, g_mainX + 5, g_mainY + (g_headerHeight - 40)/2, 40, 40, resourceImg, clrWhite, CORNER_LEFT_UPPER); //--- Use original image
   }
} else {                                                 //--- Handle image load failure
   Print("Failed to load original image resource");      //--- Log failure
   createBitmapLabel(SETUP_HEADER_IMAGE, g_mainX + 5, g_mainY + (g_headerHeight - 40)/2, 40, 40, resourceImg, clrWhite, CORNER_LEFT_UPPER); //--- Use original image
}

Um die Logik zum Laden und Skalieren des Bildes zu implementieren, deklarieren wir ein Pixel-Array „img_pixels“ und setzen die Dimensionen „orig_width“ und „orig_height“ auf Null, dann laden wir das Ressourcenbild mit „ResourceReadImage“ mit „resourceImg“, Überprüfung, ob erfolgreich („image_loaded“ und Dimensionen > 0); wenn ja, rufen wir „ScaleImage“ auf, um die Größe auf 40x40 Pixel zu ändern - was Sie ändern können, indem Sie „g_scaled_image_resource“ auf „::SetupHeaderImageScaled“, und erstellen eine neue Ressource mit ResourceCreate im ARGB-Format, gefolgt von „createBitmapLabel“, um das skalierte Bild an der Position der Kopfzeile mit weißer Farbe anzuzeigen. Wenn die Erstellung der Ressource fehlschlägt, protokollieren wir die Instanz und greifen auf das ursprüngliche Bild zurück; wenn das Laden fehlschlägt, protokollieren und verwenden wir das ursprüngliche „resourceImg“ direkt mit der Funktion „createBitmapLabel“. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

HEADER MIT SKALIERTER BILDDATEI

Nachdem wir nun die Bilddatei fertig haben, können wir die anderen Kernelemente wie folgt implementieren.

string truncated_name = truncateString(ea_name, 0, -1, 20, true); //--- Truncate EA name
int titleFontSize = getFontSizeByDPI(14);           //--- Calculate title font size
createLabel(SETUP_HEADER_TITLE, g_mainX + 5 + 40 + 5, g_mainY + 5, truncated_name, clrWhite, titleFontSize, "Arial Bold", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create title label
int subtitleFontSize = getFontSizeByDPI(10);        //--- Calculate subtitle font size
createLabel(SETUP_HEADER_SUBTITLE, g_mainX + 5 + 40 + 5, g_mainY + 25, "Streamlined configuration for optimal performance", C'200,200,200', subtitleFontSize, "Arial", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create subtitle label
int headerCancelFontSize = getFontSizeByDPI(16);    //--- Calculate cancel button font size
createLabel(SETUP_HEADER_CANCEL, g_mainX + g_mainWidth - 25, g_mainY + 10, ShortToString(0x274C), C'150,150,150', headerCancelFontSize, "Arial Rounded MT Bold", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create header cancel button
int bodyY = g_mainY + g_headerHeight + g_spacing;   //--- Calculate body y position
createRecLabel(SETUP_BODY_BG, g_mainX, bodyY, g_mainWidth, g_displayHeight, C'25,25,25', 1, C'40,40,40', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create body background
int footerY = bodyY + g_displayHeight + g_spacing;  //--- Calculate footer y position
createRecLabel(SETUP_FOOTER_BG, g_mainX, footerY, g_mainWidth, g_footerHeight, C'35,35,35', 1, C'50,50,50', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create footer background
createRecLabel(SETUP_CHECKBOX_BG, g_mainX + 10, footerY + (g_footerHeight - 20)/2, 20, 20, C'60,60,60', 1, C'80,80,80', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create checkbox background
int checkboxLabelFontSize = getFontSizeByDPI(17);   //--- Calculate checkbox label font size
createLabel(SETUP_CHECKBOX_LABEL, g_mainX + 10 + 2, footerY + (g_footerHeight - 20)/2, " ", clrWhite, checkboxLabelFontSize, "Wingdings", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create checkbox label
int checkboxTextFontSize = getFontSizeByDPI(10);    //--- Calculate checkbox text font size
createLabel(SETUP_CHECKBOX_TEXT, g_mainX + 40, footerY + (g_footerHeight - 20)/2 + 2, "Do not show this guide again", clrWhite, checkboxTextFontSize, "Calibri Bold", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create checkbox text
int buttonFontSize = getFontSizeByDPI(12);          //--- Calculate button font size
color buttonBg = C'60,60,60';                       //--- Set button background color
color buttonBorder = C'80,80,80';                   //--- Set button border color
createButton(SETUP_OK_BUTTON, g_mainX + g_mainWidth - 170 - 10, footerY + 5, 80, 30, "OK", clrWhite, buttonFontSize, buttonBg, buttonBorder, "Arial Rounded MT Bold", CORNER_LEFT_UPPER, false); //--- Create OK button
createButton(SETUP_CANCEL_BUTTON, g_mainX + g_mainWidth - 80 - 10, footerY + 5, 80, 30, "Cancel", clrWhite, buttonFontSize, buttonBg, buttonBorder, "Arial Rounded MT Bold", CORNER_LEFT_UPPER, false); //--- Create Cancel button
int textFontSize = getFontSizeByDPI(10);            //--- Calculate text font size
for (int i = 0; i < MAX_LINES; i++) {               //--- Create text line labels
   string lineName = "Setup_ResponseLine_" + IntegerToString(i); //--- Generate line name
   createLabel(lineName, 0, -100, " ", clrWhite, textFontSize, "Arial", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create line label
}

Hier implementieren wir die restlichen Dashboard-Komponenten für den Assistenten, um seine Nutzeroberfläche zu vervollständigen. Wir kürzen den Programmnamen mit „truncateString“ auf 20 Zeichen, um ihn lesbar zu machen, und erstellen mit „createLabel“ einen Titel („SETUP_HEADER_TITLE“) an berechneten Koordinaten, wobei wir eine DPI-angepasste Schriftgröße aus „getFontSizeByDPI“ (Basis 14) und „Arial Bold“ verwenden. Als Nächstes fügen wir einen Untertitel („SETUP_HEADER_SUBTITLE“) mit festem Text und einer kleineren DPI-angepassten Schriftgröße (Basis 10) hinzu, gefolgt von einer Schaltfläche zum Abbrechen der Kopfzeile („SETUP_HEADER_CANCEL“) mit einem Kreuz in Unicode (0x274C) und „Arial Rounded MT Bold“. Hier ist seine Beschreibung.

DETAILS ZUR KREUZMARKIERUNG

Wir berechnen die y-Position des Körpers („bodyY“) und erstellen seinen Hintergrund („SETUP_BODY_BG“) mit „createRecLabel“ unter Verwendung eines dunkelgrauen Hintergrunds (C'25,25,25') und Rahmens, der sich über „g_mainWidth“ und „g_displayHeight“ erstreckt. Dann berechnen wir die y-Position der Fußzeile („footerY“) und erstellen ihren Hintergrund („SETUP_FOOTER_BG“) mit einem helleren Grau, gefolgt von einem Kontrollkästchen-Hintergrund („SETUP_CHECKBOX_BG“) als 20x20 Quadrat, einem Kontrollkästchen-Label („SETUP_CHECKBOX_LABEL“) mit einem leeren Wingdings-Zeichen und dem Text („SETUP_CHECKBOX_TEXT“) mit der Aufschrift „Diese Anleitung nicht mehr anzeigen“ in „Calibri Bold“. Wir fügen OK- und Abbrecher-Schaltflächen („SETUP_OK_BUTTON“, „SETUP_CANCEL_BUTTON“) mit „createButton“ in DPI-angepasster Schriftgröße (Basis 12) und in einheitlichen Grautönen hinzu. Schließlich erstellen wir in einer Schleife bis zu „MAX_LINES“ Text-Kennzeichnungen („Setup_ResponseLine_“) mit „createLabel“, die zunächst aus dem Bildschirm ausgeblendet werden, für die dynamische Textanzeige. Auf diese Weise wird ein System für die Darstellung des interaktiven und visuell kohärenten Dashboards des Assistenten geschaffen. Bei der Ausführung des Programms erhalten wir das folgende Ergebnis.

ERWEITERTE ELEMENTE DES ASSISTENTEN

Da wir nun das Dashboard mit den Basiselementen haben, müssen wir die Anzeige aktualisieren, um die vom Assistenten vorgesehenen Beschriftungen anzuzeigen. Da es nicht so einfach ist, mehrzeilige Textbeschriftungen in MQL5 zu haben, da es keinen direkten Weg gibt, dies zu erreichen, müssen wir einen Textumbruch-Ansatz verwenden.

//+------------------------------------------------------------------+
//| Get line color based on content                                  |
//+------------------------------------------------------------------+
color GetLineColor(string lineText) {
   if (StringLen(lineText) == 0 || lineText == " ") return C'25,25,25'; //--- Set invisible for empty lines
   if (StringFind(lineText, "mutiiriallan.forex@gmail.com") >= 0) return C'255,100,100'; //--- Set light red for email
   if (StringFind(lineText, "https://t.me/Forex_Algo_Trader") >= 0) return C'150,100,200'; //--- Set light purple for group link
   if (StringFind(lineText, "@ForexAlgo-Trader") >= 0) return C'100,150,255'; //--- Set light blue for channel link
   if (StringFind(lineText, "http") >= 0 || StringFind(lineText, "t.me") >= 0) return C'100,150,255'; //--- Set light blue for general links
   string start3 = StringSubstr(lineText, 0, 3);    //--- Get first three characters
   if ((start3 == "1. " || start3 == "2. " || start3 == "3. " || start3 == "4. " || start3 == "5. ") &&
       StringFind(lineText, "Initial Setup Instructions") < 0) { //--- Check instruction lines
      return C'255,200,100';                        //--- Set light yellow for instructions
   }
   return clrWhite;                                 //--- Default to white
}

//+------------------------------------------------------------------+
//| Wrap text with colors                                            |
//+------------------------------------------------------------------+
void WrapText(const string inputText, const string font, const int fontSize, const int maxWidth, string &wrappedLines[], color &wrappedColors[], int offset = 0) {
   const int maxChars = 60;                         //--- Set maximum characters per line
   ArrayResize(wrappedLines, 0);                    //--- Clear wrapped lines array
   ArrayResize(wrappedColors, 0);                   //--- Clear wrapped colors array
   TextSetFont(font, fontSize);                     //--- Set font
   string paragraphs[];                             //--- Declare paragraphs array
   int numParagraphs = StringSplit(inputText, '\n', paragraphs); //--- Split text into paragraphs
   for (int p = 0; p < numParagraphs; p++) {        //--- Iterate through paragraphs
      string para = paragraphs[p];                  //--- Get current paragraph
      color paraColor = GetLineColor(para);         //--- Get paragraph color
      if (StringLen(para) == 0) {                   //--- Check empty paragraph
         int size = ArraySize(wrappedLines);        //--- Get current size
         ArrayResize(wrappedLines, size + 1);       //--- Resize lines array
         wrappedLines[size] = " ";                  //--- Add empty line
         ArrayResize(wrappedColors, size + 1);      //--- Resize colors array
         wrappedColors[size] = C'25,25,25';         //--- Set invisible color
         continue;                                  //--- Skip to next
      }
      string words[];                               //--- Declare words array
      int numWords = StringSplit(para, ' ', words); //--- Split paragraph into words
      string currentLine = "";                      //--- Initialize current line
      for (int w = 0; w < numWords; w++) {          //--- Iterate through words
         string testLine = currentLine + (StringLen(currentLine) > 0 ? " " : "") + words[w]; //--- Build test line
         uint wid, hei;                             //--- Declare width and height
         TextGetSize(testLine, wid, hei);           //--- Get test line size
         int textWidth = (int)wid;                  //--- Get text width
         if (textWidth + offset <= maxWidth && StringLen(testLine) <= maxChars) { //--- Check line fits
            currentLine = testLine;                 //--- Update current line
         } else {                                   //--- Line exceeds limits
            if (StringLen(currentLine) > 0) {       //--- Check non-empty line
               int size = ArraySize(wrappedLines);  //--- Get current size
               ArrayResize(wrappedLines, size + 1); //--- Resize lines array
               wrappedLines[size] = currentLine;    //--- Add line
               ArrayResize(wrappedColors, size + 1); //--- Resize colors array
               wrappedColors[size] = paraColor;     //--- Add color
            }
            currentLine = words[w];                 //--- Start new line
            TextGetSize(currentLine, wid, hei);     //--- Get new line size
            textWidth = (int)wid;                   //--- Update text width
            if (textWidth + offset > maxWidth || StringLen(currentLine) > maxChars) { //--- Check word too long
               string wrappedWord = "";             //--- Initialize wrapped word
               for (int c = 0; c < StringLen(words[w]); c++) { //--- Iterate through characters
                  string testWord = wrappedWord + StringSubstr(words[w], c, 1); //--- Build test word
                  TextGetSize(testWord, wid, hei);  //--- Get test word size
                  int wordWidth = (int)wid;         //--- Get word width
                  if (wordWidth + offset > maxWidth || StringLen(testWord) > maxChars) { //--- Check word fits
                     if (StringLen(wrappedWord) > 0) { //--- Check non-empty word
                        int size = ArraySize(wrappedLines); //--- Get current size
                        ArrayResize(wrappedLines, size + 1); //--- Resize lines array
                        wrappedLines[size] = wrappedWord; //--- Add wrapped word
                        ArrayResize(wrappedColors, size + 1); //--- Resize colors array
                        wrappedColors[size] = paraColor; //--- Add color
                     }
                     wrappedWord = StringSubstr(words[w], c, 1); //--- Start new word
                  } else {                          //--- Word fits
                     wrappedWord = testWord;        //--- Update wrapped word
                  }
               }
               currentLine = wrappedWord;          //--- Set current line to wrapped word
               if (StringLen(currentLine) > 0) {   //--- Check non-empty line
                  int size = ArraySize(wrappedLines); //--- Get current size
                  ArrayResize(wrappedLines, size + 1); //--- Resize lines array
                  wrappedLines[size] = currentLine; //--- Add line
                  ArrayResize(wrappedColors, size + 1); //--- Resize colors array
                  wrappedColors[size] = paraColor; //--- Add color
               }
               currentLine = "";                   //--- Reset current line
            }
         }
      }
      if (StringLen(currentLine) > 0) {            //--- Check remaining line
         int size = ArraySize(wrappedLines);       //--- Get current size
         ArrayResize(wrappedLines, size + 1);      //--- Resize lines array
         wrappedLines[size] = currentLine;         //--- Add line
         ArrayResize(wrappedColors, size + 1);     //--- Resize colors array
         wrappedColors[size] = paraColor;          //--- Add color
      }
   }
}

Hier implementieren wir Textformatierung und Farbkodierung, um die Lesbarkeit des Leitfadens zu verbessern. In der Funktion „GetLineColor“ weisen wir Farben auf der Grundlage des Inhalts zu: leere Zeilen erhalten ein unsichtbares Dunkelgrau (C'25,25,25'), E-Mail-Adressen werden hellrot (C'255,100,100'), Gruppenlinks hellviolett (C'150,100,200'), Autorenlinks und andere Uniform Resource Locator (URLs) sind hellblau (C'100,150,255'), Anweisungszeilen, die mit „1.“ bis „5.“ beginnen (außer der Überschrift), sind hellgelb (C'255,200,100'), und alle anderen sind weiß. Sie können jedes beliebige Zeichen definieren; wir haben dies nur aufgenommen, um einen Einblick zu geben, wie Rich-Text-Kodierung erreicht werden kann.

In der Funktion „WrapText“ teilen wir den eingegebenen Text mithilfe von StringSplit an Zeilenumbrüchen in Absätze auf, legen die Schriftart mit TextSetFont fest und rufen für jeden Absatz dessen Farbe mit „GetLineColor“ ab, wobei wir leere Absätze als Leerzeichen mit unsichtbarer Farbe hinzufügen. Absätze werden mit „StringSplit“ in Wörter aufgeteilt, wobei Zeilen gebildet werden, indem Wörter hinzugefügt werden, wenn sie innerhalb von „maxWidth“ und einer 60-Zeichen-Grenze liegen, wobei die Höchstgrenze bei 63 liegt, und zwar mit TextGetSize, andernfalls wird eine neue Zeile begonnen; Bei übergroßen Wörtern wird Zeichen für Zeichen aufgeteilt, wobei Segmente zu neuen Zeilen hinzugefügt werden, wenn sie die Grenzen überschreiten. Dabei wird sichergestellt, dass jede Zeile in „wrappedLines“ mit ihrer Farbe in „wrappedColors“ unter Verwendung der Funktion ArrayResize gespeichert wird. Mit dieser Funktion können wir die Anzeige aktualisieren. Wir werden sicherstellen, dass die Logik eine Funktion ist.

//+------------------------------------------------------------------+
//| Get text height                                                  |
//+------------------------------------------------------------------+
int TextGetHeight(string text, string font, int fontSize) {
   uint wid, hei;                                   //--- Declare width and height
   TextSetFont(font, fontSize);                     //--- Set font
   TextGetSize(text, wid, hei);                     //--- Get text size
   return (int)hei;                                 //--- Return height
}

//+------------------------------------------------------------------+
//| Check if line is a heading                                       |
//+------------------------------------------------------------------+
bool IsHeading(string lineText) {
   if (StringLen(lineText) == 0) return false;      //--- Return false for empty lines
   if (StringGetCharacter(lineText, StringLen(lineText) - 1) == ':') return true; //--- Check for colon
   if (StringFind(lineText, "Expert Advisor Initialization Guide") >= 0) return true; //--- Check main heading
   if (StringFind(lineText, "Key Features") >= 0) return true; //--- Check features heading
   if (StringFind(lineText, "Initial Setup Instructions") >= 0) return true; //--- Check instructions heading
   if (StringFind(lineText, "Important Notes") >= 0) return true; //--- Check notes heading
   if (StringFind(lineText, "Contact Methods") >= 0) return true; //--- Check contact heading
   if (StringFind(lineText, "NB:") >= 0) return true; //--- Check NB heading
   return false;                                    //--- Default to false
}

//+------------------------------------------------------------------+
//| Update body display with scrollable text                         |
//+------------------------------------------------------------------+
void UpdateBodyDisplay() {
   int textX = g_mainX + g_padding + g_textPadding; //--- Set text x position
   int textY = g_mainY + g_headerHeight + g_spacing; //--- Set text y position
   int fullMaxWidth = g_mainWidth - 2 * g_padding - 2 * g_textPadding; //--- Calculate max text width
   string font = "Arial";                           //--- Set font
   int fontSize = getFontSizeByDPI(10);             //--- Calculate font size
   int lineHeight = TextGetHeight("A", font, fontSize); //--- Get line height
   int adjustedLineHeight = lineHeight + g_lineSpacing; //--- Calculate adjusted line height
   g_adjustedLineHeight = adjustedLineHeight;       //--- Store adjusted line height
   int visibleHeight = g_displayHeight;             //--- Set visible height
   g_visible_height = visibleHeight;                //--- Store visible height
   static string wrappedLines[];                    //--- Store wrapped text lines
   static color wrappedColors[];                    //--- Store line colors
   static bool wrapped = false;                     //--- Track if text wrapped
   if (!wrapped) {                                  //--- Check if text needs wrapping
      WrapText(setup_text, font, fontSize, fullMaxWidth, wrappedLines, wrappedColors); //--- Wrap text
      wrapped = true;                               //--- Set wrapped flag
   }
   int numLines = ArraySize(wrappedLines);          //--- Get number of lines
   g_total_height = numLines * adjustedLineHeight;  //--- Calculate total text height
   bool need_scroll = g_total_height > visibleHeight; //--- Check if scrollbar needed
   bool should_show_scrollbar = false;              //--- Initialize scrollbar visibility
   int reserved_width = 0;                          //--- Initialize reserved width
   if (need_scroll && ScrollbarMode != SCROLL_NEVER) { //--- Check scrollbar mode
      should_show_scrollbar = true;                 //--- Enable scrollbar
      reserved_width = 16;                          //--- Reserve scrollbar width
   }
   if (reserved_width > 0 && fullMaxWidth - reserved_width != fullMaxWidth) { //--- Check width change
      WrapText(setup_text, font, fontSize, fullMaxWidth - reserved_width, wrappedLines, wrappedColors); //--- Rewrap text
      numLines = ArraySize(wrappedLines);           //--- Update line count
      g_total_height = numLines * adjustedLineHeight; //--- Update total height
   }

   int startLine = scroll_pos / adjustedLineHeight; //--- Calculate start line
   int currentY = textY;                            //--- Set current y position
   int labelIndex = 0;                              //--- Initialize label index
   for (int line = startLine; line < numLines; line++) { //--- Iterate visible lines
      string lineText = wrappedLines[line];         //--- Get line text
      if (StringLen(lineText) == 0) lineText = " "; //--- Set empty lines to space
      color lineColor = wrappedColors[line];        //--- Get line color
      if (IsHeading(lineText)) lineColor = clrBlue; //--- Set blue for headings
      if (currentY + adjustedLineHeight > textY + visibleHeight) break; //--- Prevent overflow
      string lineName = "Setup_ResponseLine_" + IntegerToString(labelIndex); //--- Generate line name
      if (ObjectFind(0, lineName) >= 0) {          //--- Check if label exists
         ObjectSetString(0, lineName, OBJPROP_TEXT, lineText); //--- Set line text
         ObjectSetInteger(0, lineName, OBJPROP_XDISTANCE, textX); //--- Set x position
         ObjectSetInteger(0, lineName, OBJPROP_YDISTANCE, currentY); //--- Set y position
         ObjectSetInteger(0, lineName, OBJPROP_COLOR, lineColor); //--- Set line color
         string lineFont = IsHeading(lineText) ? "Arial Bold" : "Arial"; //--- Set font
         ObjectSetString(0, lineName, OBJPROP_FONT, lineFont); //--- Set font type
         ObjectSetInteger(0, lineName, OBJPROP_FONTSIZE, fontSize); //--- Set font size
         ObjectSetInteger(0, lineName, OBJPROP_HIDDEN, false); //--- Show label
      }
      currentY += adjustedLineHeight;               //--- Increment y position
      labelIndex++;                                 //--- Increment label index
   }
   for (int i = labelIndex; i < MAX_LINES; i++) {   //--- Hide unused labels
      string lineName = "Setup_ResponseLine_" + IntegerToString(i); //--- Generate line name
      if (ObjectFind(0, lineName) >= 0) {           //--- Check if label exists
         ObjectSetInteger(0, lineName, OBJPROP_HIDDEN, true); //--- Hide label
      }
   }
   ChartRedraw();                                   //--- Redraw chart
}

Um die Textdarstellung und die Bildlauflogik zur dynamischen Anzeige des Leitfadens zu implementieren, werden in der Funktion „TextGetHeight“ die Schriftart und -größe mit TextSetFont festgelegt und mit TextGetSize die Höhe eines Beispielzeichens berechnet und für einen einheitlichen Zeilenabstand zurückgegeben. Die Funktion „IsHeading“ identifiziert Überschriften, indem sie auf leere Zeilen, Doppelpunkte am Ende oder bestimmte Abschnittsüberschriften des Leitfadens (z. B. „Key Features“) prüft und bei Übereinstimmung true zurückgibt. In der Funktion „UpdateBodyDisplay“ berechnen wir die Position („textX“, „textY“) und Breite („fullMaxWidth“) unter Verwendung von Padding und Containerabmessungen, setzen die Schriftart auf Arial mit einer DPI-angepassten Größe aus „getFontSizeByDPI“ und berechnen die Zeilenhöhe mit „TextGetHeight“ plus „g_lineSpacing“ und speichern sie in „g_adjustedLineHeight“.

Wir umhüllen den Leittext mit „WrapText“, falls noch nicht geschehen, berechnen die Gesamthöhe des Textes („g_total_height“) und bestimmen die Sichtbarkeit der Bildlaufleiste anhand von „ScrollbarMode“ und Textüberlauf, wobei wir bei Bedarf 16 Pixel für die Bildlaufleiste reservieren und den Text entsprechend umhüllen. Wir berechnen die Startlinie aus „scroll_pos“, aktualisieren sichtbare Textbeschriftungen mit ObjectSetString und ObjectSetInteger für Position, Farbe (blau für Überschriften über „IsHeading“) und Schriftart, blenden nicht verwendete Beschriftungen aus und zeichnen das Chart neu. Wenn wir diese Funktion in der Funktion zur Anzeige des Dashboards aufrufen, erhalten wir folgendes Ergebnis.

INITIALISIERTE ANZEIGE

Wie wir sehen können, ist die Anzeige fertig und der Text passt perfekt in den Anzeigebereich. Was wir tun müssen, ist, die Logik für die Anzeige der Bildlaufleiste bei Bedarf zu holen.

//+------------------------------------------------------------------+
//| Create scrollbar                                                 |
//+------------------------------------------------------------------+
void CreateScrollbar() {
   int bodyY = g_mainY + g_headerHeight + g_spacing; //--- Calculate body y position
   int textAreaY = bodyY;                            //--- Set text area y
   int textAreaHeight = g_displayHeight;             //--- Set text area height
   int scrollbar_x = g_mainX + g_mainWidth - 16 - 1; //--- Calculate scrollbar x
   int scrollbar_width = 16;                         //--- Set scrollbar width
   int button_size = 16;                             //--- Set button size
   int scrollbar_y = textAreaY + 2;                  //--- Calculate scrollbar y
   int scrollbar_height = textAreaHeight - 2 - 2;    //--- Calculate scrollbar height
   createRecLabel(SCROLL_LEADER, scrollbar_x, scrollbar_y, scrollbar_width, scrollbar_height, C'45,45,45', 1, C'60,60,60', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create scroll leader
   createRecLabel(SCROLL_UP_REC, scrollbar_x, scrollbar_y, scrollbar_width, button_size, C'60,60,60', 1, C'60,60,60', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create scroll up rectangle
   int scrollUpLabelFontSize = getFontSizeByDPI(10); //--- Calculate scroll up font size
   createLabel(SCROLL_UP_LABEL, scrollbar_x + 2, scrollbar_y - 2, CharToString(0x35), C'150,150,150', scrollUpLabelFontSize, "Webdings", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create scroll up label
   int down_rec_y = scrollbar_y + scrollbar_height - button_size; //--- Calculate scroll down y
   createRecLabel(SCROLL_DOWN_REC, scrollbar_x, down_rec_y, scrollbar_width, button_size, C'60,60,60', 1, C'60,60,60', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create scroll down rectangle
   createLabel(SCROLL_DOWN_LABEL, scrollbar_x + 2, down_rec_y - 2, CharToString(0x36), C'150,150,150', scrollUpLabelFontSize, "Webdings", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create scroll down label
   slider_height = CalculateSliderHeight();          //--- Calculate slider height
   createRecLabel(SCROLL_SLIDER, scrollbar_x, scrollbar_y + button_size, scrollbar_width, slider_height, C'80,80,80', 1, C'100,100,100', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create scroll slider
}

//+------------------------------------------------------------------+
//| Delete scrollbar                                                 |
//+------------------------------------------------------------------+
void DeleteScrollbar() {
   string scroll_objects[] = {SCROLL_LEADER, SCROLL_UP_REC, SCROLL_UP_LABEL, SCROLL_DOWN_REC, SCROLL_DOWN_LABEL, SCROLL_SLIDER}; //--- Define scroll objects
   for (int i = 0; i < ArraySize(scroll_objects); i++) { //--- Iterate through objects
      ObjectDelete(0, scroll_objects[i]);            //--- Delete object
   }
   ChartRedraw();                                    //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Calculate slider height                                          |
//+------------------------------------------------------------------+
int CalculateSliderHeight() {
   int textAreaHeight = g_displayHeight;            //--- Get text area height
   int scroll_area_height = textAreaHeight - 32;    //--- Calculate scroll area height
   int slider_min_height = 20;                      //--- Set minimum slider height
   if (g_total_height <= g_visible_height) return scroll_area_height; //--- Return full height if no scroll
   double visible_ratio = (double)g_visible_height / g_total_height; //--- Calculate visible ratio
   int height = (int)MathFloor(scroll_area_height * visible_ratio); //--- Calculate slider height
   return MathMax(slider_min_height, height);       //--- Return minimum or calculated height
}

//+------------------------------------------------------------------+
//| Update slider position                                           |
//+------------------------------------------------------------------+
void UpdateSliderPosition() {
   int bodyY = g_mainY + g_headerHeight + g_spacing; //--- Calculate body y position
   int textAreaY = bodyY;                           //--- Set text area y
   int textAreaHeight = g_displayHeight;            //--- Set text area height
   int scroll_area_height = textAreaHeight - 32;    //--- Calculate scroll area height
   int slider_min_y = textAreaY + 16;               //--- Set minimum slider y
   if (g_max_scroll <= 0) return;                   //--- Exit if no scroll
   double scroll_ratio = (double)scroll_pos / g_max_scroll; //--- Calculate scroll ratio
   int slider_max_y = slider_min_y + scroll_area_height - slider_height; //--- Calculate max slider y
   int new_y = slider_min_y + (int)MathRound(scroll_ratio * (slider_max_y - slider_min_y)); //--- Calculate new y
   ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE, new_y); //--- Set slider y position
}

//+------------------------------------------------------------------+
//| Update scrollbar button colors                                   |
//+------------------------------------------------------------------+
void UpdateButtonColors() {
   int max_scroll = g_max_scroll;                   //--- Get max scroll
   color up_color = (scroll_pos == 0) ? C'80,80,80' : (scroll_up_hovered ? C'100,100,100' : C'150,150,150'); //--- Set up button color
   color down_color = (scroll_pos >= max_scroll) ? C'80,80,80' : (scroll_down_hovered ? C'100,100,100' : C'150,150,150'); //--- Set down button color
   ObjectSetInteger(0, SCROLL_UP_LABEL, OBJPROP_COLOR, up_color); //--- Update up label color
   ObjectSetInteger(0, SCROLL_DOWN_LABEL, OBJPROP_COLOR, down_color); //--- Update down label color
   ObjectSetInteger(0, SCROLL_UP_REC, OBJPROP_BGCOLOR, scroll_up_hovered ? C'70,70,70' : C'60,60,60'); //--- Update up rectangle color
   ObjectSetInteger(0, SCROLL_DOWN_REC, OBJPROP_BGCOLOR, scroll_down_hovered ? C'70,70,70' : C'60,60,60'); //--- Update down rectangle color
}

//+------------------------------------------------------------------+
//| Scroll up                                                        |
//+------------------------------------------------------------------+
void ScrollUp() {
   if (g_adjustedLineHeight > 0 && scroll_pos > 0) { //--- Check scroll possible
      scroll_pos = MathMax(0, scroll_pos - g_adjustedLineHeight); //--- Decrease scroll position
      UpdateBodyDisplay();                           //--- Update body display
      if (scroll_visible) {                          //--- Check scrollbar visible
         UpdateSliderPosition();                     //--- Update slider position
         UpdateButtonColors();                       //--- Update button colors
      }
   }
}

//+------------------------------------------------------------------+
//| Scroll down                                                      |
//+------------------------------------------------------------------+
void ScrollDown() {
   int max_scroll = g_max_scroll;                    //--- Get max scroll
   if (g_adjustedLineHeight > 0 && scroll_pos < max_scroll) { //--- Check scroll possible
      scroll_pos = MathMin(max_scroll, scroll_pos + g_adjustedLineHeight); //--- Increase scroll position
      UpdateBodyDisplay();                           //--- Update body display
      if (scroll_visible) {                          //--- Check scrollbar visible
         UpdateSliderPosition();                     //--- Update slider position
         UpdateButtonColors();                       //--- Update button colors
      }
   }
}

Um die Scrollbar-Funktionalität zu implementieren, die eine reibungslose Navigation im Setup-Guide ermöglicht, erstellen wir die Funktion „CreateScrollbar“ und berechnen darin die Position und die Abmessungen des Scrollbars auf der Grundlage der y-Koordinate des Körpers („bodyY“), indem wir „scrollbar_x“ auf den rechten Rand des Hauptcontainers und verwenden eine Breite von 16 Pixeln, erstellen ein Führungsrechteck („SCROLL_LEADER“) mit „createRecLabel“ für die Scrollbar-Spur und Auf/Ab-Schaltflächen („SCROLL_UP_REC“, „SCROLL_DOWN_REC“) mit Beschriftungen („SCROLL_UP_LABEL“, „SCROLL_DOWN_LABEL“) mit Webdings Pfeilen (0x35, 0x36). Wir rufen „CalculateSliderHeight“ auf, um die Höhe des Schiebereglers auf der Grundlage des sichtbaren Textverhältnisses zu bestimmen, und erstellen den Schieberegler („SCROLL_SLIDER“) mit „createRecLabel“. Die Funktion „DeleteScrollbar“ entfernt mit ObjectDelete alle Scrollbar-Objekte („SCROLL_LEADER“, etc.) und zeichnet das Chart neu.

In „CalculateSliderHeight“ wird die Höhe des Schiebereglers als Verhältnis zwischen der Anzeigehöhe und der Gesamthöhe des Textes berechnet, wobei ein Minimum von 20 Pixeln gewährleistet wird. Die Funktion „UpdateSliderPosition“ passt die y-Position des Schiebereglers unter Verwendung eines aus „scroll_pos“ und „g_max_scroll“ abgeleiteten Scroll-Verhältnisses an, das mit „ObjectSetInteger“ festgelegt wird. In „UpdateButtonColors“ aktualisieren wir die Farben der Aufwärts-/Abwärts-Schaltflächen auf der Grundlage der Bildlaufposition und des Hover-Status für ein dynamisches visuelles Feedback. Die Funktionen „ScrollUp“ und „ScrollDown“ passen „scroll_pos“ um „g_adjustedLineHeight“ an und rufen „UpdateBodyDisplay“, „UpdateSliderPosition“ und „UpdateButtonColors“ auf, wenn die Bildlaufleiste sichtbar ist, um einen nahtlosen Bildlauf zu gewährleisten. Wir können diese Funktionen nun innerhalb der Funktion zur Aktualisierung der Anzeige aufrufen, um die Bildlaufleiste hinzuzufügen. Hier ist der Ansatz, mit dem wir das erreichen wollen.

int num_visible_lines = g_visible_height / g_adjustedLineHeight; //--- Calculate visible lines
g_max_scroll = MathMax(0, (numLines - num_visible_lines) * g_adjustedLineHeight); //--- Calculate max scroll
bool prev_scroll_visible = scroll_visible;       //--- Store previous scrollbar state
scroll_visible = should_show_scrollbar;          //--- Update scrollbar visibility
if (scroll_visible != prev_scroll_visible) {     //--- Check scrollbar state change
   if (scroll_visible) {                         //--- Show scrollbar
      CreateScrollbar();                         //--- Create scrollbar
   } else {                                      //--- Hide scrollbar
      DeleteScrollbar();                         //--- Delete scrollbar
   }
}
scroll_pos = MathMax(0, MathMin(scroll_pos, g_max_scroll)); //--- Clamp scroll position
if (scroll_visible) {                            //--- Update scrollbar
   slider_height = CalculateSliderHeight();      //--- Calculate slider height
   ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE, slider_height); //--- Set slider height
   UpdateSliderPosition();                       //--- Update slider position
   UpdateButtonColors();                         //--- Update button colors
}

In der Funktion „UpdateBodyDisplay“ berechnen wir die Anzahl der sichtbaren Zeilen, indem wir „g_visible_height“ durch „g_adjustedLineHeight“ dividieren und den maximalen Scroll-Abstand („g_max_scroll“) als die über die sichtbaren Zeilen hinausgehende Texthöhe bestimmen, wobei wir MathMax verwenden, um negative Werte zu vermeiden. Wir speichern den vorherigen Sichtbarkeitsstatus der Bildlaufleiste in „prev_scroll_visible“, aktualisieren „scroll_visible“ je nachdem, ob eine Bildlaufleiste benötigt wird, und rufen bei Änderungen des Status „CreateScrollbar“ auf, um die Bildlaufleiste zu zeichnen, oder „DeleteScrollbar“, um sie zu entfernen. Wir beschränken „scroll_pos“ zwischen 0 und „g_max_scroll“ mit „MathMax“ und MathMin ein, um einen Überlauf zu verhindern. Wenn die Bildlaufleiste sichtbar ist, aktualisieren wir „slider_height“ mit „CalculateSliderHeight“, setzen die Höhe des Schiebereglers mit ObjectSetInteger für „SCROLL_SLIDER“ und rufen „UpdateSliderPosition“ und „UpdateButtonColors“ auf, um das Aussehen und die Position der Bildlaufleiste zu aktualisieren. Wenn wir kompilieren, erhalten wir das folgende Ergebnis.

SCROLLBAR AKTIVIERT

Aus dem Bild geht hervor, dass die Dashboard-Elemente vollständig erstellt sind. Wir müssen nun sicherstellen, dass wir das Dashboard verwerfen, wenn wir uns abmelden oder das Programm deinitialisieren.

//+------------------------------------------------------------------+
//| Delete the dashboard                                             |
//+------------------------------------------------------------------+
void DeleteDashboard() {
   string objects[] = {                             //--- Define dashboard objects
      SETUP_MAIN, SETUP_HEADER_BG, SETUP_HEADER_IMAGE, SETUP_HEADER_TITLE, SETUP_HEADER_SUBTITLE, SETUP_HEADER_CANCEL,
      SETUP_BODY_BG, SETUP_FOOTER_BG, SETUP_CHECKBOX_BG, SETUP_CHECKBOX_LABEL, SETUP_CHECKBOX_TEXT,
      SETUP_OK_BUTTON, SETUP_CANCEL_BUTTON, SCROLL_LEADER, SCROLL_UP_REC, SCROLL_UP_LABEL,
      SCROLL_DOWN_REC, SCROLL_DOWN_LABEL, SCROLL_SLIDER
   };
   for (int i = 0; i < ArraySize(objects); i++) {   //--- Iterate through objects
      ObjectDelete(0, objects[i]);                  //--- Delete object
   }
   int total = ObjectsTotal(0);                     //--- Get total objects
   for (int j = total - 1; j >= 0; j--) {           //--- Iterate through remaining objects
      string name = ObjectName(0, j);               //--- Get object name
      if (StringFind(name, "Setup_ResponseLine_") == 0) { //--- Check for text lines
         ObjectDelete(0, name);                     //--- Delete text line
      }
   }
   ChartRedraw();                                   //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   DeleteDashboard();                                //--- Remove dashboard objects
   if (StringLen(g_scaled_image_resource) > 0) {     //--- Check if scaled image exists
      ResourceFree(g_scaled_image_resource);         //--- Free scaled image resource
   }
}

Hier implementieren wir Bereinigungsfunktionen für den Assistenten, um eine ordnungsgemäße Ressourcenverwaltung zu gewährleisten. In der Funktion „DeleteDashboard“ definieren wir ein Array von Dashboard-Objektnamen, einschließlich Hauptcontainer, Header, Body, Footer, Buttons, Checkbox und Scrollbar-Komponenten, und durchlaufen sie mit ObjectDelete, um sie aus dem Chart zu entfernen. Anschließend werden alle verbleibenden Chartobjekte mit ObjectsTotal und ObjectName in einer Schleife durchlaufen, wobei alle Textzeilenobjekte, die mit „Setup_ResponseLine_“ beginnen, mit „ObjectDelete“ gelöscht werden, und das Chart mit ChartRedraw neu gezeichnet, um eine saubere Darstellung zu erhalten. In der Funktion „OnDeinit“ rufen wir „DeleteDashboard“ auf, um alle Dashboard-Elemente zu entfernen und zu prüfen, ob eine skalierte Bildressource vorhanden ist („StringLen(g_scaled_image_resource) > 0“), und geben sie mit ResourceFree frei, um Speicher freizugeben. Jetzt können wir dem Dashboard Leben einhauchen. Wir wollen, dass beim Klicken auf die Schaltflächen die entsprechenden Aufrufe erfolgen und Hover-Effekte den Zustand des Cursors anzeigen. Der Einfachheit halber brauchen wir eine Funktion, die das erledigt.

//+------------------------------------------------------------------+
//| Update hover effects                                             |
//+------------------------------------------------------------------+
void UpdateHoverEffects(int mouseX, int mouseY) {
   int ok_x = (int)ObjectGetInteger(0, SETUP_OK_BUTTON, OBJPROP_XDISTANCE);    //--- Get OK button x
   int ok_y = (int)ObjectGetInteger(0, SETUP_OK_BUTTON, OBJPROP_YDISTANCE);    //--- Get OK button y
   int ok_width = (int)ObjectGetInteger(0, SETUP_OK_BUTTON, OBJPROP_XSIZE);    //--- Get OK button width
   int ok_height = (int)ObjectGetInteger(0, SETUP_OK_BUTTON, OBJPROP_YSIZE);   //--- Get OK button height
   bool is_ok_hovered = (mouseX >= ok_x && mouseX <= ok_x + ok_width && mouseY >= ok_y && mouseY <= ok_y + ok_height); //--- Check OK button hover
   if (is_ok_hovered != ok_button_hovered) {                                   //--- Check hover state change
      ok_button_hovered = is_ok_hovered;                                       //--- Update hover state
      color hoverBg = is_ok_hovered ? C'40,80,40' : C'60,60,60';               //--- Set hover background
      color hoverBorder = is_ok_hovered ? C'60,100,60' : C'80,80,80';          //--- Set hover border
      ObjectSetInteger(0, SETUP_OK_BUTTON, OBJPROP_BGCOLOR, hoverBg);          //--- Update background
      ObjectSetInteger(0, SETUP_OK_BUTTON, OBJPROP_BORDER_COLOR, hoverBorder); //--- Update border
      ChartRedraw();                                                           //--- Redraw chart
   }
   int cancel_x = (int)ObjectGetInteger(0, SETUP_CANCEL_BUTTON, OBJPROP_XDISTANCE);  //--- Get Cancel button x
   int cancel_y = (int)ObjectGetInteger(0, SETUP_CANCEL_BUTTON, OBJPROP_YDISTANCE);  //--- Get Cancel button y
   int cancel_width = (int)ObjectGetInteger(0, SETUP_CANCEL_BUTTON, OBJPROP_XSIZE);  //--- Get Cancel button width
   int cancel_height = (int)ObjectGetInteger(0, SETUP_CANCEL_BUTTON, OBJPROP_YSIZE); //--- Get Cancel button height
   bool is_cancel_hovered = (mouseX >= cancel_x && mouseX <= cancel_x + cancel_width && mouseY >= cancel_y && mouseY <= cancel_y + cancel_height); //--- Check Cancel button hover
   if (is_cancel_hovered != cancel_button_hovered) {                                 //--- Check hover state change
      cancel_button_hovered = is_cancel_hovered;                                     //--- Update hover state
      color hoverBg = is_cancel_hovered ? C'80,40,40' : C'60,60,60';                 //--- Set hover background
      color hoverBorder = is_cancel_hovered ? C'100,60,60' : C'80,80,80';            //--- Set hover border
      ObjectSetInteger(0, SETUP_CANCEL_BUTTON, OBJPROP_BGCOLOR, hoverBg);            //--- Update background
      ObjectSetInteger(0, SETUP_CANCEL_BUTTON, OBJPROP_BORDER_COLOR, hoverBorder);   //--- Update border
      ChartRedraw();                               //--- Redraw chart
   }
   int checkbox_x = (int)ObjectGetInteger(0, SETUP_CHECKBOX_BG, OBJPROP_XDISTANCE);  //--- Get checkbox x
   int checkbox_y = (int)ObjectGetInteger(0, SETUP_CHECKBOX_BG, OBJPROP_YDISTANCE);  //--- Get checkbox y
   int checkbox_width = (int)ObjectGetInteger(0, SETUP_CHECKBOX_BG, OBJPROP_XSIZE);  //--- Get checkbox width
   int checkbox_height = (int)ObjectGetInteger(0, SETUP_CHECKBOX_BG, OBJPROP_YSIZE); //--- Get checkbox height
   bool is_checkbox_hovered = (mouseX >= checkbox_x && mouseX <= checkbox_x + checkbox_width && mouseY >= checkbox_y && mouseY <= checkbox_y + checkbox_height); //--- Check checkbox hover
   if (is_checkbox_hovered != checkbox_hovered) {   //--- Check hover state change
      checkbox_hovered = is_checkbox_hovered;       //--- Update hover state
      if (checkbox_checked) {                       //--- Check checkbox state
         ObjectSetInteger(0, SETUP_CHECKBOX_BG, OBJPROP_BGCOLOR, is_checkbox_hovered ? C'0,150,0' : C'0,128,0'); //--- Update background
      } else {                                      //--- Unchecked state
         ObjectSetInteger(0, SETUP_CHECKBOX_BG, OBJPROP_BGCOLOR, is_checkbox_hovered ? C'70,70,70' : C'60,60,60'); //--- Update background
      }
      ChartRedraw();                                //--- Redraw chart
   }
   int header_cancel_x = (int)ObjectGetInteger(0, SETUP_HEADER_CANCEL, OBJPROP_XDISTANCE); //--- Get header cancel x
   int header_cancel_y = (int)ObjectGetInteger(0, SETUP_HEADER_CANCEL, OBJPROP_YDISTANCE); //--- Get header cancel y
   int header_cancel_width = 20;                    //--- Set header cancel width
   int header_cancel_height = 20;                   //--- Set header cancel height
   bool is_header_cancel_hovered = (mouseX >= header_cancel_x && mouseX <= header_cancel_x + header_cancel_width &&
                                    mouseY >= header_cancel_y && mouseY <= header_cancel_y + header_cancel_height); //--- Check header cancel hover
   if (is_header_cancel_hovered != header_cancel_hovered) { //--- Check hover state change
      header_cancel_hovered = is_header_cancel_hovered; //--- Update hover state
      ObjectSetInteger(0, SETUP_HEADER_CANCEL, OBJPROP_COLOR, is_header_cancel_hovered ? C'255,100,100' : C'150,150,150'); //--- Update color
      ChartRedraw();                                //--- Redraw chart
   }
   if (scroll_visible) {                            //--- Check scrollbar visible
      int scroll_up_x = (int)ObjectGetInteger(0, SCROLL_UP_REC, OBJPROP_XDISTANCE);  //--- Get scroll up x
      int scroll_up_y = (int)ObjectGetInteger(0, SCROLL_UP_REC, OBJPROP_YDISTANCE);  //--- Get scroll up y
      int scroll_up_width = (int)ObjectGetInteger(0, SCROLL_UP_REC, OBJPROP_XSIZE);  //--- Get scroll up width
      int scroll_up_height = (int)ObjectGetInteger(0, SCROLL_UP_REC, OBJPROP_YSIZE); //--- Get scroll up height
      bool is_scroll_up_hovered = (mouseX >= scroll_up_x && mouseX <= scroll_up_x + scroll_up_width &&
                                   mouseY >= scroll_up_y && mouseY <= scroll_up_y + scroll_up_height); //--- Check scroll up hover
      if (is_scroll_up_hovered != scroll_up_hovered) {                               //--- Check hover state change
         scroll_up_hovered = is_scroll_up_hovered;                                   //--- Update hover state
         ObjectSetInteger(0, SCROLL_UP_REC, OBJPROP_BGCOLOR, is_scroll_up_hovered ? C'70,70,70' : C'60,60,60'); //--- Update background
         ObjectSetInteger(0, SCROLL_UP_LABEL, OBJPROP_COLOR, (scroll_pos == 0) ? C'80,80,80' : (is_scroll_up_hovered ? C'100,100,100' : C'150,150,150')); //--- Update label color
         ChartRedraw();                                                              //--- Redraw chart
      }
      int scroll_down_x = (int)ObjectGetInteger(0, SCROLL_DOWN_REC, OBJPROP_XDISTANCE);  //--- Get scroll down x
      int scroll_down_y = (int)ObjectGetInteger(0, SCROLL_DOWN_REC, OBJPROP_YDISTANCE);  //--- Get scroll down y
      int scroll_down_width = (int)ObjectGetInteger(0, SCROLL_DOWN_REC, OBJPROP_XSIZE);  //--- Get scroll down width
      int scroll_down_height = (int)ObjectGetInteger(0, SCROLL_DOWN_REC, OBJPROP_YSIZE); //--- Get scroll down height
      bool is_scroll_down_hovered = (mouseX >= scroll_down_x && mouseX <= scroll_down_x + scroll_down_width &&
                                     mouseY >= scroll_down_y && mouseY <= scroll_down_y + scroll_down_height); //--- Check scroll down hover
      if (is_scroll_down_hovered != scroll_down_hovered) {                               //--- Check hover state change
         scroll_down_hovered = is_scroll_down_hovered;                                   //--- Update hover state
         ObjectSetInteger(0, SCROLL_DOWN_REC, OBJPROP_BGCOLOR, is_scroll_down_hovered ? C'70,70,70' : C'60,60,60'); //--- Update background
         ObjectSetInteger(0, SCROLL_DOWN_LABEL, OBJPROP_COLOR, (scroll_pos >= g_max_scroll) ? C'80,80,80' : (is_scroll_down_hovered ? C'100,100,100' : C'150,150,150')); //--- Update label color
         ChartRedraw();                                                                  //--- Redraw chart
      }
      int scroll_slider_x = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XDISTANCE);  //--- Get scroll slider x
      int scroll_slider_y = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE);  //--- Get scroll slider y
      int scroll_slider_width = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XSIZE);  //--- Get scroll slider width
      int scroll_slider_height = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE); //--- Get scroll slider height
      bool is_scroll_slider_hovered = (mouseX >= scroll_slider_x && mouseX <= scroll_slider_x + scroll_slider_width &&
                                       mouseY >= scroll_slider_y && mouseY <= scroll_slider_y + scroll_slider_height); //--- Check scroll slider hover
      if (is_scroll_slider_hovered != scroll_slider_hovered) {                           //--- Check hover state change
         scroll_slider_hovered = is_scroll_slider_hovered;                               //--- Update hover state
         ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_BGCOLOR, is_scroll_slider_hovered ? C'100,100,100' : C'80,80,80'); //--- Update background
         ChartRedraw();                                                                  //--- Redraw chart
      }
   }
}

Hier implementieren wir Hover-Effekte für die interaktiven Elemente, die wir erstellt haben, um das Nutzerfeedback zu verbessern. Wir erstellen die Funktion „UpdateHoverEffects“ und überprüfen darin die Mauskoordinaten („mouseX“, „mouseY“) mit den Positionen und Größen der OK-Schaltfläche („SETUP_OK_BUTTON“), der Abbrechen-Schaltfläche („SETUP_CANCEL_BUTTON“), des Kontrollkästchens („SETUP_CHECKBOX_BG“), der Schaltfläche zum Abbrechen der Kopfzeile („SETUP_HEADER_CANCEL“) und der Bildlaufleistenkomponenten („SCROLL_UP_REC“, „SCROLL_DOWN_REC“, „SCROLL_SLIDER“) unter Verwendung von ObjectGetInteger für deren Abmessungen.

Für jedes Element wird der Hovereffekt erkannt, indem überprüft wird, ob sich die Maus innerhalb der Grenzen befindet, und die jeweiligen Schwebezustände („ok_button_hovered“, „cancel_button_hovered“ usw.) werden aktualisiert, wenn sie sich ändern, und die Farben werden mit ObjectSetInteger angepasst: OK-Schaltfläche verwendet grüne Farbtöne (C'40,80,40'), die Schaltfläche zum Abbrechen verwendet rote Farbtöne (C'80,40,40'), das Kontrollkästchen verwendet grün, wenn es markiert ist (C'0,150,0') oder grau (C'70,70,70'), die Schaltfläche zum Abbrechen in der Kopfzeile leuchtendes Rot (C'255,100,100') und die Schaltflächen/Schieberegler in der Bildlaufleiste verschiedene Grautöne (C'70,70,70' oder C'100,100,100'), je nachdem, ob sie sich in der Schwebe befinden oder die Bildlaufposition („scroll_pos“) verändern. Alle diese Farben können nach Belieben verändert werden. Wir haben einfach wieder willkürliche Werte verwendet, um einen Vorteil gegenüber dem visuellen Feedback zu haben. Nach jeder Aktualisierung wird das Chart mit ChartRedraw neu gezeichnet. Jetzt müssen wir dies nur noch in der Ereignisbehandlung durch OnChartEvent implementieren, der sich um alle Chartereignisse kümmert, aber in unserem Fall sind wir an den Mausbewegungs- und Klickereignissen interessiert.

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {
   int mouseX = (int)lparam;                        //--- Get mouse x coordinate
   int mouseY = (int)dparam;                        //--- Get mouse y coordinate
   int body_inner_y = g_mainY + g_headerHeight + g_spacing; //--- Calculate body y position
   int textAreaEventY = body_inner_y;               //--- Set text area y position
   int textAreaEventH = g_displayHeight;            //--- Set text area height
   int bodyX = g_mainX + g_padding + g_textPadding; //--- Calculate body x position
   int bodyW = g_mainWidth - 2 * g_padding - 2 * g_textPadding - (scroll_visible ? 16 : 0); //--- Calculate body width
   if (id == CHARTEVENT_OBJECT_CLICK) {             //--- Handle object click events
      if (sparam == SETUP_HEADER_CANCEL || sparam == SETUP_CANCEL_BUTTON) { //--- Check cancel button click
         GlobalVariableSet(GV_SETUP, 0.0);          //--- Set global variable to false
         DeleteDashboard();                         //--- Remove dashboard from chart
      } else if (sparam == SETUP_OK_BUTTON) {       //--- Check OK button click
         double new_val = checkbox_checked ? 1.0 : 0.0; //--- Set global variable based on checkbox
         GlobalVariableSet(GV_SETUP, new_val);      //--- Update global variable
         DeleteDashboard();                         //--- Remove dashboard from chart
      } else if (sparam == SETUP_CHECKBOX_BG || sparam == SETUP_CHECKBOX_TEXT || sparam == SETUP_CHECKBOX_LABEL) { //--- Check checkbox click
         checkbox_checked = !checkbox_checked;      //--- Toggle checkbox state
         string check_text = checkbox_checked ? CharToString(252) : " ";         //--- Set checkbox symbol
         ObjectSetString(0, SETUP_CHECKBOX_LABEL, OBJPROP_TEXT, check_text);     //--- Update checkbox label text
         color text_color = checkbox_checked ? C'173,216,230' : clrWhite;        //--- Set text color based on state
         ObjectSetInteger(0, SETUP_CHECKBOX_TEXT, OBJPROP_COLOR, text_color);    //--- Update checkbox text color
         if (checkbox_checked) {                    //--- Check if checkbox is selected
            ObjectSetInteger(0, SETUP_CHECKBOX_BG, OBJPROP_BGCOLOR, C'0,128,0'); //--- Set checked background color
            ObjectSetInteger(0, SETUP_CHECKBOX_LABEL, OBJPROP_COLOR, clrWhite);  //--- Set checked label color
         } else {                                   //--- Handle unchecked state
            ObjectSetInteger(0, SETUP_CHECKBOX_BG, OBJPROP_BGCOLOR, C'60,60,60'); //--- Set unchecked background color
            ObjectSetInteger(0, SETUP_CHECKBOX_LABEL, OBJPROP_COLOR, clrWhite);  //--- Set unchecked label color
         }
         ChartRedraw();                            //--- Redraw chart to reflect changes
      } else if (sparam == SCROLL_UP_REC || sparam == SCROLL_UP_LABEL) {         //--- Check scroll up click
         ScrollUp();                               //--- Execute scroll up action
      } else if (sparam == SCROLL_DOWN_REC || sparam == SCROLL_DOWN_LABEL) {     //--- Check scroll down click
         ScrollDown();                             //--- Execute scroll down action
      }
   } else if (id == CHARTEVENT_MOUSE_MOVE) {       //--- Handle mouse move events
      int MouseState = (int)sparam;                //--- Get mouse state
      bool is_in = (mouseX >= bodyX && mouseX <= bodyX + bodyW && mouseY >= textAreaEventY && mouseY <= textAreaEventY + textAreaEventH); //--- Check if mouse is in body
      mouse_in_body = is_in;                       //--- Update mouse in body status
      UpdateHoverEffects(mouseX, mouseY);          //--- Update hover effects for elements
      static int prevMouseState = 0;               //--- Store previous mouse state
      if (prevMouseState == 0 && MouseState == 1 && scroll_visible) {                //--- Check for slider drag start
         int xd_slider = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XDISTANCE); //--- Get slider x position
         int yd_slider = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE); //--- Get slider y position
         int xs_slider = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XSIZE);     //--- Get slider width
         int ys_slider = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE);     //--- Get slider height
         if (mouseX >= xd_slider && mouseX <= xd_slider + xs_slider &&
             mouseY >= yd_slider && mouseY <= yd_slider + ys_slider) { //--- Check if mouse is over slider
            movingStateSlider = true;              //--- Set slider drag state
            mlbDownX_Slider = mouseX;              //--- Store mouse x position
            mlbDownY_Slider = mouseY;              //--- Store mouse y position
            mlbDown_YD_Slider = yd_slider;         //--- Store slider y position
            ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_BGCOLOR, C'100,100,100'); //--- Set drag color
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false); //--- Disable chart scrolling
         }
      }
      if (movingStateSlider) {                     //--- Handle slider dragging
         int delta_y = mouseY - mlbDownY_Slider;   //--- Calculate y displacement
         int new_y = mlbDown_YD_Slider + delta_y;  //--- Calculate new slider y position
         int textAreaY_local = body_inner_y;       //--- Set text area y position
         int textAreaHeight_local = g_displayHeight;   //--- Set text area height
         int scroll_area_y_min = textAreaY_local + 16; //--- Set minimum slider y
         int scroll_area_y_max = textAreaY_local + textAreaHeight_local - 16 - slider_height; //--- Set maximum slider y
         new_y = MathMax(scroll_area_y_min, MathMin(new_y, scroll_area_y_max));               //--- Clamp new y position
         ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE, new_y);                        //--- Update slider y position
         int max_scroll = g_max_scroll;            //--- Get maximum scroll
         double scroll_ratio = (double)(new_y - scroll_area_y_min) / (scroll_area_y_max - scroll_area_y_min); //--- Calculate scroll ratio
         int new_scroll_pos = (int)MathRound(scroll_ratio * max_scroll);                      //--- Calculate new scroll position
         if (new_scroll_pos != scroll_pos) {       //--- Check if scroll position changed
            scroll_pos = new_scroll_pos;           //--- Update scroll position
            UpdateBodyDisplay();                   //--- Update body text display
         }
         ChartRedraw();                            //--- Redraw chart
      }
      if (MouseState == 0) {                       //--- Handle mouse release
         if (movingStateSlider) {                  //--- Check if slider was being dragged
            movingStateSlider = false;             //--- Reset drag state
            int max_scroll = g_max_scroll;         //--- Get maximum scroll
            int textAreaY_local = body_inner_y;    //--- Set text area y position
            int textAreaHeight_local = g_displayHeight; //--- Set text area height
            int scroll_area_y_min_local = textAreaY_local + 16; //--- Set minimum slider y
            int scroll_area_y_max_local = textAreaY_local + textAreaHeight_local - 16 - slider_height; //--- Set maximum slider y
            int current_slider_y = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE); //--- Get current slider y
            double scroll_ratio = (double)(current_slider_y - scroll_area_y_min_local) / (scroll_area_y_max_local - scroll_area_y_min_local); //--- Calculate scroll ratio
            int temp_scroll = (int)MathRound(scroll_ratio * max_scroll); //--- Calculate temporary scroll
            if (g_adjustedLineHeight > 0) {        //--- Check if line height valid
               int snapped_line = (int)MathRound((double)temp_scroll / g_adjustedLineHeight); //--- Calculate snapped line
               scroll_pos = MathMax(0, MathMin(snapped_line * g_adjustedLineHeight, max_scroll)); //--- Snap to nearest line
            } else {                               //--- No valid line height
               scroll_pos = temp_scroll;           //--- Use temporary scroll
            }
            UpdateBodyDisplay();                   //--- Update body text display
            UpdateSliderPosition();                //--- Update slider position
            ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_BGCOLOR, scroll_slider_hovered ? C'100,100,100' : C'80,80,80'); //--- Reset slider color
            ChartSetInteger(0, CHART_MOUSE_SCROLL, true); //--- Re-enable chart scrolling
         }
      }
      prevMouseState = MouseState;                 //--- Update previous mouse state
   } else if (id == CHARTEVENT_MOUSE_WHEEL) {      //--- Handle mouse wheel events
      int wheel_delta = (int)sparam;               //--- Get wheel delta
      bool in_body = (mouseX >= bodyX && mouseX <= bodyX + bodyW && mouseY >= textAreaEventY && mouseY <= textAreaEventY + textAreaEventH); //--- Check if mouse in body
      if (in_body && g_total_height > g_visible_height && g_adjustedLineHeight > 0) { //--- Check scroll conditions
         int direction = (wheel_delta > 0) ? -1 : 1; //--- Determine scroll direction
         int notches = MathAbs(wheel_delta) / 120; //--- Calculate scroll notches
         int scroll_amount = g_adjustedLineHeight * direction * notches; //--- Calculate scroll amount
         scroll_pos += scroll_amount;              //--- Update scroll position
         int max_scroll = g_max_scroll;            //--- Get maximum scroll
         scroll_pos = MathMax(0, MathMin(scroll_pos, max_scroll)); //--- Clamp scroll position
         UpdateBodyDisplay();                      //--- Update body text display
         if (scroll_visible) {                     //--- Check if scrollbar visible
            UpdateSliderPosition();                //--- Update slider position
            UpdateButtonColors();                  //--- Update button colors
         }
         ChartRedraw();                            //--- Redraw chart
      }
   }
}

In der Funktion OnChartEvent schließlich werden Klick-Ereignisse (CHARTEVENT_OBJECT_CLICK) behandelt, indem das angeklickte Objekt („sparam“) überprüft wird: Wenn es sich um den Kopfzeilenabbruch („SETUP_HEADER_CANCEL“) oder die Schaltfläche Abbrechen („SETUP_CANCEL_BUTTON“) handelt, wird die globale Variable „GV_SETUP“ mit GlobalVariableSet auf 0.0 mit GlobalVariableSet und entfernen das Dashboard mit „DeleteDashboard“; wenn es die OK-Schaltfläche („SETUP_OK_BUTTON“) ist, setzen wir „GV_SETUP“ auf 1.0, wenn das Kontrollkästchen angekreuzt ist („checkbox_checked“) oder 0.Andernfalls 0, dann entfernen wir das Dashboard; wenn es die Checkbox-Komponenten („SETUP_CHECKBOX_BG“, „SETUP_CHECKBOX_TEXT“, „SETUP_CHECKBOX_LABEL“) sind, schalten wir „checkbox_checked“ um, aktualisieren die Beschriftung des Kontrollkästchens mit ObjectSetString auf ein Häkchen (Unicode 252) oder ein Leerzeichen, setzen die Textfarbe mit ObjectSetInteger auf hellblau (C'173,216,230') oder weiß und stellen den Hintergrund des Kontrollkästchens auf grün (C'0,128,0') oder grau (C'60,60,60'). MQL5 bietet eine detaillierte Liste der Wingdings-Zeichen, die wir verwendet haben. Sie können eine beliebige Methode Ihrer Wahl oder einen anderen Ansatz verwenden. Hier ist eine Visualisierung der möglichen MQL5 Wingdings-Zeichen, die Sie verwenden können.

MQL5 WINGDINGS

Für Klicks nach oben („SCROLL_UP_REC“, „SCROLL_UP_LABEL“) oder nach unten („SCROLL_DOWN_REC“, „SCROLL_DOWN_LABEL“) rufen wir „ScrollUp“ oder „ScrollDown“ auf. Bei Mausbewegungsereignissen (CHARTEVENT_MOUSE_MOVE) berechnen wir den Körperbereich, aktualisieren „mouse_in_body“ und rufen „UpdateHoverEffects“ mit „mouseX“ und „mouseY“ auf; wir erkennen den Beginn des Ziehens des Schiebereglers („MouseState“ 1), indem wir prüfen, ob sich die Maus über „SCROLL_SLIDER“, setzen „movingStateSlider“ und speichern Mauspositionen, und während des Ziehens passen wir die y-Position des Schiebereglers mit ObjectSetInteger an, aktualisieren „scroll_pos“ auf der Grundlage des Scroll-Verhältnisses und aktualisieren die Anzeige mit „UpdateBodyDisplay“. Beim Loslassen der Maus („MouseState“ 0) wird „scroll_pos“ auf die nächstgelegene Zeile gerastet, der Schiebereglerstatus zurückgesetzt und das Scrollen des Charts mit der Funktion ChartSetInteger wieder aktiviert. Bei Mausrad-Ereignissen (CHARTEVENT_MOUSE_WHEEL) passen wir „scroll_pos“ auf der Grundlage der Radrichtung und der Kerben an, beschränken es innerhalb von „g_max_scroll“ und aktualisieren die Anzeige und die Bildlaufleiste. Wenn wir kompilieren, erhalten wir das folgende Ergebnis.

ENDGÜLTIGER ASSISTENT MIT VOLLER FUNKTIONALITÄT

Auf dem Bild ist zu sehen, dass das Dashboard jetzt voll funktionsfähig und interaktiv ist. Wenn wir auf die Schaltfläche „OK“ klicken, sollte eine globale Variable gesetzt werden. Um auf die globale Variable zuzugreifen, müssen Sie auf „Extras“ und dann auf „Globale Variablen“ klicken oder einfach F3 auf Ihrer Tastatur drücken. Hier ist eine vollständige Visualisierung.

GLOBALE VARIABLE EINRICHTEN

Anhand der Bilder können wir sehen, dass wir den Assistenten korrekt eingerichtet und alle Ziele erreicht haben. Nun bleibt nur noch die Prüfung der Durchführbarkeit des Projekts, die im vorangegangenen Abschnitt behandelt wurde.


Testen des Einrichtungsassistenten

Wir haben die Tests durchgeführt, und unten sehen Sie die kompilierte Visualisierung in einem einzigen Graphics Interchange Format (GIF) Bitmap-Bildformat.

WIZARD BACKTEST GIF


Schlussfolgerung

Abschließend haben wir einen Assistenten für die Ersteinrichtung von Expert Advisors in MQL5 entwickelt, der ein interaktives Dashboard mit einem scrollbaren Leitfaden, dynamischer Textformatierung und Nutzerkontrollen wie Schaltflächen und einem Kontrollkästchen enthält, um die Programmkonfiguration in MetaTrader 5 zu vereinfachen. Das Tool verbessert das Onboarding von Händlern, indem es klare Anweisungen und einen Mechanismus zum Überspringen künftiger Anzeigen bereitstellt und so eine effiziente Einrichtung und Anpassbarkeit an verschiedene Bildschirmeinstellungen gewährleistet. Es ermöglicht Ihnen, die Programminitialisierung zu vereinfachen, indem es nutzerdefinierte, einmalige Einblicke für den Nutzer bereitstellt, die für weitere Anpassungen in Ihrem Handels-Toolkit bereitstehen. Viel Spaß beim Handeln!

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

Beigefügte Dateien |
Entwicklung des Price Action Analysis Toolkit (Teil 47): Verfolgen von Forex-Sitzungen und Ausbrüchen in MetaTrader 5 Entwicklung des Price Action Analysis Toolkit (Teil 47): Verfolgen von Forex-Sitzungen und Ausbrüchen in MetaTrader 5
Globale Marktsitzungen prägen den Rhythmus des Handelstages, und die Kenntnis ihrer Überschneidungen ist entscheidend für das Timing von Ein- und Ausstiegen. In diesem Artikel werden wir einen interaktiven EA für Handelssitzungen erstellen, der diese globalen Stunden direkt auf Ihrem Chart zum Leben erweckt. Der EA zeichnet automatisch farbcodierte Rechtecke für die Sitzungen in Asien, Tokio, London und New York, die in Echtzeit aktualisiert werden, sobald der jeweilige Markt eröffnet oder geschlossen wird. Sie verfügt über Schaltflächen auf dem Chart, ein dynamisches Informationspanel und eine Laufschrift, die Status- und Ausbruchsmeldungen live überträgt. Dieser bei verschiedenen Brokern getestete EA kombiniert Präzision mit Stil und hilft Händlern, Volatilitätsübergänge zu erkennen, sitzungsübergreifende Ausbrüche zu identifizieren und visuell mit dem Puls des globalen Marktes verbunden zu bleiben.
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 83):  Die Verwendung von Mustern des Stochastischen Oszillators und des FrAMA – Archetypen des Verhaltens MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 83): Die Verwendung von Mustern des Stochastischen Oszillators und des FrAMA – Archetypen des Verhaltens
Der Stochastik-Oszillator und der Fractal Adaptive Moving Average sind ein weiteres Indikatorpaar, das aufgrund seiner Fähigkeit, sich in einem MQL5 Expert Advisor zu ergänzen, verwendet werden kann. Wir betrachten den Stochastik aufgrund seiner Fähigkeit, Momentumverschiebungen zu erkennen, während der FrAMA zur Bestätigung der vorherrschenden Trends verwendet wird. Bei der Erkundung dieser Indikatorenkombination verwenden wir wie immer den MQL5-Assistenten, um ihr Potenzial zu ermitteln und zu testen.
Wiederverwendung von ungültig gemachten Orderblöcken als Mitigation Blocks (SMC) Wiederverwendung von ungültig gemachten Orderblöcken als Mitigation Blocks (SMC)
In diesem Artikel untersuchen wir, wie zuvor für ungültig erklärte Orderblöcke als Mitigation Blocks innerhalb von Smart Money Concepts (SMC) wiederverwendet werden können. Diese Zonen zeigen, wo institutionelle Händler nach einer fehlgeschlagenen Auftragssperre wieder in den Markt einsteigen, und bieten Bereiche mit hoher Wahrscheinlichkeit für eine Fortsetzung des Handels im vorherrschenden Trend.
Bivariate Copulae in MQL5 (Teil 1): Implementierung von Gauß- und Studentische t-Copulae für die Modellierung von Abhängigkeiten Bivariate Copulae in MQL5 (Teil 1): Implementierung von Gauß- und Studentische t-Copulae für die Modellierung von Abhängigkeiten
Dies ist der erste Teil einer Artikelserie, in der die Implementierung von bivariaten Copulae in MQL5 vorgestellt wird. Dieser Artikel enthält Code zur Implementierung der Gauß‘schen und Studentischen t-Copulae. Außerdem werden die Grundlagen der statistischen Copulae und verwandte Themen behandelt. Der Code basiert auf dem Python-Paket Arbitragelab von Hudson und Thames.