English Русский 中文 Español 日本語 Português 한국어 Français Italiano Türkçe
Das MQL5-Kochbuch: Steuerelemente des Indikatorunterfensters - Die Schaltflächen

Das MQL5-Kochbuch: Steuerelemente des Indikatorunterfensters - Die Schaltflächen

MetaTrader 5Beispiele | 1 Juni 2016, 09:15
1 084 0
Anatoli Kazharski
Anatoli Kazharski

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

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.

Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/750

Beigefügte Dateien |
testbuttons.mq5 (20.47 KB)
Das MQL5-Kochbuch: Steuerelemente des Indikatorunterfensters - Die Bildlaufleiste Das MQL5-Kochbuch: Steuerelemente des Indikatorunterfensters - Die Bildlaufleiste
Wir setzen das Studium der Steuerelemente fort und kommen diesmal zur Bildlaufleiste, dem Scrollbar. Genauso wie schon in dem vorhergehenden Beitrag „Das MQL5-Kochbuch: Steuerelemente des Indikatorunterfensters - Die Schaltflächen“ arbeiten wir in dem Unterfenster für den Indikator. Den genannten Beitrag sollte man gelesen haben, da in ihm die Arbeit mit Ereignissen in der Funktion OnChartEvent() ausführlich dargelegt wird, während diese hier nur oberflächlich gestreift wird. Als Beispiel wird in diesem Beitrag eine senkrechte Bildlaufleiste für eine lange Aufstellung aller mit den Möglichkeiten der Programmiersprache MQL5 zu beziehenden Kenngrößen eines Finanzinstrumentes angelegt.
Grafische Interfaces I: Testen der Bibliothek in unterschiedlichen Programmen und in dem MetaTrader 4 Terminal (Kapitel 5) Grafische Interfaces I: Testen der Bibliothek in unterschiedlichen Programmen und in dem MetaTrader 4 Terminal (Kapitel 5)
In dem vorherigen Kapitel des ersten Teils der Serie über grafische Interfaces, haben wir die Formularklasse mit Methoden für das Verwalten der Form über das Anklicken von Controls ergänzt. In diesem Teil wollen wir unsere Arbeit in verschiedenen Typen von MQL Programmen, wie zum Beispiel Indikatoren und Skripten, testen. Da unsere Bibliothek darauf ausgelegt ist, plattformübergreifend zu funktionieren, werden wir Sie in allen Metatrader-Plattformen testen, so wie zum Beispiel im MetaTrader 4.
Das MQL5-Kochbuch: Entwicklung eines mehrwährungsfähigen Kursschwankungsindikators in MQL5 Das MQL5-Kochbuch: Entwicklung eines mehrwährungsfähigen Kursschwankungsindikators in MQL5
In diesem Beitrag befassen wir uns mit der Entwicklung eines mehrwährungsfähigen Kursschwankungsindikators. Jemanden, der gerade erst beginnt, in MQL5 zu programmieren, kann die Entwicklung von Indikatoren für mehrere Währungen vor einige Schwierigkeiten stellen, aber nach der Lektüre dieses Beitrages sollte alles wesentlich einfacher sein. Die grundlegenden Fragen bei der Entwicklung mehrwährungsfähiger Indikatoren beziehen sich auf die Abstimmung der Daten anderer Kürzel auf die des aktuellen Kürzels, die Lösung des Problems des Nichtvorhandenseins eines Teils der Indikatordaten sowie auf die Ermittlung des Anfangs der „echten“ Balken des jeweiligen Zeitraums. All das wird in dem hier vorliegenden Beitrag ausführlich behandelt.
Grafische Interfaces I: Funktionen für die Form-Buttons und das Entfernen der Interface Elemente (Kapitel 4) Grafische Interfaces I: Funktionen für die Form-Buttons und das Entfernen der Interface Elemente (Kapitel 4)
In diesem Artikel werden wir mit der Entwicklung der CWindow Klasse fortschreiten, indem wir ihr Methoden hinzufügen, welche es uns erlauben das Formular über das Anklicken seiner Controls zu verwalten. Wir sind dann in der Lage das Formular über einen Button zu schließen, so wie es zu minimieren und maximieren.