English Русский
preview
MQL5-Handelswerkzeuge (Teil 14): Pixelgenaues, scrollbares Textpanel mit Anti-Aliasing und abgerundeter Scrollleiste

MQL5-Handelswerkzeuge (Teil 14): Pixelgenaues, scrollbares Textpanel mit Anti-Aliasing und abgerundeter Scrollleiste

MetaTrader 5Handelssysteme |
20 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

In unserem vorherigen Artikel (Teil 13) haben wir ein Canvas-basiertes Kurs-Dashboard in MetaQuotes Language 5 (MQL5) entwickelt, das mithilfe der Klasse CCanvas interaktive Panels bereitstellt. Es visualisiert Kursverläufe mit Linienplots und Nebeleffekten sowie Kontostatistiken und Details der Bars und unterstützt zudem Hintergrundbilder, Farbverläufe, Themenwechsel, Verschieben und Größenänderungen mit Ereignisbehandlung. In Teil 14 erweitern wir das Dashboard um ein pixelgenaues, scrollbares Textpanel mit Anti-Aliasing und einer abgerundeten Scrollleiste, um die Grenzen von MQL5 zu umgehen. Diese Erweiterung führt ein Textpanel für Bedienhinweise ein, das durch benutzerdefiniert gerenderte, geglättete Elemente ein flüssiges Scrollen ermöglicht. Es umfasst eine Scrollleiste, die sich beim Darüberfahren verbreitert, mit Schaltflächen und Schieberegler, Mausradunterstützung, themenabhängigen Hintergründen mit einstellbarer Transparenz sowie dynamischem Zeilenumbruch und Farbgebung, alles nahtlos integriert für umfassende Bedienhinweise. Wir werden die folgenden Themen behandeln:

  1. Verstehen des Frameworks für ein pixelgenaues scrollbares Textpanel
  2. Implementierung in MQL5
  3. Backtesting
  4. Schlussfolgerung

Am Ende verfügen Sie über ein MQL5-Canvas-Dashboard, das nun ein detailliertes und interaktives Textpanel für Bedienhinweise enthält und für weitere Anpassungen bereit ist. Fangen wir an!


Verstehen des Frameworks für ein pixelgenaues scrollbares Textpanel

Das Framework für ein pixelgenaues, scrollbares Textpanel umgeht die Einschränkungen des nativen Text-Scrollings in MQL5. Dafür benutzt es ein benutzerdefiniertes Rendering auf Pixelebene mit Anti-Aliasing für glatte Kanten, eine abgerundete Scrollleiste, die sich beim Hovern ausdehnt, um die Benutzerfreundlichkeit zu verbessern, und interaktive Elemente wie Aufwärts-/Abwärts-Schaltflächen und einen ziehbaren Schieberegler zum Navigieren durch lange Inhalte, einschließlich Bedienhinweisen. Es unterstützt Themes für Hintergründe mit anpassbarer Deckkraft, dynamischem Zeilenumbruch zur Anpassung an die Breite des Panels bei gleichzeitiger Beibehaltung der Farben für Überschriften/Links und Mausrad-Scrolling innerhalb des Textpanels, um Störungen durch den Chart-Zoom zu umgehen und so eine präzise Steuerung zu gewährleisten, ohne auf integrierte Standardobjekte angewiesen zu sein. Die Integration in das Dashboard ermöglicht nahtlose ereignisgesteuerte Aktualisierungen, wobei die Konsistenz von Diagrammen/Statistiken und Textpanels für ein einheitliches Überwachungsinstrument gewahrt bleibt.

Wir haben uns entschieden, die statischen MQL5-Objekte für die Linien nicht zu verwenden, und wollen die Möglichkeiten des Canvas voll ausschöpfen. Das Gute daran ist, dass wir uns mit dem Canvas keine Gedanken über das Überlaufen des Textes über die Ränder machen müssen, wie es bei den früheren Artikeln der Fall war, bei denen wir die nativen Objekte verwendet haben; das Canvas beschneidet Text automatisch an den Rändern und hilft uns, einen Scroll-Effekt wie auf einer Website zu erzielen. Die abgerundete, dynamische Scrollleiste wurde außerdem vom ansprechenden Overlay des MetaQuotes-Terminals inspiriert, das in den jüngsten Updates eingeführt wurde. Schauen Sie sich an, was wir am Ende mit der Inspiration erreichen wollen.

METAQUOTES SCROLLBAR INSPIRATION

Wir planen, Eingaben/Enumerationen für Textpanel-Optionen wie Höhe/Deckkraft zu erweitern, ein Textpanel-Objekt mit Erstellungs-/Zerstörungslogik hinzuzufügen, Globals für Scroll-Zustände/Positionen/Abmessungen/Farben zu definieren, Textumbruch mit Farberkennung und Fettschrift für Überschriften zu implementieren, Zeichnen einer abgerundeten Scrollleiste mit Hover-Effekten/Buttons/Schieberegler unter Verwendung von Bögen/Kreisen/Rechtecken, Behandlung von Mausereignissen für Hover/Klicks/Ziehen/Rad im Textpanel zur Aktualisierung von Scroll/Tooltip/Scroll-Deaktivierung und um sicherzustellen, dass Themeumstellung, Minimieren und Größenänderungen das Textpanel dynamisch anpassen. Kurz gesagt: Hier ist eine visuelle Darstellung unserer Ziele.

ZIEL DES TEXT CANVAS FRAMEWORKS


Implementierung in MQL5

Um das Programm in MQL5 zu verbessern, müssen wir das neue Textpanel als Canvas deklarieren und einige zusätzliche Eingaben und globale Variablen hinzufügen, um seine Darstellung dynamisch zu steuern.

//+------------------------------------------------------------------+
//|                                       Canvas Dashboard PART2.mq5 |
//|                           Copyright 2026, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Allan Munene Mutiiria."
#property link "https://t.me/Forex_Algo_Trader"
#property version "1.00"
#property strict

//+------------------------------------------------------------------+
//| Canvas objects                                                   |
//+------------------------------------------------------------------+

//---

CCanvas canvasText;                  // Declare text canvas

//+------------------------------------------------------------------+
//| Canvas names                                                     |
//+------------------------------------------------------------------+

//---

string canvasTextName = "TextCanvas";   // Set text canvas name

//--- Added Extra inputs

input bool EnableTextPanel            = true; // Enable Text Panel
input int TextPanelHeight             = 200; // Text Panel Height
input double TextBackgroundOpacityPercent = 85.0; // Text Background Opacity Percent

input double StatsHeaderBgOpacityPercent = 20.0; // Stats Header Bg Opacity Percent
input int StatsHeaderBgRadius         = 8; // Stats Header Bg Radius

//--- Extended the globals

uint bg_pixels_text[];               //--- Store text background pixels

int prev_mouse_state = 0;            //--- Initialize previous mouse state
int last_mouse_x = 0, last_mouse_y = 0; //--- Initialize last mouse positions
int header_height = 27;              //--- Set header height
int gap_y = 7;                       //--- Set Y gap
int button_size = 25;                //--- Set button size
int theme_x_offset = -75;            //--- Set theme X offset
int minimize_x_offset = -50;         //--- Set minimize X offset
int close_x_offset = -25;            //--- Set close X offset
bool panels_minimized = false;       //--- Initialize panels minimized flag
color HeaderColor = C'60,60,60';     //--- Set header color
color HeaderHoverColor = clrRed;     //--- Set header hover color
color HeaderDragColor = clrMediumBlue; //--- Set header drag color

Das erste, was wir tun, um die Verbesserungen zu implementieren, ist, ein zusätzliches CCanvas-Objekt mit dem Namen „canvasText“ für das neue Textpanel zu deklarieren, das den Inhalt der scrollbaren Bedienhinweise rendert. Wir fügen eine String-Konstante „canvasTextName“ hinzu, die „TextCanvas“ zugewiesen wurde, um diese Canvas eindeutig zu identifizieren, ähnlich wie die anderen. Als Nächstes erweitern wir die Eingabeparameter, um das Textpanel zu unterstützen: „EnableTextPanel“ als boolescher Wert mit dem Standardwert true, um es ein- und auszuschalten, „TextPanelHeight“ mit 200 Pixeln für die Höhe, „TextBackgroundOpacityPercent“ mit 85,0 für die Steuerung der Hintergrundtransparenz, „StatsHeaderBgOpacityPercent“ auf 20,0 für die Deckkraft des Hintergrunds der Statistik-Kopfzeile und „StatsHeaderBgRadius“ auf 8 für abgerundete Ecken der Statistik-Kopfzeile. Wir fügen ein neues uint-Array „bg_pixels_text“ ein, um skalierte Hintergrundpixel für das Textpanel zu speichern und die Konsistenz mit dem Diagramm und den Statistiken zu gewährleisten, wenn Hintergründe verwendet werden.

Als Nächstes behalten wir die Globals für die Mausinteraktion bei und initialisieren sie. Dazu gehören „prev_mouse_state“ auf Null und „last_mouse_x“ und „last_mouse_y“ auf Null zur Verfolgung der Mausposition. Wir setzen „header_height“ auf siebenundzwanzig und „gap_y“ auf sieben für die vertikalen Abstände. Die „button_size“ ist fünfundzwanzig. Für die Platzierung der Schaltflächen in Bezug auf den rechten Rand der Kopfzeile werden folgende Offsets verwendet: „theme_x_offset“ bei minus fünfundsiebzig, „minimize_x_offset“ bei minus fünfzig und „close_x_offset“ bei minus fünfundzwanzig. Außerdem bleibt „panels_minimized“ zu Beginn auf false, sodass die Panels anfangs nicht minimiert sind. Die Farben sind definiert: „HeaderColor“ als mittelgrau, „HeaderHoverColor“ als rot und „HeaderDragColor“ als mittelblau für den Kopfzeilenstatus. Der nächste Schritt ist das Hinzufügen des zu zeichnenden Textes auf dem Textpanel und die Definition seiner Steuerelement-Globals wie folgt.

