MQL5-Handelswerkzeuge (Teil 13): Entwicklung eines Canvas-basierten Kurs-Dashboards mit Chart- und Statistik-Panels
Einführung
In unserem letzten Artikel (Teil 12) haben wir das Dashboard für eine Korrelationsmatrix in MetaQuotes Language 5 (MQL5) mit interaktiven Funktionen für eine bessere Nutzerfreundlichkeit erweitert. In Teil 13 stellen wir Ihnen ein canvasbasiertes Dashboard mit Grafiken und Statistikpanel vor, das Händlern klare, praxisnahe Einblicke in Kursbewegungen und Kontometriken bietet. Mithilfe der Klasse CCanvas bietet dieses Dashboard verschiebbare und in der Größe veränderbare Panels, visualisiert die jüngsten Kursverläufe und zeigt wichtige Statistiken an, einschließlich Kontostand, Equity und die Eröffnungs-, Höchst-, Tiefst- und Schlusskurse der aktuellen Bar. Unsere Handelsentscheidungen werden durch anpassbare Hintergründe, das Umschalten zwischen Designs und Echtzeit-Updates zusätzlich unterstützt. Mit diesen Instrumenten können Sie Marktveränderungen effizienter überwachen und darauf reagieren. Wir behandeln folgende Themen:
- Grundlagen des Canvas-basierten Kurs-Dashboard-Rahmens
- Implementierung in MQL5
- Backtests
- Schlussfolgerung
Am Ende verfügen Sie über ein funktionales, auf MQL5-Canvas basierendes Dashboard zur Überwachung von Kursen und Metriken, das zur Anpassung bereit ist – beginnen wir!
Grundlagen des Canvas-basierten Kurs-Dashboard-Rahmens
Das canvasbasierte Kurs-Dashboard-Framework nutzt die Klasse CCanvas von MQL5, um nutzerdefinierte grafische Panels für die Anzeige von Echtzeit-Kurs- und Kontodaten zu erstellen und bietet eine kompakte, interaktive Alternative zu Standard-Chartindikatoren für Nutzer, die schnelle visuelle Übersichten benötigen, ohne das Hauptchart zu überladen. Es besteht aus einem Haupt-Grafikpanel, das die Schlusskurse der letzten Bar als Linie mit gefüllten Bereichen und Nebeleffekten für die Tiefe anzeigt, einem optionalen Statistikpanel, das Kontodetails wie Saldo/Equity und die OHLC-Werte der aktuellen Bar anzeigt, beide unterstützt von Hintergrundbildern mit Deckkraftüberblendung, Farbverlauf oder Volltonfüllungen und doppelten Rändern für die Ästhetik.
Zu den Verbesserungen gehören mausgesteuertes Ziehen für die Neupositionierung, Größenanpassung über Rahmen-Hover und Griffe mit Icons für Feedback, Schaltflächen zum Minimieren und Maximieren der Panels, Umschalten zwischen dunklen/hellen Modi für Farbanpassungen und Echtzeit-Updates für neue Bars, um die neuesten Kurse und Statistiken wiederzugeben.
Die Umsetzung basiert auf Mausereignissen, auf bikubischer Skalierung zur hochwertigen Bildanpassung, auf Alpha-Blending für Überlagerungen wie Nebel sowie ARGB-Farbverwaltung für Transparenzeffekte. Dadurch bleibt das Dashboard reaktionsschnell und flexibel anpassbar, ohne auf native MQL5-Objekte zurückzugreifen. Genau das war diesmal unser Ziel, da wir native Objekte bereits mehrfach verwendet haben; diesmal wechseln wir bewusst den Ansatz und nutzen die Canvas-Funktionen umfassend.
Geplant ist, zunächst die Canvas-Bibliothek einzubinden, Eingabeparameter für Position, Größe, Farben, Deckkraft und Modi zu definieren, anschließend eine Hintergrundbild-Ressource zu laden und zu skalieren sowie getrennte Canvas-Elemente für Header, Graph und Statistik mit entsprechenden Prüfungen zu erstellen. Anschließend sollen die Zeichenfunktionen für Header mit Icons/Tooltips/Rahmen implementiert werden, wie auch die Charts mit Kursplotting/Füllung/Zeitetiketten/Symbolen mit Größenänderung und Statistiken mit dem Design von Text/Gradienten/verdunkelten Rändern. Hinzugefügt werden Hilfsfunktionen für Farbinterpolation/Verdunkelung/Vermischung/ARGB-Extraktion und Handhabung von Chart-Ereignissen für Mouseover/Ziehen/Größenänderung/Schaltfunktionen mit Begrenzungen und Mindestgrößen, Aktualisierung bei neuen Tick-Daten. Kurz gesagt: Hier ist eine visuelle Darstellung unserer Ziele.

Implementierung in MQL5
Um das Programm in MQL5 zu erstellen, öffnen wir den MetaEditor, gehen zum Navigator, suchen den Ordner „Experts“, klicken auf die Registerkarte „Neu“ und folgen den Anweisungen, um die Datei zu erstellen. Sobald sie erstellt ist, müssen wir in der Programmierumgebung einige Eingabeparameter und globale Variablen deklarieren, die wir im gesamten Programm verwenden werden.
//+------------------------------------------------------------------+ //| Canvas Dashboard PART1.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> //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ CCanvas canvasGraph; //--- Declare canvas for graph CCanvas canvasStats; //--- Declare canvas for stats CCanvas canvasHeader; //--- Declare canvas for header string canvasGraphName = "GraphCanvas"; //--- Set graph canvas name string canvasStatsName = "StatsCanvas"; //--- Set stats canvas name string canvasHeaderName = "HeaderCanvas"; //--- Set header canvas name //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input int graphBars = 50; // Number of recent bars to plot in the graph input color borderColor = clrBlack; // Border color (change for white chart background) input color borderHoverColor = clrRed; // Border color on hover for resize indication input int CanvasX = 30; // Main canvas X position input int CanvasY = 50; // Main canvas Y position input int CanvasWidth = 400; // Main canvas width input int CanvasHeight = 300; // Main canvas height input bool EnableStatsPanel = true; // Enable second stats panel input int PanelGap = 10; // Gap between panels in pixels input bool UseBackground = true; // Enable background image input double FogOpacity = 0.5; // Fog opacity (0.0 = no fog/fully transparent, 1.0 = fully opaque) input bool BlendFog = true; // Blend fog with image (true: image visible under fog; false: original fog hides image) input int StatsFontSize = 12; // Font size for stats panel text input color StatsLabelColor = clrDodgerBlue; // Color for stats labels input color StatsValueColor = clrWhite; // Color for stats values input color StatsHeaderColor = clrDodgerBlue; // Color for stats headers input int StatsHeaderFontSize = 14; // Font size for stats headers input double BorderOpacityPercentReduction = 20.0; // Percent reduction for border opacity (0-100) input double BorderDarkenPercent = 30.0; // Percent to darken borders (0-100) //+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+ enum ENUM_BACKGROUND_MODE { NoColor = 0, // No color fill SingleColor = 1, // Single color fill GradientTwoColors = 2 // Gradient with two colors }; input ENUM_BACKGROUND_MODE StatsBackgroundMode = GradientTwoColors; // Stats background mode input color TopColor = clrBlack; // Top color for gradient or single fill input color BottomColor = clrRed; // Bottom color for gradient input double BackgroundOpacity = 0.7; // Opacity for stats background fill (0.0 to 1.0) enum ENUM_RESIZE_MODE { NONE, BOTTOM, RIGHT, BOTTOM_RIGHT }; //+------------------------------------------------------------------+ //| Resources | //+------------------------------------------------------------------+ #resource "1. Transparent MT5 bmp image.bmp" // Hardcoded background image resource //+------------------------------------------------------------------+ //| Global Variables Continued | //+------------------------------------------------------------------+ uint original_bg_pixels[]; //--- Declare original unscaled background uint orig_w = 0, orig_h = 0; //--- Initialize original dimensions uint bg_pixels_graph[]; //--- Declare scaled background for graph uint bg_pixels_stats[]; //--- Declare scaled background for stats int currentCanvasX = CanvasX; //--- Set current X position int currentCanvasY = CanvasY; //--- Set current Y position int currentWidth = CanvasWidth; //--- Set current width int currentHeight = CanvasHeight; //--- Set current height bool panel_dragging = false; //--- Set dragging flag int panel_drag_x = 0, panel_drag_y = 0; //--- Initialize drag start mouse coordinates int panel_start_x = 0, panel_start_y = 0; //--- Initialize drag start panel coordinates bool resizing = false; //--- Set resizing flag ENUM_RESIZE_MODE resize_mode = NONE; //--- Set resize mode ENUM_RESIZE_MODE hover_mode = NONE; //--- Set hover mode for resize int resize_start_x = 0, resize_start_y = 0; //--- Initialize resize start coordinates int start_width = 0, start_height = 0; //--- Initialize start dimensions const int resize_thickness = 5; //--- Set resize border thickness const int min_width = 200; //--- Set minimum width const int min_height = 150; //--- Set minimum height int hover_mouse_local_x = 0; //--- Set local mouse x for icon int hover_mouse_local_y = 0; //--- Set local mouse y for icon bool header_hovered = false; //--- Set header hover flag bool minimize_hovered = false; //--- Set minimize hover flag bool close_hovered = false; //--- Set close hover flag bool theme_hovered = false; //--- Set theme hover flag bool resize_hovered = false; //--- Set resize hover flag int prev_mouse_state = 0; //--- Initialize previous mouse state int last_mouse_x = 0, last_mouse_y = 0; //--- Initialize last mouse position 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 offset relative to header right int minimize_x_offset = -50; //--- Set minimize offset relative to header right int close_x_offset = -25; //--- Set close offset relative to header right bool panels_minimized = false; //--- Set 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 bool is_dark_theme = true; //--- Set dark theme flag color LightHeaderColor = clrSilver; //--- Set light header color color LightHeaderTextColor = clrBlack; //--- Set light header text color color LightStatsLabelColor = clrBlue; //--- Set light stats label color color LightStatsValueColor = clrBlack; //--- Set light stats value color color LightStatsHeaderColor = clrBlue; //--- Set light stats header color color LightBorderColor = clrBlack; //--- Set light border color color LightTopColor = clrGreen; //--- Set light top color color LightBottomColor = clrGold; //--- Set light bottom color color LightHeaderHoverColor = clrRed; //--- Set light header hover color color LightHeaderDragColor = clrMediumBlue; //--- Set light header drag color bool graphCreated = false; //--- Set graph created flag bool statsCreated = false; //--- Set stats created flag
Wir beginnen die Implementierung, indem wir die Canvas-Bibliothek mit „#include <Canvas/Canvas.mqh>“ einbinden, die die Klasse CCanvas für die Erstellung und Verwaltung von Bitmap-basierten grafischen Feldern im Chart bereitstellt. Anschließend deklarieren wir drei „CCanvas“-Objekte: „canvasGraph“ für das Kurs-Grafikpanel, „canvasStats“ für die Statistik und „canvasHeader“ für die Kopfzeile. Wir setzen String-Konstanten für ihre Namen als „GraphCanvas“, „StatsCanvas“ und „HeaderCanvas“, um sie eindeutig zu identifizieren.
Als Nächstes definieren wir Eingabeparameter, um das Dashboard anzupassen: „graphBars“ auf fünfzig für die Anzahl der darzustellenden Bars, „borderColor“ auf schwarz und „borderHoverColor“ auf rot für Rahmen und Mouseover-Anzeigen, Positionen und Abmessungen wie „CanvasX“ auf dreißig, „CanvasY“ auf fünfzig, „CanvasWidth“ auf vierhundert, „CanvasHeight“ auf dreihundert, ein boolescher Wert „EnableStatsPanel“ true für die Anzeige des Statistikpanels mit „PanelGap“ auf zehn Pixel, „UseBackground“ true für Bildhintergründe mit „FogOpacity“ auf 0.5 und „BlendFog“ true für die Überblendung, Schriftgrößen und -farben für Statistiken wie „StatsFontSize“ auf 12, „StatsLabelColor“ auf Dodger-Blau und Prozentsätze für Randanpassungen wie „BorderOpacityPercentReduction“ auf 20,0 und „BorderDarkenPercent“ auf 30,0.
Wir erstellen die Enumeration „ENUM_BACKGROUND_MODE“ mit den Optionen „NoColor“ für keine Füllung, „SingleColor“ für einheitliche Farbe und „GradientTwoColors“ für zweifarbige Farbverläufe, wobei die Eingabe „StatsBackgroundMode“ standardmäßig auf Farbverlauf und die Farben „TopColor“ auf Schwarz, „BottomColor“ auf Rot sowie „BackgroundOpacity“ auf 0.7 eingestellt sind. Dann fügen wir die Enumeration „ENUM_RESIZE_MODE“ für die Größenänderungen hinzu: „NONE“, „BOTTOM“, „RIGHT“ und „BOTTOM_RIGHT“.
Wir binden eine Ressource mit der Direktive #resource ein; „1. Transparentes MetaTrader 5 bmp image.bmp“ für ein fest kodiertes Hintergrundbild. Das Bild, das angehängt werden soll, muss eine reine Bitmap-Datei sein. Das lässt sich einfach umsetzen; unsere Bilddatei sieht nun wie folgt aus. Wir haben sie der Einfachheit halber dorthin importiert, wo sich die Programmdatei befindet. Schauen Sie sich unten an, was wir erreicht haben.

