MQL5-Handelswerkzeuge (Teil 15): Unschärfeeffekte im Canvas, Schatten-Rendering und flüssiges Scrollen mit dem Mausrad
Einführung
In unserem vorherigen Artikel (Teil 14) haben wir ein pixelgenaues, scrollbares Text-Panel in MetaQuotes Language 5 (MQL5) entwickelt, die Antialiasing für eine glatte Darstellung, eine abgerundete Bildlaufleiste mit interaktiven Steuerelementen und anpassbare Hintergründe für eine verbesserte Benutzerfreundlichkeit des Dashboards bietet. In Teil 15 entwickeln wir das Canvas-Dashboard weiter, indem wir Unschärfeeffekte für Nebelverläufe, Schattenrendering für Tiefe im Header und ein sanftes Scrollen mit dem Mausrad für nahtlose Textnavigation einbauen. Wir werden die folgenden Themen behandeln:
- Grundlagen von Unschärfe- und Schatteneffekten im Canvas
- Implementierung in MQL5
- Backtests
- Schlussfolgerung
Am Ende haben Sie ein voll funktionsfähiges MQL5-Dashboard mit visuell verbesserten und interaktiveren Elementen, die eine einfachere Anpassung und eine verbesserte Benutzererfahrung ermöglichen - legen Sie los!
Grundlagen von Unschärfe- und Schatteneffekten im Canvas
Die Unschärfeeffekte im Canvas erzeugen sanfte Farbverläufe, wie z. B. Nebelüberlagerungen auf Hintergründen, indem sie Pixelfarben mit unterschiedlicher Deckkraft interpolieren, um Tiefe zu simulieren und Bilder ohne harte Kanten weicher zu machen. Das Rendern der Schatten verleiht Elementen wie dem Header durch schichtweises Zeichnen mit versetzten Rechtecken und abnehmender Deckkraft mehr Realismus. Dabei wird eine Gauß-ähnliche Unschärfe über mehrere Durchgänge verwendet, um weiche, diffuse Kanten zu erzeugen, die das dreidimensionale Gefühl der Benutzeroberfläche verstärken. Das Scrollen mit dem Mausrad ermöglicht eine nahtlose Navigation in den Text-Panels durch schrittweises Anpassen der Scroll-Positionen, mit Begrenzung zur Vermeidung von Überlauf und Integration mit per Hover ausfahrbaren Scrollbars zur intuitiven Erkundung von Inhalten.
Wir werden bikubische Interpolation für hochwertige Bildskalierung und Antialiasing in Linien anwenden, Schattenfunktionen mit parametrischer Steuerung für Abstand und Unschärferadius implementieren und Rad-Ereignisse verarbeiten, um Textversätze zu aktualisieren und gleichzeitig Chartinteraktionen zu erhalten. Kurz gesagt, diese Verbesserungen bieten ein ausgefeiltes, reaktionsschnelles Dashboard, das Ästhetik mit benutzerfreundlichen Steuerelementen für eine bessere Handelsvisualisierung kombiniert (siehe unten).

Implementierung in MQL5
Um das Programm in MQL5 zu verbessern, müssen wir neue Eingabeparameter hinzufügen, um die Anzeige- und Unschärfeeffekte im Header zu steuern, und auch die Eingaben neu gruppieren, da es jetzt viele gibt, einfach der Übersichtlichkeit halber.
//+------------------------------------------------------------------+ //| Canvas Dashboard PART3.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 #include <Canvas/Canvas.mqh> //+------------------------------------------------------------------+ //| Resources | //+------------------------------------------------------------------+ #resource "1. Transparent MT5 bmp image.bmp" // Define background image resource //+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+ enum ENUM_BACKGROUND_MODE { // Define background mode enumeration NoColor = 0, // No color mode SingleColor = 1, // Single color mode GradientTwoColors = 2 // Gradient with two colors mode }; enum ENUM_RESIZE_MODE { // Define resize mode enumeration NONE, // No resize mode BOTTOM, // Bottom resize mode RIGHT, // Right resize mode BOTTOM_RIGHT // Bottom-right resize mode }; //+------------------------------------------------------------------+ //| Canvas objects | //+------------------------------------------------------------------+ CCanvas canvasGraph; //--- Declare graph canvas object CCanvas canvasStats; //--- Declare stats canvas object CCanvas canvasHeader; //--- Declare header canvas object CCanvas canvasText; //--- Declare text canvas object //+------------------------------------------------------------------+ //| Canvas names | //+------------------------------------------------------------------+ string canvasGraphName = "GraphCanvas"; //--- Set graph canvas name string canvasStatsName = "StatsCanvas"; //--- Set stats canvas name string canvasHeaderName = "HeaderCanvas"; //--- Set header canvas name string canvasTextName = "TextCanvas"; //--- Set text canvas name //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ sinput group "=== GENERAL CANVAS SETTINGS ===" input int CanvasX = 30; // Canvas X Position input int CanvasY = 50; // Canvas Y Position input int CanvasWidth = 400; // Canvas Width input int CanvasHeight = 300; // Canvas Height input bool EnableStatsPanel = true; // Enable Stats Panel input int PanelGap = 10; // Panel Gap input bool EnableTextPanel = true; // Enable Text Panel input int TextPanelHeight = 200; // Text Panel Height input double TextBackgroundOpacityPercent = 85.0; // Text Background Opacity Percent sinput group "=== HEADER CANVAS SETTINGS ===" input color HeaderShadowColor = clrDodgerBlue; // Header Shadow Color input double HeaderShadowOpacityPercent = 70.0; // Header Shadow Opacity Percent input int HeaderShadowDistance = 4; // Header Shadow Distance input int HeaderShadowBlurRadius = 3; // Header Shadow Blur Radius sinput group "=== GRAPH PANEL SETTINGS ===" input int graphBars = 50; // Graph Bars input color borderColor = clrBlack; // Border Color input color borderHoverColor = clrRed; // Border Hover Color input bool UseBackground = true; // Use Background input double FogOpacity = 0.5; // Fog Opacity input bool BlendFog = true; // Blend Fog sinput group "=== STATS PANEL SETTINGS ===" input int StatsFontSize = 12; // Stats Font Size input color StatsLabelColor = clrDodgerBlue; // Stats Label Color input color StatsValueColor = clrWhite; // Stats Value Color input color StatsHeaderColor = clrDodgerBlue; // Stats Header Color input int StatsHeaderFontSize = 14; // Stats Header Font Size input double BorderOpacityPercentReduction = 20.0; // Border Opacity Percent Reduction input double BorderDarkenPercent = 30.0; // Border Darken Percent input double StatsHeaderBgOpacityPercent = 20.0; // Stats Header Bg Opacity Percent input int StatsHeaderBgRadius = 8; // Stats Header Bg Radius input ENUM_BACKGROUND_MODE StatsBackgroundMode = GradientTwoColors; // Stats Background Mode input color TopColor = clrBlack; // Top Color input color BottomColor = clrRed; // Bottom Color input double BackgroundOpacity = 0.7; // Background Opacity
Die neue Organisation sieht nun wie oben beschrieben aus, wobei wir in der Eingabegruppe für die Headereinstellungen Parameter für Schatteneffekte angeben: “HeaderShadowColor“ für den Farbton des Schattens, „HeaderShadowOpacityPercent“ für die Transparenz in Prozent, „HeaderShadowDistance“ für den Abstand zum Header und „HeaderShadowBlurRadius“ für die Weichheit der Unschärfe. Wir haben die wichtigsten Änderungen der Übersichtlichkeit halber hervorgehoben. Es erscheint nun das folgende Fenster mit organisierten Eingabeparametern.