string text_usage_text = 
"\nCanvas Dashboard Usage Guide\n\n"
"Welcome to the Canvas Dashboard – Your Interactive Tool for Real-Time Market Monitoring in MetaTrader 5!\n\n"
"Enhance your trading experience with this dynamic dashboard that visualizes price data, account stats, and interactive controls. Designed for ease of use, it allows customization through dragging, resizing, theme switching, and more, while providing essential market insights at a glance.\n\n"
"Key Features:\n"
"- Price Graph Panel: Displays recent bar closes with a fog gradient fill, background image support, and resize indicators.\n"
"- Stats Panel: Shows account balance, equity, and current bar OHLC values with customizable backgrounds (single color or gradient).\n"
"- Header Controls: Drag to move the dashboard; buttons for theme toggle (dark/light), minimize/maximize panels, and close.\n"
"- Text Panel: Scrollable guide (this panel) with hover-expand scrollbar, up/down buttons, and slider for navigation.\n"
"- Interactivity: Hover for highlights/tooltips; resize via borders (bottom, right, corner); wheel scroll in text area.\n"
"- Theme Support: Switch between dark and light modes for better visibility on different chart backgrounds.\n"
"- Background Options: Enable images with fog overlay; blend modes for transparency.\n\n"
"Usage Instructions:\n"
"1. Move the Dashboard: Click and drag the header (excluding buttons) to reposition on the chart.\n"
"2. Resize Panels: Hover near the graph's bottom/right edges; click and drag when the icon appears (arrows for direction).\n"
"3. Toggle Theme: Click the '[' icon in the header to switch between dark and light modes.\n"
"4. Minimize/Maximize: Click the 'r' or 'o' icon to hide/show panels (header remains visible).\n"
"5. Navigate Text: Use the scrollbar (hovers to expand with buttons/slider); click up/down or drag slider; wheel scroll in body.\n"
"6. Close Dashboard: Click the 'X' icon in the header to remove it from the chart.\n\n"
"Important Notes:\n"
"- Risk Disclaimer: This dashboard is for informational purposes only. Always verify data and trade responsibly.\n"
"- Compatibility Check: Ensure chart settings allow mouse events; test on demo for interactions.\n"
"- Optimization Tips: Adjust input parameters like graph bars, fonts, colors, and opacity for your setup.\n"
"- Security Measures: No account modifications; use on trusted platforms.\n"
"- Legal Notice: No guarantees of accuracy. 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 Canvas Dashboard solutions. Use wisely, monitor confidently, and elevate your trading journey! 🚀\n"; //--- Set text usage content

int text_scroll_pos = 0;                   //--- Initialize text scroll position
int text_max_scroll = 0;                   //--- Initialize text max scroll
int text_slider_height = 20;               //--- Set text slider height
bool text_movingStateSlider = false;       //--- Initialize text slider moving flag
int text_mlbDownY_Slider = 0;              //--- Initialize text slider down Y
int text_mlbDown_YD_Slider = 0;            //--- Initialize text slider down YD
int text_total_height = 0;                 //--- Initialize text total height
int text_visible_height = 0;               //--- Initialize text visible height
bool text_scroll_visible = false;          //--- Initialize text scroll visible flag
bool text_mouse_in_body = false;           //--- Initialize text mouse in body flag
bool prev_text_mouse_in_body = false;      //--- Initialize previous text mouse in body flag
bool text_scroll_up_hovered = false;       //--- Initialize text scroll up hovered flag
bool text_scroll_down_hovered = false;     //--- Initialize text scroll down hovered flag
bool text_scroll_slider_hovered = false;   //--- Initialize text scroll slider hovered flag
bool text_scroll_area_hovered = false;     //--- Initialize text scroll area hovered flag
const int TEXT_MAX_LINES = 100;            //--- Set text max lines
int text_adjustedLineHeight = 0;           //--- Initialize text adjusted line height

int text_scrollbar_full_width = 16;        //--- Set text scrollbar full width
int text_scrollbar_thin_width = 2;         //--- Set text scrollbar thin width
int text_track_width = 16;                 //--- Set text track width
int text_scrollbar_margin = 5;             //--- Set text scrollbar margin
int text_button_size = 16;                 //--- Set text button size
color text_leader_color_dark = C'45,45,45'; //--- Set dark text leader color
color text_leader_color_light = C'200,200,200'; //--- Set light text leader color
color text_button_bg_dark = C'60,60,60';        //--- Set dark text button bg
color text_button_bg_light = C'220,220,220';    //--- Set light text button bg
color text_button_bg_hover_dark = C'70,70,70';  //--- Set dark text button bg hover
color text_button_bg_hover_light = C'180,180,180'; //--- Set light text button bg hover
color text_arrow_color_dark = C'150,150,150';   //--- Set dark text arrow color
color text_arrow_color_light = C'50,50,50';     //--- Set light text arrow color
color text_arrow_color_disabled_dark = C'80,80,80'; //--- Set dark disabled arrow color
color text_arrow_color_disabled_light = C'150,150,150'; //--- Set light disabled arrow color
color text_arrow_color_hover_dark = C'100,100,100'; //--- Set dark hover arrow color
color text_arrow_color_hover_light = C'0,0,0';  //--- Set light hover arrow color
color text_slider_bg_dark = C'80,80,80';        //--- Set dark slider bg
color text_slider_bg_light = C'150,150,150';    //--- Set light slider bg
color text_slider_bg_hover_dark = C'100,100,100';  //--- Set dark slider bg hover
color text_slider_bg_hover_light = C'100,100,100'; //--- Set light slider bg hover

color text_bg_light = clrWhite;                 //--- Set light text bg
color text_bg_dark = clrBlack;                  //--- Set dark text bg
color text_base_light = clrBlack;               //--- Set light text base
color text_base_dark = clrWhite;                //--- Set dark text base

Hier definieren wir die Zeichenkette „text_usage_text“, die den vollständigen Inhalt der Bedienungsanleitung enthält, die im Textpanel angezeigt werden soll. Sie enthält Absätze mit Zeilenumbrüchen, eine Willkommensnachricht, die wichtigsten Funktionen als Aufzählung, nummerierte Bedienhinweise, wichtige Hinweise zu Risiko, Kompatibilität und Sicherheit sowie Kontaktinformationen mit E-Mail/Telegram und ein Dankesschreiben. Alles ist formatiert, um eine umfassende Benutzerführung zu bieten. Dies ist nur eine willkürliche Textformatierung, die wir uns zur Veranschaulichung ausgedacht haben. Sie können sie nach Belieben ändern und an Ihre Bedürfnisse anpassen.

Dann initialisieren wir Variablen für die Verwaltung des Textscrollens: „text_scroll_pos“ auf Null für den aktuellen Versatz, „text_max_scroll“ auf Null für den maximal scrollbaren Betrag, „text_slider_height“ auf 20 für die Schiebereglergröße, „text_movingStateSlider“ auf false, um zu verfolgen, ob der Schieberegler gezogen wird, „text_mlbDownY_Slider“ und „text_mlbDown_YD_Slider“ auf Null für die Y-Positionen beim Mausklick während der Schieberegler-Interaktion, „text_total_height“ und „text_visible_height“ auf Null für den gesamten Inhalt und die sichtbare Höhe, „text_scroll_visible“ false für die Anzeige der Scrollleiste, „text_mouse_in_body“ und „prev_text_mouse_in_body“ false für den aktuellen/vorherigen Mauszeiger im Textpanel, Hover-Flags wie „text_scroll_up_hovered“ false für up/down/slider/area, Konstante „TEXT_MAX_LINES“ auf hundert als Puffergrenze und „text_adjustedLineHeight“ auf Null für den Zeilenabstand. Wir legen die Abmessungen für die Scrollleiste fest: „text_scrollbar_full_width“ auf 16 für aufgeklappt, „text_scrollbar_thin_width“ auf 2 für zugeklappt, „text_track_width“ auf 16 für die Spur, „text_scrollbar_margin“ auf 5 für die Auffüllung, „text_button_size“ auf 16 für die Schaltflächen zum Hoch- und Herunterscrollen. Wir definieren themenabhängige Farben: Farbe der Leiste/Schaltfläche bg/normal/hover, Pfeil normal/deaktiviert/hover für dunkles/helles Theme unter Verwendung der Notationen C“...“, und Basis bg/Text als weiß/schwarz für hell oder schwarz/weiß für dunkel, um die Sichtbarkeit über verschiedene Themes hinweg zu gewährleisten.

Als Nächstes werden wir die Statistik-Canvas aktualisieren, indem wir die abgerundeten Rechtecke hinzufügen. Wir werden zunächst eine Funktion definieren, um dies zu erreichen.

//+------------------------------------------------------------------+
//| Fill rounded rectangle                                           |
//+------------------------------------------------------------------+
void FillRoundedRectangle(CCanvas &cvs, int x, int y, int w, int h, int radius, uint argb_color)
  {
   if (radius <= 0) {
      cvs.FillRectangle(x, y, x + w - 1, y + h - 1, argb_color);                //--- Fill rectangle if no radius
      return;                          //--- Exit
     }
   radius = MathMin(radius, MathMin(w / 2, h / 2));                             //--- Adjust radius

   cvs.Arc(x + radius, y + radius, radius, radius, DegreesToRadians(180), DegreesToRadians(90), argb_color); //--- Draw top-left arc
   cvs.Arc(x + w - radius - 1, y + radius, radius, radius, DegreesToRadians(270), DegreesToRadians(90), argb_color); //--- Draw top-right arc
   cvs.Arc(x + w - radius - 1, y + h - radius - 1, radius, radius, DegreesToRadians(0), DegreesToRadians(90), argb_color); //--- Draw bottom-right arc
   cvs.Arc(x + radius, y + h - radius - 1, radius, radius, DegreesToRadians(90), DegreesToRadians(90), argb_color); //--- Draw bottom-left arc

   cvs.FillCircle(x + radius, y + radius, radius, argb_color);                  //--- Fill top-left circle
   cvs.FillCircle(x + w - radius - 1, y + radius, radius, argb_color);          //--- Fill top-right circle
   cvs.FillCircle(x + w - radius - 1, y + h - radius - 1, radius, argb_color);  //--- Fill bottom-right circle
   cvs.FillCircle(x + radius, y + h - radius - 1, radius, argb_color);          //--- Fill bottom-left circle

   cvs.FillRectangle(x + radius, y, x + w - radius - 1, y + h - 1, argb_color); //--- Fill horizontal areas

   cvs.FillRectangle(x, y + radius, x + w - 1, y + h - radius - 1, argb_color); //--- Fill vertical areas
  }