Nachdem die Bilddatei fertig ist, fahren wir mit den globalen Variablen fort: Arrays „original_bg_pixels“, „bg_pixels_graph“ und „bg_pixels_stats“ für Bildpixel, Dimensionen „orig_w“ und „orig_h“ auf Null, aktuelle Positionen und Größen aus Eingaben initialisiert, Booleans wie „panel_dragging“ false, „resizing“ false, Aufzählungen „resize_mode“ und „hover_mode“ auf „NONE“, Integerwerte für die Startwerte für die Größenänderung und Mindestmaße wie „resize_thickness“ auf fünf, „min_width“ auf zweihundert, Mouseover wie „header_hovered“ false, „prev_mouse_state“ auf null, Layout-Konstanten wie „header_height“ auf siebenundzwanzig, „gap_y“ auf sieben, „button_size“ auf fünfundzwanzig, Offsets für Buttons, „panels_minimized“ false, Farben wie „HeaderColor“ auf mittelgrau, „HeaderHoverColor“ auf rot, Design-Flag „is_dark_theme“ true, Light-Mode-Farben wie „LightHeaderColor“ auf silber und Flags „graphCreated“ und „statsCreated“ false.
Als Nächstes müssen wir eine Funktion erstellen, die das Bild dynamisch skaliert, damit es den neuen Abmessungen entspricht. Dies ist nützlich, wenn wir die Größe des Hintergrundbildes in den Panels ändern wollen, wenn dies erlaubt ist. Hier ist die Logik, mit der wir das erreicht haben.
//+------------------------------------------------------------------+ //| Scale image | //+------------------------------------------------------------------+ void ScaleImage(uint &pixels[], int original_width, int original_height, int new_width, int new_height) { uint scaled_pixels[]; //--- Declare scaled array ArrayResize(scaled_pixels, new_width * new_height); //--- Resize scaled for (int y = 0; y < new_height; y++) //--- Loop new rows { for (int x = 0; x < new_width; x++) //--- Loop new columns { double original_x = (double)x * original_width / new_width; //--- Compute original x double original_y = (double)y * original_height / new_height; //--- Compute original y uint pixel = BicubicInterpolate(pixels, original_width, original_height, original_x, original_y); //--- Interpolate pixel scaled_pixels[y * new_width + x] = pixel; //--- Set scaled pixel } } ArrayResize(pixels, new_width * new_height); //--- Resize original ArrayCopy(pixels, scaled_pixels); //--- Copy scaled } //+------------------------------------------------------------------+ //| Bicubic interpolate pixel | //+------------------------------------------------------------------+ uint BicubicInterpolate(uint &pixels[], int width, int height, double x, double y) { int x0 = (int)x; //--- Get integer x int y0 = (int)y; //--- Get integer y double fractional_x = x - x0; //--- Get fractional x double fractional_y = y - y0; //--- Get fractional y int x_indices[4], y_indices[4]; //--- Declare indices for (int i = -1; i <= 2; i++) //--- Loop offsets { x_indices[i + 1] = MathMin(MathMax(x0 + i, 0), width - 1); //--- Clamp x index y_indices[i + 1] = MathMin(MathMax(y0 + i, 0), height - 1); //--- Clamp y index } uint neighborhood_pixels[16]; //--- Declare neighborhood for (int j = 0; j < 4; j++) //--- Loop y indices { for (int i = 0; i < 4; i++) //--- Loop x indices { neighborhood_pixels[j * 4 + i] = pixels[y_indices[j] * width + x_indices[i]]; //--- Get pixel } } uchar alpha_components[16], red_components[16], green_components[16], blue_components[16]; //--- Declare components for (int i = 0; i < 16; i++) //--- Loop pixels { GetArgb(neighborhood_pixels[i], alpha_components[i], red_components[i], green_components[i], blue_components[i]); //--- Get ARGB } uchar alpha_out = (uchar)BicubicInterpolateComponent(alpha_components, fractional_x, fractional_y); //--- Interpolate alpha uchar red_out = (uchar)BicubicInterpolateComponent(red_components, fractional_x, fractional_y); //--- Interpolate red uchar green_out = (uchar)BicubicInterpolateComponent(green_components, fractional_x, fractional_y); //--- Interpolate green uchar blue_out = (uchar)BicubicInterpolateComponent(blue_components, fractional_x, fractional_y); //--- Interpolate blue return (((uint)alpha_out) << 24) | (((uint)red_out) << 16) | (((uint)green_out) << 8) | ((uint)blue_out); //--- Return interpolated } //+------------------------------------------------------------------+ //| Bicubic interpolate component | //+------------------------------------------------------------------+ double BicubicInterpolateComponent(uchar &components[], double fractional_x, double fractional_y) { double weights_x[4]; //--- Declare x weights double t = fractional_x; //--- Set t x weights_x[0] = (-0.5 * t * t * t + t * t - 0.5 * t); //--- Compute weight 0 weights_x[1] = (1.5 * t * t * t - 2.5 * t * t + 1); //--- Compute weight 1 weights_x[2] = (-1.5 * t * t * t + 2 * t * t + 0.5 * t); //--- Compute weight 2 weights_x[3] = (0.5 * t * t * t - 0.5 * t * t); //--- Compute weight 3 double y_values[4]; //--- Declare y values for (int j = 0; j < 4; j++) //--- Loop y { y_values[j] = weights_x[0] * components[j * 4 + 0] + weights_x[1] * components[j * 4 + 1] + weights_x[2] * components[j * 4 + 2] + weights_x[3] * components[j * 4 + 3]; //--- Compute y value } double weights_y[4]; //--- Declare y weights t = fractional_y; //--- Set t y weights_y[0] = (-0.5 * t * t * t + t * t - 0.5 * t); //--- Compute weight 0 weights_y[1] = (1.5 * t * t * t - 2.5 * t * t + 1); //--- Compute weight 1 weights_y[2] = (-1.5 * t * t * t + 2 * t * t + 0.5 * t); //--- Compute weight 2 weights_y[3] = (0.5 * t * t * t - 0.5 * t * t); //--- Compute weight 3 double result = weights_y[0] * y_values[0] + weights_y[1] * y_values[1] + weights_y[2] * y_values[2] + weights_y[3] * y_values[3]; //--- Compute result return MathMax(0, MathMin(255, result)); //--- Clamp result } //+------------------------------------------------------------------+ //| Get ARGB components | //+------------------------------------------------------------------+ void GetArgb(uint pixel, uchar &alpha, uchar &red, uchar &green, uchar &blue) { alpha = (uchar)((pixel >> 24) & 0xFF); //--- Get alpha red = (uchar)((pixel >> 16) & 0xFF); //--- Get red green = (uchar)((pixel >> 8) & 0xFF); //--- Get green blue = (uchar)(pixel & 0xFF); //--- Get blue }
Zunächst implementieren wir die Funktion „ScaleImage“, um die Größe eines Bildfeldes aus Pixeln von den ursprünglichen Abmessungen auf die neue Breite und Höhe zu ändern, wobei wir die bikubische Interpolation für eine gleichmäßige Skalierung verwenden. Sie nimmt einen Verweis auf das Pixel-Array, die ursprüngliche Breite und Höhe und die neuen Abmessungen. Wir deklarieren ein temporäres Array „scaled_pixels“ und passen die Größe an die neue Größe an, dann verschachteln wir Schleifen über die neue Höhe und Breite, um die entsprechenden Originalkoordinaten durch proportionales Mapping zu berechnen. Für jedes neue Pixel rufen wir „BicubicInterpolate“ mit dem ursprünglichen Array und den gebrochenen Koordinaten auf, um den interpolierten Wert zu erhalten und ihn in „scaled_pixels“ zu speichern. Schließlich wird die Größe des Arrays Eingangs-Pixel-Arrays auf die neue Größe angepasst und „scaled_pixels“ mit der Funktion ArrayCopy hineinkopiert.
Als Nächstes folgt die Funktion „BicubicInterpolate“ zur Berechnung eines einzelnen Pixelwerts durch bikubische Interpolation bei gegebenen Bruchteilen von x und y im Bild. Die Funktion verwendet das Pixel-Array, Breite, Höhe und die Gleitkommawerte x und y als Parameter. Wir erhalten die ganzzahligen Teile x0 und y0, die gebrochenen Teile und erstellen Index-Arrays für eine 4x4-Nachbarschaft, indem wir die Offsets von -1 bis 2 mit den Funktionen MathMin und MathMax an die Bildgrenzen anpassen. Wir extrahieren sechzehn benachbarte Pixel in ein Array, deklarieren dann Komponenten-Arrays für Alpha, Rot, Grün und Blau und füllen sie in einer Schleife mit „GetArgb“ auf. Wir interpolieren jede Komponente mit „BicubicInterpolateComponent“ und die Brüche, Casting zu uchar, und kombinieren in einem uint ARGB-Wert mit Bit-Shifts.
Anschließend erstellen wir die Funktion „BicubicInterpolateComponent“, um eine bikubische Interpolation auf dem 4x4-Komponenten-Array eines einzelnen Farbkanals unter Verwendung von gebrochenen x- und y-Werten durchzuführen. Es berechnet vier x-Gewichtungen mit der bikubischen Kernelformel auf der Grundlage von t als gebrochenes x und berechnet dann vier y-Zwischenwerte durch gewichtete Summen von Zeilen im Komponenten-Array. In ähnlicher Weise werden y-Gewichtungen mit einem Bruchteil von y als t berechnet, und das Endergebnis wird als gewichtete Summe dieser y-Werte abgeleitet, wobei mit „MathMax“ und „MathMin“ zwischen Null und 255 geklemmt wird. Schließlich implementieren wir die Funktion „GetArgb“, um ARGB-Komponenten aus einem uint-Pixelwert in uchar-Referenzen für Alpha, Rot, Grün und Blau zu extrahieren, wobei Bitverschiebungen nach rechts um 24/16/8/0 und Maskierung mit 0xFF verwendet werden.
Es ist wichtig zu verstehen, dass es kein Muss ist, einen bikubischen Ansatz zu verwenden. Sie können einen linearen oder bilinearen Ansatz verwenden, aber das ergibt ein zerklüfteteres Bild als das, was wir eigentlich wollen. Wir wählen also den besten Ansatz für eine möglichst visuell saubere Bildskalierung. Nachfolgend finden Sie verschiedene Ansätze, die Sie in Betracht ziehen könnten.

