MQL5-Trading Tools (Teil 19): Entwicklung einer interaktiven Werkzeugpalette zum Zeichnen in Charts
Einführung
In unserem vorherigen Artikel (Teil 18) haben wir vektorbasierte Rechtecke und Dreiecke kombiniert, um abgerundete Sprechblasen mit steuerbarer Ausrichtung in MetaQuotes Language 5 (MQL5) zu erstellen und damit dynamische UI-Elemente für Handelsoberflächen zu ermöglichen. In Teil 19 entwickeln wir eine interaktive Werkzeugpalette zum Zeichnen in Charts, mit verschiebbaren Panels, Größenanpassung, Umschaltung zwischen dunklem und hellem Design sowie Schaltflächen für Werkzeuge wie Fadenkreuz, Linien und Formen. Dieses individuell anpassbare System verbessert die Analyse durch Interaktionen in Echtzeit und klare Anweisungen. Wir werden die folgenden Themen behandeln:
Am Ende verfügen Sie über eine funktionsfähige Palette, mit der Sie Ihre Handelsabläufe erweitern können – legen wir los!
Entwurf interaktiver Werkzeugpaletten für Charts
Die interaktive Werkzeugpalette zum Zeichnen in Charts bündelt Schaltflächen für wichtige Funktionen wie Fadenkreuze, Trendlinien, Linien, Rechtecke, Fibonacci, Text und Pfeile und ermöglicht so eine schnelle Auswahl und Anwendung auf Handelscharts mit Echtzeit-Feedback. Sie unterstützt das Ziehen, die Größenanpassung, den Wechsel zwischen dem dunklen und dem hellen Modus sowie das Minimieren zur Optimierung des Arbeitsbereichs und bietet gleichzeitig Anleitungen und Statusmeldungen für eine intuitive Bedienung. Dieses Design optimiert die Analyse durch eine anpassbare, reaktionsfähige Benutzeroberfläche, die sich an die Interaktionen des Benutzers anpasst, ohne das Chart zu überladen. Wir planen, Zeichenflächen-Objekte für die Kopfzeile und das Panel zu verwenden, Mausereignisse für die Interaktivität zu verarbeiten, Enumerationen für Größenänderungsmodi und Werkzeuge zu definieren und die Zeichenlogik über das Erstellen von Objekten im Chart umzusetzen. Kurz gesagt: Hier ist eine visuelle Darstellung unserer Ziele.