Wir implementieren die Funktion „FillRoundedRectangle“, um ein Rechteck mit abgerundeten Ecken auf einem gegebenen CCanvas-Referenzobjekt zu zeichnen, wobei wir Parameter für die Position x/y, Breite w und Höhe h, Eckenradius und ARGB-Farbe benötigen. Wenn der Radius null oder kleiner ist, füllen wir einfach ein Standardrechteck von (x,y) bis (x+w-1, y+h-1) mit der Methode FillRectangle und beenden es frühzeitig. Andernfalls wird der Radius mithilfe von MathMin auf das Minimum der halben Breite oder Höhe festgelegt, um einen Überlauf zu vermeiden. Wir zeichnen Viertelbögen mit Arc für jede Ecke: oben links von 180 bis 270 Grad, oben rechts von 270 bis 360, unten rechts von 0 bis 90, unten links von 90 bis 180, alle umgewandelt in Bogenmaß mit einer Hilfsfunktion wie „DegreesToRadians“. Wir werden diese Funktion später definieren.

Dann füllen wir Vollkreise an jeder Ecke mit FillCircle, um die abgerundeten Bereiche zu verfestigen. Dann füllen wir den horizontalen Mittelstreifen vom linken Radius bis zum rechten Minusradius über die gesamte Höhe und die vertikalen Seiten vom oberen Radius bis zum unteren Minusradius über die gesamte Breite, jeweils mit „FillRectangle“, um die Form lückenlos zu vervollständigen. Die Hilfsfunktion zur Konvertierung lautet wie folgt.

//+------------------------------------------------------------------+
//| Convert degrees to radians                                       |
//+------------------------------------------------------------------+
double DegreesToRadians(double degrees) {
   return((M_PI * degrees) / 180.0); //--- Perform conversion
}

Wir teilen einfach die Multiplikation von „pi“ oder M_PI durch die Zielgrade durch 180. Anschließend können wir die Funktion „Abgerundetes Rechteck“ verwenden, um unsere Überschriften im Statistikbereich zu zeichnen, indem wir die Funktion aktualisieren.

//+------------------------------------------------------------------+
//| Update stats on canvas                                           |
//+------------------------------------------------------------------+
void UpdateStatsOnCanvas()
  {

//---

string headerText = "Account Stats";                               //--- Set header text
canvasStats.FontSet("Arial Bold", StatsHeaderFontSize);            //--- Set font
int textW = canvasStats.TextWidth(headerText);                     //--- Get text width
int textH = canvasStats.TextHeight(headerText);                    //--- Get text height
int pad = 5;                                                       //--- Set padding
int rectX = (statsWidth - textW) / 2 - pad;                        //--- Calculate rect X
int rectY = yPos - pad / 2;                                        //--- Calculate rect Y
int rectW = textW + 2 * pad;                                       //--- Calculate rect width
int rectH = textH + pad;                                           //--- Calculate rect height
uchar alpha = (uchar)(255 * (StatsHeaderBgOpacityPercent / 100.0)); //--- Calculate alpha
uint argbHeaderBg = ColorToARGB(GetStatsHeaderBgColor(), alpha);   //--- Convert bg to ARGB

color header_border = is_dark_theme ? clrWhite : clrBlack;         //--- Get border color
uint argbHeaderBorder = ColorToARGB(header_border, 255);           //--- Convert to ARGB
FillRoundedRectangle(canvasStats, rectX - 1, rectY - 1, rectW + 2, rectH + 2, StatsHeaderBgRadius + 1, argbHeaderBorder); //--- Fill outer rectangle

FillRoundedRectangle(canvasStats, rectX, rectY, rectW, rectH, StatsHeaderBgRadius, argbHeaderBg); //--- Fill inner rectangle

uint argbHeader = ColorToARGB(headerColor, 255);                   //--- Convert header to ARGB
canvasStats.TextOut(statsWidth / 2, yPos, headerText, argbHeader, TA_CENTER); //--- Draw header

//---

   }

Zur Umsetzung der Änderungen wird die Funktion „UpdateStatsOnCanvas“ geändert, um das Rendering der Kopfzeilen mit abgerundeten Hintergründen zu verbessern und so ein besseres Aussehen zu erzielen. Für jede Überschrift wie „Account Stats“ oder „Current Bar Stats“ setzen wir die Schriftart auf Arial Bold mit „StatsHeaderFontSize“ mit FontSet, messen die Textbreite und -höhe mit TextWidth und TextHeight, definieren ein Padding von 5 Pixeln und berechnen die Position/Abmessungen des Rechtecks zentriert mit Padding: x als (Breite minus Textbreite)/2 minus Pad, y als aktuelle „yPos“ minus halbes Pad, Breite als Text plus doppeltes Pad, Höhe als Text plus Pad.

Wir berechnen Alpha aus „StatsHeaderBgOpacityPercent“ über 100,0 mal 255 in uchar, konvertieren die Hintergrundfarbe aus „GetStatsHeaderBgColor“ in ARGB mit diesem Alpha. Für die Umrandung wählen wir Weiß im dunklen Theme oder Schwarz im hellen Theme, konvertieren in volle Deckkraft ARGB und rufen „FillRoundedRectangle“ zweimal auf: einmal für die äußere Umrandung an Position minus eins, Größe plus 2, Radius plus eins unter Verwendung von border ARGB; dann die innere an Basisposition/Größe/Radius mit Hintergrund ARGB. Wir konvertieren die Farbe der Kopfzeile in ARGB bei 255, zeichnen den Text zentriert bei der Breite der Statistik /2 und „yPos“ mit TextOut und zentrierter Ausrichtung, dann erhöhen wir „yPos“ um dreißig für den Abstand vor dem nächsten Abschnitt. Der Rest der Funktion bleibt so, wie wir sie zuvor definiert haben. Nach der Kompilierung erhalten wir nun folgendes Ergebnis.

AKTUALISIERTE STATISTIKÜBERSCHRIFTEN MIT ABGERUNDETEN RECHTECKEN

Sie sehen, dass die Überschriften des Statistik-Panels jetzt in abgerundeten Rechtecken dargestellt werden. Als Nächstes wollen wir das Textpanel rendern. Wir werden auch dafür eine Funktion erstellen, wie für die anderen Panels, aber zuerst müssen wir den Text umbrechen, damit er dynamisch ist, zusammen mit anderen Hilfsfunktionen, die wir später benötigen werden. Hier ist der Ansatz, den wir verwendet haben, um dies zu erreichen.

//+------------------------------------------------------------------+
//| Lighten color                                                    |
//+------------------------------------------------------------------+
color LightenColor(color colorValue, double factor)
  {
   int blue = (int)MathMin(255, (colorValue & 0xFF) * factor);         //--- Lighten blue
   int green = (int)MathMin(255, ((colorValue >> 8) & 0xFF) * factor); //--- Lighten green
   int red = (int)MathMin(255, ((colorValue >> 16) & 0xFF) * factor);  //--- Lighten red
   return (color)(blue | (green << 8) | (red << 16));                  //--- Return lightened color
  }

//+------------------------------------------------------------------+
//| Calculate slider height                                          |
//+------------------------------------------------------------------+
int TextCalculateSliderHeight()
  {
   int scroll_area_height = TextPanelHeight - 2 - 2 * text_button_size; //--- Calculate area height
   int slider_min_height = 20;         //--- Set min height
   if (text_total_height <= text_visible_height) return scroll_area_height; //--- Return full if no scroll
   double visible_ratio = (double)text_visible_height / text_total_height;  //--- Calculate ratio
   int height = (int)MathFloor(scroll_area_height * visible_ratio);         //--- Calculate height
   return MathMax(slider_min_height, height);                           //--- Return max of min and calculated
  }

//+------------------------------------------------------------------+
//| Scroll text up                                                   |
//+------------------------------------------------------------------+
void TextScrollUp()
  {
   if (text_adjustedLineHeight > 0 && text_scroll_pos > 0)
     {
      text_scroll_pos = MathMax(0, text_scroll_pos - text_adjustedLineHeight); //--- Scroll up
      UpdateTextOnCanvas();                                            //--- Update canvas
     }
  }

//+------------------------------------------------------------------+
//| Scroll text down                                                 |
//+------------------------------------------------------------------+
void TextScrollDown()
  {
   if (text_adjustedLineHeight > 0 && text_scroll_pos < text_max_scroll)
     {
      text_scroll_pos = MathMin(text_max_scroll, text_scroll_pos + text_adjustedLineHeight); //--- Scroll down
      UpdateTextOnCanvas();                                            //--- Update canvas
     }
  }

//+------------------------------------------------------------------+
//| Update text hover effects                                        |
//+------------------------------------------------------------------+
void TextUpdateHoverEffects(int local_x, int local_y)
  {
   if (!text_scroll_visible) return;                                    //--- Exit if no scroll
   int scrollbar_x = canvasText.Width() - text_track_width - 1;         //--- Get scrollbar X
   int scrollbar_y = 1;                                                 //--- Set scrollbar Y
   int scrollbar_height = TextPanelHeight - 2;                          //--- Get height
   text_scroll_up_hovered = text_scroll_area_hovered && (local_x >= scrollbar_x && local_x <= scrollbar_x + text_track_width - 1 &&
                             local_y >= scrollbar_y && local_y <= scrollbar_y + text_button_size - 1); //--- Check up hover
   int down_y = scrollbar_y + scrollbar_height - text_button_size;      //--- Calculate down Y
   text_scroll_down_hovered = text_scroll_area_hovered && (local_x >= scrollbar_x && local_x <= scrollbar_x + text_track_width - 1 &&
                               local_y >= down_y && local_y <= down_y + text_button_size - 1); //--- Check down hover
   int scroll_area_height = scrollbar_height - 2 * text_button_size;    //--- Calculate area height
   int slider_y = scrollbar_y + text_button_size + (int)(((double)text_scroll_pos / text_max_scroll) * (scroll_area_height - text_slider_height)); //--- Calculate slider Y
   text_scroll_slider_hovered = text_scroll_area_hovered && (local_x >= scrollbar_x && local_x <= scrollbar_x + text_track_width - 1 &&
                                 local_y >= slider_y && local_y <= slider_y + text_slider_height - 1); //--- Check slider hover
  }