Es zeigt sich, dass der Ansatz der bikubischen Interpolation im Vergleich zu den anderen Ansätzen die glatteste Bildansicht liefert. Als Nächstes werden wir die Funktionen verwenden, um die Größe unseres Ressourcenbildes zu ändern, damit es dynamisch passt. Unsere Bilddatei ist größer als die Canvas, auf der sie gerendert werden soll, also müssen wir ihre Größe ändern. Lassen Sie uns das im Initialisierungs-Ereignishandler tun.
//+------------------------------------------------------------------+ //| Initialize expert | //+------------------------------------------------------------------+ int OnInit() { currentWidth = CanvasWidth; //--- Set initial width currentHeight = CanvasHeight; //--- Set initial height currentCanvasX = CanvasX; //--- Set initial X currentCanvasY = CanvasY; //--- Set initial Y if (UseBackground) //--- Check if background enabled { if (ResourceReadImage("::1. Transparent MT5 bmp image.bmp", original_bg_pixels, orig_w, orig_h) && orig_w > 0 && orig_h > 0) //--- Load image if valid { ArrayCopy(bg_pixels_graph, original_bg_pixels); //--- Copy to graph background ScaleImage(bg_pixels_graph, (int)orig_w, (int)orig_h, currentWidth, currentHeight); //--- Scale graph background if (EnableStatsPanel) //--- Check stats panel { ArrayCopy(bg_pixels_stats, original_bg_pixels); //--- Copy to stats background ScaleImage(bg_pixels_stats, (int)orig_w, (int)orig_h, currentWidth / 2, currentHeight); //--- Scale stats background } } else //--- Handle load failure { Print("Failed to load background image from ::1. Transparent MT5 bmp image.bmp"); //--- Print error } } int header_width = currentWidth + (EnableStatsPanel ? PanelGap + currentWidth / 2 : 0); //--- Compute header width if (!canvasHeader.CreateBitmapLabel(0, 0, canvasHeaderName, currentCanvasX, currentCanvasY, header_width, header_height, COLOR_FORMAT_ARGB_NORMALIZE)) //--- Create header canvas { Print("Failed to create Header Canvas"); //--- Print error return(INIT_FAILED); //--- Return failure } if (!canvasGraph.CreateBitmapLabel(0, 0, canvasGraphName, currentCanvasX, currentCanvasY + header_height + gap_y, currentWidth, currentHeight, COLOR_FORMAT_ARGB_NORMALIZE)) //--- Create graph canvas { Print("Failed to create Graph Canvas"); //--- Print error return(INIT_FAILED); //--- Return failure } graphCreated = true; //--- Set graph created if (EnableStatsPanel) //--- Check stats panel { 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)) //--- Create stats canvas { Print("Failed to create Stats Canvas"); //--- Print error } statsCreated = true; //--- Set stats created } ChartRedraw(); //--- Redraw chart return(INIT_SUCCEEDED); //--- Return success }
Im OnInit-Ereignishandler richten wir den Ausgangszustand ein und erstellen die Canvas-Panels für das Dashboard. Wir initialisieren die aktuellen Abmessungen und Positionen durch Eingaben wie „currentWidth“ zu „CanvasWidth“, „currentHeight“ zu „CanvasHeight“, „currentCanvasX“ zu „CanvasX“ und „currentCanvasY“ zu „CanvasY“. Wenn „UseBackground“ wahr ist, laden wir das Ressourcenbild mit ResourceReadImage in „original_bg_pixels“ und erhalten seine ursprüngliche Breite und Höhe. Im Erfolgsfall kopieren wir das Bild in „bg_pixels_graph“ und skalieren es mit „ScaleImage“ auf die aktuellen Abmessungen. Für das Statistikpanel, wenn „EnableStatsPanel“ aktiviert ist, kopieren wir zu „bg_pixels_stats“ und skalieren auf die Hälfte der Breite mit voller Höhe. Bei einem Fehlschlag wird eine Fehlermeldung ausgegeben.
Wir berechnen die Breite der Kopfzeile aus der Breite des Grafik-Panels zuzüglich – falls aktiviert – der Breite des Statistik-Panels und des Abstands dazwischen und erstellen dann die Canvas für die Kopfzeile mit „CreateBitmapLabel“ im Unterfenster Null, Name „canvasHeaderName“, Position, Breite und „header_height“ unter Verwendung von COLOR_FORMAT_ARGB_NORMALIZE, wobei der Fehler ausgegeben und „INIT_FAILED“ zurückgegeben wird, wenn dies nicht erfolgreich war. In ähnlicher Weise erstellen wir den Grafik-Canvas unterhalb der Kopfzeile plus „gap_y“, wobei „graphCreated“ bei Erfolg auf true gesetzt wird oder bei einem Fehler INIT_FAILED zurückgibt. Wenn die Statistik aktiviert ist, berechnen wir ihre x-Position nach dem Diagramm plus „PanelGap“, erstellen die Statistik-Canvas und setzen „statsCreated“ auf true. Schließlich wird das Chart mit „ChartRedraw“ neu gezeichnet und INIT_SUCCEEDED zurückgegeben. So entstehen die Zeichenflächen, aber noch keine eigentliche Zeichnung. Zur Veranschaulichung sehen Sie hier ein Beispiel dafür, was bei der Kompilierung in den Tooltips zu sehen ist.

Wir sehen, dass die Canvas-Bereiche jetzt sichtbar sind. Im nächsten Schritt zeichnen wir die eigentlichen Inhalte dieser Canvas-Objekte. Wir werden mit der Kopfzeile beginnen, aber da wir themenabhängig sein müssen, werden wir zuerst einige Hilfsfunktionen für das Design definieren, die es uns ermöglichen, entweder ein dunkles oder ein helles Design zu erstellen, wo dies möglich ist.
//+------------------------------------------------------------------+ //| Get theme-aware colors | //+------------------------------------------------------------------+ color GetHeaderColor() { return is_dark_theme ? HeaderColor : LightHeaderColor; } //--- Return header color color GetHeaderHoverColor() { return is_dark_theme ? HeaderHoverColor : LightHeaderHoverColor; } //--- Return hover color color GetHeaderDragColor() { return is_dark_theme ? HeaderDragColor : LightHeaderDragColor; } //--- Return drag color color GetStatsLabelColor() { return is_dark_theme ? StatsLabelColor : LightStatsLabelColor; } //--- Return label color color GetStatsValueColor() { return is_dark_theme ? StatsValueColor : LightStatsValueColor; } //--- Return value color color GetStatsHeaderColor() { return is_dark_theme ? StatsHeaderColor : LightStatsHeaderColor; } //--- Return header color color GetBorderColor() { return is_dark_theme ? borderColor : LightBorderColor; } //--- Return border color color GetTopColor() { return is_dark_theme ? TopColor : LightTopColor; } //--- Return top color color GetBottomColor() { return is_dark_theme ? BottomColor : LightBottomColor; } //--- Return bottom color color GetHeaderTextColor() { return is_dark_theme ? clrWhite : LightHeaderTextColor; } //--- Return text color color GetIconColor(bool is_drag) { return is_drag ? GetHeaderDragColor() : GetHeaderHoverColor(); } //--- Return icon color
Hier implementieren wir mehrere Getter-Funktionen, um themenabhängige Farben auf der Grundlage des aktuellen „is_dark_theme“-Flags abzurufen und so eine konsistente Darstellung in dunklen und hellen Modi zu gewährleisten, ohne dass an anderer Stelle redundante Prüfungen durchgeführt werden. Die Funktion „GetHeaderColor“ liefert „HeaderColor“ im dunklen Design oder „LightHeaderColor“ im hellen Design für den Hintergrund der Kopfzeile. Wir verwenden dieselbe Logik für alle anderen Funktionen. Mit diesen Hilfsfunktionen können wir nun die Header-Canvas-Objekte erstellen.
//+------------------------------------------------------------------+ //| Draw header on header canvas | //+------------------------------------------------------------------+ void DrawHeaderOnCanvas() { canvasHeader.Erase(0); //--- Clear canvas color header_bg = panel_dragging ? GetHeaderDragColor() : (header_hovered ? GetHeaderHoverColor() : GetHeaderColor()); //--- Set background uint argb_bg = ColorToARGB(header_bg, 255); //--- Convert to ARGB canvasHeader.FillRectangle(0, 0, canvasHeader.Width() - 1, header_height - 1, argb_bg); //--- Fill background uint argbBorder = ColorToARGB(GetBorderColor(), 255); //--- Convert border to ARGB canvasHeader.Line(0, 0, canvasHeader.Width() - 1, 0, argbBorder); //--- Draw top border canvasHeader.Line(canvasHeader.Width() - 1, 0, canvasHeader.Width() - 1, header_height - 1, argbBorder); //--- Draw right border canvasHeader.Line(canvasHeader.Width() - 1, header_height - 1, 0, header_height - 1, argbBorder); //--- Draw bottom border canvasHeader.Line(0, header_height - 1, 0, 0, argbBorder); //--- Draw left border canvasHeader.FontSet("Arial Bold", 15); //--- Set font uint argbText = ColorToARGB(GetHeaderTextColor(), 255); //--- Convert text to ARGB canvasHeader.TextOut(10, (header_height - 15) / 2, "Price Dashboard", argbText, TA_LEFT); //--- Draw title int theme_x = canvasHeader.Width() + theme_x_offset; //--- Compute theme x string theme_symbol = CharToString((uchar)91); //--- Set theme symbol color theme_color = theme_hovered ? clrYellow : GetHeaderTextColor(); //--- Set theme color canvasHeader.FontSet("Wingdings", 22); //--- Set font uint argb_theme = ColorToARGB(theme_color, 255); //--- Convert to ARGB canvasHeader.TextOut(theme_x, (header_height - 22) / 2, theme_symbol, argb_theme, TA_CENTER); //--- Draw theme icon int min_x = canvasHeader.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(); //--- Set minimize color canvasHeader.FontSet("Wingdings", 22); //--- Set font uint argb_min = ColorToARGB(min_color, 255); //--- Convert to ARGB canvasHeader.TextOut(min_x, (header_height - 22) / 2, min_symbol, argb_min, TA_CENTER); //--- Draw minimize icon int close_x = canvasHeader.Width() + close_x_offset; //--- Compute close x string close_symbol = CharToString((uchar)114); //--- Set close symbol color close_color = close_hovered ? clrRed : GetHeaderTextColor(); //--- Set close color canvasHeader.FontSet("Webdings", 22); //--- Set font uint argb_close = ColorToARGB(close_color, 255); //--- Convert to ARGB canvasHeader.TextOut(close_x, (header_height - 22) / 2, close_symbol, argb_close, TA_CENTER); //--- Draw close icon canvasHeader.Update(); //--- Update canvas }
Hier implementieren wir die Funktion „DrawHeaderOnCanvas“, um die Kopfzeile auf dem „canvasHeader“-Objekt darzustellen und einen Titel und interaktive Symbole mit dynamischen Farben basierend auf Zuständen wie Ziehen oder Mouseover bereitzustellen. Wir beginnen damit, die Canvas mit der Methode „Erase“ zu löschen, die auf Null gesetzt ist. Wir legen die Hintergrundfarbe nach bestimmten Kriterien fest: wenn „panel_dragging“ wahr ist, verwenden wir „GetHeaderDragColor“; wenn „header_hovered“, verwenden wir „GetHeaderHoverColor“; andernfalls „GetHeaderColor“. Wir konvertieren sie in ARGB mit ColorToARGB bei voller Deckkraft 255 und füllen ein Rechteck von (0,0) bis Breite minus eins und „header_height“ minus eins mit der Methode FillRectangle.
Für die Umrandung konvertieren wir die Farbe der Umrandung von „GetBorderColor“ in ARGB bei 255 und zeichnen Linien mit Line für den oberen, rechten, unteren und linken Rand der Kopfzeile. Wir stellen die Schriftart Arial Bold in Größe fünfzehn mit FontSet ein, konvertieren die Farbe des Kopfzeilentextes von „GetHeaderTextColor“ in ARGB und zeichnen den Titel „Price Dashboard“ bei x zehn vertikal zentriert mit TextOut mit linker Ausrichtung.
Für das Designsymbol berechnen wir sein x als Canvas-Breite plus „theme_x_offset“, setzen das Symbol auf Zeichen 91 aus dem uchar-Cast, die Farbe auf gelb, wenn „theme_hovered“, sonst auf die Textfarbe der Kopfzeile, ändern die Schriftart auf Wingdings bei zweiundzwanzig, konvertieren in ARGB und zeichnen es horizontal und vertikal zentriert mit „TextOut“ und zentrierter Ausrichtung. In ähnlicher Weise wird für das Minimieren-Symbol x mit „minimize_x_offset“ berechnet, das Symbol bedingt auf das Zeichen 111 gesetzt, wenn „panels_minimized“, oder andernfalls auf 114, die Farbe auf gelb bei Mouseover, sonst auf Textfarbe, die Schrift Wingdings verwendet, konvertiert und zentriert gezeichnet. Für das Schließen-Symbol berechnen wir x mit „close_x_offset“, setzen das Symbol auf 114, die Farbe auf rot bei „close_hovered“, sonst Textfarbe, wechseln die Schriftart zu Webdings bei zweiundzwanzig, konvertieren und zeichnen zentriert. Schließlich aktualisieren wir die Canvas mit „Update“, um die Änderungen anzuzeigen. Wenn wir diese Funktion bei der Initialisierung aufrufen, erhalten wir das folgende Ergebnis.

