MQL5-Handelswerkzeuge (Teil 14): Pixelgenaues, scrollbares Textpanel mit Anti-Aliasing und abgerundeter Scrollleiste
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:
- Verstehen des Frameworks für ein pixelgenaues scrollbares Textpanel
- Implementierung in MQL5
- Backtesting
- 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.

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.

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.

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:

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:

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.

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
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Larry Williams‘ Marktgeheimnisse (Teil 9): Mit Mustern zum Gewinn
Der MQL5 Standard Library Explorer (Teil 6): Optimierung eines generierten Expert Advisors
Integration externer Anwendungen mit MQL5 Community OAuth
Workshop für nutzerdefinierte Indikatoren (Teil 1): Aufbau des Supertrend-Indikators in MQL5
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.