Als Nächstes müssen wir die Logik für das Zeichnen des Headers anpassen, da dies die Grundlage für unsere Verbesserungen ist, um das Rendering von verwischten Mehrschichtschatten mit Schleifen und Alpha-Fading für einen weichen Schlagschatteneffekt hinzuzufügen. Wir müssen alle Zeichnungen versetzen, um den Header innerhalb einer vergrößerten Canvas-Fläche zu zentrieren, was uns hilft, eine 3D-ähnliche Tiefenwirkung zu erstellen und die Ästhetik und den Fokus auf dem Header zu verbessern. Ränder und Elemente werden jetzt relativ zu extra gezeichnet, um eine falsche Ausrichtung zu verhindern. Hier ist die Logik, mit der wir das erreicht haben.
//+------------------------------------------------------------------+ //| Draw header on canvas | //+------------------------------------------------------------------+ void DrawHeaderOnCanvas() { // Render header elements canvasHeader.Erase(0); //--- Clear header canvas int extra = HeaderShadowBlurRadius + HeaderShadowDistance; //--- Compute extra space int inner_header_width = canvasHeader.Width() - 2 * extra; //--- Compute inner width int header_left = extra; //--- Set header left int header_top = extra; //--- Set header top int header_right = header_left + inner_header_width - 1; //--- Set header right int header_bottom = header_top + header_height - 1; //--- Set header bottom if (HeaderShadowBlurRadius > 0 || HeaderShadowDistance > 0) { //--- Check shadow settings int offset_x = HeaderShadowDistance; //--- Set X offset int offset_y = HeaderShadowDistance; //--- Set Y offset int blur = HeaderShadowBlurRadius; //--- Set blur radius for(int layer = blur; layer >= 0; layer--) { //--- Loop through layers double factor = (double)layer / (blur + 1.0); //--- Compute factor uchar alpha = (uchar)(255 * (HeaderShadowOpacityPercent / 100.0) * (1.0 - factor)); //--- Compute alpha uint argb_shadow = ColorToARGB(HeaderShadowColor, alpha); //--- Convert to ARGB int s_left = header_left + offset_x - layer; //--- Set shadow left int s_top = header_top + offset_y - layer; //--- Set shadow top int s_right = header_right + offset_x + layer; //--- Set shadow right int s_bottom = header_bottom + offset_y + layer; //--- Set shadow bottom int s_width = s_right - s_left + 1; //--- Compute shadow width int s_height = s_bottom - s_top + 1; //--- Compute shadow height int s_radius = layer; //--- Set shadow radius if (s_width > 0 && s_height > 0) { //--- Check valid dimensions FillRoundedRectangle(canvasHeader, s_left, s_top, s_width, s_height, s_radius, argb_shadow); //--- Fill shadow rectangle } } } color header_bg = panel_dragging ? GetHeaderDragColor() : (header_hovered ? GetHeaderHoverColor() : GetHeaderColor()); //--- Determine bg color uint argb_bg = ColorToARGB(header_bg, 255); //--- Convert to ARGB canvasHeader.FillRectangle(header_left, header_top, header_right, header_bottom, argb_bg); //--- Fill header rectangle uint argbBorder = ColorToARGB(GetBorderColor(), 255); //--- Convert border to ARGB canvasHeader.Line(header_left, header_top, header_right, header_top, argbBorder); //--- Draw top border canvasHeader.Line(header_right, header_top, header_right, header_bottom, argbBorder); //--- Draw right border canvasHeader.Line(header_right, header_bottom, header_left, header_bottom, argbBorder); //--- Draw bottom border canvasHeader.Line(header_left, header_bottom, header_left, header_top, argbBorder); //--- Draw left border canvasHeader.FontSet("Arial Bold", 15); //--- Set font for title uint argbText = ColorToARGB(GetHeaderTextColor(), 255); //--- Convert text to ARGB canvasHeader.TextOut(header_left + 10, header_top + (header_height - 15) / 2, "Price Dashboard", argbText, TA_LEFT); //--- Draw title text int theme_x = header_left + inner_header_width + theme_x_offset; //--- Compute theme X string theme_symbol = CharToString((uchar)91); //--- Set theme symbol color theme_color = theme_hovered ? clrYellow : GetHeaderTextColor(); //--- Determine theme color canvasHeader.FontSet("Wingdings", 22); //--- Set font for theme uint argb_theme = ColorToARGB(theme_color, 255); //--- Convert to ARGB canvasHeader.TextOut(theme_x, header_top + (header_height - 22) / 2, theme_symbol, argb_theme, TA_CENTER); //--- Draw theme symbol int min_x = header_left + inner_header_width + minimize_x_offset; //--- Compute minimize X string min_symbol = panels_minimized ? CharToString((uchar)111) : CharToString((uchar)114); //--- Set minimize symbol color min_color = minimize_hovered ? clrYellow : GetHeaderTextColor(); //--- Determine minimize color canvasHeader.FontSet("Wingdings", 22); //--- Set font for minimize uint argb_min = ColorToARGB(min_color, 255); //--- Convert to ARGB canvasHeader.TextOut(min_x, header_top + (header_height - 22) / 2, min_symbol, argb_min, TA_CENTER); //--- Draw minimize symbol int close_x = header_left + inner_header_width + close_x_offset; //--- Compute close X string close_symbol = CharToString((uchar)114); //--- Set close symbol color close_color = close_hovered ? clrRed : GetHeaderTextColor(); //--- Determine close color canvasHeader.FontSet("Webdings", 22); //--- Set font for close uint argb_close = ColorToARGB(close_color, 255); //--- Convert to ARGB canvasHeader.TextOut(close_x, header_top + (header_height - 22) / 2, close_symbol, argb_close, TA_CENTER); //--- Draw close symbol canvasHeader.Update(); //--- Update header canvas }
Wir verbessern die Funktion „DrawHeaderOnCanvas“, um den Header des Dashboards mit visuellen Verbesserungen darzustellen. Zuerst löschen wir die Canvas mit „canvasHeader.Erase“, um mit einer leeren Fläche zu beginnen. Wir berechnen den zusätzlichen Platz als Summe von „HeaderShadowBlurRadius“ und „HeaderShadowDistance“ und bestimmen dann die innere Headerbreite, indem wir den doppelten zusätzlichen Platz von der Canvas-Breite abziehen. Die Positionen für das Headerrechteck werden festgelegt: links und oben mit extra, rechts als links plus innere Breite minus 1 und unten als oben plus „header_height“ minus 1.
Wenn entweder der Unschärferadius oder der Schattenabstand positiv ist, erzeugen wir einen Schatteneffekt, indem wir eine Schleife vom Unschärferadius bis hinunter zu 0 ziehen. Für jede Ebene wird ein Faktor berechnet, der sich aus Ebene über Unschärfe plus 1 ergibt, und dann ein Alphawert, der sich aus 255 mal Prozentsatz der Schattentrübung mal 1 minus Faktor ergibt. Wir konvertieren „HeaderShadowColor“ in ARGB mit diesem Alpha für die Schattenfarbe. Die Positionen der Schattenrechtecke werden durch den Versatz und die Ebene für die Ausbreitung angepasst: links als Header links plus Versatz minus Ebene, und ähnlich für oben, rechts und unten. Wir berechnen die Breite und Höhe des Schattens, setzen den Radius auf die Ebene, und wenn die Abmessungen positiv sind, füllen wir ein abgerundetes Rechteck mit „FillRoundedRectangle“ unter Verwendung der Schattenfarbe.
Die Hintergrundfarbe des Headers wird bedingt bestimmt: Beim Ziehen wird die Ziehfarbe verwendet, beim Mouseover die Hover-Farbe, ansonsten die Standardfarbe des Headers. Diese Farbe wird mit voller Deckkraft in ARGB umgewandelt und füllt das Headerrechteck. Umrandungen werden als Linien um den Header gezeichnet, wobei die Farbe der Umrandung in ARGB umgewandelt wird. Für den Text stellen wir die Schriftart „Arial Bold“ in der Größe 15 ein, konvertieren die Farbe des Kopftextes in ARGB und geben „Price Dashboard“ linksbündig mit Padding aus.
Es folgen die Icons: für Theme bei berechnetem X verwenden wir die Wingdings-Schriftart 22, setzen das Symbol auf Zeichen 91, färben es gelb, wenn der Mauszeiger darüber liegt, und zeichnen es zentriert. Minimieren Symbol an seinem X verwendet Wingdings, Symbol 111, wenn minimiert sonst 114, gelb gefärbt beim Mouseover, zentriert gezeichnet. Das Symbol zum Schließen an seinem X verwendet Webdings, Symbol 114, rot bei Hover, zentriert gezeichnet. Schließlich rufen wir „canvasHeader.Update“ auf, um die Anzeige zu aktualisieren, genau wie in den vorherigen Teilen. Als Nächstes werden wir die Logik für den Mauszeiger über dem Header ändern, um den Schatten zu berücksichtigen, indem wir die Treffererkennung auf den Schattenbereich um den Header herum ausdehnen, um Hover- und Klickereignisse zuverlässig zu erfassen und Fehldetektionen an den Rändern aufgrund von Schattenauffüllungen zu vermeiden, was die Benutzerfreundlichkeit verbessert.
//+------------------------------------------------------------------+ //| Check mouse over header | //+------------------------------------------------------------------+ bool IsMouseOverHeader(int mouse_x, int mouse_y) { // Detect header hover int extra = HeaderShadowBlurRadius + HeaderShadowDistance; //--- Compute extra space int inner_header_width = currentWidth + (EnableStatsPanel && !panels_minimized ? PanelGap + currentWidth / 2 : 0); //--- Compute inner width int header_canvas_x = currentCanvasX - extra; //--- Set canvas X int header_canvas_y = currentCanvasY - extra; //--- Set canvas Y int header_canvas_w = inner_header_width + 2 * extra; //--- Set canvas width int header_canvas_h = header_height + 2 * extra; //--- Set canvas height if (mouse_x < header_canvas_x || mouse_x > header_canvas_x + header_canvas_w || mouse_y < header_canvas_y || mouse_y > header_canvas_y + header_canvas_h) return false; //--- Return false if outside int theme_left = header_canvas_x + extra + inner_header_width + theme_x_offset - button_size / 2; //--- Set theme left int theme_right = theme_left + button_size; //--- Set theme right int theme_top = header_canvas_y + extra; //--- Set theme top int theme_bottom = theme_top + header_height; //--- Set theme bottom if (mouse_x >= theme_left && mouse_x <= theme_right && mouse_y >= theme_top && mouse_y <= theme_bottom) return false; //--- Return false if over theme int min_left = header_canvas_x + extra + inner_header_width + minimize_x_offset - button_size / 2; //--- Set minimize left int min_right = min_left + button_size; //--- Set minimize right int min_top = header_canvas_y + extra; //--- Set minimize top int min_bottom = min_top + header_height; //--- Set minimize bottom if (mouse_x >= min_left && mouse_x <= min_right && mouse_y >= min_top && mouse_y <= min_bottom) return false; //--- Return false if over minimize int close_left = header_canvas_x + extra + inner_header_width + close_x_offset - button_size / 2; //--- Set close left int close_right = close_left + button_size; //--- Set close right int close_top = header_canvas_y + extra; //--- Set close top int close_bottom = close_top + header_height; //--- Set close bottom if (mouse_x >= close_left && mouse_x <= close_right && mouse_y >= close_top && mouse_y <= close_bottom) return false; //--- Return false if over close return true; //--- Return true if over header }
Hier aktualisieren wir die Funktion „IsMouseOverHeader“, um genau zu erkennen, wenn die Maus über dem Header schwebt, und berücksichtigen jetzt die zusätzlichen Polsterungen, die durch Schatteneffekte entstehen. Um dies zu erreichen, berechnen wir „extra“ als die Summe von „HeaderShadowBlurRadius“ und „HeaderShadowDistance“ und passen dann die Koordinaten der Header-Canvas an: „header_canvas_x“ als „currentCanvasX“ minus „extra“, „header_canvas_y“ als „currentCanvasY“ minus „extra“, „header_canvas_w“ als „inner_header_width“ plus zweimal „extra“, und „header_canvas_h“ als „header_height“ plus zweimal „extra“.
Wir prüfen, ob die Mausposition außerhalb dieser erweiterten Canvas-Grenzen liegt und geben in diesem Fall false zurück. Für Schaltflächenbereiche berechnen wir Positionen wie „theme_left“ unter Verwendung von „header_canvas_x“ plus „extra“ plus „inner_header_width“ plus „theme_x_offset“ minus die Hälfte von „button_size“, und ähnlich für rechts, oben und unten, wobei wir false zurückgeben, wenn sich die Maus über den Schaltflächen „theme“, „minimize“ oder „close“ befindet, um sie vom allgemeinen Hover-Zustand des Headers auszuschließen. Wir müssen nun die Größe des Headers so anpassen, dass die Größenanpassung des Headers so erweitern, dass der zusätzliche Schattenbereich auch beim Minimieren korrekt berücksichtigt wird, um sicherzustellen, dass der Schatten beim Ein- und Ausblenden der Panels korrekt dargestellt wird und die visuelle Konsistenz erhalten bleibt.
//+------------------------------------------------------------------+ //| Toggle minimize | //+------------------------------------------------------------------+ void ToggleMinimize() { // Switch minimize state panels_minimized = !panels_minimized; //--- Invert minimized state if (panels_minimized) { //--- Check minimized canvasGraph.Destroy(); //--- Destroy graph canvas graphCreated = false; //--- Reset graph flag if (EnableStatsPanel) { //--- Check stats enabled canvasStats.Destroy(); //--- Destroy stats canvas statsCreated = false; //--- Reset stats flag } if (EnableTextPanel) { //--- Check text enabled canvasText.Destroy(); //--- Destroy text canvas textCreated = false; //--- Reset text flag } } else { //--- Handle maximized if (!canvasGraph.CreateBitmapLabel(0, 0, canvasGraphName, currentCanvasX, currentCanvasY + header_height + gap_y, currentWidth, currentHeight, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Recreate graph Print("Failed to recreate Graph Canvas"); //--- Log failure } graphCreated = true; //--- Set graph flag UpdateGraphOnCanvas(); //--- Update graph if (EnableStatsPanel) { //--- Check stats enabled int statsX = currentCanvasX + currentWidth + PanelGap; //--- Compute stats X if (!canvasStats.CreateBitmapLabel(0, 0, canvasStatsName, statsX, currentCanvasY + header_height + gap_y, currentWidth / 2, currentHeight, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Recreate stats Print("Failed to recreate Stats Canvas"); //--- Log failure } statsCreated = true; //--- Set stats flag UpdateStatsOnCanvas(); //--- Update stats } if (EnableTextPanel) { //--- Check text enabled int textY = currentCanvasY + header_height + gap_y + currentHeight + PanelGap; //--- Compute text Y int inner_header_width = currentWidth + (EnableStatsPanel ? PanelGap + currentWidth / 2 : 0); //--- Compute width int text_width = inner_header_width; //--- Set text width int text_height = TextPanelHeight; //--- Set text height if (!canvasText.CreateBitmapLabel(0, 0, canvasTextName, currentCanvasX, textY, text_width, text_height, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Recreate text Print("Failed to recreate Text Canvas"); //--- Log failure } textCreated = true; //--- Set text flag UpdateTextOnCanvas(); //--- Update text } } int extra = HeaderShadowBlurRadius + HeaderShadowDistance; //--- Compute extra space int inner_header_width = currentWidth + (EnableStatsPanel && !panels_minimized ? PanelGap + currentWidth / 2 : 0); //--- Compute inner width int header_canvas_width = inner_header_width + 2 * extra; //--- Compute header width canvasHeader.Resize(header_canvas_width, header_height + 2 * extra); //--- Resize header ObjectSetInteger(0, canvasHeaderName, OBJPROP_XSIZE, header_canvas_width); //--- Set X size ObjectSetInteger(0, canvasHeaderName, OBJPROP_YSIZE, header_height + 2 * extra); //--- Set Y size DrawHeaderOnCanvas(); //--- Redraw header canvasHeader.Update(); //--- Update header ChartRedraw(); //--- Redraw chart }
Wir aktualisieren die Funktion „ToggleMinimize“, um den Wechsel zwischen minimiertem und maximiertem Zustand für die Dashboard-Panels zu ermöglichen. In dieser Funktion wird „panels_minimized“ invertiert, um den Status umzuschalten. Wenn minimiert wird, zerstören wir die Graph-Canvas mit „canvasGraph.Destroy“ und setzen „graphCreated“ auf false; wenn Statistiken aktiviert sind, zerstören wir „canvasStats“ und setzen „statsCreated“ zurück; wenn Text aktiviert ist, zerstören wir „canvasText“ und setzen „textCreated“ zurück.
Beim Wiederherstellen der Ansicht wird der Graph mit „canvasGraph.CreateBitmapLabel“ an der aktuellen Position und Größe neu erstellt, „graphCreated“ auf true gesetzt und mit „UpdateGraphOnCanvas“ aktualisiert. Wenn die Statistik aktiviert ist, wird die X-Position berechnet, „canvasStats“ neu erstellt, „statsCreated“ auf true gesetzt und mit „UpdateStatsOnCanvas“ aktualisiert. Für Text, falls aktiviert, berechnen wir die Y-Position, bestimmen die Breite als „inner_header_width“ einschließlich der Statistiken, falls vorhanden, erstellen „canvasText“ mit dieser Breite und „TextPanelHeight“ neu, setzen „textCreated“ auf true und aktualisieren mit „UpdateTextOnCanvas“.
Dann wird die Größe des Headers angepasst: „extra“ wird als Unschärferadius plus Schattenabstand berechnet, „inner_header_width“ basiert auf der aktuellen Breite und den Statistiken, wenn sie nicht minimiert ist, und „header_canvas_width“ als inner plus zweimal extra. Wir ändern die Größe von „canvasHeader“ auf diese Breite und „header_height“ plus zweimal extra, setzen Objekteigenschaften für X- und Y-Größen, zeichnen des Header mit „DrawHeaderOnCanvas“ neu, aktualisieren sie und zeichnen das Chart neu.
Im Vergleich zur Vorgängerversion, bei der der Header direkt auf eine neue Breite ohne Schattenextra und Höhenpolsterung angepasst wurde, enthält diese Überarbeitung „Extra“ für Schatten in den Breiten- und Höhenberechnungen, wodurch sichergestellt wird, ohne dass die unscharfen Schatten abgeschnitten werden, während die gleiche Logik zur Neuerstellung der Panels beibehalten wird. Als Nächstes müssen wir während der Initialisierung die Header-Canvas erweitern, um zusätzlichen Platz für den Schatten auf allen Seiten vorzusehen und die Position des Header-Canvas entsprechend zu versetzen. Dadurch wird verhindert, dass der Schatten abgeschnitten wird, und es wird sichergestellt, dass er außerhalb der Headerbegrenzungen gerendert wird.
//+------------------------------------------------------------------+ //| Initialize expert | //+------------------------------------------------------------------+ int OnInit() { // Initialize expert advisor //--- existing logic int extra = HeaderShadowBlurRadius + HeaderShadowDistance; //--- Compute extra space int inner_header_width = currentWidth + (EnableStatsPanel ? PanelGap + currentWidth / 2 : 0); //--- Compute inner width int header_canvas_width = inner_header_width + 2 * extra; //--- Compute header width int header_canvas_height = header_height + 2 * extra; //--- Compute header height if (!canvasHeader.CreateBitmapLabel(0, 0, canvasHeaderName, currentCanvasX - extra, currentCanvasY - extra, header_canvas_width, header_canvas_height, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create header canvas Print("Failed to create Header Canvas"); //--- Log creation failure return(INIT_FAILED); //--- Return initialization failure } //--- existing logic return(INIT_SUCCEEDED); //--- Return initialization success }
Hier erweitern wir den OnInit-Ereignishandler, um Schatteneffekte im Header-Canvas während der Initialisierung zu berücksichtigen. Nachdem wir die aktuellen Abmessungen und Positionen festgelegt haben, berechnen wir „extra“ als Summe von „HeaderShadowBlurRadius“ und „HeaderShadowDistance“, um Platz für die Schatten zu schaffen. Wir berechnen „inner_header_width“ als „currentWidth“ plus Ergänzungen des Statistikpanels, falls aktiviert, und setzen dann „header_canvas_width“ auf die innere Breite plus zweimal extra und „header_canvas_height“ auf „header_height“ plus zweimal extra, um den Schatten vollständig einzuschließen. Wir erstellen den Header mit „canvasHeader.CreateBitmapLabel“ an versetzten Positionen (aktuelles X und Y minus extra), unter Verwendung der erweiterten Breite und Höhe und des normalisierten ARGB-Farbformats. Wenn die Erstellung fehlschlägt, wird eine Fehlermeldung ausgegeben und INIT_FAILED zurückgegeben; andernfalls wird mit der bestehenden Logik fortgefahren und bei Erfolg INIT_SUCCEEDED zurückgegeben. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

