Das MQL5-Kochbuch: Steuerelemente des Indikatorunterfensters - Die Schaltflächen
Anatoli Kazharski | 1 Juni, 2016
Einleitung
In diesem Beitrag wird ein Beispiel für die Programmierung einer eigenen Benutzeroberfläche mit Steuerelementen der Art Schaltfläche betrachtet. Als Hinweis für den Anwender darauf, dass das besagte Steuerelement ansprechbar ist, richten wir es so ein, dass die Schaltfläche ihre Farbe ändert, wenn der Mauszeiger darüber fährt. Beim Darüberfahren des Mauszeigers wird die Farbe der Schaltfläche etwas dunkler, und beim Betätigen der Maustaster augenfällig noch dunkler. Außerdem erhält jede Schaltfläche weitere automatisch aufklappende Kurzinformationen. Auf diese Weise erhalten wir eine selbsterklärende Benutzeroberfläche.
Ebenfalls betrachtet werden in diesem Beitrag folgende Ereignisse: die Änderung der Position des Mauszeigers, der Zustand der linken Maustaste, der Linksklick auf ein Objekt sowie bei einer Änderung der Diagrammeigenschaften auftretende Ereignisse. Wir legen ein Schaltflächenfeld an, das die gesamte Fläche des Indikatorunterfensters einnimmt. Als Beispiel erstellen wir drei Zeilen mit jeweils vier Schaltflächen.
Die Entwicklung
Zum Anlegen von Schaltflächen können in MQL5 unterschiedliche grafische Objekte verwendet werden, als da wären OBJ_BUTTON (Schaltfläche), OBJ_BITMAP (grafisches Element), OBJ_BITMAP_LABEL (grafische Markierung) oder OBJ_EDIT (Eingabefeld).
In diesem Beitrag erstellen wir Schaltflächen mithilfe der Funktion OBJ_EDIT. Für diese Objekte kann die Texteingabe mittels Mauszeiger gesperrt werden. Das ist auch deshalb praktisch, weil in ihnen der wiederzugebende Text angegeben werden kann, und ihre Enden können spitz gestaltet werden, ohne den Rahmen zu verändern.
Legen wir also mithilfe des MQL5-Assistenten einen Indikator an. Nach ein paar kleinen Überarbeitungen sieht dessen Code aus wie folgt:
//+------------------------------------------------------------------+ //| TestButtons.mq5 | //| Copyright 2013, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2013, MetaQuotes Software Corp." #property link "http://www.mql5.com" #property version "1.00" //--- #property indicator_separate_window // Indicator is in the subwindow #property indicator_plots 0 // No plotting series //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { //--- //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { //--- } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- } //+------------------------------------------------------------------+
Das heißt, wir haben zunächst ein leeres Fenster ohne jegliche grafische Reihe. Die Notwendigkeit eines Zeitgebers besprechen wir noch.
Jetzt werden die beim Anlegen der Funktionen zu verwendenden Konstanten, Variablen und Datenfelder hinzugefügt. Alle Datenfelder weisen zwei Dimensionen auf. In der ersten Dimension wird die über die Höhe des Unterfensters verteilte Anzahl der Schaltflächen angegeben, in der zweiten die Anzahl in der Breite:
//--- #define BUTTON_COLUMNS 4 // Number of buttons across the width #define BUTTON_ROWS 3 // Number of buttons across the height //+------------------------------------------------------------------+ //| Global parameters | //+------------------------------------------------------------------+ //--- Font string font_name="Calibri"; //--- Indicator subwindow properties int subwindow_number =WRONG_VALUE; // Subwindow number int subwindow_height =0; // Subwindow height string subwindow_shortname ="TestButtons"; // Short name of the indicator string prefix =subwindow_shortname+"_"; // Prefix for object names int chart_width =0; // Chart width int chart_height =0; // Chart height int chart_y_offset =0; // Distance from the chart top to the subwindow //--- Colors of button elements color background_color =clrSteelBlue; // Button color color font_color =clrWhite; // Font color color hover_background_color =C'38,118,166'; // Button color when the cursor goes over color clicked_background_color =C'2,72,136'; // Clicked button color //--- Text displayed on buttons string button_texts[BUTTON_ROWS][BUTTON_COLUMNS]= { {"Button 01","Button 02","Button 03","Button 04"}, {"Button 05","Button 06","Button 07","Button 08"}, {"Button 09","Button 10","Button 11","Button 12"} }; //--- Object names string button_object_names[BUTTON_ROWS][BUTTON_COLUMNS]= { {"button_01","button_02","button_03","button_04"}, {"button_05","button_06","button_07","button_08"}, {"button_09","button_10","button_11","button_12"} }; //--- Button widths int button_widths[BUTTON_ROWS][BUTTON_COLUMNS]; //--- Button heights int button_heights[BUTTON_ROWS][BUTTON_COLUMNS]; //--- X-coordinates int button_x_distances[BUTTON_ROWS][BUTTON_COLUMNS]; //--- Y-coordinates int button_y_distances[BUTTON_ROWS][BUTTON_COLUMNS]; //--- Button states bool button_states[BUTTON_ROWS][BUTTON_COLUMNS]= { {true,false,false,false}, {false,false,false,false}, {false,false,false,false} }; //--- Button colors color button_colors[BUTTON_ROWS][BUTTON_COLUMNS];
Während der Indikator in das Diagramm geladen wird, müssen die Datenfelder mit den Eigenschaften der Objekte nach der Berechnung der entsprechenden Koordinaten und Größen in der Funktion OnInit() bereitgestellt werden. Zudem muss die Nachverfolgung der Bewegungen des Mauszeigers eingeschaltet werden. Schließlich müssen die Schaltflächen noch zu dem Unterfenster für den Indikator hinzugefügt werden. Alle diese Vorgänge übertragen wir der Einfachheit halber auf einzelne Funktionen, die wir uns der Reihe nach ansehen werden. Der Code der Funktion OnInit() nimmt daraufhin folgende Gestalt an:
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set the timer at 1-second intervals EventSetTimer(1); //--- Add prefix to object names AddPrefix(); //--- Enable tracking of mouse events ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,true); //--- Set the short name IndicatorSetString(INDICATOR_SHORTNAME,subwindow_shortname); //--- Set subwindow properties SetSubwindowProperties(); //--- Set button properties SetButtonColors(); // Colors SetButtonCoordinates(); // Coordinates SetButtonSizes(); // Sizes //--- Add the button panel AddButtonsPanel(); //--- Refresh the chart ChartRedraw(); //--- Everything completed successfully return(INIT_SUCCEEDED); }
In der Funktion AddPrefix() wird der Bezeichnung eines jeden grafischen Objektes ein Präfix vorangestellt, das die Kurzbezeichnung des Indikators wiedergibt. Das ist erforderlich, um auszuschließen, dass Objekte ausgetauscht/entfernt/verschoben werden, wenn bei Verwendung mehrerer Programme in einem Diagramm die Bezeichnungen von Objekten zusammenfallen.
//+------------------------------------------------------------------+ //| Adding prefix to all object names | //+------------------------------------------------------------------+ void AddPrefix() { //--- Add prefix to object names for(int i=0; i<BUTTON_COLUMNS; i++) for(int j=0; j<BUTTON_ROWS; j++) button_object_names[j][i]=prefix+button_object_names[j][i]; }
Die für die Berechnungen benötigten Diagrammeigenschaften werden in der Funktion SetSubwindowProperties() bereitgestellt:
//+------------------------------------------------------------------+ //| Setting subwindow properties | //+------------------------------------------------------------------+ void SetSubwindowProperties() { //--- Indicator subwindow number subwindow_number=ChartWindowFind(0,subwindow_shortname); //--- Subwindow width and height chart_width=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS); subwindow_height=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,subwindow_number); }
Nachdem wir die Diagrammeigenschaften bezogen haben, können die Berechnungen zur Bestimmung der Farbe der Schaltflächen sowie der Werte ihrer Koordinaten und ihrer Größe ausgeführt werden. All das geschieht in drei separaten Einzelfunktionen, die wie folgt aussehen:
//+------------------------------------------------------------------+ //| Setting button color | //+------------------------------------------------------------------+ void SetButtonColors() { for(int i=0; i<BUTTON_COLUMNS; i++) { for(int j=0; j<BUTTON_ROWS; j++) { //--- If the button is clicked if(button_states[j][i]) button_colors[j][i]=clicked_background_color; //--- If the button is unclicked else button_colors[j][i]=background_color; } } } //+------------------------------------------------------------------+ //| Setting X and Y coordinates for buttons | //+------------------------------------------------------------------+ void SetButtonCoordinates() { int button_width=chart_width/BUTTON_COLUMNS; int button_height=subwindow_height/BUTTON_ROWS; //--- for(int i=0; i<BUTTON_COLUMNS; i++) { for(int j=0; j<BUTTON_ROWS; j++) { if(i==0) button_x_distances[j][i]=0; else button_x_distances[j][i]=(button_width*i)-i; //--- if(j==0) button_y_distances[j][i]=0; else button_y_distances[j][i]=(button_height*j)-j; } } } //+------------------------------------------------------------------+ //| Setting button width and height | //+------------------------------------------------------------------+ void SetButtonSizes() { int button_width=chart_width/BUTTON_COLUMNS; int button_height=subwindow_height/BUTTON_ROWS; //--- for(int i=0; i<BUTTON_COLUMNS; i++) { for(int j=0; j<BUTTON_ROWS; j++) { if(i==BUTTON_COLUMNS-1) button_widths[j][i]=chart_width-(button_width*(BUTTON_COLUMNS-1)-i); else button_widths[j][i]=button_width; //--- if(j==BUTTON_ROWS-1) button_heights[j][i]=subwindow_height-(button_height*(BUTTON_ROWS-1)-j)-1; else button_heights[j][i]=button_height; } } }
Und zu guter Letzt werden die Schaltflächen dem Indikatorunterfenster mithilfe der Funktion AddButtonsPanel() hinzugefügt:
//+------------------------------------------------------------------+ //| Adding buttons to the indicator subwindow | //+------------------------------------------------------------------+ void AddButtonsPanel() { //--- Create buttons for(int i=0; i<BUTTON_COLUMNS; i++) { for(int j=0; j<BUTTON_ROWS; j++) { CreateButton(0,subwindow_number,button_object_names[j][i],button_texts[j][i], CORNER_LEFT_UPPER,font_name,8,font_color,button_colors[j][i],clrNONE, button_widths[j][i],button_heights[j][i], button_x_distances[j][i],button_y_distances[j][i],2,true,button_texts[j][i]); } } }
Der Code der Hilfsfunktion CreateButton() hat folgendes Aussehen:
//+------------------------------------------------------------------+ //| Creating a button (graphical object of the Edit type) | //+------------------------------------------------------------------+ void CreateButton(long chart_id, // chart id int sub_window, // (sub)window number string object_name, // object name string text, // displayed text long corner, // chart corner string font, // font int font_size, // font size color c_font, // font color color c_background, // background color color c_border, // border color int x_size, // width int y_size, // height int x_dist, // X-coordinate int y_dist, // Y-coordinate long zorder, // Z-order bool read_only, // Read Only flag string tooltip) // tooltip { //--- If the object has been created successfully, set the remaining properties if(ObjectCreate(chart_id,object_name,OBJ_EDIT,subwindow_number,0,0)) { ObjectSetString(chart_id,object_name,OBJPROP_TEXT,text); // name ObjectSetInteger(chart_id,object_name,OBJPROP_CORNER,corner); // chart corner ObjectSetString(chart_id,object_name,OBJPROP_FONT,font); // font ObjectSetInteger(chart_id,object_name,OBJPROP_FONTSIZE,font_size); // font size ObjectSetInteger(chart_id,object_name,OBJPROP_COLOR,c_font); // font color ObjectSetInteger(chart_id,object_name,OBJPROP_BGCOLOR,c_background); // background color ObjectSetInteger(chart_id,object_name,OBJPROP_BORDER_COLOR,c_border); // border color ObjectSetInteger(chart_id,object_name,OBJPROP_XSIZE,x_size); // width ObjectSetInteger(chart_id,object_name,OBJPROP_YSIZE,y_size); // height ObjectSetInteger(chart_id,object_name,OBJPROP_XDISTANCE,x_dist); // X-coordinate ObjectSetInteger(chart_id,object_name,OBJPROP_YDISTANCE,y_dist); // Y-coordinate ObjectSetInteger(chart_id,object_name,OBJPROP_SELECTABLE,false); // object is not available for selection ObjectSetInteger(chart_id,object_name,OBJPROP_ZORDER,zorder); // Z-order ObjectSetInteger(chart_id,object_name,OBJPROP_READONLY,read_only); // Read Only text ObjectSetInteger(chart_id,object_name,OBJPROP_ALIGN,ALIGN_CENTER); // align center ObjectSetString(chart_id,object_name,OBJPROP_TOOLTIP,tooltip); // no tooltip if "\n" } }
Achten Sie bitte auf den letzten Parameter der Funktion CreateButton(): er ist für den Hinweis zuständig, der automatisch aufklappt, wenn der Mauszeiger über ein grafisches Objekt geführt wird. In der Funktion AddButtonsPanel() werden beispielsweise als derartiger Parameter die Werte aus dem Datenfeld button_texts (der auf der jeweiligen Schaltfläche angezeigte Text) weitergegeben. Falls gewünscht kann jedoch auch ein eigenes Datenfeld mit ausführlicheren Erläuterungen angelegt werden.
Wenn wir den Indikator jetzt in das Diagramm laden, erhalten wir in etwa folgendes Ergebnis:
Abb. 1. Hinzufügen der Schaltflächen zum Unterfenster für den Indikator
Bis jetzt handelt es sich lediglich um aneinander gereihte Objekte im Indikatorunterfenster, die nicht auf irgendeine Handlung des Anwenders reagieren. Es gilt, ihnen „Leben einzuhauchen“.
Zunächst sorgen wir dafür, dass sich die Größe der Schaltflächen bei einer Änderung der Größe des Unterfensters mit ändert. Dazu schreiben wir zwei weitere Funktionen: UpdateButtonCoordinates() und ResizeButtons(). Sie legen die Koordinaten und die Größe der Schaltflächen fest:
//+------------------------------------------------------------------+ //| Updating button coordinates | //+------------------------------------------------------------------+ void UpdateButtonCoordinates() { //--- Set coordinates for(int i=0; i<BUTTON_COLUMNS; i++) { for(int j=0; j<BUTTON_ROWS; j++) { ObjectSetInteger(0,button_object_names[j][i],OBJPROP_XDISTANCE,button_x_distances[j][i]); ObjectSetInteger(0,button_object_names[j][i],OBJPROP_YDISTANCE,button_y_distances[j][i]); } } } //+------------------------------------------------------------------+ //| Updating button sizes | //+------------------------------------------------------------------+ void ResizeButtons() { //--- Set sizes for(int i=0; i<BUTTON_COLUMNS; i++) { for(int j=0; j<BUTTON_ROWS; j++) { ObjectSetInteger(0,button_object_names[j][i],OBJPROP_XSIZE,button_widths[j][i]); ObjectSetInteger(0,button_object_names[j][i],OBJPROP_YSIZE,button_heights[j][i]); } } }
Zur Verarbeitung des Ereignisses der Änderung der Eigenschaften und der Größe des Diagramms wird der Bezeichner CHARTEVENT_CHART_CHANGE benötigt. Unten sehen Sie, um welchen Code der Hauptteil der Funktion OnChartEvent() erweitert werden muss:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, // event identifier const long &lparam, // parameter of the event of type long const double &dparam, // parameter of the event of type double const string &sparam) // parameter of the event of type string { //--- Tracking the event of modifying the chart properties and resizing the chart if(id==CHARTEVENT_CHART_CHANGE) { //--- Set subwindow properties SetSubwindowProperties(); //--- Set button coordinates SetButtonCoordinates(); //--- Set button sizes SetButtonSizes(); //--- Set new button coordinates UpdateButtonCoordinates(); //--- Set new button sizes ResizeButtons(); //--- Refresh the chart ChartRedraw(); return; } }
Wenn der Indikator jetzt in das Diagramm geladen wird (bzw. sein Code neu zusammengestellt wird, falls er bereits dort angelegt wurde), passen die Schaltflächen bei einer Änderung der Größe des Diagrammfensters oder des Indikatorunterfensters ihre Größe und ihre Lage auf dem Bildschirm automatisch an.
Außerdem richten wir es ein, dass die Schaltflächen ihre Farbe ändern, wenn der Mauszeiger über sie geführt wird. Aber bevor wir den Code der Funktionen schreiben, müssen wir zunächst klären, wie ein Ereignis mit dem Bezeichner CHARTEVENT_MOUSE_MOVE verarbeitet wird.
In der Funktion OnInit() liegt bereits eine Zeile vor, die das Programm veranlasst, die Bewegungen sowie den Zustand der linken Taste des Mauszeigers zu verfolgen:
//--- Enable tracking of mouse events ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,true);
Ohne diese Zeile (oder wenn als letzter Parameter der Wert „false“ weitergegeben wurde) werden Ereignisse mit dem Bezeichner CHARTEVENT_MOUSE_MOVE in der Funktion OnChartEvent() nicht verfolgt. Das ist ganz praktisch, da die Verfolgung dieser Ereignisse nicht in jedem Programm erforderlich ist.
Um nachzuvollziehen, wie die Verfolgung von Mauszeigerereignissen verläuft, können wir den Code der Funktion OnChartEvent() vorübergehend um die Ausgabe eines entsprechenden Kommentars in dem Diagramm erweitern:
//--- Mouse movement and left-click tracking if(id==CHARTEVENT_MOUSE_MOVE) { Comment("id: ",CHARTEVENT_MOUSE_MOVE,"\n", "lparam (x): ",lparam,"\n", "dparam (y): ",dparam,"\n", "sparam (state of the mouse buttons): ",sparam );
Wenn der Mauszeiger jetzt in dem Diagramm bewegt wird, sind die aktuellen Werte der Koordinaten des Mauszeigers in der linken oberen Ecke zu sehen. Bei Betätigung der linken Maustaste sieht man die Änderung in der Kommentarzeile sparam (Zustand der Maustaste), mit (1) bei gedrückter Maustaste und Null (0) bei freigegebener.
Wenn man wissen muss, in welchem Unterfenster der Mauszeiger sich gerade befindet, kann die Funktion ChartXYToTimePrice() helfen. An sie werden die Koordinaten weitergegeben, und sie gibt ihrerseits die Kennziffer des Fensters/Unterfensters sowie die Zeit und den Kurs (an die durch eine Verknüpfung mit ihr verbundenen Variablen) aus. Das ist zu sehen, wenn wir folgenden Code probelaufen lassen:
//--- Mouse movement and left-click tracking if(id==CHARTEVENT_MOUSE_MOVE) { int x =(int)lparam; // X-coordinate int y =(int)dparam; // Y-coordinate int window =WRONG_VALUE; // Number of the window where the cursor is located datetime time =NULL; // Time corresponding to the X-coordinate double price =0.0; // Price corresponding to the Y-coordinate //--- Get the position of the cursor if(ChartXYToTimePrice(0,x,y,window,time,price)) { Comment("id: ",CHARTEVENT_MOUSE_MOVE,"\n", "x: ",x,"\n", "y: ",y,"\n", "sparam (state of the mouse buttons): ",sparam,"\n", "window: ",window,"\n", "time: ",time,"\n", "price: ",DoubleToString(price,_Digits) ); } //--- return; }
Die Berechnungen in dem Indikatorunterfenster sind einfacher anhand relativer Koordinaten auszuführen. In unserem Fall betrifft das die Koordinaten der Y-Achse (die Kursleiste). Um den relativen Wert zu ermitteln, reicht es aus, den Abstand von dem oberen Rand des Diagramms bis zu dem Unterfenster für den Indikator von dem aktuellen Koordinatenwert abzuziehen. Das kann etwa folgendermaßen geschehen:
//--- Get the position of the cursor if(ChartXYToTimePrice(0,x,y,window,time,price)) { //--- Get the distance from the chart top to the indicator subwindow chart_y_offset=(int)ChartGetInteger(0,CHART_WINDOW_YDISTANCE,subwindow_number); //--- Convert the Y-coordinate to the relative value y-=chart_y_offset; Comment("id: ",CHARTEVENT_MOUSE_MOVE,"\n", "x: ",x,"\n", "y: ",y,"\n", "sparam (state of the mouse buttons): ",sparam,"\n", "window: ",window,"\n", "time: ",time,"\n", "price: ",DoubleToString(price,_Digits) ); }
Der Wert in der Variablen y wird jetzt negativ, wenn sich der Mauszeiger oberhalb des Indikatorunterfensters befindet, und positiv, sobald er über dessen Fläche geführt wird.
Voreingestellt ist die Möglichkeit, dass Diagramm unabhängig von der jeweiligen Position des Mauszeigers, entlang der Zeitleiste zu verschieben. Aber die Möglichkeit zur Verschiebung des Diagramms kann auch ausgeschaltet werden. Das ist meistens dann erforderlich, wenn sich der Mauszeiger über dem Bedienfeld oder den benutzerdefinierten Steuerelementen befindet. Um zum Beispiel das Verschieben des Diagramms abzuschalten, wenn sich der Mauszeiger in dem Unterfenster für den Indikator befindet, und es wieder einzuschalten, wenn er es verlässt, muss der folgende Code geschrieben werden:
//--- If the cursor is in the subwindow area, disable chart scrolling if(window==subwindow_number) ChartSetInteger(0,CHART_MOUSE_SCROLL,false); //--- Enable chart scrolling if the cursor moves out of the indicator subwindow area else ChartSetInteger(0,CHART_MOUSE_SCROLL,true);
Außerdem schreiben wir die Funktion ChangeButtonColorOnHover() zur Änderung der Farbe der Schaltfläche, wenn sich der Mauszeiger auf dieser befindet:
//+------------------------------------------------------------------+ //| Changing the button color when the cursor hovers over the button | //+------------------------------------------------------------------+ void ChangeButtonColorOnHover(int x,int y) { int x1,y1,x2,y2; //--- Initialize the array of XY coordinates for buttons SetButtonCoordinates(); //--- Determine if the cursor is over any of the buttons for(int i=0; i<BUTTON_COLUMNS; i++) { for(int j=0; j<BUTTON_ROWS; j++) { //--- If this button is clicked, go to the next one if(button_states[j][i]) continue; //--- Get the button boundaries x1=button_x_distances[j][i]; y1=button_y_distances[j][i]; x2=button_x_distances[j][i]+button_widths[j][i]; y2=button_y_distances[j][i]+button_heights[j][i]; //--- If the cursor is within the button area, set the new button color if(x>x1 && x<x2 && y>y1 && y<y2) ObjectSetInteger(0,button_object_names[j][i],OBJPROP_BGCOLOR,hover_background_color); //--- Otherwise set the standard color else ObjectSetInteger(0,button_object_names[j][i],OBJPROP_BGCOLOR,background_color); } } }
Infolgedessen erscheint in dem Zweig mit dem Bezeichner CHARTEVENT_MOUSE_MOVE der folgende Code:
//--- Mouse movement and left-click tracking if(id==CHARTEVENT_MOUSE_MOVE) { int x =(int)lparam; // X-coordinate int y =(int)dparam; // Y-coordinate int window =WRONG_VALUE; // Number of the window where the cursor is located datetime time =NULL; // Time corresponding to the X-coordinate double price =0.0; // Price corresponding to the Y-coordinate //--- Get the position of the cursor if(ChartXYToTimePrice(0,x,y,window,time,price)) { //--- Get the distance from the chart top to the indicator subwindow chart_y_offset=(int)ChartGetInteger(0,CHART_WINDOW_YDISTANCE,subwindow_number); //--- Convert the Y-coordinate to the relative value y-=chart_y_offset; //--- If the cursor is in the subwindow area, disable chart scrolling if(window==subwindow_number) ChartSetInteger(0,CHART_MOUSE_SCROLL,false); //--- Enable chart scrolling if the cursor moves out of the indicator subwindow area else ChartSetInteger(0,CHART_MOUSE_SCROLL,true); //--- Change the button color when the cursor is hovered over ChangeButtonColorOnHover(x,y); } //--- Refresh the chart ChartRedraw(); return; }
Wenn wir den Mauszeiger jetzt über die Schaltflächen bewegen, können wir sehen, wie sich ihre Farbe ändert.
Momentan hat nur die Schaltfläche Button 01 die Farbe der betätigten Schaltfläche. Klickt man jetzt mit der Maus eine andere Schaltfläche an, so erfolgt keine Reaktion, und die Farbe ändert sich nicht. Um das umzusetzen, müssen wir ein Ereignis mit dem Bezeichner CHARTEVENT_OBJECT_CLICK einsetzen.
Wir schreiben zwei Funktionen: InitializeButtonStates() und ChangeButtonColorOnClick(). In Ersterer erfolgt die Überprüfung, ob die Schaltfläche betätigt worden ist, unter Berücksichtigung des Präfixes in ihrer Bezeichnung. Wenn die Betätigung der Schaltfläche festgestellt wurde, wird in dem Arbeitsgang das Datenfeld mit den Zuständen der Schaltflächen (button_states) bereitgestellt, und die Funktion gibt den Wert „true“ aus.
//+------------------------------------------------------------------+ //| Initializing button states in case of click | //+------------------------------------------------------------------+ bool InitializeButtonStates(string clicked_object) { //--- Get the indicator subwindow number subwindow_number=ChartWindowFind(0,subwindow_shortname); //--- If a button in the indicator subwindow has been clicked if(ObjectFind(0,clicked_object)==subwindow_number && StringFind(clicked_object,prefix+"button_",0)>=0) { //--- Determine the clicked button for(int i=0; i<BUTTON_COLUMNS; i++) { for(int j=0; j<BUTTON_ROWS; j++) { //--- Determine the state of all buttons if(clicked_object==button_object_names[j][i]) button_states[j][i]=true; else button_states[j][i]=false; } } //--- return(true); } //--- return(false); }
Danach wird die Farbe der Schaltflächen in der Funktion ChangeButtonColorOnClick() entsprechend den Werten in dem Datenfeld button_states eingestellt.
//+------------------------------------------------------------------+ //| Changing the button color in case of click | //+------------------------------------------------------------------+ void ChangeButtonColorOnClick() { for(int i=0; i<BUTTON_COLUMNS; i++) { for(int j=0; j<BUTTON_ROWS; j++) { //--- If the button has been clicked, it is set a distinctive color if(button_states[j][i]) ObjectSetInteger(0,button_object_names[j][i],OBJPROP_BGCOLOR,clicked_background_color); //--- Set the standard color to the unclicked button else ObjectSetInteger(0,button_object_names[j][i],OBJPROP_BGCOLOR,background_color); } } }
Damit sich das alles lohnt, dürfen wir nicht vergessen, die Funktion OnChartEvent() um die Verarbeitung des Ereignisses der Betätigung einer Schaltflächen zu erweitern:
//--- Tracking left mouse button clicks on a graphical object if(id==CHARTEVENT_OBJECT_CLICK) { //--- If the button has been clicked if(InitializeButtonStates(sparam)) { //--- Set button colors ChangeButtonColorOnClick(); } //--- Refresh the chart ChartRedraw(); return; }
Jetzt ändert die Schaltfläche ihre Farbe, wenn sie betätigt wird.
Einige Feinheiten bleiben noch zu erledigen. Bei der Entfernung des Indikators aus dem Diagramm muss die Verschiebung des Diagramms in der Funktion OnDeinit() für den Bereich des Unterfensters wieder ein- und die Nachverfolgung der Mauszeigerereignisse abgeschaltet werden. Das könnte wichtig sein, wenn auf das Diagramm mehrere Programme mit Ereignisnachverfolgung gleichzeitig angewendet werden.
//+------------------------------------------------------------------+ //| Deinitialization | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(reason==REASON_REMOVE || // If the indicator has been deleted from the chart or reason==REASON_RECOMPILE) // the program has been recompiled { //--- Deactivate the timer EventKillTimer(); //--- Delete the objects DeleteButtons(); //--- Enable chart scrolling ChartSetInteger(0,CHART_MOUSE_SCROLL,true); //--- Disable tracking of mouse events ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,false); //--- Refresh the chart ChartRedraw(); } }
Die Funktionen zum Löschen der grafischen Objekte des Programms:
//+------------------------------------------------------------------+ //| Deleting all buttons | //+------------------------------------------------------------------+ void DeleteButtons() { for(int i=0; i<BUTTON_COLUMNS; i++) for(int j=0; j<BUTTON_ROWS; j++) DeleteObjectByName(button_object_names[j][i]); } //+------------------------------------------------------------------+ //| Deleting the object by name | //+------------------------------------------------------------------+ void DeleteObjectByName(string object_name) { //--- If such object exists if(ObjectFind(0,object_name)>=0) { //--- If an error occurred when deleting, print the relevant message if(!ObjectDelete(0,object_name)) Print("Error ("+IntegerToString(GetLastError())+") when deleting the object!"); } }
Abschließend die Erklärung, warum wir in diesem Programm einen Zeitgeber einschalten müssen. Wenn beispielsweise in einem Diagramm mehr als ein Programm ausgeführt wird, und in jedem davon die Mauszeigerereignisse nachverfolgt werden müssen, wird bei Entfernung eines der Programme aus dem Diagramm die Nachverfolgung in der Funktion OnDeinit() für alle Programme abgeschaltet. Deshalb wäre es als Variante möglich, jede Sekunde zu prüfen, ob die Nachverfolgung von Mauszeigerereignissen eingeschaltet ist.
//+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { //--- Check whether tracking of mouse events is enabled CheckChartEventMouseMove(); }
Der Code der Funktion CheckChartEventMouseMove() sieht aus wie folgt:
Manchmal reicht es vollkommen aus, diese Überprüfung für Ereignisse mit dem Bezeichner CHARTEVENT_CHART_CHANGE einzurichten.
Es folgt ein kurzer Film zur Veranschaulichung unseres Ergebnisses:
Fazit
Damit kommen wir zum Schluss. Der Indikator TestButtons.mq5 kann im Anhang zu diesem Beitrag heruntergeladen werden. Aus diesem Beispiel lässt sich ein recht interessantes Hauptmenü erstellen, wenn man den Gedanken weiterspinnt. Beispielsweise könnte der Anwender durch das Betätigen einer bestimmten Schaltfläche zu den für ihn interessanten Informationen gelangen. Die Anzahl der Schaltflächen kann beliebig erweitert werden.