Le MQL5 Cookbook : Commandes de la sous-fenêtre d'indicateur - Boutons

Anatoli Kazharski | 13 janvier, 2022

Introduction

Dans cet article, nous examinerons un exemple de développement d'une interface utilisateur avec des commandes de bouton. Pour transmettre l'idée d'interactivité à l'utilisateur, les boutons changeront de couleur lorsque le curseur les survolera. Avec le curseur sur un bouton, la couleur du bouton sera légèrement assombrie, devenant nettement plus sombre lorsque le bouton est cliqué. De plus, nous ajouterons des info-bulles à chaque bouton, créant ainsi une interface intuitive.

L'article couvrira également certains événements : l'événement de déplacement de la souris, l'état du bouton gauche de la souris, le clic gauche sur un objet et l'événement de modification des propriétés du graphique. Nous allons créer un panneau de boutons qui occupera tout l'espace de la sous-fenêtre de l'indicateur. À des fins d'illustration, les boutons seront disposés en trois rangées, avec quatre boutons dans chaque rangée.

 

Développement

Dans MQL5, les boutons peuvent être créés en utilisant divers objets graphiques, comme OBJ_BUTTON (Bouton), OBJ_BITMAP (Bitmap), OBJ_BITMAP_LABEL (Bitmap Label) ou OBJ_EDIT (Edit).

Dans cet article, nous allons créer des boutons en utilisant OBJ_EDIT. Les objets de ce type peuvent être mis en lecture seule. Ils sont également utiles dans la mesure où ils peuvent afficher le texte que vous spécifiez. De plus, vous pouvez rendre les coins de l'objet nets, tout en gardant sa bordure.

Créons donc un indicateur à l'aide de MQL5 Wizard. Légèrement retravaillé, le code source de l'indicateur sera le suivant :

//+------------------------------------------------------------------+
//|                                                  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)
  {
//---

  }
//+------------------------------------------------------------------+

Ce que nous avons en ce moment, c'est une fenêtre vide avec zéro série de tracés. La nécessité d'une minuterie sera discutée un peu plus tard.

Ajoutons maintenant des constantes, des variables et des tableaux qui seront utilisés dans la création de fonctions. Tous les tableaux sont à deux dimensions. La première dimension indique le nombre de boutons sur toute la hauteur de la fenêtre et la deuxième dimension indique le nombre de boutons sur toute la largeur de la fenêtre :

//---
#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];

Lors du chargement de l'indicateur dans le graphique, les tableaux doivent être initialisés aux propriétés de l'objet dans les OnInit(), après avoir calculé les coordonnées et les tailles. Nous devrions également activer le suivi du curseur. Et enfin, nous devons ajouter des boutons à la sous-fenêtre de l'indicateur. Pour plus de commodité, ces actions seront effectuées dans des fonctions distinctes que nous allons examiner une par une plus loin. Par conséquent, les OnInit() se présentent comme suit :

//+------------------------------------------------------------------+
//| 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);
  }

Dans la fonction AddPrefix(), le préfixe, c'est-à-dire le nom court de l'indicateur, est ajouté au nom de chaque objet graphique. Ceci est nécessaire pour exclure le remplacement/suppression/shift d'objets en cas de correspondance de noms d'objets où plus d'un programme s'exécute sur le graphique.

//+------------------------------------------------------------------+
//| 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];
  }

Les propriétés du graphique requises pour les calculs seront initialisées dans la fonction SetSubwindowProperties() :

//+------------------------------------------------------------------+
//| 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);
  }

Après avoir obtenu les propriétés du graphique, nous pouvons effectuer des calculs pour déterminer les couleurs des boutons, les valeurs de coordonnées et les tailles. Toutes ces actions sont effectuées dans trois fonctions distinctes fournies ci-dessous :

//+------------------------------------------------------------------+
//| 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;
        }
     }
  }

Et enfin, la fonction AddButtonsPanel() ajoute des boutons à la sous-fenêtre de l'indicateur :

//+------------------------------------------------------------------+
//| 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]);
        }
     }
  }

Le code source de la fonction auxiliaire CreateButton() est le suivant :

//+------------------------------------------------------------------+
//| 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"
     }
  }