//+------------------------------------------------------------------+
//| Get line color                                                   |
//+------------------------------------------------------------------+
color GetLineColor(string lineText)
  {
   if (StringLen(lineText) == 0 || lineText == " ") return C'25,25,25'; //--- Return gray for empty
   if (StringFind(lineText, "mutiiriallan.forex@gmail.com") >= 0) return C'255,100,100';   //--- Return red for email
   if (StringFind(lineText, "https://t.me/Forex_Algo_Trader") >= 0) return C'150,100,200'; //--- Return purple for link
   if (StringFind(lineText, "@ForexAlgo-Trader") >= 0) return C'100,150,255';              //--- Return blue for channel
   if (StringFind(lineText, "http") >= 0 || StringFind(lineText, "t.me") >= 0) return C'100,150,255'; //--- Return blue for links
   string start3 = StringSubstr(lineText, 0, 3);                        //--- Get start substring
   if ((start3 == "1. " || start3 == "2. " || start3 == "3. " || start3 == "4. " || start3 == "5. ") &&
       StringFind(lineText, "Initial Setup Instructions") < 0) return C'255,200,100';      //--- Return orange for numbered
   return clrWhite;                                                     //--- Return white default
  }

//+------------------------------------------------------------------+
//| Check if heading                                                 |
//+------------------------------------------------------------------+
bool IsHeading(string lineText)
  {
   if (StringLen(lineText) == 0) return false;                          //--- False for empty
   if (StringGetCharacter(lineText, StringLen(lineText) - 1) == ':') return true; //--- True for colon end
   if (StringFind(lineText, "Canvas Dashboard Usage Guide") >= 0) return true;    //--- True for guide
   if (StringFind(lineText, "Key Features") >= 0) return true;          //--- True for features
   if (StringFind(lineText, "Usage Instructions") >= 0) return true;    //--- True for instructions
   if (StringFind(lineText, "Important Notes") >= 0) return true;       //--- True for notes
   if (StringFind(lineText, "Contact Methods") >= 0) return true;       //--- True for contacts
   if (StringFind(lineText, "NB:") >= 0) return true;                   //--- True for NB
   return false;                                                        //--- Return false
  }

//+------------------------------------------------------------------+
//| Wrap text                                                        |
//+------------------------------------------------------------------+
void WrapText(const string inputText, const string font, const int fontSize, const int maxWidth, string &wrappedLines[], color &wrappedColors[])
  {
   ArrayResize(wrappedLines, 0);                                        //--- Reset lines
   ArrayResize(wrappedColors, 0);                                       //--- Reset colors
   string paragraphs[];                                                 //--- Declare paragraphs
   int numParagraphs = StringSplit(inputText, '\n', paragraphs);        //--- Split by newline
   for (int p = 0; p < numParagraphs; p++)
     {
      string para = paragraphs[p];                                      //--- Get paragraph
      color paraColor = GetLineColor(para);                             //--- Get color
      if (StringLen(para) == 0)
        {
         int size = ArraySize(wrappedLines);                            //--- Get size
         ArrayResize(wrappedLines, size + 1);                           //--- Resize
         wrappedLines[size] = " ";                                      //--- Add space
         ArrayResize(wrappedColors, size + 1);                          //--- Resize colors
         wrappedColors[size] = C'25,25,25';                             //--- Set gray
         continue;                                                      //--- Continue
        }
      string words[];                                                   //--- Declare words
      int numWords = StringSplit(para, ' ', words);                     //--- Split by space
      string currentLine = "";                                          //--- Initialize line
      for (int w = 0; w < numWords; w++)
        {
         string testLine = currentLine + (StringLen(currentLine) > 0 ? " " : "") + words[w]; //--- Build test line
         canvasText.FontSet(font, fontSize);                            //--- Set font
         int textW = canvasText.TextWidth(testLine);                    //--- Get width
         if (textW <= maxWidth)
           {
            currentLine = testLine;                                     //--- Update line
           }
         else
           {
            if (StringLen(currentLine) > 0)
              {
               int size = ArraySize(wrappedLines);                     //--- Get size
               ArrayResize(wrappedLines, size + 1);                    //--- Resize
               wrappedLines[size] = currentLine;                       //--- Add line
               ArrayResize(wrappedColors, size + 1);                   //--- Resize colors
               wrappedColors[size] = paraColor;                        //--- Set color
              }
            currentLine = words[w];                                    //--- Start new line
           }
        }
      if (StringLen(currentLine) > 0)
        {
         int size = ArraySize(wrappedLines);                           //--- Get size
         ArrayResize(wrappedLines, size + 1);                          //--- Resize
         wrappedLines[size] = currentLine;                             //--- Add last line
         ArrayResize(wrappedColors, size + 1);                         //--- Resize colors
         wrappedColors[size] = paraColor;                              //--- Set color
        }
     }
  }

Wir definieren eine Reihe von Hilfsfunktionen. Zunächst implementieren wir die Funktion „LightenColor“, um eine Farbe aufzuhellen, indem wir ihre RGB-Komponenten mit einem Faktor größer als eins multiplizieren, wobei wir sie auf 255 begrenzen, um einen Überlauf zu vermeiden, und zwar zur Verwendung bei Themeanpassungen wie Textfarben im dunklen Design. Es nimmt die Farbe „colorValue“ und einen doppelten Faktor, extrahiert Blau/Grün/Rot mit Maske/Verschiebungen, multipliziert jede mit dem Faktor, der mit MathMin 255 in int umgewandelt wird, und kombiniert mit Bit-Verschiebungen/OR-Werten zu Farbe.

Als Nächstes erstellen wir die Funktion „TextCalculateSliderHeight“, um die Größe des Schiebereglers proportional zum sichtbaren Inhalt zu berechnen und so ein Minimum an Benutzerfreundlichkeit zu gewährleisten. Sie berechnet die Höhe des Bereichs als „TextPanelHeight“ minus Ränder minus den doppelten Wert von „text_button_size“, setzt min auf 20, gibt den vollen Bereich zurück, wenn Scrollen deaktiviert ist, sonst den Bodenbereich mal sichtbares Verhältnis, und gibt das Maximum von min und dem Wert zurück. Dann definieren wir „TextScrollUp“, um den Text nach oben zu scrollen, indem wir „text_scroll_pos“ um „text_adjustedLineHeight“ dekrementieren, wenn es positiv und größer als Null ist, und rufen „UpdateTextOnCanvas“ auf, um den Text neu zu zeichnen. In ähnlicher Weise blättert „TextScrollDown“ nach unten, indem „text_scroll_pos“ um eine Zeilenhöhe erhöht wird, wenn es kleiner ist als „text_max_scroll“, das auf „max“ festgelegt ist, und aktualisiert die Canvas.

Die Funktion „TextUpdateHoverEffects“ erkennt das Hovern über Teile der Scrollleiste anhand lokaler Koordinaten, um ein visuelles Feedback zu geben. Ist die Scrollleiste nicht sichtbar, wird die Funktion beendet. Berechnet die Positionen/Abmessungen der Scrollleiste, prüft „text_scroll_up_hovered“, wenn sich der Mauszeiger darüber und sich in den Grenzen der oberen Schaltfläche befindet, „text_scroll_down_hovered“ für den unteren Bereich, „text_scroll_slider_hovered“ für den aktuellen Positionsbereich des Schiebereglers, alles in Abhängigkeit von „text_scroll_area_hovered“. Wir implementieren „GetLineColor“, um die Zeilenfarben für das Styling nach Inhalt zu kategorisieren: grau für Leerzeichen, rot für E-Mail, lila/blau für bestimmte Links/Kanäle/http/t.me, orange für nummerierte Listen, nicht für bestimmte Phrasen, standardmäßig weiß. „IsHeading“ identifiziert Überschriften: false für leere Überschriften, true, wenn sie mit einem Doppelpunkt enden oder auf Phrasen wie guide/features/instructions/notes/contacts/NB passen. Eigentlich könnten wir für die Bezeichnungen hier auch HTML-Zeichen wie Format verwenden, aber einfachheitshalber haben wir die Zeichen, die wir erkennen wollen, in die Funktion aufgenommen. Es gibt viele Ideen, also folgen Sie derjenigen, die zu Ihnen passt.

Schließlich bricht „WrapText“ Eingaben in Zeilen mit eingeschränkter Breite und entsprechenden Farben um. Es setzt die Ausgabe-Arrays zurück, trennt nach Zeilenumbruch in Absätze, erhält für jeden eine Farbe, fügt Leerzeichen/Grau hinzu, falls leer. Anschließend werden die Absätze in Wörter aufgeteilt und Zeilen erstellt/prüft: Wenn die Zeile der gemessenen Breite entspricht (vorläufige Schriftart), wird sie angehängt; andernfalls wird die aktuelle Zeile mit Farbe hinzugefügt, eine neue Zeile begonnen und die letzte Zeile ergänzt, falls sie vorhanden ist. Mit diesen Hilfsfunktionen können wir nun die Haupt-Canvas mit der folgenden Logik zeichnen.