Auf dem Bild ist zu erkennen, dass die Kopfzeile der Canvas korrekt beschriftet ist. Als Nächstes zeichnen wir den Inhalt im Grafikpanel, in dem wir die Kurse analysieren und ihr Liniendiagramm erstellen sollen. Im Wesentlichen zeichnen wir also unser eigenes einfaches Kurschart. Beachten Sie jedoch, dass dies nur unser Ansatz ist; es ist der einfachste, den wir für diese Demonstration dachten. Sie können Ihre eigenen komplexen Berechnungen anstellen, wenn Sie dies für richtig halten. Hier ist der Ansatz, mit dem wir das erreichen.
//+------------------------------------------------------------------+ //| Update the price graph on the main Canvas | //+------------------------------------------------------------------+ void UpdateGraphOnCanvas() { canvasGraph.Erase(0); //--- Clear canvas if (UseBackground && ArraySize(bg_pixels_graph) == currentWidth * currentHeight) //--- Check background { for (int y = 0; y < currentHeight; y++) //--- Loop rows { for (int x = 0; x < currentWidth; x++) //--- Loop columns { canvasGraph.PixelSet(x, y, bg_pixels_graph[y * currentWidth + x]); //--- Set pixel } } } uint argbBorder = ColorToARGB(GetBorderColor(), 255); //--- Convert border to ARGB canvasGraph.Line(0, 0, currentWidth - 1, 0, argbBorder); //--- Draw top outer canvasGraph.Line(currentWidth - 1, 0, currentWidth - 1, currentHeight - 1, argbBorder); //--- Draw right outer canvasGraph.Line(currentWidth - 1, currentHeight - 1, 0, currentHeight - 1, argbBorder); //--- Draw bottom outer canvasGraph.Line(0, currentHeight - 1, 0, 0, argbBorder); //--- Draw left outer canvasGraph.Line(1, 1, currentWidth - 2, 1, argbBorder); //--- Draw top inner canvasGraph.Line(currentWidth - 2, 1, currentWidth - 2, currentHeight - 2, argbBorder); //--- Draw right inner canvasGraph.Line(currentWidth - 2, currentHeight - 2, 1, currentHeight - 2, argbBorder); //--- Draw bottom inner canvasGraph.Line(1, currentHeight - 2, 1, 1, argbBorder); //--- Draw left inner double closePrices[]; //--- Declare close array ArrayResize(closePrices, graphBars); //--- Resize close if (CopyClose(_Symbol, _Period, 0, graphBars, closePrices) != graphBars) //--- Copy closes { Print("Failed to copy close prices"); //--- Print error return; //--- Exit } datetime timeArr[]; //--- Declare time array ArrayResize(timeArr, graphBars); //--- Resize time if (CopyTime(_Symbol, _Period, 0, graphBars, timeArr) != graphBars) //--- Copy times { Print("Failed to copy times"); //--- Print error return; //--- Exit } double minPrice = closePrices[0]; //--- Set initial min double maxPrice = closePrices[0]; //--- Set initial max for (int i = 1; i < graphBars; i++) //--- Loop prices { if (closePrices[i] < minPrice) minPrice = closePrices[i]; //--- Update min if (closePrices[i] > maxPrice) maxPrice = closePrices[i]; //--- Update max } double priceRange = maxPrice - minPrice; //--- Compute range if (priceRange == 0) priceRange = _Point; //--- Avoid zero int graphLeft = 2; //--- Set left margin int graphRight = currentWidth - 3; //--- Set right margin double graphWidth_d = graphRight - graphLeft; //--- Compute width int graphHeight = currentHeight - 4; //--- Compute height int bottomY = 2 + graphHeight; //--- Set bottom y int x_pos[]; //--- Declare x positions int y_pos[]; //--- Declare y positions ArrayResize(x_pos, graphBars); //--- Resize x ArrayResize(y_pos, graphBars); //--- Resize y for (int i = 0; i < graphBars; i++) //--- Loop bars { double norm = (graphBars > 1) ? (double)i / (graphBars - 1) : 0.0; //--- Normalize x_pos[i] = graphLeft + (int)(norm * graphWidth_d + 0.5); //--- Set x double price = closePrices[graphBars - 1 - i]; //--- Get price (flipped) y_pos[i] = 2 + (int)(graphHeight * (maxPrice - price) / priceRange + 0.5); //--- Set y } color lineColor = clrBlue; //--- Set line color uint argbLine = ColorToARGB(lineColor, 255); //--- Convert to ARGB for (int i = 0; i < graphBars - 1; i++) //--- Loop segments { int x1 = (currentWidth - 1) - x_pos[i]; //--- Set x1 (flipped) int y1 = y_pos[i]; //--- Set y1 int x2 = (currentWidth - 1) - x_pos[i + 1]; //--- Set x2 (flipped) int y2 = y_pos[i + 1]; //--- Set y2 canvasGraph.LineAA(x1, y1, x2, y2, argbLine); //--- Draw line } int min_flipped_x = (currentWidth - 1) - graphRight; //--- Set min flipped x int max_flipped_x = (currentWidth - 1) - graphLeft; //--- Set max flipped x for (int colX = min_flipped_x; colX <= max_flipped_x; colX++) //--- Loop columns { int logical_colX = (currentWidth - 1) - colX; //--- Get logical x int seg = -1; //--- Initialize segment for (int j = 0; j < graphBars - 1; j++) //--- Loop segments { if (x_pos[j] <= logical_colX && logical_colX <= x_pos[j + 1]) //--- Check segment { seg = j; //--- Set segment break; //--- Exit } } if (seg == -1) continue; //--- Skip if no segment double dx = x_pos[seg + 1] - x_pos[seg]; //--- Compute dx double t = (dx > 0) ? (logical_colX - x_pos[seg]) / dx : 0.0; //--- Compute t double interpY = y_pos[seg] + t * (y_pos[seg + 1] - y_pos[seg]); //--- Interpolate y int topY = (int)(interpY + 0.5); //--- Round top y for (int fillY = topY; fillY < bottomY; fillY++) //--- Loop fill { double fadeFactor = (double)(bottomY - fillY) / (bottomY - topY); //--- Compute fade uchar alpha = (uchar)(255 * fadeFactor * FogOpacity); //--- Compute alpha uint argbFill = ColorToARGB(lineColor, alpha); //--- Convert fill if (BlendFog) //--- Check blend { uint currentPixel = canvasGraph.PixelGet(colX, fillY); //--- Get pixel uint blendedPixel = BlendPixels(currentPixel, argbFill); //--- Blend pixels canvasGraph.PixelSet(colX, fillY, blendedPixel); //--- Set blended } else //--- Handle no blend { canvasGraph.PixelSet(colX, fillY, argbFill); //--- Set fill } } } canvasGraph.FontSet("Arial", 12); //--- Set font uint argbText = ColorToARGB(is_dark_theme ? clrBlack : clrGray, 255); //--- Convert text canvasGraph.TextOut(currentWidth / 2, 10, "Price Graph (" + _Symbol + ")", argbText, TA_CENTER); //--- Draw title canvasGraph.FontSet("Arial", 12); //--- Set font string newTime = TimeToString(timeArr[0], TIME_DATE | TIME_MINUTES); //--- Get new time string oldTime = TimeToString(timeArr[graphBars - 1], TIME_DATE | TIME_MINUTES); //--- Get old time canvasGraph.TextOut(10, currentHeight - 15, newTime, argbText, TA_LEFT); //--- Draw new time canvasGraph.TextOut(currentWidth - 10, currentHeight - 15, oldTime, argbText, TA_RIGHT); //--- Draw old time if (resize_hovered || resizing) //--- Check resize state { ENUM_RESIZE_MODE active_mode = resizing ? resize_mode : hover_mode; //--- Get active mode if (active_mode == NONE) //--- Check none { canvasGraph.Update(); //--- Update canvas return; //--- Exit } string icon_font = "Wingdings 3"; //--- Set icon font int icon_size = 25; //--- Set icon size uchar icon_code; //--- Declare code int angle = 0; //--- Set angle switch (active_mode) //--- Switch mode { case BOTTOM: icon_code = (uchar)'2'; //--- Set bottom code angle = 0; //--- Set angle break; case RIGHT: icon_code = (uchar)'1'; //--- Set right code angle = 0; //--- Set angle break; case BOTTOM_RIGHT: icon_code = (uchar)'2'; //--- Set corner code angle = 450; //--- Set angle break; default: canvasGraph.Update(); return; } string icon_symbol = CharToString(icon_code); //--- Set symbol color icon_color = GetIconColor(resizing); //--- Get icon color uint argb_icon = ColorToARGB(icon_color, 255); //--- Convert to ARGB canvasGraph.FontSet(icon_font, icon_size); //--- Set font canvasGraph.FontAngleSet(angle); //--- Set angle int icon_x = 0; //--- Initialize x int icon_y = 0; //--- Initialize y switch (active_mode) //--- Switch for position { case BOTTOM: icon_x = MathMax(0, MathMin(hover_mouse_local_x - (icon_size / 2), currentWidth - icon_size)); //--- Set x icon_y = currentHeight - icon_size - 2; //--- Set y break; case RIGHT: icon_y = MathMax(0, MathMin(hover_mouse_local_y - (icon_size / 2), currentHeight - icon_size)); //--- Set y icon_x = currentWidth - icon_size - 2; //--- Set x break; case BOTTOM_RIGHT: icon_x = currentWidth - icon_size - 10; //--- Set x icon_y = currentHeight - icon_size; //--- Set y break; default: break; } canvasGraph.TextOut(icon_x, icon_y, icon_symbol, argb_icon, TA_LEFT | TA_TOP); //--- Draw icon canvasGraph.FontAngleSet(0); //--- Reset angle } canvasGraph.Update(); //--- Update canvas }
Hier implementieren wir die Funktion „UpdateGraphOnCanvas“, um das Kurschart auf dem „canvasGraph“-Objekt darzustellen, das die Schlusskurse der jüngsten Bars als Liniendiagramm mit gefüllten Bereichen, Beschriftungen und optionalen Größenänderungsindikatoren darstellt. Wir beginnen damit, die Canvas zu löschen, wobei Erase auf Null gesetzt wird. Wenn „UseBackground“ wahr ist und „bg_pixels_graph“ mit den aktuellen Abmessungen übereinstimmt, durchlaufen wir Höhe und Breite in einer Schleife, um jedes Pixel aus dem skalierten Hintergrund-Array mit der PixelSet-Methode zu setzen. Wir konvertieren die Farbe des Rahmens von „GetBorderColor“ in ARGB bei voller Deckkraft mit ColorToARGB und zeichnen äußere und innere Ränder mit „Line“ für den oberen, rechten, unteren und linken Rand, wodurch ein doppelter Rahmeneffekt entsteht.
Wir deklarieren ein Double-Array „closePrices“ und passen die Größe an „graphBars“ an, kopieren die Schlusskurse mit CopyClose für das aktuelle Symbol und den aktuellen Zeitraum, beginnend bei Bar Null, geben einen Fehler aus und beenden die Anwendung, wenn sie unvollständig ist. In ähnlicher Weise holen wir Zeiten in ein „timeArr“-Datetime-Array mit CopyTime und behandeln Fehler. Wir finden die Minimal- und MaximalKurse, indem wir auf den ersten Schlusskurs initialisieren und in einer Schleife aktualisieren, den Bereich berechnen und bei Null auf _Point zurückgreifen, um Divisionsprobleme zu vermeiden.
Wir setzen die Ränder wie „graphLeft“ auf zwei, „graphRight“ auf Breite minus drei, berechnen die effektive Breite und Höhe und unten y auf zwei plus Höhe. Wir passen die Größe der Integer-Arrays „x_pos“ und „y_pos“ an „graphBars“ an und führen dann eine Schleife durch, um die Positionen zu normalisieren: x als links plus normalisierter Bruchteil der Breite gerundet, y als zwei plus skaliert (max minus Kurs) über den Bereich gerundet, wobei der Index des Kurs-Arrays für den letzten Wert auf der linken Seite gespiegelt wird. Wir setzen die Linienfarbe auf Blau, konvertieren in ARGB und durchlaufen in einer Schleife über die Segmente, um mit LineAA Anti-Aliasing-Linien zu zeichnen, wobei die X-Positionen für die Ausrichtung von rechts nach links gespiegelt werden.
Zum Ausfüllen berechnen wir den minimalen und maximalen umgedrehten x-Wert, durchlaufen die Spalten von min bis max umgedreht (von rechts nach links), leiten den logischen x-Wert ab, suchen das Segment, das ihn enthält, indem wir die Positionen überprüfen, und überspringen es, falls keines vorhanden ist. Wir berechnen den Interpolationsfaktor t, interpolieren y und runden auf das TopY. Anschließend iterieren wir von topY bis bottomY, berechnen Fade-Faktor von unten, Alpha als 255 mal Fade mal „FogOpacity“, konvertieren Füllung zu ARGB mit diesem Alpha. Wenn „BlendFog“ wahr ist, wird das aktuelle Pixel mit „PixelGet“ ermittelt, mit „BlendPixels“ überblendet und gesetzt; andernfalls wird es direkt mit „PixelSet“ gesetzt. Für das Überblenden der Pixel verwenden wir eine nutzerdefinierte Hilfsfunktion, deren Codeschnipsel wie folgt aussieht.
//+------------------------------------------------------------------+ //| Alpha blending function for two ARGB colors | //+------------------------------------------------------------------+ uint BlendPixels(uint bg, uint fg) { uchar bgA = (uchar)((bg >> 24) & 0xFF); //--- Get bg alpha uchar bgR = (uchar)((bg >> 16) & 0xFF); //--- Get bg red uchar bgG = (uchar)((bg >> 8) & 0xFF); //--- Get bg green uchar bgB = (uchar)(bg & 0xFF); //--- Get bg blue uchar fgA = (uchar)((fg >> 24) & 0xFF); //--- Get fg alpha uchar fgR = (uchar)((fg >> 16) & 0xFF); //--- Get fg red uchar fgG = (uchar)((fg >> 8) & 0xFF); //--- Get fg green uchar fgB = (uchar)(fg & 0xFF); //--- Get fg blue if (fgA == 0) return bg; //--- Return bg if transparent if (fgA == 255) return fg; //--- Return fg if opaque double alphaFg = fgA / 255.0; //--- Compute fg alpha double alphaBg = 1.0 - alphaFg; //--- Compute bg alpha uchar outR = (uchar)(fgR * alphaFg + bgR * alphaBg); //--- Blend red uchar outG = (uchar)(fgG * alphaFg + bgG * alphaBg); //--- Blend green uchar outB = (uchar)(fgB * alphaFg + bgB * alphaBg); //--- Blend blue uchar outA = (uchar)(fgA + bgA * alphaBg); //--- Blend alpha return (((uint)outA) << 24) | (((uint)outR) << 16) | (((uint)outG) << 8) | ((uint)outB); //--- Return blended }
Für die Funktion verwenden wir einen ähnlichen Ansatz wie bei der ARGB-Funktion, indem wir bitweise Operationen verwenden. Wir haben der Klarheit halber Kommentare hinzugefügt. Wir stellen die Schriftart Arial auf zwölf ein, konvertieren die Textfarbe (schwarz in dunkel, grau in hell) in ARGB und zeichnen einen zentrierten Titel „Kurschart“ mit einem Symbol am oberen Rand. Wir formatieren die neuesten und ältesten Zeiten mit TimeToString unter Verwendung von Datum und Minuten, zeichnen linksbündig unten links und rechtsbündig unten rechts.
Wenn „resize_hovered“ oder „resizing“ wahr ist, erhalten wir das aktive Design von „resize_mode“ oder „hover_mode“ und beenden den Vorgang vorzeitig, wenn es keinen gibt. Wir setzen die Schriftart des Symbols auf Wingdings 3 bei fünfundzwanzig, bestimmen Code und Winkel auf der Grundlage des Designs (unten/rechts als '2'/'1' bei Null, Ecke '2' bei 450) und konvertieren das Symbol mit der Funktion CharToString. Was die Symbole angeht, können Sie die besten auswählen, die zu Ihrem Stil passen. Wir haben uns für diese entschieden, da MQL5 noch keine eingebauten Cursor-Änderungen hat, also mussten wir kreativ sein. Hier finden Sie eine Visualisierung der Schriftartsymbole, die Sie verwenden können.