Nachdem der Schatten fertig ist, müssen wir als Nächstes das Chart-Ereignis aktualisieren, damit beim Scrollen mit dem Mausrad im Text-Canvas die Skalierung des Charts nicht beeinflusst wird. Um dies zu erreichen, löschen wir die Logik, die die Skala des Charts ursprünglich geändert hat.
//+------------------------------------------------------------------+ //| Handle chart events | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { // Process chart events //--- existing logic } else if (id == CHARTEVENT_MOUSE_WHEEL) { //--- Check 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) { //--- Check text wheel 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) { //--- Handle over 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 // REMOVED: Scale revert code (this was causing the chart scale interference) // No need to change CHART_SCALE; wheel now only scrolls text content. 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 } } } }
Wir haben die störende Skalierungslogik markiert; in der finalen Version wird dieser Block entfernt, sodass das Mausrad nur noch den Text scrollt. Wir haben die spezifische Logik hervorgehoben. Die endgültige Logik des OnChartEvent-Ereignishandlers lautet wie folgt.
//+------------------------------------------------------------------+ //| Handle chart events | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { // Process chart events if (id == CHARTEVENT_CHART_CHANGE) { //--- Check chart change DrawHeaderOnCanvas(); //--- Redraw header UpdateGraphOnCanvas(); //--- Update graph if (EnableStatsPanel) UpdateStatsOnCanvas(); //--- Update stats if (EnableTextPanel) UpdateTextOnCanvas(); //--- Update text ChartRedraw(); //--- Redraw chart } else if (id == CHARTEVENT_MOUSE_MOVE) { //--- Check mouse move int mouse_x = (int)lparam; //--- Get mouse X int mouse_y = (int)dparam; //--- Get mouse Y int mouse_state = (int)sparam; //--- Get mouse state bool prev_header_hovered = header_hovered; //--- Store prev header hover bool prev_min_hovered = minimize_hovered; //--- Store prev minimize hover bool prev_close_hovered = close_hovered; //--- Store prev close hover bool prev_theme_hovered = theme_hovered; //--- Store prev theme hover bool prev_resize_hovered = resize_hovered; //--- Store prev resize hover header_hovered = IsMouseOverHeader(mouse_x, mouse_y); //--- Check header hover theme_hovered = IsMouseOverTheme(mouse_x, mouse_y); //--- Check theme hover minimize_hovered = IsMouseOverMinimize(mouse_x, mouse_y); //--- Check minimize hover close_hovered = IsMouseOverClose(mouse_x, mouse_y); //--- Check close hover resize_hovered = IsMouseOverResize(mouse_x, mouse_y, hover_mode); //--- Check resize hover if (resize_hovered || resizing) { //--- Check resize state hover_mouse_local_x = mouse_x - currentCanvasX; //--- Set local X hover_mouse_local_y = mouse_y - (currentCanvasY + header_height + gap_y); //--- Set local Y } bool hover_changed = (prev_header_hovered != header_hovered || prev_min_hovered != minimize_hovered || prev_close_hovered != close_hovered || prev_theme_hovered != theme_hovered || prev_resize_hovered != resize_hovered); //--- Check hover change if (hover_changed) { //--- Handle change DrawHeaderOnCanvas(); //--- Redraw header UpdateGraphOnCanvas(); //--- Update graph ChartRedraw(); //--- Redraw chart } else if ((resize_hovered || resizing) && (mouse_x != last_mouse_x || mouse_y != last_mouse_y)) { //--- Check position change UpdateGraphOnCanvas(); //--- Update graph ChartRedraw(); //--- Redraw chart } string header_tooltip = ""; //--- Initialize tooltip if (theme_hovered) header_tooltip = "Toggle Theme (Dark/Light)"; //--- Set theme tooltip else if (minimize_hovered) header_tooltip = panels_minimized ? "Maximize Panels" : "Minimize Panels"; //--- Set minimize tooltip else if (close_hovered) header_tooltip = "Close Dashboard"; //--- Set close tooltip ObjectSetString(0, canvasHeaderName, OBJPROP_TOOLTIP, header_tooltip); //--- Apply header tooltip string resize_tooltip = ""; //--- Initialize resize tooltip if (resize_hovered || resizing) { //--- Check resize state ENUM_RESIZE_MODE active_mode = resizing ? resize_mode : hover_mode; //--- Get mode switch (active_mode) { //--- Switch mode case BOTTOM: //--- Handle bottom resize_tooltip = "Resize Bottom"; //--- Set tooltip break; //--- Exit case case RIGHT: //--- Handle right resize_tooltip = "Resize Right"; //--- Set tooltip break; //--- Exit case case BOTTOM_RIGHT: //--- Handle bottom-right resize_tooltip = "Resize Bottom-Right"; //--- Set tooltip break; //--- Exit case default: //--- Handle default break; //--- Exit case } } ObjectSetString(0, canvasGraphName, OBJPROP_TOOLTIP, resize_tooltip); //--- Apply graph tooltip if (EnableTextPanel && !panels_minimized) { //--- Check text enabled 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 text width int text_canvas_h = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_YSIZE); //--- Get text 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) { //--- Handle over text int local_x = mouse_x - text_canvas_x; //--- Compute local X int local_y = mouse_y - text_canvas_y; //--- Compute local Y if (local_x >= text_canvas_w - text_track_width) { //--- Check in scroll area 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) { //--- Check change UpdateTextOnCanvas(); //--- Update text ChartRedraw(); //--- Redraw chart } text_mouse_in_body = (local_x < text_canvas_w - text_track_width); //--- Set body mouse } else { //--- Handle not over text bool need_redraw = prev_scroll_hovered || text_scroll_up_hovered || text_scroll_down_hovered || text_scroll_slider_hovered; //--- Check need redraw if (need_redraw) { //--- Handle 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 mouse } if (text_mouse_in_body != prev_text_mouse_in_body) { //--- Check body change ChartSetInteger(0, CHART_MOUSE_SCROLL, !text_mouse_in_body); //--- Set mouse scroll prev_text_mouse_in_body = text_mouse_in_body; //--- Update prev } } if (mouse_state == 1 && prev_mouse_state == 0) { //--- Check mouse down if (header_hovered) { //--- Handle header click panel_dragging = true; //--- Set dragging panel_drag_x = mouse_x; //--- Set drag X panel_drag_y = mouse_y; //--- Set drag Y panel_start_x = currentCanvasX; //--- Set start X panel_start_y = currentCanvasY; //--- Set start Y ChartSetInteger(0, CHART_MOUSE_SCROLL, false); //--- Disable scroll DrawHeaderOnCanvas(); //--- Redraw header ChartRedraw(); //--- Redraw chart } else if (theme_hovered) { //--- Handle theme click ToggleTheme(); //--- Toggle theme } else if (minimize_hovered) { //--- Handle minimize click ToggleMinimize(); //--- Toggle minimize } else if (close_hovered) { //--- Handle close click CloseDashboard(); //--- Close dashboard } else { //--- Handle other clicks ENUM_RESIZE_MODE temp_mode = NONE; //--- Set temp mode if (!panel_dragging && !resizing && IsMouseOverResize(mouse_x, mouse_y, temp_mode)) { //--- Check resize start resizing = true; //--- Set resizing resize_mode = temp_mode; //--- Set mode resize_start_x = mouse_x; //--- Set start X resize_start_y = mouse_y; //--- Set start Y start_width = currentWidth; //--- Set start width start_height = currentHeight; //--- Set start height ChartSetInteger(0, CHART_MOUSE_SCROLL, false); //--- Disable scroll UpdateGraphOnCanvas(); //--- Update graph ChartRedraw(); //--- Redraw chart } } if (EnableTextPanel && !panels_minimized && text_scroll_visible && text_scroll_area_hovered) { //--- Check text click 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; //--- Compute local X int local_y = mouse_y - text_canvas_y; //--- Compute local Y int scrollbar_x = canvasText.Width() - text_track_width; //--- Set scrollbar X int scrollbar_y = 0; //--- Set scrollbar Y int scrollbar_height = canvasText.Height(); //--- Set height int scroll_area_y = scrollbar_y + text_button_size; //--- Set area Y int scroll_area_height = scrollbar_height - 2 * text_button_size; //--- Compute area height int slider_y = scroll_area_y + (int)(((double)text_scroll_pos / text_max_scroll) * (scroll_area_height - text_slider_height)); //--- Compute slider Y if (local_x >= scrollbar_x && local_x <= scrollbar_x + text_track_width - 1) { //--- Check in scrollbar if (local_y >= scrollbar_y && local_y <= scrollbar_y + text_button_size - 1) { //--- Check up button TextScrollUp(); //--- Scroll up } else if (local_y >= scrollbar_y + scrollbar_height - text_button_size && local_y <= scrollbar_y + scrollbar_height - 1) { //--- Check down button TextScrollDown(); //--- Scroll down } else if (local_y >= scroll_area_y && local_y <= scroll_area_y + scroll_area_height - 1) { //--- Check slider area if (local_y >= slider_y && local_y <= slider_y + text_slider_height - 1) { //--- Check on slider text_movingStateSlider = true; //--- Set moving state 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 { //--- Handle track click int new_slider_y = local_y - text_slider_height / 2; //--- Compute 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); //--- Compute ratio text_scroll_pos = (int)MathRound(ratio * text_max_scroll); //--- Set scroll pos } UpdateTextOnCanvas(); //--- Update text ChartRedraw(); //--- Redraw chart } } } } else if (panel_dragging && mouse_state == 1) { //--- Check dragging int dx = mouse_x - panel_drag_x; //--- Compute delta X int dy = mouse_y - panel_drag_y; //--- Compute delta Y int new_x = panel_start_x + dx; //--- Compute new X int new_y = panel_start_y + dy; //--- Compute new Y int chart_w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Get chart width int chart_h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Get chart height int full_w = currentWidth + (EnableStatsPanel && !panels_minimized ? PanelGap + currentWidth / 2 : 0); //--- Compute full width int full_h = header_height + gap_y + (panels_minimized ? 0 : currentHeight) + (EnableTextPanel && !panels_minimized ? PanelGap + TextPanelHeight : 0); //--- Compute full height new_x = MathMax(0, MathMin(chart_w - full_w, new_x)); //--- Clamp new X new_y = MathMax(0, MathMin(chart_h - full_h, new_y)); //--- Clamp new Y currentCanvasX = new_x; //--- Update canvas X currentCanvasY = new_y; //--- Update canvas Y int extra = HeaderShadowBlurRadius + HeaderShadowDistance; //--- Compute extra ObjectSetInteger(0, canvasHeaderName, OBJPROP_XDISTANCE, new_x - extra); //--- Set header X ObjectSetInteger(0, canvasHeaderName, OBJPROP_YDISTANCE, new_y - extra); //--- Set header Y if (!panels_minimized) { //--- Check not minimized ObjectSetInteger(0, canvasGraphName, OBJPROP_XDISTANCE, new_x); //--- Set graph X ObjectSetInteger(0, canvasGraphName, OBJPROP_YDISTANCE, new_y + header_height + gap_y); //--- Set graph Y if (EnableStatsPanel) { //--- Check stats enabled int stats_x = new_x + currentWidth + PanelGap; //--- Compute stats X ObjectSetInteger(0, canvasStatsName, OBJPROP_XDISTANCE, stats_x); //--- Set stats X ObjectSetInteger(0, canvasStatsName, OBJPROP_YDISTANCE, new_y + header_height + gap_y); //--- Set stats Y } if (EnableTextPanel) { //--- Check text enabled int textY = new_y + header_height + gap_y + currentHeight + PanelGap; //--- Compute text Y ObjectSetInteger(0, canvasTextName, OBJPROP_XDISTANCE, new_x); //--- Set text X ObjectSetInteger(0, canvasTextName, OBJPROP_YDISTANCE, textY); //--- Set text Y } } ChartRedraw(); //--- Redraw chart } else if (resizing && mouse_state == 1) { //--- Check resizing int dx = mouse_x - resize_start_x; //--- Compute delta X int dy = mouse_y - resize_start_y; //--- Compute delta Y int new_width = currentWidth; //--- Set new width int new_height = currentHeight; //--- Set new height if (resize_mode == RIGHT || resize_mode == BOTTOM_RIGHT) { //--- Check right modes new_width = MathMax(min_width, start_width + dx); //--- Adjust width } if (resize_mode == BOTTOM || resize_mode == BOTTOM_RIGHT) { //--- Check bottom modes new_height = MathMax(min_height, start_height + dy); //--- Adjust height } if (new_width != currentWidth || new_height != currentHeight) { //--- Check dimension change int chart_w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Get chart width int chart_h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Get chart height int avail_w = chart_w - currentCanvasX; //--- Compute available width int avail_h = chart_h - (currentCanvasY + header_height + gap_y); //--- Compute available height new_height = MathMin(new_height, avail_h - (EnableTextPanel ? PanelGap + TextPanelHeight : 0)); //--- Clamp height if (EnableStatsPanel) { //--- Check stats enabled double max_w_d = (avail_w - PanelGap) / 1.5; //--- Compute max width double int max_w = (int)MathFloor(max_w_d); //--- Floor max width new_width = MathMin(new_width, max_w); //--- Clamp width } else { //--- Handle no stats new_width = MathMin(new_width, avail_w); //--- Clamp width } currentWidth = new_width; //--- Update width currentHeight = new_height; //--- Update height if (UseBackground && ArraySize(original_bg_pixels) > 0) { //--- Check background ArrayCopy(bg_pixels_graph, original_bg_pixels); //--- Copy graph pixels ScaleImage(bg_pixels_graph, (int)orig_w, (int)orig_h, currentWidth, currentHeight); //--- Scale graph if (EnableStatsPanel) { //--- Check stats ArrayCopy(bg_pixels_stats, original_bg_pixels); //--- Copy stats pixels ScaleImage(bg_pixels_stats, (int)orig_w, (int)orig_h, currentWidth / 2, currentHeight); //--- Scale stats } } canvasGraph.Resize(currentWidth, currentHeight); //--- Resize graph ObjectSetInteger(0, canvasGraphName, OBJPROP_XSIZE, currentWidth); //--- Set graph X size ObjectSetInteger(0, canvasGraphName, OBJPROP_YSIZE, currentHeight); //--- Set graph Y size if (EnableStatsPanel) { //--- Check stats int stats_width = currentWidth / 2; //--- Compute stats width canvasStats.Resize(stats_width, currentHeight); //--- Resize stats ObjectSetInteger(0, canvasStatsName, OBJPROP_XSIZE, stats_width); //--- Set stats X size ObjectSetInteger(0, canvasStatsName, OBJPROP_YSIZE, currentHeight); //--- Set stats Y size int stats_x = currentCanvasX + currentWidth + PanelGap; //--- Compute stats X ObjectSetInteger(0, canvasStatsName, OBJPROP_XDISTANCE, stats_x); //--- Set stats X distance } int extra = HeaderShadowBlurRadius + HeaderShadowDistance; //--- Compute extra int inner_header_width = currentWidth + (EnableStatsPanel ? PanelGap + currentWidth / 2 : 0); //--- Compute inner width int header_canvas_width = inner_header_width + 2 * extra; //--- Compute header width canvasHeader.Resize(header_canvas_width, header_height + 2 * extra); //--- Resize header ObjectSetInteger(0, canvasHeaderName, OBJPROP_XSIZE, header_canvas_width); //--- Set header X size ObjectSetInteger(0, canvasHeaderName, OBJPROP_YSIZE, header_height + 2 * extra); //--- Set header Y size if (EnableTextPanel) { //--- Check text int text_width = inner_header_width; //--- Set text width canvasText.Resize(text_width, TextPanelHeight); //--- Resize text ObjectSetInteger(0, canvasTextName, OBJPROP_XSIZE, text_width); //--- Set text X size ObjectSetInteger(0, canvasTextName, OBJPROP_YSIZE, TextPanelHeight); //--- Set text Y size int textY = currentCanvasY + header_height + gap_y + currentHeight + PanelGap; //--- Compute text Y ObjectSetInteger(0, canvasTextName, OBJPROP_YDISTANCE, textY); //--- Set text Y distance } DrawHeaderOnCanvas(); //--- Redraw header UpdateGraphOnCanvas(); //--- Update graph if (EnableStatsPanel) UpdateStatsOnCanvas(); //--- Update stats if (EnableTextPanel) UpdateTextOnCanvas(); //--- Update text ChartRedraw(); //--- Redraw chart } } else if (text_movingStateSlider && mouse_state == 1) { //--- Check slider moving int text_canvas_y = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_YDISTANCE); //--- Get text Y int local_y = mouse_y - text_canvas_y; //--- Compute local Y int delta_y = local_y - text_mlbDownY_Slider; //--- Compute delta Y int new_slider_y = text_mlbDown_YD_Slider + delta_y; //--- Compute new Y int scrollbar_y = 0; //--- Set scrollbar Y int scrollbar_height = canvasText.Height(); //--- Set height int slider_min_y = scrollbar_y + text_button_size; //--- Set min Y int slider_max_y = scrollbar_y + scrollbar_height - text_button_size - text_slider_height; //--- Set 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); //--- Compute ratio int new_scroll_pos = (int)MathRound(scroll_ratio * text_max_scroll); //--- Compute new pos if (new_scroll_pos != text_scroll_pos) { //--- Check change text_scroll_pos = new_scroll_pos; //--- Update pos UpdateTextOnCanvas(); //--- Update text ChartRedraw(); //--- Redraw chart } } else if (mouse_state == 0 && prev_mouse_state == 1) { //--- Check mouse up if (panel_dragging) { //--- Handle drag end panel_dragging = false; //--- Reset dragging ChartSetInteger(0, CHART_MOUSE_SCROLL, true); //--- Enable scroll DrawHeaderOnCanvas(); //--- Redraw header ChartRedraw(); //--- Redraw chart } if (resizing) { //--- Handle resize end resizing = false; //--- Reset resizing ChartSetInteger(0, CHART_MOUSE_SCROLL, true); //--- Enable scroll UpdateGraphOnCanvas(); //--- Update graph ChartRedraw(); //--- Redraw chart } if (text_movingStateSlider) { //--- Handle slider end text_movingStateSlider = false; //--- Reset 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) { //--- Check 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) { //--- Check text wheel 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) { //--- Handle over 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 // REMOVED: Scale revert code (this was causing the chart scale interference) // No need to change CHART_SCALE; wheel now only scrolls text content. ChartRedraw(); //--- Redraw chart } } } }
Ursprünglich war das Scrollverhalten des Rads so.