//+------------------------------------------------------------------+
//| Update text on canvas                                            |
//+------------------------------------------------------------------+
void UpdateTextOnCanvas()
  {
   canvasText.Erase(0);                //--- Clear text canvas
   
   int textWidth = canvasText.Width(); //--- Get text width
   int textHeight = TextPanelHeight;   //--- Get text height
   
   color text_bg = is_dark_theme ? text_bg_dark : text_bg_light;                  //--- Get bg color
   uint argb_bg = ColorToARGB(text_bg, (uchar)(255 * (TextBackgroundOpacityPercent / 100.0))); //--- Convert to ARGB
   canvasText.FillRectangle(0, 0, textWidth - 1, textHeight - 1, argb_bg);        //--- Fill background
   
   uint argbBorder = ColorToARGB(GetBorderColor(), 255);                          //--- Convert border
   canvasText.Line(0, 0, textWidth - 1, 0, argbBorder);                           //--- Draw top
   canvasText.Line(textWidth - 1, 0, textWidth - 1, textHeight - 1, argbBorder);  //--- Draw right
   canvasText.Line(textWidth - 1, textHeight - 1, 0, textHeight - 1, argbBorder); //--- Draw bottom
   canvasText.Line(0, textHeight - 1, 0, 0, argbBorder); //--- Draw left
   
   int padding = 10;                   //--- Set padding
   int textAreaX = 1 + padding;        //--- Calculate area X
   int textAreaY = 1;                  //--- Set area Y
   int textAreaWidth = textWidth - 2 - padding * 2; //--- Calculate area width
   int textAreaHeight = textHeight - 2; //--- Calculate area height
   string font = "Arial";              //--- Set font
   int fontSize = 16;                  //--- Set font size
   canvasText.FontSet(font, fontSize); //--- Set font
   int lineHeight = canvasText.TextHeight("A"); //--- Get line height
   text_adjustedLineHeight = lineHeight + 3; //--- Adjust line height
   
   text_visible_height = textAreaHeight; //--- Set visible height
   static string wrappedLines[];       //--- Declare wrapped lines
   static color wrappedColors[];       //--- Declare wrapped colors
   static bool wrapped = false;        //--- Initialize wrapped flag
   bool need_scroll = false;           //--- Initialize scroll need
   int reserved_width = 0;             //--- Initialize reserved width
   if (!wrapped)
     {
      WrapText(text_usage_text, font, fontSize, textAreaWidth, wrappedLines, wrappedColors); //--- Wrap text
      wrapped = true;                  //--- Set wrapped flag
     }
   int numLines = ArraySize(wrappedLines);                 //--- Get number of lines
   text_total_height = numLines * text_adjustedLineHeight; //--- Calculate total height
   need_scroll = text_total_height > text_visible_height;  //--- Check if scroll needed
   if (need_scroll)
     {
      reserved_width = text_track_width; //--- Set reserved width
      textAreaWidth -= reserved_width;   //--- Adjust area width
      WrapText(text_usage_text, font, fontSize, textAreaWidth, wrappedLines, wrappedColors); //--- Rewrap text
      numLines = ArraySize(wrappedLines); //--- Update num lines
      text_total_height = numLines * text_adjustedLineHeight;               //--- Update total height
     }
   text_max_scroll = MathMax(0, text_total_height - text_visible_height);   //--- Calculate max scroll
   text_scroll_visible = need_scroll;                                       //--- Set scroll visible
   text_scroll_pos = MathMax(0, MathMin(text_scroll_pos, text_max_scroll)); //--- Adjust scroll pos
   if (text_scroll_visible)
     {
      int scrollbar_y = 1;                //--- Set scrollbar Y
      int scrollbar_height = textHeight - 2; //--- Calculate scrollbar height
      int scroll_area_height = scrollbar_height - 2 * text_button_size;      //--- Calculate area height
      text_slider_height = TextCalculateSliderHeight();                      //--- Calculate slider height
      int scrollbar_x = textWidth - text_track_width - 1;                    //--- Calculate scrollbar X
      color leader_color = is_dark_theme ? text_leader_color_dark : text_leader_color_light; //--- Get leader color
      uint argb_leader = ColorToARGB(leader_color, 255);                     //--- Convert to ARGB
      canvasText.FillRectangle(scrollbar_x, scrollbar_y, scrollbar_x + text_track_width - 1, scrollbar_y + scrollbar_height - 1, argb_leader); //--- Fill leader
      
      int slider_y = scrollbar_y + text_button_size + (int)(((double)text_scroll_pos / text_max_scroll) * (scroll_area_height - text_slider_height)); //--- Calculate slider Y
      
      if (text_scroll_area_hovered)
        {
         color button_bg = is_dark_theme ? text_button_bg_dark : text_button_bg_light;                   //--- Get button bg
         color button_bg_hover = is_dark_theme ? text_button_bg_hover_dark : text_button_bg_hover_light; //--- Get hover bg
         color up_bg = text_scroll_up_hovered ? button_bg_hover : button_bg;                             //--- Determine up bg
         uint argb_up_bg = ColorToARGB(up_bg, 255);                                                      //--- Convert to ARGB
         canvasText.FillRectangle(scrollbar_x, scrollbar_y, scrollbar_x + text_track_width - 1, scrollbar_y + text_button_size - 1, argb_up_bg); //--- Fill up button
         color arrow_color = is_dark_theme ? text_arrow_color_dark : text_arrow_color_light;             //--- Get arrow color
         color arrow_color_disabled = is_dark_theme ? text_arrow_color_disabled_dark : text_arrow_color_disabled_light; //--- Get disabled arrow
         color arrow_color_hover = is_dark_theme ? text_arrow_color_hover_dark : text_arrow_color_hover_light; //--- Get hover arrow
         color up_arrow = (text_scroll_pos == 0) ? arrow_color_disabled : (text_scroll_up_hovered ? arrow_color_hover : arrow_color); //--- Determine up arrow color
         uint argb_up_arrow = ColorToARGB(up_arrow, 255);                                                //--- Convert to ARGB
         canvasText.FontSet("Webdings", 22);                                                             //--- Set font
         int arrow_x = scrollbar_x + text_track_width / 2;                                               //--- Calculate arrow X
         int arrow_y = scrollbar_y + (text_button_size / 2) - (canvasText.TextHeight(CharToString(0x35)) / 2); //--- Calculate arrow Y
         canvasText.TextOut(arrow_x, arrow_y, CharToString(0x35), argb_up_arrow, TA_CENTER);             //--- Draw up arrow
         
         int down_y = scrollbar_y + scrollbar_height - text_button_size;                                 //--- Calculate down Y
         color down_bg = text_scroll_down_hovered ? button_bg_hover : button_bg;                         //--- Determine down bg
         uint argb_down_bg = ColorToARGB(down_bg, 255);                                                  //--- Convert to ARGB
         canvasText.FillRectangle(scrollbar_x, down_y, scrollbar_x + text_track_width - 1, down_y + text_button_size - 1, argb_down_bg); //--- Fill down button
         color down_arrow = (text_scroll_pos >= text_max_scroll) ? arrow_color_disabled : (text_scroll_down_hovered ? arrow_color_hover : arrow_color); //--- Determine down arrow
         uint argb_down_arrow = ColorToARGB(down_arrow, 255);                                            //--- Convert to ARGB
         int down_arrow_x = scrollbar_x + text_track_width / 2;                                          //--- Calculate down arrow X
         int down_arrow_y = down_y + (text_button_size / 2) - (canvasText.TextHeight(CharToString(0x36)) / 2); //--- Calculate down arrow Y
         canvasText.TextOut(down_arrow_x, down_arrow_y, CharToString(0x36), argb_down_arrow, TA_CENTER); //--- Draw down arrow
         
         int slider_x = scrollbar_x + text_scrollbar_margin;                                             //--- Calculate slider X
         int slider_w = text_track_width - 2 * text_scrollbar_margin;                                    //--- Calculate slider width
         int cap_radius = slider_w / 2;                                                                  //--- Calculate cap radius
         color slider_bg_color = is_dark_theme ? text_slider_bg_dark : text_slider_bg_light;             //--- Get slider bg
         color slider_bg_hover_color = is_dark_theme ? text_slider_bg_hover_dark : text_slider_bg_hover_light; //--- Get hover bg
         color slider_bg = text_scroll_slider_hovered || text_movingStateSlider ? slider_bg_hover_color : slider_bg_color; //--- Determine bg
         uint argb_slider = ColorToARGB(slider_bg, 255);                                                 //--- Convert to ARGB

         canvasText.Arc(slider_x + cap_radius, slider_y + cap_radius, cap_radius, cap_radius, DegreesToRadians(180), DegreesToRadians(360), argb_slider); //--- Draw top arc
         canvasText.FillCircle(slider_x + cap_radius, slider_y + cap_radius, cap_radius, argb_slider);   //--- Fill top circle

         canvasText.Arc(slider_x + cap_radius, slider_y + text_slider_height - cap_radius, cap_radius, cap_radius, DegreesToRadians(0), DegreesToRadians(180), argb_slider); //--- Draw bottom arc
         canvasText.FillCircle(slider_x + cap_radius, slider_y + text_slider_height - cap_radius, cap_radius, argb_slider); //--- Fill bottom circle

         canvasText.FillRectangle(slider_x, slider_y + cap_radius, slider_x + slider_w, slider_y + text_slider_height - cap_radius, argb_slider); //--- Fill middle
        }
      else
        {
         int thin_w = text_scrollbar_thin_width;                                                         //--- Set thin width
         int thin_x = scrollbar_x + (text_track_width - thin_w) / 2;                                     //--- Calculate thin X
         int cap_radius = thin_w / 2;                                                                    //--- Calculate cap radius
         color slider_bg_color = is_dark_theme ? text_slider_bg_dark : text_slider_bg_light;             //--- Get bg
         uint argb_slider = ColorToARGB(slider_bg_color, 255);                                           //--- Convert to ARGB

         canvasText.Arc(thin_x + cap_radius, slider_y + cap_radius, cap_radius, cap_radius, DegreesToRadians(180), DegreesToRadians(360), argb_slider); //--- Draw top
         canvasText.FillCircle(thin_x + cap_radius, slider_y + cap_radius, cap_radius, argb_slider);     //--- Fill top

         canvasText.Arc(thin_x + cap_radius, slider_y + text_slider_height - cap_radius, cap_radius, cap_radius, DegreesToRadians(0), DegreesToRadians(180), argb_slider); //--- Draw bottom
         canvasText.FillCircle(thin_x + cap_radius, slider_y + text_slider_height - cap_radius, cap_radius, argb_slider); //--- Fill bottom

         canvasText.FillRectangle(thin_x, slider_y + cap_radius, thin_x + thin_w, slider_y + text_slider_height - cap_radius, argb_slider); //--- Fill middle
        }
     }
   
   color text_base = is_dark_theme ? text_base_dark : text_base_light;                                   //--- Get base color
   for (int line = 0; line < numLines; line++)
     {
      string lineText = wrappedLines[line];   //--- Get line text
      if (StringLen(lineText) == 0) continue; //--- Skip empty
      color lineColor = wrappedColors[line];  //--- Get line color
      if (is_dark_theme) lineColor = (lineColor == clrWhite) ? clrWhite : LightenColor(lineColor, 1.5);  //--- Adjust for dark
      else lineColor = (lineColor == clrWhite) ? clrBlack : DarkenColor(lineColor, 0.7);                 //--- Adjust for light
      int line_y = textAreaY + line * text_adjustedLineHeight - text_scroll_pos;                         //--- Calculate line Y
      if (line_y + text_adjustedLineHeight < 0 || line_y > textAreaHeight) continue;                     //--- Skip out of view
      if (IsHeading(lineText))
        {
         canvasText.FontSet("Arial Bold", fontSize); //--- Set bold font
         lineColor = clrDodgerBlue;                  //--- Set heading color
        }
      else canvasText.FontSet(font, fontSize);       //--- Set regular font
      uint argbText = ColorToARGB(lineColor, 255);   //--- Convert to ARGB
      canvasText.TextOut(textAreaX, line_y, lineText, argbText, TA_LEFT); //--- Draw line
     }
   
   canvasText.Update();                       //--- Update text canvas
  }