Als Nächstes holen wir uns die Farbe des Symbols aus „GetIconColor“ mit dem Flag für die Größenänderung, konvertieren in ARGB, setzen Schriftart und Winkel mit FontSet und FontAngleSet, berechnen die Position basierend auf dem Design mit „MathMax“/“MathMin“ für die Begrenzung und schweben lokale Koordinaten oder feste Offsets, zeichnen mit TextOut links oben ausgerichtet, und setzen den Winkel auf Null. Schließlich aktualisieren wir die Canvas mit Update, um die Grafik anzuzeigen. Wenn wir die Funktion im Initialisierungs-Ereignishandler aufrufen, erhalten wir das folgende Ergebnis.

Nachdem das Grafik-Canvas gerendert wurde, müssen wir nun das andere Statistik-Panel auf der rechten Seite des Grafik-Canvas erstellen. In diesem Fall wollen wir etwas weiter gehen und zwei Farben für den Hintergrund mischen, wo sie sich treffen, indem wir lineare Interpolation verwenden, da dies eine einfache Sache ist, die wir haben wollen, und auch die Randfarben basierend auf den ausgewählten Hintergrundfarben verdunkeln, anstatt der statischen Randfarben, die wir bisher für die Kopfzeile und das Grafik-Canvas hatten. Um dies mit Leichtigkeit zu erreichen, benötigen wir einige Hilfsfunktionen.
//+------------------------------------------------------------------+ //| Linear interpolation between two colors | //+------------------------------------------------------------------+ color InterpolateColor(color start, color end, double factor) { uchar r1 = (uchar)((start >> 16) & 0xFF); //--- Get start red uchar g1 = (uchar)((start >> 8) & 0xFF); //--- Get start green uchar b1 = (uchar)(start & 0xFF); //--- Get start blue uchar r2 = (uchar)((end >> 16) & 0xFF); //--- Get end red uchar g2 = (uchar)((end >> 8) & 0xFF); //--- Get end green uchar b2 = (uchar)(end & 0xFF); //--- Get end blue uchar r = (uchar)(r1 + factor * (r2 - r1)); //--- Interpolate red uchar g = (uchar)(g1 + factor * (g2 - g1)); //--- Interpolate green uchar b = (uchar)(b1 + factor * (b2 - b1)); //--- Interpolate blue return (r << 16) | (g << 8) | b; //--- Return color } //+------------------------------------------------------------------+ //| Darken a color by a factor (0.0 to 1.0) | //+------------------------------------------------------------------+ color DarkenColor(color colorValue, double factor) { int blue = int((colorValue & 0xFF) * factor); //--- Darken blue int green = int(((colorValue >> 8) & 0xFF) * factor); //--- Darken green int red = int(((colorValue >> 16) & 0xFF) * factor); //--- Darken red return (color)(blue | (green << 8) | (red << 16)); //--- Return darkened }
Zunächst implementieren wir die Funktion „InterpolateColor“, die eine lineare Überblendung zwischen zwei Farben auf der Grundlage eines Faktors von null bis eins ermöglicht und die interpolierte Farbe für Effekte wie Farbverläufe zurückgibt. Er benötigt die Farbparameter start und end sowie einen Doppelfaktor. Wir extrahieren die Rot-, Grün- und Blau-Komponenten aus dem Anfang, indem wir die Bits um sechzehn/acht/null nach rechts verschieben und mit 0xFF nach uchar maskieren, ebenso für das Ende, genau wie bei den anderen farbbasierten Funktionen. Wir interpolieren jeden Kanal als einen uchar von Startwert plus Faktor mal die Differenz, dann kombinieren wir mit rot verschoben links sechzehn, grün acht und blau Null mit Bit-Verschiebungen und OR.
Als Nächstes erstellen wir die Funktion „DarkenColor“, um die Helligkeit einer Farbe um einen Faktor von null bis eins zu verringern, wobei der Wert eins die Farbe unverändert lässt und die niedrigeren Werte sie abdunkeln und die angepasste Farbe zurückgeben. Die Funktion nimmt den Farbwert „colorValue“ und einen Faktor vom Typ „double“ entgegen, dunkelt Blau um den Faktor des Blauanteils der Maske 0xFF ab, Grün um den Faktor der um acht verschobenen Maske und Rot um den Faktor der um sechzehn verschobenen Maske. Als Ergebnis wird die Kombination aus Blau ODER Grün (um acht verschoben) ODER Rot (um sechzehn verschoben) zurückgegeben, die in den Typ „color“ umgewandelt wird. Wir können diese Funktionen nun bei der Erstellung des Statistikpanels verwenden.
//+------------------------------------------------------------------+ //| Update the stats on the second Canvas | //+------------------------------------------------------------------+ void UpdateStatsOnCanvas() { canvasStats.Erase(0); //--- Clear canvas int statsWidth = currentWidth / 2; //--- Compute width if (UseBackground && ArraySize(bg_pixels_stats) == statsWidth * currentHeight) //--- Check background { for (int y = 0; y < currentHeight; y++) //--- Loop rows { for (int x = 0; x < statsWidth; x++) //--- Loop columns { canvasStats.PixelSet(x, y, bg_pixels_stats[y * statsWidth + x]); //--- Set pixel } } } if (StatsBackgroundMode != NoColor) //--- Check mode { for (int y = 0; y < currentHeight; y++) //--- Loop rows { double factor = (double)y / (currentHeight - 1); //--- Compute factor color currentColor = (StatsBackgroundMode == SingleColor) ? GetTopColor() : InterpolateColor(GetTopColor(), GetBottomColor(), factor); //--- Get color uchar alpha = (uchar)(255 * BackgroundOpacity); //--- Compute alpha uint argbFill = ColorToARGB(currentColor, alpha); //--- Convert fill for (int x = 0; x < statsWidth; x++) //--- Loop columns { uint currentPixel = canvasStats.PixelGet(x, y); //--- Get pixel uint blendedPixel = BlendPixels(currentPixel, argbFill); //--- Blend canvasStats.PixelSet(x, y, blendedPixel); //--- Set blended } } } if (StatsBackgroundMode != NoColor) //--- Check mode for borders { double reduction = BorderOpacityPercentReduction / 100.0; //--- Compute reduction double opacity = MathMax(0.0, MathMin(1.0, BackgroundOpacity * (1.0 - reduction))); //--- Compute opacity uchar alpha = (uchar)(255 * opacity); //--- Set alpha double darkenReduction = BorderDarkenPercent / 100.0; //--- Compute darken double darkenFactor = MathMax(0.0, MathMin(1.0, 1.0 - darkenReduction)); //--- Set factor for (int y = 0; y < currentHeight; y++) //--- Loop vertical { double factor = (StatsBackgroundMode == SingleColor) ? 0.0 : (double)y / (currentHeight - 1); //--- Get factor color baseColor = (StatsBackgroundMode == SingleColor) ? GetTopColor() : InterpolateColor(GetTopColor(), GetBottomColor(), factor); //--- Get base color darkColor = DarkenColor(baseColor, darkenFactor); //--- Darken color uint argb = ColorToARGB(darkColor, alpha); //--- Convert to ARGB canvasStats.PixelSet(0, y, argb); //--- Set left outer canvasStats.PixelSet(1, y, argb); //--- Set left inner canvasStats.PixelSet(statsWidth - 1, y, argb); //--- Set right outer canvasStats.PixelSet(statsWidth - 2, y, argb); //--- Set right inner } double factorTop = 0.0; //--- Set top factor color baseTop = GetTopColor(); //--- Get top base color darkTop = DarkenColor(baseTop, darkenFactor); //--- Darken top uint argbTop = ColorToARGB(darkTop, alpha); //--- Convert top for (int x = 0; x < statsWidth; x++) //--- Loop top { canvasStats.PixelSet(x, 0, argbTop); //--- Set top outer canvasStats.PixelSet(x, 1, argbTop); //--- Set top inner } double factorBot = (StatsBackgroundMode == SingleColor) ? 0.0 : 1.0; //--- Set bottom factor color baseBot = (StatsBackgroundMode == SingleColor) ? GetTopColor() : GetBottomColor(); //--- Get bottom base color darkBot = DarkenColor(baseBot, darkenFactor); //--- Darken bottom uint argbBot = ColorToARGB(darkBot, alpha); //--- Convert bottom for (int x = 0; x < statsWidth; x++) //--- Loop bottom { canvasStats.PixelSet(x, currentHeight - 1, argbBot); //--- Set bottom outer canvasStats.PixelSet(x, currentHeight - 2, argbBot); //--- Set bottom inner } } else //--- Handle no color { uint argbBorder = ColorToARGB(GetBorderColor(), 255); //--- Convert border canvasStats.Line(0, 0, statsWidth - 1, 0, argbBorder); //--- Draw top outer canvasStats.Line(statsWidth - 1, 0, statsWidth - 1, currentHeight - 1, argbBorder); //--- Draw right outer canvasStats.Line(statsWidth - 1, currentHeight - 1, 0, currentHeight - 1, argbBorder); //--- Draw bottom outer canvasStats.Line(0, currentHeight - 1, 0, 0, argbBorder); //--- Draw left outer canvasStats.Line(1, 1, statsWidth - 2, 1, argbBorder); //--- Draw top inner canvasStats.Line(statsWidth - 2, 1, statsWidth - 2, currentHeight - 2, argbBorder); //--- Draw right inner canvasStats.Line(statsWidth - 2, currentHeight - 2, 1, currentHeight - 2, argbBorder); //--- Draw bottom inner canvasStats.Line(1, currentHeight - 2, 1, 1, argbBorder); //--- Draw left inner } color labelColor = GetStatsLabelColor(); //--- Get label color color valueColor = GetStatsValueColor(); //--- Get value color color headerColor = GetStatsHeaderColor(); //--- Get header color int yPos = 20; //--- Set initial y canvasStats.FontSet("Arial Bold", StatsHeaderFontSize); //--- Set header font uint argbHeader = ColorToARGB(headerColor, 255); //--- Convert header canvasStats.TextOut(statsWidth / 2, yPos, "Account Stats", argbHeader, TA_CENTER); //--- Draw account header yPos += 30; //--- Increment y canvasStats.FontSet("Arial Bold", StatsFontSize); //--- Set font uint argbLabel = ColorToARGB(labelColor, 255); //--- Convert label uint argbValue = ColorToARGB(valueColor, 255); //--- Convert value canvasStats.TextOut(10, yPos, "Name:", argbLabel, TA_LEFT); //--- Draw name label canvasStats.TextOut(statsWidth - 10, yPos, AccountInfoString(ACCOUNT_NAME), argbValue, TA_RIGHT); //--- Draw name value yPos += 20; //--- Increment y canvasStats.TextOut(10, yPos, "Balance:", argbLabel, TA_LEFT); //--- Draw balance label canvasStats.TextOut(statsWidth - 10, yPos, DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2), argbValue, TA_RIGHT); //--- Draw balance value yPos += 20; //--- Increment y canvasStats.TextOut(10, yPos, "Equity:", argbLabel, TA_LEFT); //--- Draw equity label canvasStats.TextOut(statsWidth - 10, yPos, DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2), argbValue, TA_RIGHT); //--- Draw equity value yPos += 30; //--- Increment y canvasStats.FontSet("Arial Bold", StatsHeaderFontSize); //--- Set header font canvasStats.TextOut(statsWidth / 2, yPos, "Current Bar Stats", argbHeader, TA_CENTER); //--- Draw bar header yPos += 30; //--- Increment y canvasStats.FontSet("Arial Bold", StatsFontSize); //--- Set font double barOpen = iOpen(_Symbol, _Period, 0); //--- Get open double barHigh = iHigh(_Symbol, _Period, 0); //--- Get high double barLow = iLow(_Symbol, _Period, 0); //--- Get low double barClose = iClose(_Symbol, _Period, 0); //--- Get close canvasStats.TextOut(10, yPos, "Open:", argbLabel, TA_LEFT); //--- Draw open label canvasStats.TextOut(statsWidth - 10, yPos, DoubleToString(barOpen, _Digits), argbValue, TA_RIGHT); //--- Draw open value yPos += 20; //--- Increment y canvasStats.TextOut(10, yPos, "High:", argbLabel, TA_LEFT); //--- Draw high label canvasStats.TextOut(statsWidth - 10, yPos, DoubleToString(barHigh, _Digits), argbValue, TA_RIGHT); //--- Draw high value yPos += 20; //--- Increment y canvasStats.TextOut(10, yPos, "Low:", argbLabel, TA_LEFT); //--- Draw low label canvasStats.TextOut(statsWidth - 10, yPos, DoubleToString(barLow, _Digits), argbValue, TA_RIGHT); //--- Draw low value yPos += 20; //--- Increment y canvasStats.TextOut(10, yPos, "Close:", argbLabel, TA_LEFT); //--- Draw close label canvasStats.TextOut(statsWidth - 10, yPos, DoubleToString(barClose, _Digits), argbValue, TA_RIGHT); //--- Draw close value canvasStats.Update(); //--- Update canvas }
Hier implementieren wir die Funktion „UpdateStatsOnCanvas“, um das Statistik-Panel auf dem „canvasStats“-Objekt darzustellen, das Konto- und aktuelle Kennzahlen der Bars mit dem Design der Hintergründe, Füllungen, Rahmen und Text anzeigt. Wir löschen die Canvas, indem wir Erase auf Null setzen. Wenn „UseBackground“ wahr ist und „bg_pixels_stats“ mit den Dimensionen übereinstimmt (halbe Grafikbreite mal Höhe), wird in einer Schleife über Zeilen und Spalten durchlaufen, um jedes Pixel aus dem skalierten Hintergrund-Array mit der Methode PixelSet zu setzen.
Wenn „StatsBackgroundMode“ nicht „NoColor“ ist, berechnen wir in einer Schleife über die Höhe einen vertikalen Faktor, bestimmen die Zeilenfarbe als „GetTopColor“, wenn es sich um einen Einzeldesign handelt, oder interpoliert zwischen oben und unten mit „InterpolateColor“, wenn es sich um einen Farbverlauf handelt, berechnen Alpha aus „BackgroundOpacity“ mal 255, konvertieren in ARGB. Für jedes x in der Zeile lesen wir das aktuelle Pixel mit PixelGet aus, mischen es mit der Füllung mit „BlendPixels“ und setzen das gemischte Pixel.
Für Ränder in Füllmodi berechnen wir die reduzierte Deckkraft aus „BorderOpacityPercentReduction“ geteilt durch 100,0, geklemmt auf 0,0 bis 1,0 mal „BackgroundOpacity“, Alpha als 255 mal das und den Verdunkelungsfaktor als 1,0 minus „BorderDarkenPercent“ über 100,0, geklemmt. Wir iterieren in einer Schleife über y, um den Zeilenfaktor zu erhalten (0,0, wenn einzeln, sonst normalisiert), Grundfarbe als obere oder interpolierte Farbe, Abdunkeln mit „DarkenColor“, Konvertierung in ARGB mit Alpha, Setzen der linken äußeren/inneren und rechten äußeren/inneren Pixel. Für die oberste Zeile verwenden wir den Faktor 0,0, die Grundfarbe oben, Verdunkeln, ARGB, iterieren über x, um oben außen/innen einzustellen. Für unten, Faktor 1,0 oder 0,0, wenn einfach, Basis unten oder oben, abdunkeln, ARGB, unten außen/innen einstellen. Wenn keine Füllung, konvertieren wir den Rahmen von „GetBorderColor“ in ARGB bei 255, zeichnen äußere und innere horizontale/vertikale Linien mit „Line“ für oben/rechts/unten/links.
Wir rufen die Farben des Designs für Beschriftungen, Werte und Überschriften mithilfe von Gettern ab. Anfangswert y auf 20 setzen, Schriftart Arial Bold bei „StatsHeaderFontSize“, Kopfzeile in ARGB konvertieren, Kontostatistiken mit TextOut zentriert zeichnen, y um 30 erhöhen.
Schriftart auf Arial Bold bei „StatsFontSize“ setzen, Label und Wert in ARGB konvertieren. Den Namen zeichnen wir linksbündig: bei x zehn, rechtsbündig Kontoname aus AccountInfoString mit ACCOUNT_NAME bei Breite minus zehn, Inkrement y zwanzig. Ähnlich für den Kontostand mit AccountInfoDouble „ACCOUNT_BALANCE“ auf zwei Dezimalstellen und Equity mit ACCOUNT_EQUITY. Inkrementieren von y um 30, Schriftart der Kopfzeile festlegen, die Statistik der aktuellen Bar zentriert zeichnen, y um 30 erhöhen. Schriftart zurücksetzen, aktuellen Bar Open/High/Low/Close mit iOpen/iHigh/iLow/iClose für Symbol/Periode/Bar Zero abrufen. Open: Beschriftung und Wert in _Digits Dezimalen rechtsbündig zeichnen, y zwanzig inkrementieren; für High:, Low:, Close: wiederholen. Schließlich wird die Canvas mit Update, aktualisiert um die Statistiken anzuzeigen. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