Nach der Änderung erhalten wir dies.

Anhand der Visualisierung können wir sehen, dass wir das Canvas-basierte Dashboard durch Hinzufügen von Unschärfe- und Schatteneffekten auf dem Header verbessert und das Scrollverhalten per Mausrad verbessert, wodurch wir unsere Ziele erreicht haben. Nun bleibt nur noch, die Funktionsfähigkeit des Systems zu testen, was im vorangegangenen Abschnitt behandelt wurde.
Backtests
Wir haben die Tests durchgeführt, und unten sehen Sie die kompilierte Visualisierung in einem einzigen Graphics Interchange Format (GIF) Bitmap-Bildformat.

Schlussfolgerung
Abschließend haben wir das MQL5-Canvas-Dashboard mit Unschärfe-Effekten für sanfte Nebelverläufe, Schatten-Rendering, um dem Header mehr Tiefe zu verleihen, und nahtlosem Mausrad-Scrollen für interaktive Textnavigation erweitert und damit eine ausgefeiltere und benutzerfreundlichere Oberfläche geschaffen. Mit diesem verbesserten Canvas-Dashboard sind Sie in der Lage, Marktdaten noch effektiver zu visualisieren und Ihre Handelsaktivitäten weiter zu optimieren. Viel Spaß beim Handeln!
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/21140
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.
Entwicklung des Price Action Analysis Toolkit (Teil 57): Ein Modul zur Klassifizierung von Marktzuständen in MQL5
Graphentheorie: Einsatz von Breadth-First Search (BFS) im Trading
Einführung in MQL5 (Teil 37): Beherrschung von API und WebRequest in MQL5 (XI)
Vom Einsteiger zum Experten: Erstellung eines Liquiditätszonenindikators
- 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.