Hier implementieren wir die Funktion „UpdateTextOnCanvas“, um den scrollbaren Textinhalt auf dem „canvasText“-Objekt zu zeichnen, Hintergründe, Ränder, dynamischen Umbruch, themenabhängige Textdarstellung mit Farbanpassungen und eine benutzerdefinierte Scrollleiste mit Hover-Effekten für die Navigation zu behandeln. Wir löschen die Canvas zunächst mit Erase, rufen die aktuelle Breite / Höhe ab, bestimmen das Theme der Hintergrundfarbe (dunkel oder hell), konvertieren in ARGB mit Deckkraft von „TextBackgroundOpacityPercent“ über 100,0 mal 255 umgewandelt in uchar, und füllen das Rechteck von (0,0) bis Breite / Höhe minus 1 mit der Methode FillRectangle. Wir konvertieren die Randfarbe von „GetBorderColor“ in ARGB bei 255 und zeichnen die oberen/rechten/unteren/linken Linien mit Line.

Wir setzen Padding auf 10, berechnen das Textpanel als x 1 plus Padding, y 1, Breite als Summe minus Ränder minus den doppelten Wert von Padding, Höhe minus der Ränder. Mit FontSet stellen wir die Schriftart Arial auf 16 ein, mit TextHeight die Zeilenhöhe von Zeichen A ermitteln, „text_adjustedLineHeight“ um drei für den Abstand erhöhen. Wir setzen „text_visible_height“ auf die Höhe des Bereichs. Wenn der Text nicht umgebrochen wurde (statisch „wrapped“ ist false), rufen wir „WrapText“ mit Verwendungstext, Schriftart/Größe/Flächenbreite auf, um die statischen „wrappedLines“ und „wrappedColors“ zu füllen, und setzen „wrapped“ auf true. Anzahl der Zeilen aus Array-Größe ermitteln, „text_total_height“ als Zeilen mal angepasste Höhe berechnen. Wir prüfen, ob ein Scrollen erforderlich ist, wenn die Gesamtzahl die sichtbare Höhe übersteigt; wenn ja, setzen wir „reserved_width“ auf „text_track_width“, verringern die Breite des Bereichs, brechen den Text neu um und aktualisieren die Anzahl der Zeilen/Gesamthöhe.

Wir berechnen „text_max_scroll“ als Maximum von Null oder Gesamthöhe minus dem sichtbaren Bereich, setzen „text_scroll_visible“ auf need_scroll, begrenzen „text_scroll_pos“ zwischen null und max. Wenn sichtbar, erfolgt die Berechnung der Scrollleisten-Positionen: y um 1, Höhe als Text minus Ränder, Bereichshöhe minus dem doppelten Wert von „text_button_size“, „text_slider_height“ aus „TextCalculateSliderHeight“, x als Breite minus Spur minus eins. Ermittelt die Farbe der Scrollleiste, wandelt sie in ARGB um und füllt den Hintergrund der Scrollleiste. Wir berechnen Schieberegler y auf der Grundlage des Positionsverhältnisses mal (Fläche minus Schieberegler). Wenn „text_scroll_area_hovered“, rufen wir die Themes für Schaltfläche bg/hover ab, bestimmen oben bg, wenn „text_scroll_up_hovered“, konvertieren sie in ARGB und füllen die Schaltfläche des Rechtecks. Wir ermitteln die Farben für den Pfeil (normal/deaktiviert/Hover); deaktivieren die Farbe des Aufwärtspfeils, wenn die Position null ist, andernfalls Hover/normal; konvertieren die Werte; stellen die Schriftart „Webdings“ auf Größe 22 ein; berechnen die zentrierten x/y-Koordinaten des Pfeils; zeichnen den Aufwärtspfeil „0x35“ mit TextOut und zentrierter Ausrichtung. Ähnlich verhält es sich mit der Abwärts-Schaltfläche unten: y, bg/hover, füllen, Farbe des Abwärtspfeils deaktiviert, wenn die Position am Maximum liegt, andernfalls hover/normal, Abwärtspfeil „0x36“ zentriert zeichnen.

Für den Schieberegler berechnen wir x als x plus Rand, w als Spur minus doppeltem Rand, den Endpunktradius als die Hälfte von w und rufen den themenabhängigen Hintergrund/Hover-Effekt des Schiebereglers ab, bestimmen den Hintergrund je nachdem, ob der Mauszeiger darüberfährt oder sich bewegt, und konvertieren in ARGB. Wir zeichnen bzw. füllen die oberen und unteren Bögen/Kreise mit „Arc“/FillCircle unter Verwendung von Radianten aus „DegreesToRadians“ und füllen das mittlere Rechteck. Andernfalls, wenn kein Mauszeiger darüberliegt, zeichne einen schmalen Schieberegler in der Mitte (x) mit geringer Breite und einem Radius in Höhe der halben Breite, und einem themenabhängigen ARGB-Hintergrund, zeichne/fülle die oberen und unteren Bögen/Kreise und fülle den schmalen mittleren Bereich.

Wir holen uns das Theme für die Grundfarbe, laufen in einer Schleife über „wrappedLines“: Ist sie leer, wird sie übersprungen, Farbe anrufen und anpassen (wenn weiß/schwarz, beibehalten, sonst aufhellen um 1,5 bei dunklem Theme oder abdunkeln um 0,7 bei hellem), Zeile y berechnen als Bereich y plus Zeile mal angepasst minus „text_scroll_pos“ und überspringen, wenn außerhalb des Sichtbereichs (negativ oder außerhalb des Bereichs). Wenn „IsHeading“, setze fette Schrift und blaue Farbe; sonst normale Schrift. Dann wird es in ARGB konvertiert und an die Fläche x und Linie y linksbündig gezeichnet. Schließlich aktualisieren wir die Canvas mit Update, um den Text anzuzeigen. Wir rufen die Funktion in der Initialisierung auf, nachdem wir ihren Canvas-Bereich festgelegt haben.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Other panels logic
   
   if (EnableTextPanel)
     {
      int textY = currentCanvasY + header_height + gap_y + currentHeight + PanelGap; //--- Calculate text Y
      if (!canvasText.CreateBitmapLabel(0, 0, canvasTextName, currentCanvasX, textY, header_width, TextPanelHeight, COLOR_FORMAT_ARGB_NORMALIZE))
        {
         Print("Failed to create Text Canvas");       //--- Log text creation failure
        }
      textCreated = true;                             //--- Set text created flag
     }
   
   if (EnableTextPanel) UpdateTextOnCanvas();         //--- Update text if enabled
   
   ChartSetInteger(0, CHART_EVENT_MOUSE_WHEEL, true); //--- Enable mouse wheel events
   ChartRedraw();                                     //--- Redraw chart
   return(INIT_SUCCEEDED);                            //--- Return success
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if (textCreated) canvasText.Destroy();             //--- Destroy text if created
   ChartRedraw();                                     //--- Redraw chart
  }


//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   static datetime lastBarTime = 0;                   //--- Initialize last time
   datetime currentBarTime = iTime(_Symbol, _Period, 0); //--- Get current time
   if (currentBarTime > lastBarTime)
     {
      UpdateGraphOnCanvas();                         //--- Update graph
      if (EnableStatsPanel) UpdateStatsOnCanvas();   //--- Update stats
      if (EnableTextPanel) UpdateTextOnCanvas();     //--- Update text
      ChartRedraw();                                 //--- Redraw chart
      lastBarTime = currentBarTime;                  //--- Update last time
     }
  }

Zunächst wird der OnInit-Ereignishandler so geändert, dass er die Erstellung des Textpanels einbezieht, wenn „EnableTextPanel“ wahr ist. Nach der anderen Logik des Panels berechnen wir seine y-Position als „currentCanvasY“ plus „header_height“, „gap_y“, „currentHeight“ und „PanelGap“. Dann erstellen wir das Bitmap-Label mit CreateBitmapLabel im Unterfenster Null, geben den Namen „canvasTextName“, die Position, die Breite der Kopfzeile, „TextPanelHeight“ und COLOR_FORMAT_ARGB_NORMALIZE ein und geben einen Fehler aus, wenn der Vorgang fehlgeschlagen ist, und setzen „textCreated“ bei Erfolg auf true. Wir rufen „UpdateTextOnCanvas“ auf, wenn dies aktiviert ist, um den Text zunächst zu zeichnen. Wir aktivieren Mausrad-Ereignisse mit ChartSetInteger unter Verwendung von CHART_EVENT_MOUSE_WHEEL true, die wir benötigen, um die Bewegung des Chart-Rads für dynamisches Scrollen zu identifizieren, mit ChartRedraw neu zu zeichnen und INIT_SUCCEEDED zurückzugeben.

Dann aktualisieren wir den „OnDeinit“-Ereignishandler, um das Textpanel bei Bedarf zu zerstören, wenn „textCreated“ wahr ist, indem wir „canvasText.Destroy“ verwenden, und dann neu zu zeichnen. Wir passen den OnTick-Ereignishandler so an, dass das Textpanel bei neuen Bars aktualisiert wird. Mit statischer „lastBarTime“ auf Null, aktuelle Zeit der Bar mit iTime ermitteln, falls größer, „UpdateGraphOnCanvas“ aufrufen, optional „UpdateStatsOnCanvas“ wenn „EnableStatsPanel“, „UpdateTextOnCanvas“ wenn „EnableTextPanel“, neu zeichnen, letzte Bar-Zeit aktualisieren. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

GERENDERTES TEXTPANEL