Aus dem Bild können wir ersehen, dass wir das Statistikpanel mit der Farbinterpolation erstellt haben. Wenn Sie auf die Interpolation verzichten wollen, können Sie einen harten Übergang ohne Interpolation wie folgt durchführen: Lassen Sie die Mathematik einfach weg.
//+------------------------------------------------------------------+ //| Color selection WITHOUT interpolation (hard switch only) | //+------------------------------------------------------------------+ color InterpolateColor(color start, color end, double factor) { // Clamp factor just to be safe if(factor <= 0.0) return start; if(factor >= 1.0) return end; // HARD boundary — no mixing at all return (factor < 0.5 ? start : end); }
Wenn Sie diesen Ansatz wählen, erhalten Sie das folgende Ergebnis.

Auf dem Bild ist zu erkennen, dass sich die Grenzen nicht linear vermischen. Es liegt also wieder an Ihnen, den Ansatz zu wählen, der zu Ihrem Stil passt. Damit ist das Dashboard-Rendering für das dunkle Design, den wir als Standard ausgewählt haben, abgeschlossen. Um die Interaktion mit dem Chart zu ermöglichen, müssen wir die Mausbewegungen während der Initialisierung einschalten. Jetzt sieht der Initialisierungs-Event-Handler am Ende wie folgt aus.
//+------------------------------------------------------------------+ //| Initialize expert | //+------------------------------------------------------------------+ int OnInit() //--- Existing init logic DrawHeaderOnCanvas(); //--- Draw header UpdateGraphOnCanvas(); //--- Update graph if (EnableStatsPanel) UpdateStatsOnCanvas(); //--- Update stats ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); //--- Enable mouse events ChartRedraw(); //--- Redraw chart return(INIT_SUCCEEDED); //--- Return success }
Wir verwenden einfach die Funktion ChartSetInteger, um die Erkennung der Mausbewegung auf true zu setzen. Damit können wir einige Hilfsfunktionen erstellen, um die Mauszustände zu verfolgen und Änderungen vorzunehmen, wenn wir mit unseren Dashboard-Objekten interagieren.
//+------------------------------------------------------------------+ //| Check if mouse is over header (excluding buttons) | //+------------------------------------------------------------------+ bool IsMouseOverHeader(int mouse_x, int mouse_y) { int header_x = currentCanvasX; //--- Get header x int header_y = currentCanvasY; //--- Get header y int header_w = currentWidth + (EnableStatsPanel && !panels_minimized ? PanelGap + currentWidth / 2 : 0); //--- Compute width int header_h = header_height; //--- Get height if (mouse_x < header_x || mouse_x > header_x + header_w || mouse_y < header_y || mouse_y > header_y + header_h) return false; //--- Check outside int theme_left = header_x + header_w + theme_x_offset - button_size / 2; //--- Compute theme left int theme_right = theme_left + button_size; //--- Compute theme right int theme_top = header_y; //--- Set theme top int theme_bottom = theme_top + header_h; //--- Compute theme bottom if (mouse_x >= theme_left && mouse_x <= theme_right && mouse_y >= theme_top && mouse_y <= theme_bottom) return false; //--- Check in theme int min_left = header_x + header_w + minimize_x_offset - button_size / 2; //--- Compute minimize left int min_right = min_left + button_size; //--- Compute minimize right int min_top = header_y; //--- Set minimize top int min_bottom = min_top + header_h; //--- Compute minimize bottom if (mouse_x >= min_left && mouse_x <= min_right && mouse_y >= min_top && mouse_y <= min_bottom) return false; //--- Check in minimize int close_left = header_x + header_w + close_x_offset - button_size / 2; //--- Compute close left int close_right = close_left + button_size; //--- Compute close right int close_top = header_y; //--- Set close top int close_bottom = close_top + header_h; //--- Compute close bottom if (mouse_x >= close_left && mouse_x <= close_right && mouse_y >= close_top && mouse_y <= close_bottom) return false; //--- Check in close return true; //--- Return in header } //+------------------------------------------------------------------+ //| Check if mouse over theme button | //+------------------------------------------------------------------+ bool IsMouseOverTheme(int mouse_x, int mouse_y) { int header_w = currentWidth + (EnableStatsPanel && !panels_minimized ? PanelGap + currentWidth / 2 : 0); //--- Compute width int theme_left = currentCanvasX + header_w + theme_x_offset - button_size / 2; //--- Compute left int theme_right = theme_left + button_size; //--- Compute right int theme_top = currentCanvasY; //--- Set top int theme_bottom = theme_top + header_height; //--- Compute bottom return (mouse_x >= theme_left && mouse_x <= theme_right && mouse_y >= theme_top && mouse_y <= theme_bottom); //--- Check in theme } //+------------------------------------------------------------------+ //| Check if mouse over minimize button | //+------------------------------------------------------------------+ bool IsMouseOverMinimize(int mouse_x, int mouse_y) { int header_w = currentWidth + (EnableStatsPanel && !panels_minimized ? PanelGap + currentWidth / 2 : 0); //--- Compute width int min_left = currentCanvasX + header_w + minimize_x_offset - button_size / 2; //--- Compute left int min_right = min_left + button_size; //--- Compute right int min_top = currentCanvasY; //--- Set top int min_bottom = min_top + header_height; //--- Compute bottom return (mouse_x >= min_left && mouse_x <= min_right && mouse_y >= min_top && mouse_y <= min_bottom); //--- Check in minimize } //+------------------------------------------------------------------+ //| Check if mouse over close button | //+------------------------------------------------------------------+ bool IsMouseOverClose(int mouse_x, int mouse_y) { int header_w = currentWidth + (EnableStatsPanel && !panels_minimized ? PanelGap + currentWidth / 2 : 0); //--- Compute width int close_left = currentCanvasX + header_w + close_x_offset - button_size / 2; //--- Compute left int close_right = close_left + button_size; //--- Compute right int close_top = currentCanvasY; //--- Set top int close_bottom = close_top + header_height; //--- Compute bottom return (mouse_x >= close_left && mouse_x <= close_right && mouse_y >= close_top && mouse_y <= close_bottom); //--- Check in close } //+------------------------------------------------------------------+ //| Check if mouse over resize borders | //+------------------------------------------------------------------+ bool IsMouseOverResize(int mx, int my, ENUM_RESIZE_MODE &rmode) { if (panels_minimized) return false; //--- Check if minimized int graph_x = currentCanvasX; //--- Get graph x int graph_y = currentCanvasY + header_height + gap_y; //--- Get graph y int graph_right = graph_x + currentWidth; //--- Compute right int graph_bottom = graph_y + currentHeight; //--- Compute bottom bool over_right = (mx >= graph_right - resize_thickness && mx <= graph_right + resize_thickness) && (my >= graph_y && my <= graph_bottom); //--- Check right bool over_bottom = (my >= graph_bottom - resize_thickness && my <= graph_bottom + resize_thickness) && (mx >= graph_x && mx <= graph_right); //--- Check bottom if (over_bottom && over_right) //--- Check corner { rmode = BOTTOM_RIGHT; //--- Set bottom-right return true; //--- Return true } else if (over_bottom) //--- Check bottom only { rmode = BOTTOM; //--- Set bottom return true; //--- Return true } else if (over_right) //--- Check right only { rmode = RIGHT; //--- Set right return true; //--- Return true } return false; //--- Return false } //+------------------------------------------------------------------+ //| Toggle theme | //+------------------------------------------------------------------+ void ToggleTheme() { is_dark_theme = !is_dark_theme; //--- Switch theme Print("Switched to ", (is_dark_theme ? "Dark" : "Light"), " theme"); //--- Print switch DrawHeaderOnCanvas(); //--- Redraw header UpdateGraphOnCanvas(); //--- Update graph if (EnableStatsPanel) UpdateStatsOnCanvas(); //--- Update stats ChartRedraw(); //--- Redraw chart } //+------------------------------------------------------------------+ //| Toggle minimize state | //+------------------------------------------------------------------+ void ToggleMinimize() { panels_minimized = !panels_minimized; //--- Toggle minimized if (panels_minimized) //--- Handle minimize { canvasGraph.Destroy(); //--- Destroy graph graphCreated = false; //--- Reset graph flag if (EnableStatsPanel) //--- Check stats { canvasStats.Destroy(); //--- Destroy stats statsCreated = false; //--- Reset stats flag } } else //--- Handle maximize { 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"); //--- Print error } graphCreated = true; //--- Set graph flag UpdateGraphOnCanvas(); //--- Update graph if (EnableStatsPanel) //--- Check stats { 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"); //--- Print error } statsCreated = true; //--- Set stats flag UpdateStatsOnCanvas(); //--- Update stats } } int new_header_width = currentWidth + (EnableStatsPanel && !panels_minimized ? PanelGap + currentWidth / 2 : 0); //--- Compute new width canvasHeader.Resize(new_header_width, header_height); //--- Resize header ObjectSetInteger(0, canvasHeaderName, OBJPROP_XSIZE, new_header_width); //--- Update header width ObjectSetInteger(0, canvasHeaderName, OBJPROP_YSIZE, header_height); //--- Update header height DrawHeaderOnCanvas(); //--- Redraw header canvasHeader.Update(); //--- Update header ChartRedraw(); //--- Redraw chart } //+------------------------------------------------------------------+ //| Close the dashboard | //+------------------------------------------------------------------+ void CloseDashboard() { canvasHeader.Destroy(); //--- Destroy header canvasGraph.Destroy(); //--- Destroy graph if (EnableStatsPanel) canvasStats.Destroy(); //--- Destroy stats ChartRedraw(); //--- Redraw chart }
Hier implementieren wir mehrere Mouseover-Erkennungsfunktionen, um Mauspositionen über bestimmten Bereichen wie der Kopfzeile (mit Ausnahme der Schaltflächen) und einzelnen Schaltflächen für das Design, das Minimieren und das Schließen zu erkennen, wobei die aktuellen Positionen und Größen verwendet werden, um Boolesche Werte für Statusaktualisierungen zurückzugeben. Wir fügen auch Überprüfungen für die Größenanpassung der Ränder des Grafikpanels hinzu, indem wir das Design (unten, rechts oder in der Ecke) anhand der Dicke und des Verweises auf aktualisierte Mouseover- oder Größenanpassungsaufzählungen bestimmen.
Außerdem erstellen wir Umschaltfunktionen für den Umschalten zwischen hellem und dunklem Design, indem wir das Flag invertieren, das neue Design ausdrucken und alle Canvas neu zeichnen, und für die Minimierung, indem wir den Status umschalten, Diagramm- und Statistik-Leinwände je nach Bedarf zerstören/neu erstellen, die Größe der Kopfzeile ändern, sie neu zeichnen und aktualisieren. Schließlich definieren wir eine Schließfunktion, um alle Canvas zu zerstören und das Chart neu zu zeichnen. Wir definieren die Funktion „IsMouseOverHeader“, um zu prüfen, ob sich die Maus über die Kopfzeile befindet, ohne die Schaltflächen zu überlappen, und geben einen booleschen Wert zurück. Die Position der Kopfzeile wird aus „currentCanvasX“ und „currentCanvasY“ ermittelt, die Breite einschließlich optionaler Statistiken und Abstand, falls nicht minimiert, die Höhe aus „header_height“, und es wird „false“ zurückgegeben, falls sie außerhalb der Grenzen liegt.
Dann berechnen wir die Schaltflächenbereiche für das Design, „Minimize“ und „Close“ mithilfe von Offsets und „button_size“ und geben andernfalls true zurück, wenn sich der Mauszeiger über der Kopfzeile befindet. Die nächste Funktion müssen wir nicht im Detail erläutern, da wir einen ähnlichen Ansatz bereits in den vorherigen Artikeln dieser Serie verwendet haben. Wir haben Kommentare zur Verdeutlichung hinzugefügt. Als Nächstes werden wir diese Funktionen in der Ereignisbehandlung des Charts wie folgt verwenden.
//+------------------------------------------------------------------+ //| Handle chart event | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if (id == CHARTEVENT_CHART_CHANGE) //--- Check change event { DrawHeaderOnCanvas(); //--- Redraw header UpdateGraphOnCanvas(); //--- Update graph if (EnableStatsPanel) UpdateStatsOnCanvas(); //--- Update stats ChartRedraw(); //--- Redraw chart } else if (id == CHARTEVENT_MOUSE_MOVE) //--- Handle 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 previous header bool prev_min_hovered = minimize_hovered; //--- Store previous minimize bool prev_close_hovered = close_hovered; //--- Store previous close bool prev_theme_hovered = theme_hovered; //--- Store previous theme bool prev_resize_hovered = resize_hovered; //--- Store previous resize 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 change if (hover_changed) //--- If changed { 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); //--- Set header tooltip string resize_tooltip = ""; //--- Initialize resize tooltip if (resize_hovered || resizing) //--- Check resize { ENUM_RESIZE_MODE active_mode = resizing ? resize_mode : hover_mode; //--- Get mode switch (active_mode) //--- Switch mode { case BOTTOM: resize_tooltip = "Resize Bottom"; break; //--- Set bottom case RIGHT: resize_tooltip = "Resize Right"; break; //--- Set right case BOTTOM_RIGHT: resize_tooltip = "Resize Bottom-Right"; break; //--- Set corner default: break; } } ObjectSetString(0, canvasGraphName, OBJPROP_TOOLTIP, resize_tooltip); //--- Set graph tooltip if (mouse_state == 1 && prev_mouse_state == 0) //--- Check mouse down { if (header_hovered) //--- Check header { panel_dragging = true; //--- Start drag 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(); //--- Show drag color ChartRedraw(); //--- Redraw chart } else if (theme_hovered) //--- Check theme { ToggleTheme(); //--- Toggle theme } else if (minimize_hovered) //--- Check minimize { ToggleMinimize(); //--- Toggle minimize } else if (close_hovered) //--- Check close { CloseDashboard(); //--- Close dashboard } else //--- Handle resize { ENUM_RESIZE_MODE temp_mode = NONE; //--- Initialize temp if (!panel_dragging && !resizing && IsMouseOverResize(mouse_x, mouse_y, temp_mode)) //--- Check resize { resizing = true; //--- Start 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(); //--- Show icon ChartRedraw(); //--- Redraw chart } } } else if (panel_dragging && mouse_state == 1) //--- Handle dragging { int dx = mouse_x - panel_drag_x; //--- Compute dx int dy = mouse_y - panel_drag_y; //--- Compute dy 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); //--- Compute full height new_x = MathMax(0, MathMin(chart_w - full_w, new_x)); //--- Clamp x new_y = MathMax(0, MathMin(chart_h - full_h, new_y)); //--- Clamp y currentCanvasX = new_x; //--- Update x currentCanvasY = new_y; //--- Update y ObjectSetInteger(0, canvasHeaderName, OBJPROP_XDISTANCE, new_x); //--- Update header x ObjectSetInteger(0, canvasHeaderName, OBJPROP_YDISTANCE, new_y); //--- Update header y if (!panels_minimized) //--- Check if shown { ObjectSetInteger(0, canvasGraphName, OBJPROP_XDISTANCE, new_x); //--- Update graph x ObjectSetInteger(0, canvasGraphName, OBJPROP_YDISTANCE, new_y + header_height + gap_y); //--- Update graph y if (EnableStatsPanel) //--- Check stats { int statsX = new_x + currentWidth + PanelGap; //--- Compute stats x ObjectSetInteger(0, canvasStatsName, OBJPROP_XDISTANCE, statsX); //--- Update stats x ObjectSetInteger(0, canvasStatsName, OBJPROP_YDISTANCE, new_y + header_height + gap_y); //--- Update stats y } } ChartRedraw(); //--- Redraw chart } else if (resizing && mouse_state == 1) //--- Handle resizing { int dx = mouse_x - resize_start_x; //--- Compute dx int dy = mouse_y - resize_start_y; //--- Compute dy int new_width = currentWidth; //--- Initialize new width int new_height = currentHeight; //--- Initialize new height if (resize_mode == RIGHT || resize_mode == BOTTOM_RIGHT) //--- Check right { new_width = MathMax(min_width, start_width + dx); //--- Update width } if (resize_mode == BOTTOM || resize_mode == BOTTOM_RIGHT) //--- Check bottom { new_height = MathMax(min_height, start_height + dy); //--- Update height } if (new_width != currentWidth || new_height != currentHeight) //--- Check 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); //--- Clamp height if (EnableStatsPanel) //--- Check stats { double max_w_d = (avail_w - PanelGap) / 1.5; //--- Compute max width int max_w = (int)MathFloor(max_w_d); //--- Floor max new_width = MathMin(new_width, max_w); //--- Clamp width } else //--- 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 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 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); //--- Update graph width ObjectSetInteger(0, canvasGraphName, OBJPROP_YSIZE, currentHeight); //--- Update graph height 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); //--- Update stats width ObjectSetInteger(0, canvasStatsName, OBJPROP_YSIZE, currentHeight); //--- Update stats height int stats_x = currentCanvasX + currentWidth + PanelGap; //--- Compute stats x ObjectSetInteger(0, canvasStatsName, OBJPROP_XDISTANCE, stats_x); //--- Update stats x } canvasHeader.Resize(currentWidth + (EnableStatsPanel ? PanelGap + currentWidth / 2 : 0), header_height); //--- Resize header ObjectSetInteger(0, canvasHeaderName, OBJPROP_XSIZE, currentWidth + (EnableStatsPanel ? PanelGap + currentWidth / 2 : 0)); //--- Update header width ObjectSetInteger(0, canvasHeaderName, OBJPROP_YSIZE, header_height); //--- Update header height DrawHeaderOnCanvas(); //--- Redraw header UpdateGraphOnCanvas(); //--- Update graph if (EnableStatsPanel) UpdateStatsOnCanvas(); //--- Update stats ChartRedraw(); //--- Redraw chart } } else if (mouse_state == 0 && prev_mouse_state == 1) //--- Check mouse up { if (panel_dragging) //--- Check dragging { panel_dragging = false; //--- Stop drag ChartSetInteger(0, CHART_MOUSE_SCROLL, true); //--- Enable scroll DrawHeaderOnCanvas(); //--- Reset color ChartRedraw(); //--- Redraw chart } if (resizing) //--- Check resizing { resizing = false; //--- Stop resize ChartSetInteger(0, CHART_MOUSE_SCROLL, true); //--- Enable scroll UpdateGraphOnCanvas(); //--- Remove icon 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 state } }
Wenn die id CHARTEVENT_CHART_CHANGE ist, rufen wir im OnChartEvent-Ereignishandler „DrawHeaderOnCanvas“ auf, um die Kopfzeile neu zu zeichnen, „UpdateGraphOnCanvas“ für die Grafik, „UpdateStatsOnCanvas“, wenn „EnableStatsPanel“ aktiviert ist und ChartRedraw, um die Anzeige zu aktualisieren. Für CHARTEVENT_MOUSE_MOVE wird lparam auf Maus x, dparam auf y und sparam auf state als Ganzzahlen gesetzt. Wir speichern frühere Mouseover-Zustände in lokalen Werten und aktualisieren dann „header_hovered“ mit „IsMouseOverHeader“, „theme_hovered“ mit „IsMouseOverTheme“, „minimize_hovered“ mit „IsMouseOverMinimize“, „close_hovered“ mit „IsMouseOverClose“, und „resize_hovered“ mit „IsMouseOverResize“, wobei ein Verweis auf „hover_mode“ übergeben wird. Wenn „resize“ im Mouseover-Zustand ist oder „resizing“ auf „true“ gesetzt ist, werden „hover_mouse_local_x“ und „hover_mouse_local_y“ relativ zur Position im Diagramm festgelegt. Wir prüfen, ob sich der Mouseover-Status geändert hat, indem wir den vorherigen mit dem aktuellen vergleichen, und wenn ja, zeichnen wir die Kopfzeile und das Diagramm neu, dann zeichnen wir neu. Andernfalls, wenn der Größenänderungsstatus und die Mausposition von „last_mouse_x“/“last_mouse_y“ abweichen, wird das Diagramm aktualisiert und neu gezeichnet.
Wir initialisieren eine Kopfzeilen-Tooltip-Zeichenfolge, die bei „theme_hovered“ auf „Toggle Theme (Dark/Light)“, bei „minimize_hovered“ auf „maximierter/minimierter Tooltip-Text abhängig vom Zustand von „panels_minimized“, bei „close_hovered“ auf „close Dashboard“ und bei „canvasHeaderName“ mit ObjectSetString auf OBJPROP_TOOLTIP angewendet wird. In ähnlicher Weise bestimmen Sie für den Tooltip zur Größenänderung das aktive Design aus „resize_mode“ oder „hover_mode“, setzen die Zeichenfolge auf der Grundlage von „bottom/right/bottom-right“ und wenden sie auf „canvasGraphName“ an.
Wenn der Mausstatus eins und „prev_mouse_state“ null ist, wird nach unten gedrückt: wenn „header_hovered“, setze „panel_dragging“ true, speichere Drag/Start-Koordinaten, deaktiviere Chart-Maus-Scroll mit ChartSetInteger „CHART_MOUSE_SCROLL“ false, zeichne Header neu, zeichne Chart neu; sonst wenn „theme_hovered“, rufe „ToggleTheme“; wenn „minimize_hovered“wenn „minimize_hovered“, Aufruf von „ToggleMinimize“; wenn „close_hovered“, Aufruf von „CloseDashboard“; wenn nicht ziehen/verkleinern und „IsMouseOverResize“ true in das temporäre Design, setzen von „resize“ true, „resize_mode“ auf temp, Startkoordinaten/Dimms speichern, Scrollen deaktivieren, Grafik aktualisieren, neu zeichnen.
Wenn „panel_dragging“ auf „true“ gesetzt ist und der Status „held“ lautet, berechnen wir die Deltas, neue x/y vom Start plus Deltas, ermitteln Grafikbreite/-höhe mit ChartGetInteger „CHART_WIDTH_IN_PIXELS“/CHART_HEIGHT_IN_PIXELS, volle Panelbreite/-höhe einschließlich optionaler Statistiken/Abstand/Kopfzeile, wenn nicht minimiert, klemme neue x/y auf Null bis zum Chart minus volle Dims mit „MathMax“/“MathMin“, Aktualisierung von „currentCanvasX“/“currentCanvasY“, Festlegen der x/y-Abstände des Objekts für die Kopfzeile und, falls nicht minimiert, für das Chart und die optionalen Statistiken (Berechnung von stats x), dann erneutes Zeichnen.
Wenn „resizing“ true und state one ist, werden die Deltas berechnet, die neue Breite/Höhe wird mit der aktuellen initialisiert, dx wird addiert, wenn der rechte oder Eckmodus auf „min_width“ geklemmt ist, dy für unten oder Ecke auf „min_height“. Falls geändert, werden die Chartabmessungen, die verfügbare Breite/Höhe von der aktuellen Position aus ermittelt, die neue Höhe wird auf die verfügbare Höhe geklemmt, die Breite wird auf den Boden (verfügbare Breite minus Abstand)/1,5 geklemmt, andernfalls auf die verfügbare Höhe, und die Ströme werden aktualisiert. Wenn ein Hintergrund verwendet wird, kopieren wir die Originalpixel in das Diagramm/die Statistik und skalieren sie mit „ScaleImage“ auf die neuen Maße (halbe Breite der Statistik). Wir ändern die Größe von „canvasGraph“ mit Resize und setzen „OBJPROP_XSIZE“/“YSIZE“, ebenso für „stats“, falls aktiviert, und aktualisieren die X-Position und die Kopfzeile auf volle Breite. Kopfzeile/Grafik/Statistiken neu zeichnen, falls aktiviert, Chart neu zeichnen.
Wenn der Zustand Null und „prev_mouse_state“ Eins für „Hoch“: Wenn „panel_dragging“, auf „false“ setzen, Scrollen aktivieren, Kopfzeile neu zeichnen, Chart neu zeichnen; wenn „resizing“, auf „false“ setzen, Scrollen aktivieren, Grafik aktualisieren, Chart neu zeichnen. Wir aktualisieren „last_mouse_x“/“last_mouse_y“ auf current, „prev_mouse_state“ auf state. Wir müssen auch das Dashboard pro Tick aktualisieren, um die neuen Kurse widerzuspiegeln.
//+------------------------------------------------------------------+ //| Handle tick event | //+------------------------------------------------------------------+ void OnTick() { static datetime lastBarTime = 0; //--- Initialize last time datetime currentBarTime = iTime(_Symbol, _Period, 0); //--- Get current time if (currentBarTime > lastBarTime) //--- Check new bar { UpdateGraphOnCanvas(); //--- Update graph if (EnableStatsPanel) UpdateStatsOnCanvas(); //--- Update stats ChartRedraw(); //--- Redraw chart lastBarTime = currentBarTime; //--- Update last time } }
Im OnTick-Ereignishandler verwenden wir eine statische „lastBarTime“-Datetime, die auf Null initialisiert ist, um die Eröffnungszeit der vorherigen Bar zu verfolgen und die aktuelle Bar-Zeit mit iTime für das Symbol, die Periode und die Bar Null zu erhalten. Wenn die aktuelle Zeit größer als „lastBarTime“ ist, was auf einen neuen Bars hinweist, rufen wir „UpdateGraphOnCanvas“ auf, um das Kurschart zu aktualisieren, „UpdateStatsOnCanvas“, wenn „EnableStatsPanel“ für Statistiken aktiviert ist, zeichnen das Chart neu und aktualisieren „lastBarTime“ auf den aktuellen Wert. Schließlich müssen wir gerenderte Objekte löschen, um Unordnung zu vermeiden, wenn sie nicht benötigt werden.
//+------------------------------------------------------------------+ //| Deinitialize expert | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { canvasHeader.Destroy(); //--- Destroy header if (graphCreated) canvasGraph.Destroy(); //--- Destroy graph if created if (statsCreated) canvasStats.Destroy(); //--- Destroy stats if created ChartRedraw(); //--- Redraw chart }
Im OnDeinit-Ereignishandler zerstören wir das Header-Canvas mit „canvasHeader.Destroy“, dann, bedingt, das Grafikpanel, wenn „graphCreated“ wahr ist, mit „canvasGraph.Destroy“, und das Stats-Canvas, wenn „statsCreated“ wahr ist, mit „canvasStats.Destroy“. Zum Schluss wird das Chart neu gezeichnet, um sicherzustellen, dass alle Überreste aus der Anzeige entfernt werden. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