Veuillez noter le dernier paramètre de la fonction CreateButton() : il est responsable de l'info-bulle lorsque le curseur de la souris passe sur un objet graphique. Par exemple, dans la fonction AddButtonsPanel() ce paramètre est représenté par les valeurs passées du tableau button_texts (texte affiché sur les boutons). Vous pouvez créer un tableau séparé avec des descriptions plus détaillées, si vous le souhaitez.

Maintenant, si vous attachez l'indicateur au graphique, le résultat sera le suivant :

Fig. 1. Boutons ajoutés à la sous-fenêtre de l'indicateur

Fig. 1. Boutons ajoutés à la sous-fenêtre de l'indicateur

Pour le moment, ce ne sont que des objets disposés dans la sous-fenêtre de l'indicateur. L'interaction avec l'utilisateur n'est pas encore implémentée. Insufflons maintenant la vie à ces objets.

Tout d'abord, nous allons implémenter la possibilité d'ajuster la taille des boutons en fonction de la taille de la sous-fenêtre lorsque celle-ci est redimensionnée. À cette fin, nous écrirons deux autres fonctions - UpdateButtonCoordinates() et ResizeButtons(). Ils définiront les coordonnées et les tailles des boutons :

//+------------------------------------------------------------------+
//| 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]);
        }
     }
  }

Pour gérer l'événement de modification des propriétés du graphique et de redimensionnement du graphique, nous devons utiliser l'identifiant CHARTEVENT_CHART_CHANGE. Ci-dessous, vous pouvez voir le code que vous devez ajouter aux OnChartEvent() :

//+------------------------------------------------------------------+
//| 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;
     }

  }

Si nous ajoutons l'indicateur au graphique maintenant (ou recompilons le code si l'indicateur est déjà sur le graphique), les boutons seront automatiquement redimensionnés et repositionnés dès que la fenêtre du graphique ou la sous-fenêtre de l'indicateur sera redimensionnée.

Nous implémentons en outre le changement de couleur du bouton lorsque le curseur survole un bouton. Mais avant d'écrire le code de la fonction, examinons d'abord le processus de gestion de l'événement avec les CHARTEVENT_MOUSE_MOVE identifiant.

Dans la OnInit(), nous avons déjà une chaîne qui indique au programme de suivre le mouvement du curseur de la souris, ainsi que l'état du bouton gauche de la souris :

//--- Enable tracking of mouse events
   ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,true);

Sans cette chaîne (ou si la dernière valeur de paramètre passée est fausse), les événements avec CHARTEVENT_MOUSE_MOVE ne seront pas suivis dans la OnChartEvent(). Cela peut sembler très utile car il n'est peut-être pas nécessaire de suivre de tels événements dans chaque programme.

Pour comprendre le fonctionnement du suivi des événements souris, nous pouvons ajouter temporairement au code de la fonction OnChartEvent() la possibilité d'afficher le commentaire correspondant dans le graphique :

//--- 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
              );

Si vous commencez maintenant à déplacer le curseur de la souris dans le graphique, vous pourrez voir les coordonnées actuelles du curseur dans le coin supérieur gauche. Lors d'un clic gauche, les modifications seront affichées dans la ligne de commentaire sparam (état des boutons de la souris), où un (1) signifie que le bouton de la souris est cliqué et zéro (0) signifie qu'il est relâché.

Si vous avez besoin de connaître la sous-fenêtre où se trouve actuellement le curseur de la souris, vous pouvez utiliser les ChartXYToTimePrice(). Il obtient les coordonnées et renvoie le numéro de fenêtre/sous-fenêtre, l'heure et le prix (aux variables qui lui sont transmises par référence). Vous pouvez le voir en action en testant le code suivant :

//--- 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;
     }

Les calculs dans la sous-fenêtre de l'indicateur seront plus faciles si des coordonnées relatives sont utilisées. Dans ce cas, il s'agit de la coordonnée Y (barème de prix). Pour obtenir la valeur relative, il vous suffit de soustraire la distance entre le partie supérieure du graphique et la sous-fenêtre de l'indicateur de la valeur actuelle. Cela peut être fait comme suit :

      //--- 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)
                 );
        }