Auf dem Bild ist zu sehen, dass das Textpanel nun gezeichnet wird. Jetzt müssen wir die Chart-Ereignishandler aktualisieren, um Chart-Ereignisse für das Scrollen des gezeichneten Textes zu verarbeiten. Hier ist die Logik, mit der wir das erreicht haben. Wir haben nur die Änderungen hinzugefügt, um das endgültige Ergebnis zu erreichen, und die andere Logik, die wir in den vorherigen Versionen implementiert haben, entfernt.

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   if (id == CHARTEVENT_CHART_CHANGE)
     {
      //--- Existing logic
      
      if (EnableTextPanel) UpdateTextOnCanvas(); //--- Update text
      ChartRedraw();                             //--- Redraw chart
     }
   else if (id == CHARTEVENT_MOUSE_MOVE)
     {
      //--- Existing logic
      
      if (EnableTextPanel && !panels_minimized)
        {
         int text_canvas_x = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_XDISTANCE); //--- Get text X
         int text_canvas_y = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_YDISTANCE); //--- Get text Y
         int text_canvas_w = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_XSIZE);     //--- Get width
         int text_canvas_h = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_YSIZE);     //--- Get height
         bool is_over_text = (mouse_x >= text_canvas_x && mouse_x <= text_canvas_x + text_canvas_w &&
                              mouse_y >= text_canvas_y && mouse_y <= text_canvas_y + text_canvas_h); //--- Check over text
         bool prev_scroll_hovered = text_scroll_area_hovered;                             //--- Store prev scroll hover
         text_scroll_area_hovered = false;         //--- Reset area hover
         if (is_over_text)
           {
            int local_x = mouse_x - text_canvas_x; //--- Get local X
            int local_y = mouse_y - text_canvas_y; //--- Get local Y
            if (local_x >= text_canvas_w - text_track_width - 1)
              {
               text_scroll_area_hovered = true;    //--- Set area hover
              }
            bool prev_up = text_scroll_up_hovered; //--- Store prev up
            bool prev_down = text_scroll_down_hovered;     //--- Store prev down
            bool prev_slider = text_scroll_slider_hovered; //--- Store prev slider
            TextUpdateHoverEffects(local_x, local_y);      //--- Update hovers
            if (prev_scroll_hovered != text_scroll_area_hovered || prev_up != text_scroll_up_hovered || prev_down != text_scroll_down_hovered || prev_slider != text_scroll_slider_hovered)
              {
               UpdateTextOnCanvas();               //--- Update text
               ChartRedraw();                      //--- Redraw chart
              }
            text_mouse_in_body = (local_x < text_canvas_w - text_track_width - 1); //--- Set body flag
           }
         else
           {
            bool need_redraw = prev_scroll_hovered || text_scroll_up_hovered || text_scroll_down_hovered || text_scroll_slider_hovered; //--- Check redraw need
            if (need_redraw)
              {
               text_scroll_area_hovered = false;   //--- Reset area
               text_scroll_up_hovered = false;     //--- Reset up
               text_scroll_down_hovered = false;   //--- Reset down
               text_scroll_slider_hovered = false; //--- Reset slider
               UpdateTextOnCanvas();               //--- Update text
               ChartRedraw();                      //--- Redraw chart
              }
            text_mouse_in_body = false;            //--- Reset body flag
           }
         // New: Toggle CHART_MOUSE_SCROLL on enter/leave text body
         if (text_mouse_in_body != prev_text_mouse_in_body)
           {
            ChartSetInteger(0, CHART_MOUSE_SCROLL, !text_mouse_in_body); //--- Toggle scroll
            prev_text_mouse_in_body = text_mouse_in_body;                //--- Update prev
           }
        }
      
      if (mouse_state == 1 && prev_mouse_state == 0)
        {
         
         //--- Existing logic
         
         if (EnableTextPanel && !panels_minimized && text_scroll_visible && text_scroll_area_hovered)
           {
            int text_canvas_x = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_XDISTANCE); //--- Get text X
            int text_canvas_y = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_YDISTANCE); //--- Get text Y
            int local_x = mouse_x - text_canvas_x;  //--- Get local X
            int local_y = mouse_y - text_canvas_y;  //--- Get local Y
            int scrollbar_x = canvasText.Width() - text_track_width - 1;                     //--- Get scrollbar X
            int scrollbar_y = 1;                    //--- Set scrollbar Y
            int scrollbar_height = TextPanelHeight - 2;                                      //--- Get height
            int scroll_area_y = scrollbar_y + text_button_size;                              //--- Calculate area Y
            int scroll_area_height = scrollbar_height - 2 * text_button_size;                //--- Calculate area height
            int slider_y = scroll_area_y + (int)(((double)text_scroll_pos / text_max_scroll) * (scroll_area_height - text_slider_height)); //--- Calculate slider Y
            if (local_x >= scrollbar_x && local_x <= scrollbar_x + text_track_width - 1)
              {
               if (local_y >= scrollbar_y && local_y <= scrollbar_y + text_button_size - 1)
                 {
                  TextScrollUp();                   //--- Scroll up
                 }
               else if (local_y >= scrollbar_y + scrollbar_height - text_button_size && local_y <= scrollbar_y + scrollbar_height - 1)
                 {
                  TextScrollDown();                 //--- Scroll down
                 }
               else if (local_y >= scroll_area_y && local_y <= scroll_area_y + scroll_area_height - 1)
                 {
                  if (local_y >= slider_y && local_y <= slider_y + text_slider_height - 1)
                    {
                     text_movingStateSlider = true;  //--- Set moving slider
                     text_mlbDownY_Slider = local_y; //--- Set down Y
                     text_mlbDown_YD_Slider = slider_y;                                      //--- Set down YD
                     ChartSetInteger(0, CHART_MOUSE_SCROLL, false);                          //--- Disable scroll
                    }
                  else
                    {
                     int new_slider_y = local_y - text_slider_height / 2;                    //--- Calculate new Y
                     new_slider_y = MathMax(scroll_area_y, MathMin(new_slider_y, scroll_area_y + scroll_area_height - text_slider_height)); //--- Clamp Y
                     double ratio = (double)(new_slider_y - scroll_area_y) / (scroll_area_height - text_slider_height); //--- Calculate ratio
                     text_scroll_pos = (int)MathRound(ratio * text_max_scroll);              //--- Update pos
                    }
                  UpdateTextOnCanvas();             //--- Update text
                  ChartRedraw();                    //--- Redraw chart
                 }
              }
           }
        }

      else if (text_movingStateSlider && mouse_state == 1)
        {
         int text_canvas_y = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_YDISTANCE);    //--- Get text Y
         int local_y = mouse_y - text_canvas_y;                                              //--- Get local Y
         int delta_y = local_y - text_mlbDownY_Slider;                                       //--- Calculate delta Y
         int new_slider_y = text_mlbDown_YD_Slider + delta_y;                                //--- Calculate new slider Y
         int scrollbar_y = 1;                                                                //--- Set scrollbar Y
         int scrollbar_height = TextPanelHeight - 2;                                         //--- Get height
         int slider_min_y = scrollbar_y + text_button_size;                                  //--- Calculate min Y
         int slider_max_y = scrollbar_y + scrollbar_height - text_button_size - text_slider_height;  //--- Calculate max Y
         new_slider_y = MathMax(slider_min_y, MathMin(new_slider_y, slider_max_y));                   //--- Clamp Y
         double scroll_ratio = (double)(new_slider_y - slider_min_y) / (slider_max_y - slider_min_y); //--- Calculate ratio
         int new_scroll_pos = (int)MathRound(scroll_ratio * text_max_scroll);                //--- Calculate new pos
         if (new_scroll_pos != text_scroll_pos)
           {
            text_scroll_pos = new_scroll_pos; //--- Update pos
            UpdateTextOnCanvas();             //--- Update text
            ChartRedraw();                    //--- Redraw chart
           }
        }
      else if (mouse_state == 0 && prev_mouse_state == 1)
        {
         //--- Existing logic
         
         if (text_movingStateSlider)
           {
            text_movingStateSlider = false;  //--- Reset slider moving
            ChartSetInteger(0, CHART_MOUSE_SCROLL, true); //--- Enable scroll
            UpdateTextOnCanvas();            //--- Update text
            ChartRedraw();                   //--- Redraw chart
           }
        }
      
      last_mouse_x = mouse_x;               //--- Update last X
      last_mouse_y = mouse_y;               //--- Update last Y
      prev_mouse_state = mouse_state;       //--- Update prev state
     }
   else if (id == CHARTEVENT_MOUSE_WHEEL)
     {
      int flg_keys = (int)(lparam >> 32);   //--- Get keys
      int mx = (int)(short)lparam;          //--- Get X
      int my = (int)(short)(lparam >> 16);  //--- Get Y
      int delta = (int)dparam;              //--- Get delta
      
      if (EnableTextPanel && !panels_minimized && text_scroll_visible)
        {
         int text_canvas_x = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_XDISTANCE);       //--- Get text X
         int text_canvas_y = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_YDISTANCE);       //--- Get text Y
         int text_canvas_w = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_XSIZE);           //--- Get width
         int text_canvas_h = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_YSIZE);           //--- Get height
         bool is_over_text_body = (mx >= text_canvas_x && mx <= text_canvas_x + text_canvas_w - text_track_width &&
                                   my >= text_canvas_y && my <= text_canvas_y + text_canvas_h); //--- Check over body
         if (is_over_text_body)
           {
            int scroll_step = 20;            //--- Set step
            text_scroll_pos += (delta > 0 ? -scroll_step : scroll_step);                        //--- Adjust pos
            text_scroll_pos = MathMax(0, MathMin(text_scroll_pos, text_max_scroll));            //--- Clamp pos
            UpdateTextOnCanvas();            //--- Update text
            
            int current_scale = (int)ChartGetInteger(0, CHART_SCALE);                           //--- Get scale
            int adjust = (delta > 0 ? 1 : -1); // Swap to (delta > 0 ? -1 : 1) if wheel direction is opposite
            int revert_scale = current_scale + adjust;                                          //--- Calculate revert
            revert_scale = MathMax(0, MathMin(5, revert_scale));                                //--- Clamp scale
            ChartSetInteger(0, CHART_SCALE, revert_scale);                                      //--- Set scale
            
            ChartRedraw();                    //--- Redraw chart
           }
        }
     }
  }