Implementierung in MQL5
Um das Programm in MQL5 zu erstellen, öffnen Sie den MetaEditor, wechseln Sie zum „Navigator“, suchen Sie den Ordner „Experts“, klicken Sie auf die Registerkarte „Neu“ und folgen Sie den Anweisungen, um die Datei zu erstellen. Sobald dies erledigt ist, müssen wir in der Programmierumgebung einige Eingabeparameter sowie die globalen Variablen deklarieren, die wir im gesamten Programm verwenden werden.
//+------------------------------------------------------------------+ //| Tools Palette.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 canvasHeader; //--- Declare header canvas CCanvas canvasTools; //--- Declare tools canvas string canvasHeaderName = "HeaderCanvas"; //--- Set header canvas name string canvasToolsName = "ToolsCanvas"; //--- Set tools canvas name input int CanvasX = 20; // Panel X position input int CanvasY = 30; // Panel Y position input int CanvasWidth = 350; // Panel width input int CanvasHeight = 200; // Panel height input double BackgroundOpacity = 0.8; // Background opacity bool is_dark_theme = true; //--- Set initial dark theme flag color DarkHeaderColor = C'60,60,60'; //--- Set dark header color color DarkHeaderHoverColor = clrRed; //--- Set dark header hover color color DarkHeaderDragColor = clrMediumBlue; //--- Set dark header drag color color DarkHeaderTextColor = clrWhite; //--- Set dark header text color color DarkBorderColor = clrBlack; //--- Set dark border color color DarkTopColor = clrBlack; //--- Set dark top color color DarkIconColor = clrWhite; //--- Set dark icon color color LightHeaderColor = clrSilver; //--- Set light header color color LightHeaderHoverColor = clrRed; //--- Set light header hover color color LightHeaderDragColor = clrMediumBlue; //--- Set light header drag color color LightHeaderTextColor = clrBlack; //--- Set light header text color color LightBorderColor = clrBlack; //--- Set light border color color LightTopColor = clrWhite; //--- Set light top color color LightIconColor = clrBlack; //--- Set light icon color color DarkInstructionColor = clrLime; //--- Set dark instruction color color LightInstructionColor = clrGreen; //--- Set light instruction color color DarkStatusColor = clrMagenta; //--- Set dark status color color LightStatusColor = clrPurple; //--- Set light status color color DarkActiveBtnColor = clrGreen; //--- Set dark active button color color LightActiveBtnColor = clrDarkGreen; //--- Set light active button color color DarkHoverBtnColor = clrDarkGray; //--- Set dark hover button color color LightHoverBtnColor = clrLightGray; //--- Set light hover button color int currentCanvasX = CanvasX; //--- Initialize current X position int currentCanvasY = CanvasY; //--- Initialize current Y position int currentWidth = CanvasWidth; //--- Initialize current width int currentHeight = CanvasHeight; //--- Initialize current height bool panel_dragging = false; //--- Initialize panel dragging flag int panel_drag_x = 0, panel_drag_y = 0; //--- Initialize drag coordinates int panel_start_x = 0, panel_start_y = 0; //--- Initialize start coordinates bool resizing = false; //--- Initialize resizing flag enum ENUM_RESIZE_MODE { NONE, // No resize BOTTOM, // Bottom resize RIGHT, // Right resize BOTTOM_RIGHT // Bottom-right resize }; ENUM_RESIZE_MODE resize_mode = NONE; //--- Initialize resize mode ENUM_RESIZE_MODE hover_mode = NONE; //--- Initialize hover mode 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 thickness const int min_width = 150; //--- Set minimum width const int min_height = 200; //--- Set minimum height int hover_mouse_local_x = 0; //--- Initialize local hover X int hover_mouse_local_y = 0; //--- Initialize local hover Y bool header_hovered = false; //--- Initialize header hover flag bool minimize_hovered = false; //--- Initialize minimize hover flag bool close_hovered = false; //--- Initialize close hover flag bool theme_hovered = false; //--- Initialize theme hover flag bool resize_hovered = false; //--- Initialize resize hover flag int prev_mouse_state = 0; //--- Initialize previous mouse state int header_height = 27; //--- Set header height int button_size = 25; //--- Set button size int theme_x_offset = -75; //--- Set theme X offset int minimize_x_offset = -50; //--- Set minimize X offset int close_x_offset = -25; //--- Set close X offset bool panel_minimized = false; //--- Initialize minimized flag enum TOOL_TYPE { TOOL_NONE, // No tool TOOL_CROSSHAIR, // Crosshair tool TOOL_TRENDLINE, // Trendline tool TOOL_HLINE, // Horizontal line tool TOOL_VLINE, // Vertical line tool TOOL_RECTANGLE, // Rectangle tool TOOL_FIBO, // Fibonacci tool TOOL_TEXT, // Text tool TOOL_ARROW // Arrow tool }; TOOL_TYPE active_tool = TOOL_NONE; //--- Initialize active tool bool drawing_first_click = false; //--- Initialize first click flag datetime draw_time1 = 0; //--- Initialize draw time 1 double draw_price1 = 0.0; //--- Initialize draw price 1 long local_chart_id = 0; //--- Initialize local chart ID string current_instruction = ""; //--- Initialize current instruction struct ToolButton { // Define tool button structure string symbol; // Store symbol string font; // Store font TOOL_TYPE type; // Store type string tooltip; // Store tooltip }; ToolButton buttons[8]; //--- Declare buttons array int hovered_tool_index = -1; //--- Initialize hovered tool index bool measuring = false; //--- Initialize measuring flag datetime fixed_time = 0; //--- Initialize fixed time double fixed_price = 0.0; //--- Initialize fixed price ulong last_click_time = 0; //--- Initialize last click time const int tools_y_offset = -1; //--- Set tools Y offset
Wir beginnen die Implementierung, indem wir die Zeichenflächen-Bibliothek mit #include <Canvas/Canvas.mqh> einbinden, um auf die zentralen Grafikfunktionen für die Erstellung der Palette zugreifen zu können. Als Nächstes deklarieren wir die globalen Zeichenflächen-Objekte „canvasHeader“ und „canvasTools“ für den Kopfbereich und den Hauptbereich des Panels und weisen ihnen zur besseren Übersicht Namen wie „HeaderCanvas“ und „ToolsCanvas“ zu. Außerdem fügen wir Benutzereingaben für Position, Größe und Hintergrundtransparenz hinzu, um eine individuelle Anpassung zu ermöglichen. Um den Wechsel zwischen Designs zu unterstützen, setzen wir „is_dark_theme“ auf „true“ und definieren Farbkonstanten für den dunklen und den hellen Modus, die die Überschriften, Rahmen, Symbole, Anweisungen, Statusanzeigen und Schaltflächen abdecken und so eine einheitliche Optik bei allen Umschaltoptionen gewährleisten.
Wir verfolgen den Status des Panels mithilfe von Variablen für die aktuelle Position und die Abmessungen, Drag-Flags, Größenänderungsmodi über die Enumeration ENUM_RESIZE_MODE (NONE, BOTTOM, RIGHT, BOTTOM_RIGHT), Hover-Zustände sowie Konstanten wie Mindestgrößen und Randbreiten, um die Bedienbarkeit sicherzustellen. Für Werkzeuge definieren wir die Enumeration „TOOL_TYPE“, die Optionen von NONE bis ARROW auflistet, initialisieren „active_tool“ auf NONE, und verwalten Zeichenflags wie „drawing_first_click“ sowie eine „ToolButton“-Struktur, die Symbol, Schriftart, Typ und Tooltip enthält, und erstellen ein Array „buttons[8]“ für die Palettenelemente. Abschließend haben wir Variablen für den Messmodus, Fixpunkte, Klick-Timing, Hover-Indizes und einen y-Versatz für die Ausrichtung der Werkzeuge eingerichtet, um interaktive Funktionen vorzubereiten. Wir benötigen dann einige Hilfsfunktionen, um den Code modular zu gestalten.
//+------------------------------------------------------------------+ //| Get theme-aware colors | //+------------------------------------------------------------------+ color GetHeaderColor() { return is_dark_theme ? DarkHeaderColor : LightHeaderColor; } //--- Return header color based on theme color GetHeaderHoverColor() { return is_dark_theme ? DarkHeaderHoverColor : LightHeaderHoverColor; } //--- Return header hover color based on theme color GetHeaderDragColor() { return is_dark_theme ? DarkHeaderDragColor : LightHeaderDragColor; } //--- Return header drag color based on theme color GetHeaderTextColor() { return is_dark_theme ? DarkHeaderTextColor : LightHeaderTextColor; } //--- Return header text color based on theme color GetBorderColor() { return is_dark_theme ? DarkBorderColor : LightBorderColor; } //--- Return border color based on theme color GetTopColor() { return is_dark_theme ? DarkTopColor : LightTopColor; } //--- Return top color based on theme color GetIconColor() { return is_dark_theme ? DarkIconColor : LightIconColor; } //--- Return icon color based on theme color GetInstructionColor() { return is_dark_theme ? DarkInstructionColor : LightInstructionColor; } //--- Return instruction color based on theme color GetStatusColor() { return is_dark_theme ? DarkStatusColor : LightStatusColor; } //--- Return status color based on theme color GetActiveBtnColor() { return is_dark_theme ? DarkActiveBtnColor : LightActiveBtnColor; } //--- Return active button color based on theme color GetHoverBtnColor() { return is_dark_theme ? DarkHoverBtnColor : LightHoverBtnColor; } //--- Return hover button color based on theme //+------------------------------------------------------------------+ //| Get tool name from type | //+------------------------------------------------------------------+ string GetToolName(TOOL_TYPE t) { for (int i = 0; i < 8; i++) { //--- Loop over buttons if (buttons[i].type == t) { //--- Check if type matches return StringSubstr(buttons[i].tooltip, 0, StringFind(buttons[i].tooltip, " Tool")); //--- Return tool name } } return "None"; //--- Return default if no match } //+------------------------------------------------------------------+ //| Check if tool is single-click | //+------------------------------------------------------------------+ bool IsSingleClickTool(TOOL_TYPE t) { switch(t) { //--- Switch on tool type case TOOL_HLINE: //--- Handle horizontal line case TOOL_VLINE: //--- Handle vertical line case TOOL_TEXT: //--- Handle text case TOOL_ARROW: //--- Handle arrow return true; //--- Return true for single-click tools default: //--- Handle default return false; //--- Return false otherwise } }
Anschließend definieren wir eine Reihe von Getter-Funktionen wie „GetHeaderColor“, um themenabhängige Farben abzurufen. Jede dieser Funktionen gibt je nach dem Flag „is_dark_theme“ die dunkle oder helle Variante zurück und gewährleistet so ein einheitliches Styling für Kopfzeilen, Hover-Effekte, Ziehvorgänge, Texte, Rahmen, Symbole, Anweisungen, Statusanzeigen, aktive Schaltflächen und Hover-Effekte – und das ohne überflüssige Prüfungen. Anschließend extrahiert die Funktion „GetToolName“ den Namen eines Werkzeugs anhand seines Typs, indem sie das Array „buttons“ durchläuft, nach dem Typ sucht und zur übersichtlichen Darstellung den Tooltip bis zum Wort „Tool“ kürzt; wird keine Übereinstimmung gefunden, wird standardmäßig „None“ zurückgegeben. Um Werkzeuge zu klassifizieren, implementieren wir „IsSingleClickTool“ mithilfe einer switch-Anweisung für den Werkzeugtyp, die für horizontale/vertikale Linien, Text und Pfeile, die nur einen Klick erfordern, den Wert „true“ zurückgibt, und andernfalls den Wert „false“, um eine präzise Ereignisbehandlung zu gewährleisten. Nachdem das erledigt ist, können wir mit der Erstellung des Headers und des Hauptteils der Palette beginnen.
//+------------------------------------------------------------------+ //| Draw header | //+------------------------------------------------------------------+ void DrawHeader() { canvasHeader.Erase(0); //--- Erase header canvas color header_bg = panel_dragging ? GetHeaderDragColor() : (header_hovered ? GetHeaderHoverColor() : GetHeaderColor()); //--- Get header background color canvasHeader.FillRectangle(0, 0, currentWidth - 1, header_height - 1, ColorToARGB(header_bg, 255)); //--- Fill header rectangle uint argbBorder = ColorToARGB(GetBorderColor(), 255); //--- Get border ARGB canvasHeader.Rectangle(0, 0, currentWidth - 1, header_height - 1, argbBorder); //--- Draw border rectangle canvasHeader.FontSet("Arial Bold", 13); //--- Set font for title canvasHeader.TextOut(10, (header_height - 13)/2, "Tools Palette", ColorToARGB(GetHeaderTextColor(), 255)); //--- Draw title text int button_y = (header_height - 20)/2; //--- Compute button Y position int theme_button_x = currentWidth + theme_x_offset; //--- Compute theme button X canvasHeader.FontSet("Wingdings", 20); //--- Set font for theme symbol string theme_symbol = CharToString(91); //--- Set theme symbol int theme_w = canvasHeader.TextWidth(theme_symbol); //--- Get theme symbol width int theme_x = theme_button_x + (button_size - theme_w) / 2; //--- Compute theme X position color theme_color = theme_hovered ? clrYellow : GetHeaderTextColor(); //--- Get theme color canvasHeader.TextOut(theme_x, button_y, theme_symbol, ColorToARGB(theme_color, 255)); //--- Draw theme symbol int min_button_x = currentWidth + minimize_x_offset; //--- Compute minimize button X string min_symbol = panel_minimized ? CharToString(111) : CharToString(114); //--- Set minimize symbol int min_w = canvasHeader.TextWidth(min_symbol); //--- Get minimize symbol width int min_x = min_button_x + (button_size - min_w) / 2; //--- Compute minimize X position color min_color = minimize_hovered ? clrYellow : GetHeaderTextColor(); //--- Get minimize color canvasHeader.TextOut(min_x, button_y, min_symbol, ColorToARGB(min_color, 255)); //--- Draw minimize symbol int close_button_x = currentWidth + close_x_offset; //--- Compute close button X canvasHeader.FontSet("Webdings", 20); //--- Set font for close symbol string close_symbol = CharToString(114); //--- Set close symbol int close_w = canvasHeader.TextWidth(close_symbol); //--- Get close symbol width int close_x = close_button_x + (button_size - close_w) / 2; //--- Compute close X position color close_color = close_hovered ? clrRed : GetHeaderTextColor(); //--- Get close color canvasHeader.TextOut(close_x, button_y, close_symbol, ColorToARGB(close_color, 255)); //--- Draw close symbol canvasHeader.Update(); //--- Update header canvas } //+------------------------------------------------------------------+ //| Draw tools panel | //+------------------------------------------------------------------+ void DrawToolsPanel() { canvasTools.Erase(0); //--- Erase tools canvas uint argb_fill = ColorToARGB(GetTopColor(), (uchar)(255 * BackgroundOpacity)); //--- Get fill ARGB canvasTools.FillRectangle(0, 0, currentWidth - 1, currentHeight - 1, argb_fill); //--- Fill background rectangle uint argb_border = ColorToARGB(GetBorderColor(), 255); //--- Get border ARGB canvasTools.Rectangle(0, 0, currentWidth - 1, currentHeight - 1, argb_border); //--- Draw border rectangle int columns = 4; //--- Set number of columns int padding = 5; //--- Set padding int button_width = (currentWidth - (columns + 1) * padding) / columns; //--- Compute button width int button_height = 70; //--- Set button height int start_x = padding; //--- Set start X int start_y = padding; //--- Set start Y for (int i = 0; i < 8; i++) { //--- Loop over buttons int col = i % columns; //--- Compute column int row = i / columns; //--- Compute row int x = start_x + col * (button_width + padding); //--- Compute X position int y = start_y + row * (button_height + padding); //--- Compute Y position color btn_bg = (active_tool == buttons[i].type) ? GetActiveBtnColor() : ((i == hovered_tool_index) ? GetHoverBtnColor() : clrNONE); //--- Get button background if (btn_bg != clrNONE) canvasTools.FillRectangle(x, y, x + button_width - 1, y + button_height - 1, ColorToARGB(btn_bg, 255)); //--- Fill button rectangle if needed canvasTools.FontSet(buttons[i].font, 35); //--- Set font for icon int text_w = canvasTools.TextWidth(buttons[i].symbol); //--- Get icon width int text_h = canvasTools.TextHeight(buttons[i].symbol); //--- Get icon height int icon_x = x + (button_width - text_w) / 2; //--- Compute icon X int icon_y = y + 5; //--- Set icon Y canvasTools.TextOut(icon_x, icon_y, buttons[i].symbol, ColorToARGB(GetIconColor(), 255)); //--- Draw icon string tool_name = StringSubstr(buttons[i].tooltip, 0, StringFind(buttons[i].tooltip, " Tool")); //--- Get tool name canvasTools.FontSet("Arial Bold", 12); //--- Set font for name int name_w = canvasTools.TextWidth(tool_name); //--- Get name width canvasTools.TextOut(x + (button_width - name_w) / 2, y + button_height - 20, tool_name, ColorToARGB(GetIconColor(), 255)); //--- Draw tool name } if (StringLen(current_instruction) > 0) { //--- Check if instruction exists canvasTools.FontSet("Arial", 14); //--- Set font for instruction int instr_w = canvasTools.TextWidth(current_instruction); //--- Get instruction width int instr_x = (currentWidth - instr_w) / 2; //--- Compute instruction X canvasTools.TextOut(instr_x, currentHeight - 30, current_instruction, ColorToARGB(GetInstructionColor(), 255)); //--- Draw instruction } string status_text = "Active: " + GetToolName(active_tool); //--- Set status text canvasTools.FontSet("Arial Bold", 14); //--- Set font for status int status_w = canvasTools.TextWidth(status_text); //--- Get status width int status_x = (currentWidth - status_w) / 2; //--- Compute status X canvasTools.TextOut(status_x, currentHeight - 15, status_text, ColorToARGB(GetStatusColor(), 255)); //--- Draw status if (resize_hovered || resizing) { //--- Check if resize hovered or resizing string icon_font = "Wingdings 3"; //--- Set icon font int icon_size = 25; //--- Set icon size uchar icon_code = 0; //--- Initialize icon code int angle = 0; //--- Initialize angle switch (hover_mode) { //--- Switch on hover mode case BOTTOM: icon_code = (uchar)'2'; angle = 0; break; //--- Set for bottom case RIGHT: icon_code = (uchar)'1'; angle = 0; break; //--- Set for right case BOTTOM_RIGHT: icon_code = (uchar)'2'; angle = 450; break; //--- Set for bottom-right } if (icon_code != 0) { //--- Check if icon code set canvasTools.FontSet(icon_font, icon_size); //--- Set font for icon canvasTools.FontAngleSet(angle); //--- Set font angle color icon_color = (resize_hovered || resizing) ? clrRed : GetIconColor(); //--- Get icon color int icon_x = (hover_mode == BOTTOM) ? MathMin(MathMax(hover_mouse_local_x - icon_size/2, 0), currentWidth - icon_size) : currentWidth - icon_size - 2; //--- Compute icon X int icon_y = (hover_mode == RIGHT) ? MathMin(MathMax(hover_mouse_local_y - icon_size/2, 0), currentHeight - icon_size) : currentHeight - icon_size - 2; //--- Compute icon Y if (hover_mode == BOTTOM_RIGHT) { icon_x = currentWidth - icon_size - 10; icon_y = currentHeight - icon_size; } //--- Adjust for bottom-right canvasTools.TextOut(icon_x, icon_y, CharToString(icon_code), ColorToARGB(icon_color, 255)); //--- Draw icon canvasTools.FontAngleSet(0); //--- Reset font angle } } canvasTools.Update(); //--- Update tools canvas }
Um die anfängliche Darstellung zu erstellen, implementieren wir die Funktion „DrawHeader“, um die Kopfzeile der Palette auf der Zeichenfläche darzustellen. Dabei löschen wir sie zunächst mit Erase auf 0 und füllen dann ein Rechteck mit einer dynamischen Hintergrundfarbe, die über Getter wie „GetHeaderDragColor“ beim Ziehen abgerufen wird, „GetHeaderHoverColor“, wenn der Mauszeiger darüber schwebt, oder andernfalls die Standardfarbe, umgewandelt in ARGB. Als Nächstes zeichnen wir den Rahmen mit Rectangle und ARGB-Farben aus „GetBorderColor“ und legen für den Titel „Tools Palette“ mit FontSet und TextOut die Schriftart „Arial“ in fetter Schrift in der Textfarbe des Themes fest. Um Steuerelemente hinzuzufügen, berechnen wir die Y-Zentrierung der Schaltfläche, legen dann für das Design die Schriftart Wingdings fest, positionieren und färben das Symbol „[“ je nach Hover-Zustand ein und zeichnen es mit „TextOut“. Ähnlich verfahren wir beim Minimieren (Umschalten zwischen den Symbolen „o“ und „r“) und beim Schließen („X“ in Webdings), wobei wir die Farben für den Hover-Zustand wie Gelb oder Rot anpassen. Abschließend aktualisieren wir die Zeichenfläche mit Update, um die Änderungen anzuzeigen. Bei Bedarf können Sie die Symbole mit Ihren eigenen Zeichen anpassen. Sehen Sie sich eine Auswahl der verfügbaren Schriftzeichen an.

In der Funktion „DrawToolsPanel“ löschen wir die Zeichenfläche der Werkzeuge, füllen sie mit der aus „GetTopColor“ abgeleiteten ARGB-Hintergrundfarbe, die entsprechend „BackgroundOpacity“ angepasst wurde, und zeichnen das Rahmenrechteck. Wir ordnen die Schaltflächen in vier Spalten mit Abstand an, füllen den Hintergrund in einer Schleife, wenn sie aktiv sind oder der Mauszeiger darüberfährt, unter Verwendung von „GetActiveBtnColor“ oder „GetHoverBtnColor“, legen benutzerdefinierte Schriftarten für Symbole wie das „v“ in „Wingdings“ fest, zentrieren und zeichnen sie mit TextOut in der Symbolfarbe, anschließend fügen wir darunter die fettgedruckten Werkzeugnamen hinzu. Wenn sich in „current_instruction“ eine Anweisung befindet, zeichnen wir diese mit „GetInstructionColor“ zentriert im unteren Bereich; den Status des aktiven Werkzeugs fügen wir auf ähnliche Weise hinzu. Um die Größenänderung anzuzeigen, wenn „resize_hovered“ oder „resizing“ gilt, legen wir die Schriftart „Wingdings 3“ fest, bestimmen den Pfeilcode und den Winkel entsprechend „hover_mode“ (z. B. ‚2‘ bei 450 für unten rechts), positionieren den Pfeil dynamisch, zeichnen ihn in Rot oder der Symbolfarbe und setzen den Winkel zurück. Zum Abschluss aktualisieren wir die Zeichenfläche mit „Update“. Wir können dies nun initialisieren, indem wir es anhand der folgenden Logik zeichnen.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { currentCanvasX = CanvasX; //--- Set current X from input currentCanvasY = CanvasY; //--- Set current Y from input currentWidth = CanvasWidth; //--- Set current width from input currentHeight = CanvasHeight; //--- Set current height from input if (!canvasHeader.CreateBitmapLabel(0, 0, canvasHeaderName, currentCanvasX, currentCanvasY, currentWidth, header_height, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create header canvas Print("Failed to create Header Canvas"); //--- Print failure message return(INIT_FAILED); //--- Return initialization failure } if (!canvasTools.CreateBitmapLabel(0, 0, canvasToolsName, currentCanvasX, currentCanvasY + header_height + tools_y_offset, currentWidth, currentHeight, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create tools canvas Print("Failed to create Tools Canvas"); //--- Print failure message return(INIT_FAILED); //--- Return initialization failure } buttons[0].symbol = CharToString('v'); buttons[0].font = "Wingdings"; buttons[0].type = TOOL_CROSSHAIR; buttons[0].tooltip = "Crosshair Tool"; //--- Initialize button 0 buttons[1].symbol = CharToString('.'); buttons[1].font = "Wingdings 3"; buttons[1].type = TOOL_TRENDLINE; buttons[1].tooltip = "Trendline Tool"; //--- Initialize button 1 buttons[2].symbol = CharToString('*'); buttons[2].font = "Wingdings 3"; buttons[2].type = TOOL_HLINE; buttons[2].tooltip = "Horizontal Line Tool"; //--- Initialize button 2 buttons[3].symbol = CharToString('+'); buttons[3].font = "Wingdings 3"; buttons[3].type = TOOL_VLINE; buttons[3].tooltip = "Vertical Line Tool"; //--- Initialize button 3 buttons[4].symbol = CharToString('c'); buttons[4].font = "Webdings"; buttons[4].type = TOOL_RECTANGLE; buttons[4].tooltip = "Rectangle Tool"; //--- Initialize button 4 buttons[5].symbol = CharToString('"'); buttons[5].font = "Webdings"; buttons[5].type = TOOL_FIBO; buttons[5].tooltip = "Fibonacci Tool"; //--- Initialize button 5 buttons[6].symbol = CharToString('>'); buttons[6].font = "Webdings"; buttons[6].type = TOOL_TEXT; buttons[6].tooltip = "Text Tool"; //--- Initialize button 6 buttons[7].symbol = CharToString(225); buttons[7].font = "Wingdings"; buttons[7].type = TOOL_ARROW; buttons[7].tooltip = "Arrow Tool"; //--- Initialize button 7 DrawHeader(); //--- Draw header DrawToolsPanel(); //--- Draw tools panel ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); //--- Enable mouse move events ChartRedraw(); //--- Redraw chart local_chart_id = ChartID(); //--- Set local chart ID return(INIT_SUCCEEDED); //--- Return initialization success }
In OnInit initialisieren wir die aktuellen Positionen und Abmessungen der Werkzeugpalette anhand der Benutzereingaben und stellen dabei sicher, dass diese mit den konfigurierten Werten übereinstimmen. Als Nächstes erstellen wir die Header-Zeichenfläche mithilfe von CreateBitmapLabel mit ARGB-Normalisierung für die Transparenz, protokollieren Fehler und geben bei Misserfolg INIT_FAILED zurück; ebenso verfahren wir beim Werkzeug-Panel, das mit einem y-Versatz unterhalb der Header-Zeichenfläche positioniert ist. Um die Palette zu füllen, initialisieren wir die Elemente des Arrays „buttons“ mit bestimmten Symbolen (z. B. „v“ für das Fadenkreuz), Schriftarten wie Wingdings, Werkzeugtypen aus der Enumeration und Tooltips für Informationen beim Darüberfahren mit der Maus. Anschließend zeichnen wir die Kopfzeile und das Werkzeug-Panel mithilfe der entsprechenden Funktionen, aktivieren Mausbewegungsereignisse mit ChartSetInteger, um Interaktionen zu erfassen, zeichnen das Chart neu, speichern die lokale Chart-ID für die Objektverwaltung und geben INIT_SUCCEEDED zurück. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

Wir können sehen, dass das Panel korrekt initialisiert wurde. Nun erweitern wir das Panel um interaktive Funktionen und Designänderungen. Wir werden noch einige weitere Hilfsfunktionen definieren, um das ganz einfach zu bewerkstelligen.
//+------------------------------------------------------------------+ //| Toggle theme | //+------------------------------------------------------------------+ void ToggleTheme() { is_dark_theme = !is_dark_theme; //--- Toggle theme flag DrawHeader(); //--- Redraw header DrawToolsPanel(); //--- Redraw tools panel ChartRedraw(); //--- Redraw chart } //+------------------------------------------------------------------+ //| Toggle minimize | //+------------------------------------------------------------------+ void ToggleMinimize() { panel_minimized = !panel_minimized; //--- Toggle minimized flag if (panel_minimized) { //--- Check if minimized canvasTools.Destroy(); //--- Destroy tools canvas } else { //--- Handle restored canvasTools.CreateBitmapLabel(0, 0, canvasToolsName, currentCanvasX, currentCanvasY + header_height + tools_y_offset, currentWidth, currentHeight, COLOR_FORMAT_ARGB_NORMALIZE); //--- Recreate tools canvas DrawToolsPanel(); //--- Redraw tools panel } DrawHeader(); //--- Redraw header ChartRedraw(); //--- Redraw chart } //+------------------------------------------------------------------+ //| Is mouse over header excluding buttons | //+------------------------------------------------------------------+ bool IsMouseOverHeader(int mx, int my) { if (mx < currentCanvasX || mx > currentCanvasX + currentWidth || my < currentCanvasY || my > currentCanvasY + header_height) return false; //--- Check outside header int btn_left = currentCanvasX + currentWidth + theme_x_offset; //--- Compute buttons left if (mx >= btn_left && mx <= btn_left + button_size * 3) return false; //--- Check over buttons return true; //--- Return true if over header } //+------------------------------------------------------------------+ //| Is mouse over specific button | //+------------------------------------------------------------------+ bool IsMouseOverButton(int mx, int my, int offset) { int btn_left = currentCanvasX + currentWidth + offset; //--- Compute button left return (mx >= btn_left && mx <= btn_left + button_size && my >= currentCanvasY && my <= currentCanvasY + header_height); //--- Return if over button } //+------------------------------------------------------------------+ //| Is mouse over resize area | //+------------------------------------------------------------------+ bool IsMouseOverResize(int mx, int my, ENUM_RESIZE_MODE &rm) { if (panel_minimized) return false; //--- Return false if minimized int tx = currentCanvasX; //--- Set tools X int ty = currentCanvasY + header_height + tools_y_offset; //--- Set tools Y int tr = tx + currentWidth; //--- Set right int tb = ty + currentHeight; //--- Set bottom bool over_right = mx >= tr - resize_thickness && mx <= tr && my >= ty && my <= tb; //--- Check over right bool over_bottom = my >= tb - resize_thickness && my <= tb && mx >= tx && mx <= tr; //--- Check over bottom if (over_bottom && over_right) { rm = BOTTOM_RIGHT; return true; } //--- Set and return bottom-right if (over_bottom) { rm = BOTTOM; return true; } //--- Set and return bottom if (over_right) { rm = RIGHT; return true; } //--- Set and return right return false; //--- Return false otherwise } //+------------------------------------------------------------------+ //| Get hovered tool | //+------------------------------------------------------------------+ int GetHoveredTool(int mx, int my) { int lx = mx - currentCanvasX; //--- Compute local X int ly = my - (currentCanvasY + header_height + tools_y_offset); //--- Compute local Y if (lx < 0 || lx > currentWidth || ly < 0 || ly > currentHeight) return -1; //--- Return -1 if outside int columns = 4; //--- Set columns int padding = 5; //--- Set padding int button_width = (currentWidth - (columns + 1) * padding) / columns; //--- Compute button width int button_height = 70; //--- Set button height for (int i = 0; i < 8; i++) { //--- Loop over buttons int col = i % columns; //--- Compute column int row = i / columns; //--- Compute row int x = padding + col * (button_width + padding); //--- Compute X int y = padding + row * (button_height + padding); //--- Compute Y if (lx >= x && lx < x + button_width && ly >= y && ly < y + button_height) return i; //--- Return index if over } return -1; //--- Return -1 if none } //+------------------------------------------------------------------+ //| Toggle tool activation | //+------------------------------------------------------------------+ void ToggleTool(TOOL_TYPE type) { if (active_tool == type) { //--- Check if already active if (active_tool == TOOL_CROSSHAIR) { //--- Handle crosshair deactivation DeleteCrosshair(); //--- Delete crosshair measuring = false; //--- Reset measuring ChartSetInteger(0, CHART_MOUSE_SCROLL, true); //--- Enable scroll } active_tool = TOOL_NONE; //--- Deactivate tool drawing_first_click = false; //--- Reset first click current_instruction = ""; //--- Clear instruction } else { //--- Handle activation if (active_tool == TOOL_CROSSHAIR) { //--- Deactivate previous crosshair DeleteCrosshair(); //--- Delete crosshair measuring = false; //--- Reset measuring ChartSetInteger(0, CHART_MOUSE_SCROLL, true); //--- Enable scroll } active_tool = type; //--- Set active tool drawing_first_click = false; //--- Reset first click if (active_tool == TOOL_CROSSHAIR) { //--- Handle crosshair instruction current_instruction = "Move mouse for crosshair. Double-click to start measuring."; //--- Set instruction } else { //--- Handle other tools current_instruction = "Click on chart to draw."; //--- Set instruction } } DrawToolsPanel(); //--- Redraw tools panel ChartRedraw(); //--- Redraw chart } //+------------------------------------------------------------------+ //| Deactivate current tool on Escape | //+------------------------------------------------------------------+ void DeactivateTool() { if (active_tool == TOOL_CROSSHAIR) { //--- Check if crosshair active DeleteCrosshair(); //--- Delete crosshair measuring = false; //--- Reset measuring ChartSetInteger(0, CHART_MOUSE_SCROLL, true); //--- Enable scroll } active_tool = TOOL_NONE; //--- Deactivate tool drawing_first_click = false; //--- Reset first click current_instruction = ""; //--- Clear instruction DrawToolsPanel(); //--- Redraw tools panel ChartRedraw(); //--- Redraw chart } //+------------------------------------------------------------------+ //| Delete measure objects | //+------------------------------------------------------------------+ void DeleteMeasureObjects() { ObjectDelete(local_chart_id, "Cross_HF"); //--- Delete fixed horizontal ObjectDelete(local_chart_id, "Cross_VF"); //--- Delete fixed vertical ObjectDelete(local_chart_id, "Measure_Line"); //--- Delete measure line ObjectDelete(local_chart_id, "Measure_Label"); //--- Delete measure label } //+------------------------------------------------------------------+ //| Delete crosshair | //+------------------------------------------------------------------+ void DeleteCrosshair() { ObjectDelete(local_chart_id, "Cross_H"); //--- Delete moving horizontal ObjectDelete(local_chart_id, "Cross_V"); //--- Delete moving vertical DeleteMeasureObjects(); //--- Delete measure objects ChartRedraw(); //--- Redraw chart }
Hier implementieren wir die Funktion „ToggleTheme“, um zwischen dem dunklen und dem hellen Modus zu wechseln, indem wir das Flag „is_dark_theme“ umkehren, anschließend die Kopfzeile und das Werkzeug-Panel neu zeichnen, um die neuen Farben anzuwenden, und das Chart aktualisieren, damit die Änderung sofort sichtbar wird. Als Nächstes erstellen wir „ToggleMinimize“, um den Zustand des Panels mit „panel_minimized“ umzuschalten. Dabei wird die Werkzeug-Zeichenfläche beim Minimieren aus Platzgründen gelöscht und beim Wiederherstellen neu erstellt, gefolgt von einer Neuzeichnung der Kopfzeile und einer Aktualisierung des Charts. Um Interaktionen zu erkennen, definieren wir „IsMouseOverHeader“, das prüft, ob sich die Maus innerhalb der Grenzen der Kopfzeile befindet, dabei jedoch den Schaltflächenbereich ab dem Theme-Offset ausschließt und nur für verschiebbare Bereiche den Wert „true“ zurückgibt.
Ebenso überprüft „IsMouseOverButton“ die Mausposition über einer bestimmten Schaltfläche, indem es deren linken Rand anhand des Offsets berechnet und diesen mit der Größe der Schaltfläche sowie der Höhe der Kopfzeile vergleicht. Zur Größenanpassung prüft „IsMouseOverResize“, ob sich der Mauszeiger in der Nähe der unteren, rechten oder einer der Ecken des Werkzeugfensters befindet (wobei minimierte Fenster ignoriert werden), aktualisiert den Referenzmodus entsprechend (z. B. „BOTTOM_RIGHT“) und gibt „true“ zurück, wenn dies festgestellt wird. Wir fügen „GetHoveredTool“ hinzu, um die Schaltfläche unter dem Mauszeiger zu identifizieren, indem wir die Koordinaten in lokale Koordinaten umrechnen, ein 4-spaltiges Raster mit Abständen durchlaufen und den Index zurückgeben, wenn sich der Mauszeiger innerhalb der Grenzen einer Schaltfläche befindet, andernfalls -1.
Zur Verwaltung der Werkzeuge wird „ToggleTool“ je nach Typ aktiviert oder deaktiviert. Dabei wird beim Wechsel das Fadenkreuz entfernt, die Zeichenflags werden zurückgesetzt und vor dem Neuzeichnen des Charts wird eine entsprechende Anweisung wie „Zum Zeichnen auf das Chart klicken“ angezeigt. „DeactivateTool“ setzt das aktive Werkzeug vollständig zurück, wobei insbesondere Fadenkreuzelemente gelöscht und das Scrollen aktiviert werden; anschließend werden die Anweisungen aktualisiert und die Anzeige neu gezeichnet. Abschließend führen wir eine Bereinigung durch: Mit „DeleteMeasureObjects“ werden das feste Fadenkreuz sowie die Messlinie und die Beschriftung entfernt, und mit „DeleteCrosshair“ wird das bewegliche Fadenkreuz gelöscht sowie die Funktion zum Löschen der Messung aufgerufen, woraufhin das Chart neu gezeichnet wird. Wir werden uns nun mit den Funktionen der Objekte und deren Erstellung befassen.
//+------------------------------------------------------------------+ //| Handle chart drawing for tools | //+------------------------------------------------------------------+ void HandleDrawing(int mx, int my) { datetime time; //--- Declare time double price; //--- Declare price int sw; //--- Declare subwindow if (!ChartXYToTimePrice(local_chart_id, mx, my, sw, time, price)) return; //--- Convert XY to time/price or return string name = "Tool_" + TimeToString(TimeCurrent(), TIME_SECONDS); //--- Set object name color col = clrBlue; //--- Set color ENUM_OBJECT obj_type = OBJ_VLINE; //--- Initialize object type datetime time1 = time; //--- Set time1 double price1 = price; //--- Set price1 datetime time2 = 0; //--- Set time2 double price2 = 0.0; //--- Set price2 bool create = true; //--- Set create flag bool valid_obj = false; //--- Set valid object flag string tool_name = GetToolName(active_tool); //--- Get tool name if (IsSingleClickTool(active_tool)) { //--- Check if single-click tool switch (active_tool) { //--- Switch on tool case TOOL_HLINE: //--- Handle horizontal line obj_type = OBJ_HLINE; //--- Set type time1 = 0; //--- Set time1 price1 = price; //--- Set price1 time2 = 0; //--- Set time2 price2 = 0; //--- Set price2 valid_obj = true; //--- Set valid break; //--- Break case TOOL_VLINE: //--- Handle vertical line obj_type = OBJ_VLINE; //--- Set type time1 = time; //--- Set time1 price1 = 0; //--- Set price1 time2 = 0; //--- Set time2 price2 = 0; //--- Set price2 valid_obj = true; //--- Set valid break; //--- Break case TOOL_TEXT: //--- Handle text obj_type = OBJ_TEXT; //--- Set type time2 = 0; //--- Set time2 price2 = 0; //--- Set price2 valid_obj = true; //--- Set valid break; //--- Break case TOOL_ARROW: //--- Handle arrow obj_type = OBJ_ARROW; //--- Set type time2 = 0; //--- Set time2 price2 = 0; //--- Set price2 valid_obj = true; //--- Set valid break; //--- Break } } else { //--- Handle two-click tools if (!drawing_first_click) { //--- Check if first click draw_time1 = time; //--- Set draw time1 draw_price1 = price; //--- Set draw price1 drawing_first_click = true; //--- Set first click flag create = false; //--- Set no create current_instruction = "Click second point for " + tool_name + "."; //--- Set instruction } else { //--- Handle second click ChartXYToTimePrice(local_chart_id, mx, my, sw, time2, price2); //--- Get time2/price2 drawing_first_click = false; //--- Reset first click time1 = draw_time1; //--- Set time1 price1 = draw_price1; //--- Set price1 current_instruction = "Click on chart to draw."; //--- Set instruction } switch (active_tool) { //--- Switch on tool case TOOL_TRENDLINE: obj_type = OBJ_TREND; valid_obj = true; break; //--- Set trendline case TOOL_RECTANGLE: obj_type = OBJ_RECTANGLE; valid_obj = true; break; //--- Set rectangle case TOOL_FIBO: obj_type = OBJ_FIBO; valid_obj = true; break; //--- Set fibo } } if (create && valid_obj) { //--- Check if create and valid if (ObjectCreate(local_chart_id, name, obj_type, sw, time1, price1, time2, price2)) { //--- Create object ObjectSetInteger(local_chart_id, name, OBJPROP_COLOR, col); //--- Set color ObjectSetInteger(local_chart_id, name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style ObjectSetInteger(local_chart_id, name, OBJPROP_WIDTH, 1); //--- Set width if (obj_type == OBJ_TEXT) ObjectSetString(local_chart_id, name, OBJPROP_TEXT, "Text"); //--- Set text if (obj_type == OBJ_ARROW) ObjectSetInteger(local_chart_id, name, OBJPROP_ARROWCODE, 233); //--- Set arrow code if (obj_type == OBJ_HLINE || obj_type == OBJ_VLINE) ObjectSetInteger(local_chart_id, name, OBJPROP_STYLE, STYLE_DASH); //--- Set dash for lines ObjectSetInteger(local_chart_id, name, OBJPROP_SELECTABLE, true); //--- Set selectable ObjectSetInteger(local_chart_id, name, OBJPROP_SELECTED, true); //--- Set selected ChartRedraw(local_chart_id); //--- Redraw chart active_tool = TOOL_NONE; //--- Deactivate tool current_instruction = ""; //--- Clear instruction } } DrawToolsPanel(); //--- Redraw tools panel }
Um die Chartdarstellung zu steuern, implementieren wir die Funktion „HandleDrawing“, die die Erstellung von Chart-Objekten basierend auf dem aktiven Werkzeug und der Mausposition verwaltet. Dabei werden zunächst die Koordinaten mit ChartXYToTimePrice in Zeit- und Kurswerte umgewandelt; bei Fehlschlag wird die Funktion vorzeitig beendet. Als Nächstes generieren wir einen eindeutigen Objektnamen unter Verwendung der aktuellen Uhrzeit, legen die Standardfarbe Blau und den vertikalen Linientyp fest und verzweigen dann über „IsSingleClickTool“ zu den Ein-Klick-Werkzeugen, wobei wir Objekttypen wie OBJ_HLINE für horizontale Linien (Zurücksetzen der Zeiten) konfigurieren, OBJ_VLINE für vertikale Linien (Preise zurücksetzen), OBJ_TEXT oder OBJ_ARROW, markieren diese als gültig und setzen das Erstellungsflag.
Bei Zwei-Klick-Werkzeugen erfassen wir den ersten Punkt, wenn „drawing_first_click“ falsch ist, und aktualisieren dabei die globalen Variablen und die Anweisung, ohne das Objekt zu erstellen; beim zweiten Klick wird der Endpunkt abgerufen, das Flag zurückgesetzt und Typen wie OBJ_TREND für Trendlinien, OBJ_RECTANGLE oder OBJ_FIBO konfiguriert, wobei das Objekt validiert wird. Wenn die Erstellung markiert und gültig ist, verwenden wir ObjectCreate mit Parametern, legen Eigenschaften wie Farbe, Stil, Breite, Text oder gegebenenfalls den Pfeilcode fest, wählen „gestrichelte“ Linien sowie die Zustände „auswählbar“ und „ausgewählt“ aus, zeichnen das Chart anschließend mit ChartRedraw neu und deaktivieren das Werkzeug, wobei wir die Anweisungen löschen. Abschließend aktualisieren wir das Werkzeug-Panel mit „DrawToolsPanel“, um die Änderungen zu übernehmen. Kümmern wir uns nun um das Fadenkreuz; der Rest ist unkompliziert.
//+------------------------------------------------------------------+ //| Update moving crosshair | //+------------------------------------------------------------------+ void UpdateMovingCrosshair(datetime time, double price) { string h_name = "Cross_H"; //--- Set horizontal name string v_name = "Cross_V"; //--- Set vertical name if (ObjectFind(local_chart_id, h_name) < 0) { //--- Check if horizontal exists ObjectCreate(local_chart_id, h_name, OBJ_HLINE, 0, 0, price); //--- Create horizontal line ObjectSetInteger(local_chart_id, h_name, OBJPROP_COLOR, clrRed); //--- Set color ObjectSetInteger(local_chart_id, h_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style } else { //--- Handle existing ObjectMove(local_chart_id, h_name, 0, 0, price); //--- Move horizontal } if (ObjectFind(local_chart_id, v_name) < 0) { //--- Check if vertical exists ObjectCreate(local_chart_id, v_name, OBJ_VLINE, 0, time, 0); //--- Create vertical line ObjectSetInteger(local_chart_id, v_name, OBJPROP_COLOR, clrRed); //--- Set color ObjectSetInteger(local_chart_id, v_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style } else { //--- Handle existing ObjectMove(local_chart_id, v_name, 0, time, 0); //--- Move vertical } } //+------------------------------------------------------------------+ //| Update fixed crosshair | //+------------------------------------------------------------------+ void UpdateFixedCrosshair() { string hf_name = "Cross_HF"; //--- Set fixed horizontal name string vf_name = "Cross_VF"; //--- Set fixed vertical name if (ObjectFind(local_chart_id, hf_name) < 0) { //--- Check if fixed horizontal exists ObjectCreate(local_chart_id, hf_name, OBJ_HLINE, 0, 0, fixed_price); //--- Create fixed horizontal ObjectSetInteger(local_chart_id, hf_name, OBJPROP_COLOR, clrGreen); //--- Set color ObjectSetInteger(local_chart_id, hf_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style } else { //--- Handle existing ObjectMove(local_chart_id, hf_name, 0, 0, fixed_price); //--- Move fixed horizontal } if (ObjectFind(local_chart_id, vf_name) < 0) { //--- Check if fixed vertical exists ObjectCreate(local_chart_id, vf_name, OBJ_VLINE, 0, fixed_time, 0); //--- Create fixed vertical ObjectSetInteger(local_chart_id, vf_name, OBJPROP_COLOR, clrGreen); //--- Set color ObjectSetInteger(local_chart_id, vf_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style } else { //--- Handle existing ObjectMove(local_chart_id, vf_name, 0, fixed_time, 0); //--- Move fixed vertical } }
Wir implementieren die Funktion „UpdateMovingCrosshair“, um ein dynamisches Fadenkreuz im Chart zu verwalten. Dabei definieren wir Namen für die horizontale Linie „Cross_H“ und die vertikale Linie „Cross_V“, prüfen mit ObjectFind, ob diese jeweils vorhanden sind (bei Nichtvorhandensein wird ein negativer Rückgabewert ausgegeben), erstellen sie über „ObjectCreate“ als OBJ_HLINE oder „OBJ_VLINE“ mit durchgezogener roter Linie, falls sie noch nicht vorhanden sind, oder verschieben sie mit ObjectMove auf den aktuellen Zeitpunkt und Kurs, falls vorhanden, und sorgen so für eine Cursorverfolgung in Echtzeit. Als Nächstes erstellen wir die Funktion „UpdateFixedCrosshair“ für ein statisches Referenzfadenkreuz unter Verwendung der Namen „Cross_HF“ und „Cross_VF“. Dabei prüfen wir ebenfalls die Existenz, erstellen das Fadenkreuz mit einem durchgehenden grünen Stil zu festgelegten Zeitpunkten („fixed_time“) und Kursen („fixed_price“) oder aktualisieren Positionen, um im Messmodus dauerhafte Markierungen für Vergleiche zu ermöglichen. Nun werden wir den Klick darauf behandeln.
//+------------------------------------------------------------------+ //| Handle crosshair click | //+------------------------------------------------------------------+ void HandleCrosshairClick(datetime time, double price) { ulong now = GetMicrosecondCount(); //--- Get current microseconds if (now - last_click_time < 500000) { //--- Check for double-click if (!measuring) { //--- Check not measuring fixed_time = time; //--- Set fixed time fixed_price = price; //--- Set fixed price measuring = true; //--- Set measuring flag ChartSetInteger(0, CHART_MOUSE_SCROLL, false); //--- Disable scroll current_instruction = "Measuring. Double-click to exit."; //--- Set instruction } else { //--- Handle exit measuring measuring = false; //--- Reset measuring DeleteMeasureObjects(); //--- Delete objects ChartSetInteger(0, CHART_MOUSE_SCROLL, true); //--- Enable scroll current_instruction = "Move mouse for crosshair. Double-click to start measuring."; //--- Set instruction } DrawToolsPanel(); //--- Redraw tools panel last_click_time = 0; //--- Reset click time } else { //--- Handle single click last_click_time = now; //--- Update click time } }
Wir implementieren die Funktion „HandleCrosshairClick“, um Klicks im Fadenkreuzmodus zu verarbeiten. Dabei rufen wir mit GetMicrosecondCount die aktuelle Mikrosekundenanzahl ab, um Doppelklicks innerhalb von 500 ms nach „last_click_time“ zu erkennen. Bei einem Doppelklick wird die Messfunktion umgeschaltet: Ist sie nicht aktiv, werden der Fixpunkt auf die aktuelle Zeit und den aktuellen Kurs gesetzt, die Messung aktiviert, das Scrollen des Charts mit ChartSetInteger deaktiviert und die Anweisung aktualisiert; falls aktiv, wird die Messung zurückgesetzt, Objekte werden mit „DeleteMeasureObjects“ gelöscht, Bildlauf wird wieder aktiviert und die Standardanweisung wiederhergestellt, anschließend wird das Werkzeugfenster neu gezeichnet und die Klickzeit zurückgesetzt. Bei Einzelklicks setzen wir „last_click_time“ auf den aktuellen Zeitpunkt, wodurch nachfolgende Klicks als Doppelklicks erkannt werden können. Definieren wir nun eine Logik zur Aktualisierung der Messlinie zwischen den Fadenkreuzen.
//+------------------------------------------------------------------+ //| Update measure line | //+------------------------------------------------------------------+ void UpdateMeasureLine(datetime time, double price) { string line_name = "Measure_Line"; //--- Set measure line name if (ObjectFind(local_chart_id, line_name) < 0) { //--- Check if line exists ObjectCreate(local_chart_id, line_name, OBJ_TREND, 0, fixed_time, fixed_price, time, price); //--- Create trend line ObjectSetInteger(local_chart_id, line_name, OBJPROP_COLOR, clrBlue); //--- Set color ObjectSetInteger(local_chart_id, line_name, OBJPROP_STYLE, STYLE_DASHDOT); //--- Set style ObjectSetInteger(local_chart_id, line_name, OBJPROP_WIDTH, 1); //--- Set width ObjectSetInteger(local_chart_id, line_name, OBJPROP_RAY, false); //--- Disable ray } else { //--- Handle existing ObjectMove(local_chart_id, line_name, 0, fixed_time, fixed_price); //--- Move point 0 ObjectMove(local_chart_id, line_name, 1, time, price); //--- Move point 1 } } //+------------------------------------------------------------------+ //| Update measure label | //+------------------------------------------------------------------+ void UpdateMeasureLabel(int mx, int my, datetime time, double price) { string label_name = "Measure_Label"; //--- Set measure label name long period_sec = PeriodSeconds(_Period); //--- Get period seconds long virtual_fixed = fixed_time / period_sec; //--- Compute virtual fixed long virtual_time = time / period_sec; //--- Compute virtual time int bars_diff = (int)MathAbs(virtual_fixed - virtual_time); //--- Compute bars difference double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); //--- Get point value long digits = SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); //--- Get digits double pip_size = (digits == 3 || digits == 5) ? point * 10 : point; //--- Compute pip size double pips = MathAbs(price - fixed_price) / pip_size; //--- Compute pips string price_diff = DoubleToString(MathAbs(price - fixed_price), (int)digits); //--- Format price diff string text = StringFormat("%d bars, %.1f pips, Price Diff: %s", bars_diff, pips, price_diff); //--- Format text if (ObjectFind(local_chart_id, label_name) < 0) { //--- Check if label exists ObjectCreate(local_chart_id, label_name, OBJ_LABEL, 0, 0, 0); //--- Create label ObjectSetInteger(local_chart_id, label_name, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner ObjectSetInteger(local_chart_id, label_name, OBJPROP_XDISTANCE, mx + 20); //--- Set X distance ObjectSetInteger(local_chart_id, label_name, OBJPROP_YDISTANCE, my); //--- Set Y distance ObjectSetString(local_chart_id, label_name, OBJPROP_TEXT, text); //--- Set text ObjectSetInteger(local_chart_id, label_name, OBJPROP_FONTSIZE, 9); //--- Set font size ObjectSetString(local_chart_id, label_name, OBJPROP_FONT, "Arial"); //--- Set font ObjectSetInteger(local_chart_id, label_name, OBJPROP_COLOR, clrBlue); //--- Set color } else { //--- Handle existing ObjectSetInteger(local_chart_id, label_name, OBJPROP_XDISTANCE, mx + 20); //--- Update X distance ObjectSetInteger(local_chart_id, label_name, OBJPROP_YDISTANCE, my); //--- Update Y distance ObjectSetString(local_chart_id, label_name, OBJPROP_TEXT, text); //--- Update text } }
Für die Messlinie implementieren wir die Funktion „UpdateMeasureLine“, um im Messmodus eine Verbindungslinie anzuzeigen, wobei wir den Namen „Measure_Line“ definieren, überprüfen mit ObjectFind, ob sie vorhanden ist, und erstellen sie als OBJ_TREND vom festen zum aktuellen Zeitpunkt/Kurs mit blauem Strich-Punkt-Stil, Breite 1 und ohne Strahl, falls sie nicht vorhanden ist, oder verschieben ihre Punkte mit ObjectMove, falls sie vorhanden ist, um eine visuelle Verbindung zwischen den Punkten herzustellen. Wir erstellen die Funktion „UpdateMeasureLabel“, um dynamische Kennzahlen in der Nähe der Maus anzuzeigen. Dazu berechnen wir die Differenz der Bars, indem wir die Zeiten durch die Periodensekunden aus PeriodSeconds dividieren, um virtuelle Bars zu erhalten, und mit MathAbs den Absolutwert ermitteln. Anschließend berechnen wir die Pips anhand des Symbolwerts aus SymbolInfoDouble, der auf 3 oder 5 Stellen angepasst wird (durch Multiplikation mit 10), wobei die absolute Kursdifferenz gemäß der Funktion SymbolInfoInteger auf die entsprechende Stellenanzahl formatiert wird.
Um den Text mit StringFormat zu formatieren – einschließlich Bars, Pips, Rundung auf eine Dezimalstelle und Kursdifferenz –, prüfen wir, ob eine Beschriftung vorhanden ist, erstellen diese als OBJ_LABEL in der oberen linken Ecke mit Maus-Offset-Position, Schriftart „Arial“ in Schriftgröße 9, Farbe Blau, falls neu, oder aktualisieren Position und Text, falls bereits vorhanden, um das Benutzer-Feedback zu den Messwerten zu verbessern. Diese Kennzahlenberechnung ist von entscheidender Bedeutung, da sie Zeit- und Preisspannen über unterschiedliche Symbolgenauigkeiten und Zeiträume hinweg genau quantifiziert und so eine präzise Analyse ohne manuelle Berechnungen ermöglicht. Um die Palette zum Leben zu erwecken, werden wir im Ereignis-Handler Chartereignisse verarbeiten, um intuitive Interaktionen zu ermöglichen.
//+------------------------------------------------------------------+ //| Chart event handler | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if (id == CHARTEVENT_KEYDOWN) { //--- Check key down event if (lparam == 27) { //--- Check escape key DeactivateTool(); //--- Deactivate tool } return; //--- Return } if (id != CHARTEVENT_MOUSE_MOVE) return; //--- Return if not mouse move int mx = (int)lparam; //--- Set mouse X int my = (int)dparam; //--- Set mouse Y int ms = (int)sparam; //--- Set mouse state bool prev_hh = header_hovered; //--- Store previous header hover bool prev_th = theme_hovered; //--- Store previous theme hover bool prev_mh = minimize_hovered; //--- Store previous minimize hover bool prev_ch = close_hovered; //--- Store previous close hover bool prev_rh = resize_hovered; //--- Store previous resize hover header_hovered = IsMouseOverHeader(mx, my); //--- Update header hover theme_hovered = IsMouseOverButton(mx, my, theme_x_offset); //--- Update theme hover minimize_hovered = IsMouseOverButton(mx, my, minimize_x_offset); //--- Update minimize hover close_hovered = IsMouseOverButton(mx, my, close_x_offset); //--- Update close hover resize_hovered = IsMouseOverResize(mx, my, hover_mode); //--- Update resize hover string resize_tooltip = ""; //--- Initialize resize tooltip if (resize_hovered) { //--- Check resize hovered switch (hover_mode) { //--- Switch on hover mode case BOTTOM: resize_tooltip = "Resize Bottom"; break; //--- Set bottom tooltip case RIGHT: resize_tooltip = "Resize Right"; break; //--- Set right tooltip case BOTTOM_RIGHT: resize_tooltip = "Resize Bottom-Right"; break; //--- Set bottom-right tooltip } ObjectSetString(0, canvasToolsName, OBJPROP_TOOLTIP, resize_tooltip); //--- Set tooltip } else if (!panel_minimized && hovered_tool_index >= 0) { //--- Check tool hovered ObjectSetString(0, canvasToolsName, OBJPROP_TOOLTIP, buttons[hovered_tool_index].tooltip); //--- Set tool tooltip } else { //--- Handle no hover ObjectSetString(0, canvasToolsName, OBJPROP_TOOLTIP, ""); //--- Clear tooltip } if (prev_hh != header_hovered || prev_th != theme_hovered || prev_mh != minimize_hovered || prev_ch != close_hovered || prev_rh != resize_hovered) { //--- Check if hovers changed DrawHeader(); //--- Redraw header if (!panel_minimized) DrawToolsPanel(); //--- Redraw tools if not minimized ChartRedraw(); //--- Redraw chart } if (!panel_minimized) { //--- Check not minimized int ht = GetHoveredTool(mx, my); //--- Get hovered tool if (ht != hovered_tool_index) { //--- Check if changed hovered_tool_index = ht; //--- Update index DrawToolsPanel(); //--- Redraw tools ChartRedraw(); //--- Redraw chart } } if (resize_hovered || resizing) { //--- Check resize hovered or resizing hover_mouse_local_x = mx - currentCanvasX; //--- Update local X hover_mouse_local_y = my - (currentCanvasY + header_height + tools_y_offset); //--- Update local Y DrawToolsPanel(); //--- Redraw tools ChartRedraw(); //--- Redraw chart } if (ms == 1 && prev_mouse_state == 0) { //--- Check mouse down if (header_hovered) { //--- Check header hovered panel_dragging = true; //--- Set dragging flag panel_drag_x = mx; //--- Set drag X panel_drag_y = my; //--- 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 DrawHeader(); //--- Redraw header ChartRedraw(); //--- Redraw chart } else if (theme_hovered) { //--- Check theme hovered ToggleTheme(); //--- Toggle theme } else if (minimize_hovered) { //--- Check minimize hovered ToggleMinimize(); //--- Toggle minimize } else if (close_hovered) { //--- Check close hovered OnDeinit(0); //--- Call deinit } else if (!panel_minimized) { //--- Check not minimized int ht = GetHoveredTool(mx, my); //--- Get hovered tool if (ht >= 0) { //--- Check valid tool ToggleTool(buttons[ht].type); //--- Toggle tool } else if (active_tool != TOOL_NONE && active_tool != TOOL_CROSSHAIR) { //--- Check active tool HandleDrawing(mx, my); //--- Handle drawing } else if (IsMouseOverResize(mx, my, resize_mode)) { //--- Check resize area resizing = true; //--- Set resizing flag resize_start_x = mx; //--- Set start X resize_start_y = my; //--- Set start Y start_width = currentWidth; //--- Set start width start_height = currentHeight; //--- Set start height ChartSetInteger(0, CHART_MOUSE_SCROLL, false); //--- Disable scroll DrawToolsPanel(); //--- Redraw tools ChartRedraw(); //--- Redraw chart } } } if (panel_dragging && ms == 1) { //--- Check dragging int dx = mx - panel_drag_x; //--- Compute delta X int dy = my - panel_drag_y; //--- Compute delta Y currentCanvasX = MathMax(0, MathMin((int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS) - currentWidth, panel_start_x + dx)); //--- Update X currentCanvasY = MathMax(0, MathMin((int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS) - header_height - (panel_minimized ? 0 : currentHeight) - tools_y_offset, panel_start_y + dy)); //--- Update Y ObjectSetInteger(0, canvasHeaderName, OBJPROP_XDISTANCE, currentCanvasX); //--- Set header X ObjectSetInteger(0, canvasHeaderName, OBJPROP_YDISTANCE, currentCanvasY); //--- Set header Y if (!panel_minimized) { //--- Check not minimized ObjectSetInteger(0, canvasToolsName, OBJPROP_XDISTANCE, currentCanvasX); //--- Set tools X ObjectSetInteger(0, canvasToolsName, OBJPROP_YDISTANCE, currentCanvasY + header_height + tools_y_offset); //--- Set tools Y } ChartRedraw(); //--- Redraw chart } if (resizing && ms == 1) { //--- Check resizing int dx = mx - resize_start_x; //--- Compute delta X int dy = my - resize_start_y; //--- Compute delta Y int nw = MathMax(min_width, start_width + (resize_mode == RIGHT || resize_mode == BOTTOM_RIGHT ? dx : 0)); //--- Compute new width int nh = MathMax(min_height, start_height + (resize_mode == BOTTOM || resize_mode == BOTTOM_RIGHT ? dy : 0)); //--- Compute new height nw = MathMin(nw, (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS) - currentCanvasX); //--- Clamp width nh = MathMin(nh, (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS) - currentCanvasY - header_height - tools_y_offset); //--- Clamp height currentWidth = nw; //--- Update width currentHeight = nh; //--- Update height canvasTools.Resize(currentWidth, currentHeight); //--- Resize tools canvas ObjectSetInteger(0, canvasToolsName, OBJPROP_XSIZE, currentWidth); //--- Set tools X size ObjectSetInteger(0, canvasToolsName, OBJPROP_YSIZE, currentHeight); //--- Set tools Y size canvasHeader.Resize(currentWidth, header_height); //--- Resize header canvas ObjectSetInteger(0, canvasHeaderName, OBJPROP_XSIZE, currentWidth); //--- Set header X size ObjectSetInteger(0, canvasHeaderName, OBJPROP_YSIZE, header_height); //--- Set header Y size DrawHeader(); //--- Redraw header DrawToolsPanel(); //--- Redraw tools ChartRedraw(); //--- Redraw chart } if (ms == 0 && prev_mouse_state == 1) { //--- Check mouse up panel_dragging = false; //--- Reset dragging resizing = false; //--- Reset resizing ChartSetInteger(0, CHART_MOUSE_SCROLL, true); //--- Enable scroll DrawHeader(); //--- Redraw header DrawToolsPanel(); //--- Redraw tools ChartRedraw(); //--- Redraw chart } datetime time = 0; //--- Initialize time double price = 0.0; //--- Initialize price int sw = 0; //--- Initialize subwindow if (ChartXYToTimePrice(local_chart_id, mx, my, sw, time, price)) { //--- Convert XY to time/price if (active_tool == TOOL_CROSSHAIR) { //--- Check crosshair active UpdateMovingCrosshair(time, price); //--- Update moving crosshair if (measuring) { //--- Check measuring UpdateFixedCrosshair(); //--- Update fixed crosshair UpdateMeasureLine(time, price); //--- Update measure line UpdateMeasureLabel(mx, my, time, price); //--- Update measure label } ChartRedraw(); //--- Redraw chart } if (ms == 1 && prev_mouse_state == 0 && active_tool == TOOL_CROSSHAIR && !header_hovered && !theme_hovered && !minimize_hovered && !close_hovered && !resize_hovered && (panel_minimized || GetHoveredTool(mx, my) == -1)) { //--- Check crosshair click conditions HandleCrosshairClick(time, price); //--- Handle click ChartRedraw(); //--- Redraw chart } } prev_mouse_state = ms; //--- Update previous state }
Wir verwenden die Ereignisbehandlung von OnChartEvent, um alle Benutzerinteraktionen mit der Palette und den Werkzeugen zu verwalten. Zunächst prüfen wir auf CHARTEVENT_KEYDOWN, um die Escape-Taste (lparam 27) zu erkennen, deaktivieren das aktuelle Werkzeug mit „DeactivateTool“ und kehren dann vorzeitig zurück. Falls Sie sich fragen sollten: Das Escape-Zeichen hat den Code 27 in der ASCII-Tabelle. Siehe unten.

Als Nächstes: Wenn es sich nicht um ein CHARTEVENT_MOUSE_MOVE handelt, beenden wir die Prozedur; ansonsten extrahieren wir die Mauskoordinaten X und Y sowie den Status aus den Parametern, speichern die vorherigen Hover-Flags und aktualisieren sie mithilfe von Funktionen wie „IsMouseOverHeader“, „IsMouseOverButton“ für Theme/Minimieren/Schließen und „IsMouseOverResize“, die „hover_mode“ setzen. Als Nächstes legen wir mit ObjectSetString die Tooltips für das jeweils unter dem Mauszeiger liegende Werkzeug für Größenänderungsmodi oder bei Mauszeiger-Überfahrt aktivierte Werkzeugschaltflächen fest und löschen diese, falls keine vorhanden sind; wenn sich eine Mauszeiger-Überfahrt geändert hat, werden die Kopfzeile und die Werkzeuge (sofern nicht minimiert) neu gezeichnet, gefolgt von der Funktion ChartRedraw. Wenn das Fenster nicht minimiert ist, ermitteln wir den Index des Werkzeugs, über dem der Mauszeiger schwebt, mit „GetHoveredTool“ und zeichnen es neu, falls sich etwas geändert hat; bei Größenänderungen werden die lokalen Hover-Koordinaten aktualisiert und die Werkzeuge neu gezeichnet.
Beim Herunterdrücken der Maustaste (ms 1, zuvor 0) initiieren wir das Ziehen, wenn der Mauszeiger über der Kopfzeile schwebt, indem wir Flags setzen, das Scrollen deaktivieren und die Kopfzeile neu zeichnen; das Thema umschalten oder minimieren, wenn der Mauszeiger über den entsprechenden Schaltflächen schwebt; bei „Schließen“ die Funktion „deinit“ aufrufen; oder, wenn der Mauszeiger über einer Werkzeugschaltfläche schwebt, deren Typ umschalten; das Zeichnen von Elementen, die kein Fadenkreuz sind, mit „HandleDrawing“ verarbeiten; die Größenänderung starten, wenn sich der Mauszeiger über dem Größenänderungsbereich befindet, indem Flags gesetzt, Startvorgänge ausgelöst und das Scrollen deaktiviert werden, bevor die Werkzeuge neu gezeichnet werden. Während des Ziehvorgangs (ms 1) berechnen wir die Deltas, begrenzen die neuen Positionen mithilfe der Funktionen MathMax und MathMin auf die Chartgrenzen, aktualisieren die globalen Variablen und legen die Objektabstände für die Kopfzeile und die Werkzeuge fest (sofern diese sichtbar sind); anschließend wird das Chart neu gezeichnet. Zur Größenanpassung (ms 1) werden die neuen Abmessungen auf Basis des Modus berechnet und begrenzt, die globalen Variablen aktualisiert, die Zeichenflächen mit Resize angepasst und die Größen über ObjectSetInteger festgelegt; anschließend werden Kopfzeile, Werkzeuge und Chart neu gezeichnet.
Beim Loslassen der Maustaste (ms 0, prev 1) werden die Flags für das Ziehen und die Größenänderung zurückgesetzt, das Scrollen wieder aktiviert, die Kopfzeile und die Werkzeuge neu gezeichnet und das Chart aktualisiert. Wir wandeln die Mausposition mit ChartXYToTimePrice in Zeit/Preis um und aktualisieren, falls das Fadenkreuz aktiv ist, das bewegliche Fadenkreuz; bei einer Messung werden außerdem das feste Fadenkreuz, die Messlinie und die Beschriftung aktualisiert; bei einem qualifizierten Klick außerhalb der Palette wird dies mit „HandleCrosshairClick“ verarbeitet und neu gezeichnet. Zuletzt sollten Sie „prev_mouse_state“ für nachfolgende Ereignisse auf den aktuellen Wert aktualisieren, um eine reaktionsschnelle, zustandsbehaftete Interaktivität zu gewährleisten. Schließlich müssen wir unsere Objekte löschen, wenn sie nicht mehr benötigt werden.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { canvasHeader.Destroy(); //--- Destroy header canvas canvasTools.Destroy(); //--- Destroy tools canvas DeleteCrosshair(); //--- Delete crosshair measuring = false; //--- Reset measuring flag ChartSetInteger(0, CHART_MOUSE_SCROLL, true); //--- Enable mouse scroll int total = ObjectsTotal(local_chart_id); //--- Get total objects for (int i = total - 1; i >= 0; i--) { //--- Loop over objects backward string obj_name = ObjectName(local_chart_id, i); //--- Get object name if (StringFind(obj_name, "Tool_") == 0) { //--- Check if our tool ObjectDelete(local_chart_id, obj_name); //--- Delete object } } ChartRedraw(); //--- Redraw chart }
In OnDeinit führen wir eine Bereinigung durch, indem wir die Header- und Werkzeuge-Zeichenflächen mit der Methode Destroy zerstören, alle aktiven Fadenkreuze über „DeleteCrosshair“ entfernen, das Messflag auf „false“ zurücksetzen und das Maus-Scrollen über die Funktion ChartSetInteger wieder aktivieren. Um sicherzustellen, dass keine Objekte übrig bleiben, ermitteln wir die Gesamtanzahl mit ObjectsTotal im lokalen Chart, durchlaufen die Liste rückwärts, um ein sicheres Löschen zu gewährleisten, rufen die Namen mit ObjectName ab und entfernen diejenigen, deren Namen mit „Tool_“ beginnen, mithilfe der Funktion ObjectDelete. Abschließend rufen wir ChartRedraw auf, um das Chart zu aktualisieren, und schließen damit die Freigabe der Ressourcen beim Beenden des Programms ab. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

Aus der Visualisierung geht hervor, dass wir eine interaktive Werkzeugpalette erstellt und damit unsere Ziele erreicht haben. Nun bleibt nur noch, die Funktionsfähigkeit des Systems zu testen, was in den folgenden Teilen behandelt wurde.
Backtests
Wir haben die Tests durchgeführt; unten sehen Sie die resultierende Visualisierung als einzelnes GIF-Bild.

Schlussfolgerung
Zusammenfassend lässt sich sagen, dass wir in MQL5 eine interaktive Werkzeugpalette zum Zeichnen in Charts entwickelt haben, die über verschiebbare und in der Größe anpassbare Panels sowie einen Wechsel zwischen dem dunklen und dem hellen Modus verfügt. Wir haben Schaltflächen für verschiedene Werkzeuge wie Fadenkreuze, Trendlinien, Linien, Rechtecke, Fibonacci-Retracements, Text und Pfeile integriert und die Mausereignisse so gesteuert, dass eine nahtlose Aktivierung und Interaktion direkt im Chart möglich ist. Dieses anpassbare System verbessert die Handelsanalyse durch Echtzeit-Feedback und Anweisungen und bietet eine vielseitige Benutzeroberfläche für effiziente Arbeitsabläufe. Mit dieser Palette interaktiver Werkzeuge sind Sie bestens gerüstet, um Anmerkungen und Messungen in Charts zu optimieren – und können diese nahtlos in fortgeschrittene Handelsstrategien integrieren. In den folgenden Teilen werden wir zu einer detaillierteren Palette mit besser organisierten Werkzeugen übergehen. Bleiben Sie dran!
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/21275
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.
Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
Verwendung des MQL5-Wirtschaftskalenders zur Nachrichtenfilterung (Teil 1): Implementierung von Zeitfenstern vor und nach Nachrichtenereignissen in MQL5
Eine alternative Log-datei mit der Verwendung der HTML und CSS
MetaTrader 5 – Blaupause für maschinelles Lernen (Teil 7): Von verstreuten Experimenten zu reproduzierbaren Ergebnissen
- 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.