Anhand der Visualisierung können wir sehen, dass wir das Canvas-Dashboard korrekt eingerichtet haben und dass alle Canvas-Komponenten korrekt gerendert und interaktiv sind, sodass wir unser Ziel 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 als einzelne Animation im GIF-Format.

Schlussfolgerung
Abschließend haben wir ein canvasbasiertes Kurs-Dashboard in MQL5 entwickelt, das die CCanvas-Klasse verwendet, um verschiebbare und in der Größe veränderbare Panels für Echtzeit-Kursverläufe mit Liniendiagrammen, gefüllten Bereichen und Nebeleffekten zu erstellen, zusammen mit einem optionalen Statistikpanel für Kontometriken wie Balance/Equity und aktuellem Bar-OHLC, das Hintergrundbilder, Farbverläufe, Umschalten zwischen hellem und dunklem Design und effiziente Ereignisbehandlung für Interaktivität unterstützt. Das System umfasst eine bikubische Skalierung für eine reibungslose Größenänderung, Alpha-Blending für Überlagerungen und Aktualisierungen bei neuen Bars und bietet so ein anpassbares Werkzeug für die visuelle Überwachung ohne native Objekte. Im nächsten Teil werden wir das Dashboard erweitern, indem wir einen Canvas-basierten Textbildschirm zum Lesen und Scrollen hinzufügen, der verschiedene Textdesign integrieren kann, mit einer dynamischen Bildlaufleiste, wie sie in den neuen aktualisierten MetaQuotes-Terminals zu finden ist. Bleiben Sie dran.
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/21038
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.
Workshop für nutzerdefinierte Indikatoren (Teil 1): Aufbau des Supertrend-Indikators in MQL5
Data Science und ML (Teil 48): Sind Transformer für das Trading wirklich relevant?
Der MQL5 Standard Library Explorer (Teil 6): Optimierung eines generierten Expert Advisors
Marktsimulation (Teil 20): Erste Schritte mit SQL (III)
- 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.