Hier modifizieren wir den OnChartEvent-Ereignishandler, um die Interaktivität des Textpanels einzubinden, indem wir die Behandlung von Mausbewegungen erweitern, wenn „EnableTextPanel“ wahr und nicht „panels_minimized“ ist. Wir rufen die Position, Breite und Höhe des Textpanels mit ObjectGetInteger für OBJPROP_XDISTANCE/“OBJPROP_YDISTANCE“/“OBJPROP_XSIZE“/“OBJPROP_YSIZE“ ab, prüfen, ob sich die Maus über dem gesamten Textpanel befindet, speichern den vorherigen „text_scroll_area_hovered“ und setzen ihn auf false zurück. Wenn sich die Maus darüber befindet, berechnen wir local_x/local_y der Maus minus Canvas Position, und, wenn local_x in Bereich der Scrollleiste (Breite minus „text_track_width“ minus eins bis Ende) ist, setzen wir area_hovered auf true.

Wir speichern vorherige Werte Up/Down/Schieberegler-Hover, rufen „TextUpdateHoverEffects“ mit lokalen Koordinaten auf, um sie zu aktualisieren, wenn sich ein Hover oder ein Bereich gegenüber dem vorherigen geändert hat, rufen „UpdateTextOnCanvas“ auf und zeichnen es neu. Wir setzen „text_mouse_in_body“ auf true, wenn das lokale x links von der Scrollleiste liegt. Andernfalls, wenn nicht über Text, aber frühere Hover darauf hinweisen, dass neu gezeichnet werden muss, setzen wir die Hover von Bereich/Auf/Ab/Schieberegler auf false zurück und aktualisieren Text/Redraw. Wenn „text_mouse_in_body“ von „prev_text_mouse_in_body“ geändert wurde, schalten wir das Scrollen der Chart-Maus mit ChartSetInteger „CHART_MOUSE_SCROLL“ auf false im Hauptteil (true out) um, um das Scrollen mit dem Rad ohne Chart-Zoom zu ermöglichen, und aktualisieren das vorherige.

Bei gedrückter Maustaste (Zustand eins, vorheriger Zustand null): Wenn der Mauszeiger über Text, Scrollleiste oder Bereich bewegt wird, werden die local_x- und local_y-Koordinaten, die x- und y-Koordinaten der Scrollleiste, die Höhe des Bereichs und die Höhe des Schiebereglers wie zuvor ermittelt. Befindet sich die lokale x-Koordinate in der Scrollleiste: Bei der lokalen y-Koordinate im Bereich der Aufwärtstaste wird „TextScrollUp“ aufgerufen, bei der Abwärtstaste „TextScrollDown“. Befindet sich die lokale x-Koordinate im Bereich, werden „text_movingStateSlider“ auf „true“ gesetzt, „text_mlbDownY_Slider“ als lokale y-Koordinate und „text_mlbDown_YD_Slider“ als Schieberegler-y-Koordinate gespeichert und das Scrollen deaktiviert. Andernfalls wird die neue Schieberegler-y-Koordinate berechnet als lokale y-Koordinate minus der Hälfte der auf den Bereich begrenzten „text_slider_height“ plus Höhe minus „text_slider_height“, das Verhältnis als (neue y-Koordinate minus Bereichs-y) geteilt durch (Höhe minus „text_slider_height“). „text_scroll_pos“ wird auf das gerundete Verhältnis mal „text_max_scroll“ gesetzt. dann Text aktualisieren/neu zeichnen.

Wenn „text_movingStateSlider“ wahr und der Zustand eins (gehalten) ist, wird die lokale y-Koordinate aus der Mausposition minus der Text-y-Koordinate ermittelt. Die Differenz ergibt sich aus der lokalen y-Koordinate minus „text_mlbDownY_Slider“. Die neue y-Koordinate des Schiebereglers ergibt sich aus „text_mlbDown_YD_Slider“ plus der Differenz. Die y-Koordinate des Schiebereglers wird auf den minimalen Wert (y plus „text_button_size“) bis zum maximalen Wert (y plus Höhe minus „text_button_size“ minus „text_slider_height“) begrenzt. Das Verhältnis wird als (neu minus minimal) / (max minus minimal) berechnet. Die neue Position wird als gerundetes Verhältnis multipliziert mit „text_max_scroll“ berechnet. Falls sich diese Position von der aktuellen „text_scroll_pos“ unterscheidet, wird der Text neu gezeichnet.

Wenn die Maustaste losgelassen wird (Zustand Null, vorheriger Zustand) und „text_movingStateSlider“ auf „false“ gesetzt ist, aktiviere das Scrollen, aktualisiere den Text und zeichne die Seite neu. Wir fügen dem Ereignis einer Chartänderung einen optionalen Aufruf von „UpdateTextOnCanvas“ hinzu, wenn „EnableTextPanel“ vor dem Neuzeichnen wahr ist. Für Mausrad-Ereignis: cast „flg_keys“ von „lparam“ shift 32, mx von short „lparam“, my von short „lparam“ shift 16, delta von „dparam“ als Integer. Wenn Text/Scroll sichtbar, Textprops holen, prüfen, ob die Maus innerhalb des Textpanels (mx/my in x zu x plus Breite minus „text_track_width“, y zu y plus Höhe) ist, wenn ja, Schritt „text_scroll_pos“ um zwanzig negativ/positiv bei delta >0/<0, Null auf „text_max_scroll“ klammern, Text aktualisieren, aktuelle Chart-Skala mit ChartGetInteger „CHART_SCALE“ ermitteln, +1/-1 auf Delta >0/<0 klammern 0-5, mit „ChartSetInteger“ zurücksetzen, um Zoom zurückzusetzen, neu zeichnen. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

TEST DES TEXTPANELS

Anhand der Visualisierung können wir sehen, dass wir das Canvas-basierte Dashboard durch das Hinzufügen des Textpanels mit allen Interaktionen verbessert und somit unsere Ziele erreicht haben. Nun bleibt nur noch, die Funktionsfähigkeit des Systems zu testen, was im vorangegangenen Abschnitt behandelt wurde.


Backtesting

Wir haben die Tests durchgeführt, und unten sehen Sie die kompilierte Visualisierung in einem einzigen Bild im GIF-Format.

VOLLSTÄNDIGER CANVAS DASHBOARD BACKTEST


Schlussfolgerung

Zusammenfassend lässt sich sagen, dass wir das Canvas-basierte Kurs-Dashboard in MQL5 verbessert haben, indem wir ein pixelgenaues, scrollbares Textpanel für Bedienhinweise implementiert, native Einschränkungen durch Anti-Aliasing für eine glatte, saubere Darstellung umgangen, eine abgerundete, benutzerdefinierte Scrollleiste mit Schaltflächen und Schieberegler zur Navigation hinzugefügt, themenabhängige Elemente, dynamischen Zeilenumbruch mit Farbanpassungen sowie Unterstützung für Mausrad, Klick und Ziehen für eine nahtlose Interaktion realisiert haben. Das System integriert das Textpanel mit den vorhandenen Diagramm- und Statistikbereichen unter Beibehaltung der Funktionen zum Ziehen/Größenänderung/Theme/Minimieren und gewährleistet effiziente ereignisgesteuerte Aktualisierungen für ein umfassendes Überwachungstool. Mit dieser pixelgenauen Erweiterung des scrollbaren Textpanels sind Sie in der Lage, eine detaillierte Benutzerführung innerhalb des Dashboards bereitzustellen, die Sie für weitere Optimierungen in Ihrem Handel benutzen können. Viel Spaß beim Handeln!

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

Beigefügte Dateien |
Larry Williams‘ Marktgeheimnisse (Teil 9): Mit Mustern zum Gewinn Larry Williams‘ Marktgeheimnisse (Teil 9): Mit Mustern zum Gewinn
Eine empirische Studie von Larry Williams' kurzfristigen Handelsmustern, die zeigt, wie klassische Setups in MQL5 automatisiert, an realen Marktdaten getestet und auf Konsistenz, Rentabilität und praktischen Handelswert bewertet werden können.
Der MQL5 Standard Library Explorer (Teil 6): Optimierung eines generierten Expert Advisors Der MQL5 Standard Library Explorer (Teil 6): Optimierung eines generierten Expert Advisors
In dieser Diskussion knüpfen wir an den zuvor entwickelten Multi-Signal-Expert Advisor an, mit dem Ziel, verfügbare Optimierungsmethoden zu erforschen und anzuwenden. Ziel ist es, festzustellen, ob die Handelsleistung des EA durch systematische Optimierung auf Basis historischer Daten sinnvoll verbessert werden kann.
Integration externer Anwendungen mit MQL5 Community OAuth Integration externer Anwendungen mit MQL5 Community OAuth
Erfahren Sie, wie Sie Ihrer Android-App mit dem OAuth-2.0-Autorisierungscodefluss die Funktion „Sign in with MQL5“ hinzufügen. Die Anleitung behandelt die App-Registrierung, Endpunkte, Redirect URI, Custom Tabs, Deep-Link-Handling und ein PHP-Backend, das den Code für ein Access-Token über HTTPS austauscht. Sie werden echte MQL5-Nutzer authentifizieren und auf Profildaten wie Rang und Ruf zugreifen.
Workshop für nutzerdefinierte Indikatoren (Teil 1): Aufbau des Supertrend-Indikators in MQL5 Workshop für nutzerdefinierte Indikatoren (Teil 1): Aufbau des Supertrend-Indikators in MQL5
So erstellen Sie in MQL5 für MetaTrader 5 einen Supertrend ohne Repainting von Grund auf. Wir verwenden ein iATR-Handle und CopyBuffer für die Volatilität, binden Puffer mit SetIndexBuffer und konfigurieren Plots (DRAWCOLORCANDLES plus zwei Linienbänder) über PlotIndexSetInteger. Die Logik wird nur bei geschlossenen Kerzen mit EMPTY_VALUE aktualisiert, um inaktive Bänder zu unterdrücken, wobei die Eingabeparameter atrPeriod und atrMultiplier für den Nutzer verfügbar gemacht werden. Sie erhalten ein sauberes, EA-fähiges Overlay mit dokumentierten Puffern für Strategien und Signale.