Désormais, la valeur de la variable y sera négative si le curseur de la souris est au-dessus de la sous-fenêtre de l'indicateur et positive lorsque le curseur passe sur la zone de la sous-fenêtre.

Par défaut, il est possible de faire défiler le graphique le long de l'échelle de temps, quelle que soit la position du curseur sur le graphique. Le défilement du graphique peut cependant être désactivé, si et quand cela est nécessaire. Cela sera surtout nécessaire lorsque le curseur est situé au-dessus du panneau ou des commandes personnalisées. Le code pour désactiver le défilement du graphique lorsque le curseur est dans la sous-fenêtre de l'indicateur et l'activer lorsque le curseur sort de la sous-fenêtre peut, par exemple, être le suivant :

         //--- 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);

De plus, écrivons une fonction qui changera la couleur du bouton lorsque le curseur survole le bouton correspondant - ChangeButtonColorOnHover() :

//+------------------------------------------------------------------+
//| 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);
        }
     }
  }

En conséquence, nous avons le code source suivant dans les CHARTEVENT_MOUSE_MOVE :

//--- 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;
     }

Maintenant, si vous déplacez le curseur sur les boutons, vous pourrez voir la couleur des boutons changer/revenir à la normale.

Actuellement, seul le bouton 01 a la couleur du bouton cliqué. Si vous essayez de cliquer sur d'autres boutons, il n'y aura aucune réponse et donc aucun changement de couleur. Pour implémenter le changement de couleur dans ce cas, nous devons utiliser un événement avec les CHARTEVENT_OBJECT_CLICK.

Écrivons deux fonctions : InitializeButtonStates() et ChangeButtonColorOnClick(). La fonction InitializeButtonStates() vérifiera si un bouton donné a été cliqué, en tenant compte du préfixe de son nom. Si l'événement click est identifié, le tableau des états des boutons (button_states) est alors initialisé dans une boucle et la fonction retourne true.

//+------------------------------------------------------------------+
//| 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);
  }

Ensuite, la fonction ChangeButtonColorOnClick() définit les couleurs des boutons en fonction des valeurs du tableau button_states.

//+------------------------------------------------------------------+
//| 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);
        }
     }
  }

Pour que tout fonctionne, assurez-vous d'ajouter la gestion des clics sur les boutons à la OnChartEvent() :

//--- 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;
     }

Désormais, lorsque vous cliquez dessus, le bouton change de couleur.

Nous avons encore quelques points à régler. Dans la OnDeinit(), nous devons activer le défilement du graphique dans la zone de la sous-fenêtre et désactiver le suivi des événements de la souris lors de la suppression de l'indicateur à partir du graphique. Cela peut être important si plusieurs programmes qui utilisent le suivi des événements s'exécutent dans le graphique en même temps.

//+------------------------------------------------------------------+
//| 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();
     }
  }

Fonctions de suppression des objets graphiques du programme :

//+------------------------------------------------------------------+
//| 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!");
     }
  }

Et enfin, voici la raison pour laquelle nous avons besoin d'une minuterie dans ce programme. Par exemple, si plusieurs programmes sont en cours d'exécution dans le graphique et que chacun des programmes est requis pour suivre les événements de la souris, alors lorsque l'un d'entre eux est supprimé du graphique, le suivi sera désactivé dans les OnDeinit () pour tous les programmes. Par conséquent, vous pouvez, à titre d'alternative, exécuter une vérification toutes les secondes pour voir si le suivi des événements de souris est activé :

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
//--- Check whether tracking of mouse events is enabled
   CheckChartEventMouseMove();

  }

Le code de la fonction CheckChartEventMouseMove() est fourni ci-dessous :

Parfois, il peut être tout à fait suffisant de faire cette vérification pour un événement avec les CHARTEVENT_CHART_CHANGE.

Ci-dessous, vous pouvez voir la vidéo démontrant ce que nous avons obtenu comme résultat :

 

Conclusion

Eh bien, cela termine essentiellement. L'indicateur TestButtons.mq5 est joint à l'article et est disponible en téléchargement. Avec le développement ultérieur, cet exemple pourrait devenir un menu principal intéressant. Par exemple, l'utilisateur pourrait accéder à l'information pertinente en cliquant sur un certain bouton. Le nombre de boutons peut être augmenté, si nécessaire.