Guía práctica de MQL5: Controles de la subventana del indicador: Botones

Anatoli Kazharski | 7 mayo, 2014

Introducción

En este artículo, vamos a ver un ejemplo del desarrollo de una interfaz de usuario con controles mediante botones. Para transmitir la idea de interactividad al usuario, los botones cambiarán de color cuando se pasa el cursor por encima de ellos. Cuando el cursor está encima de un botón, el color de este último será ligeramente oscurecido, haciéndose notablemente más oscuro cuando se pulsa el botón. Además, vamos a añadir tooltips (información sobre herramientas) a cada botón, creando así una interfaz intuitiva.

El artículo abarcará también algunos eventos: evento de movimiento del ratón, estado del botón izquierdo del ratón, clic con el botón izquierdo en un objeto y evento de modificación de las propiedades del gráfico. Vamos a crear un panel de botones que ocupará todo el espacio de la subventana del indicador. Para fines ilustrativos, los botones se dispondrán en tres filas, con cuatro botones en cada fila.

 

Desarrollo

En MQL5, se pueden crear botones mediante varios objetos gráficos, como OBJ_BUTTON (Botón), OBJ_BITMAP (Bitmap), OBJ_BITMAP_LABEL (Etiqueta Bitmap) o OBJ_EDIT (Editar).

En este artículo, vamos a crear botones mediante OBJ_EDIT. Los objetos de este tipo se pueden hacer de sólo lectura. También son útiles por el hecho de que le permiten mostrar un texto determinado. Además, puede hacer que los objetos tengan esquinas agudas, manteniendo sus bordes.

Así que, vamos a crear un indicador mediante MQL5 Wizard. Ligeramente retocado, el código fuente del indicador será el siguiente:

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

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

Lo que tenemos ahora es una ventana vacía sin representación gráfica. Más adelante, comentaremos la necesidad de un temporizador.

Ahora vamos a añadir las constantes, variables y matrices que se van a utilizar en la creación de las funciones. Todas las matrices son bidimensionales. La primera dimensión indica el número de botones a lo largo de la altura de la ventana y la segunda dimensión indica el número de botones a lo largo de la anchura de la ventana:

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

Durante la carga del indicador en el gráfico, hay que inicializar las matrices a las propiedades del objeto en la función OnInit(), después de calcular las coordenadas y los tamaños. Tenemos que activar también el seguimiento del cursor. Y por último, tenemos que añadir botones a la subventana del indicador. Para mayor comodidad, se llevarán a cabo estas acciones en funciones separadas que vamos a estudiar una por una a continuación. Como resultado, la función OnInit() tendrá el siguiente código:

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

En la función Addprefix(), se añade el prefijo, es decir, el nombre corto del indicador, al nombre de cada objeto gráfico. Esto es necesario para excluir la sustitución / eliminación / cambio de los objetos en caso de que coincidan los nombres de los objetos, cuando haya más de un programa ejecutándose en un gráfico.

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

Se inicializarán las propiedades de los gráficos que requieren los cálculos en la función 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);
  }

Después de obtener las propiedades del gráfico, podemos realizar los cálculos para determinar los colores de los botones, los valores de coordenadas y los tamaños. Todo esto se lleva a cabo en tres funciones separadas que se proporcionan a continuación:

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

Y por último, la función AddButtonsPanel() añade botones a la subventana del indicador:

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

El código fuente de la función adicional CreateButton() es el siguiente:

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

Tenga en cuenta el último parámetro de la función CreateButton(): se encarga de la información sobre herramientas (tooltips) cuando pasa el cursor del ratón por encima de un objeto gráfico. Por ejemplo, en la función AddButtonsPanel() se representa este parámetro por los valores enviados a partir de la matriz button_texts (el texto que se muestra en los botones). Si quiere, puede crear una matriz separada con descripciones más detalladas.

Si carga el indicador en el gráfico ahora, el resultado será el siguiente:

Fig. 1. Botones añadidos a la subventana del indicador

Fig. 1. Botones añadidos a la subventana del indicador

Por el momento, estos son meros objetos dispuestos en la subventana del indicador. La interacción con el usuario aún no está implementada. Ahora vamos a "dar vida" a estos objetos.

En primer lugar, vamos a implementar la posibilidad de ajustar los tamaños de los botones en función del tamaño de la subventana cuando cambia el tamaño de esta última. Para ello, vamos a escribir dos funciones más; UpdateButtonCoordinates() y ResizeButtons(). Establecerán las coordenadas y los tamaños de los botones:

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

Para controlar el evento de modificación de las propiedades del gráfico y cambiar el tamaño del mismo, tenemos que utilizar el identificador CHARTEVENT_CHART_CHANGE. A continuación, puede ver el código que tiene que añadir al cuerpo de la función 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 añadimos el indicador al gráfico ahora (o volvemos a compilar el código si el indicador está ya en el gráfico), nada más cambia el tamaño de la ventana del gráfico o el de la subventana del indicador, cambiará automáticamente el tamaño y posición de los botones.

Implementamos además el cambio de color del botón cuando el cursor pasa por encima de él. Pero antes de escribir el código de la función, examinemos primero el proceso de control del evento con el identificador CHARTEVENT_MOUSE_MOVE.

En la función OnInit(), ya tenemos una cadena que indica al programa que tiene que seguir el movimiento del cursor del ratón, así como el estado del botón izquierdo del ratón:

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

Sin esta cadena (o si el valor del parámetro enviado es false), no se hará el seguimiento de los eventos con el identificador CHARTEVENT_MOUSE_MOVE en la función OnChartEvent(). Esto parece bastante útil, ya que puede existir la necesidad de realizar un seguimiento de este tipo de eventos en cada programa.

Para entender cómo funciona el seguimiento de los eventos del ratón, podemos añadir temporalmente al código de la función OnChartEvent() la posibilidad de mostrar el comentario correspondiente en el gráfico:

//--- 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 ahora empieza a mover el cursor del ratón en el gráfico, podrá de ver las coordenadas actuales del cursor en la esquina superior izquierda. Al hacer un clic con el botón izquierdo, se mostrarán los cambios en la línea de comentario sparam (estado de los botones del ratón), donde uno (1) significa que el botón del ratón está pulsado y cero (0) significa que está suelto.

Si tiene que saber en qué subventana se encuentra el cursor del ratón en este momento, puede utilizar la función ChartXYToTimePrice(). Obtiene las coordenadas y devuelve la ventana / número de subventana, el tiempo y el precio (para las variables que se le envían por referencia). Puede ver esto probando el siguiente código:

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

Los cálculos en la subventana del indicador serán más fáciles si se usan coordenadas relativas. En este caso, se trata de la coordenada Y (escala del precio). Para obtener el valor relativo, sólo tiene que restar la distancia desde la parte superior del gráfico hasta la subventana del indicador del valor actual. Se puede hacer esto de la siguiente manera:

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

Ahora, el valor de la variable y será negativo si el cursor del ratón está por encima de la subventana del indicador y positivo si el cursor está por encima de área de la subventana.

Por defecto, existe una posibilidad para desplazar el gráfico a lo largo de la escala de tiempo, independientemente de la posición del cursor en el gráfico. No obstante, se puede desactivar el desplazamiento del gráfico, si es necesario. Será necesario sobre todo cuando el cursor se encuentra encima del panel o de los controles personalizados. El código para desactivar el desplazamiento del gráfico cuando el cursor se encuentra en la subventana del indicador y desactivarlo cuando el cursor se mueve fuera de la subventana puede ser, por ejemplo, el siguiente:

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

Además, vamos a escribir una función que va a cambiar el color del botón cuando el cursor pasa por encima del botón correspondiente; 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);
        }
     }
  }

Como resultado, tenemos el siguiente código fuente en la rama del identificador 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;
     }

Ahora, si mueve el cursor por encima de los botones, podrá observar el cambio de color del botón o su vuelta a la normalidad.

Ahora mismo, solo Button 01 tiene el color de un botón pulsado. Si intenta pulsar otros botones, no habrá respuesta y por lo tanto no hay cambio de color. Para implementar el cambio de color en este caso, tenemos que utilizar un evento con el identificador CHARTEVENT_OBJECT_CLICK.

Vamos a escribir dos funciones: InitializeButtonStates() y ChangeButtonColorOnClick(). La función InitializeButtonStates() comprobará si se ha pulsado un botón, teniendo en cuenta el prefijo en su nombre. Si se identifica el evento clic, la matriz de estados del botón (button_states) se inicializa entonces en un bucle y la función devuelve 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);
  }

A continuación, la función ChangeButtonColorOnClick() establece los colores de los botones en función de los valores de la matriz 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);
        }
     }
  }

Para hacer que todo funcione, asegúrese de añadir el control de los clics de los botones a la función de seguimiento de eventos 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;
     }

Al hacer clic ahora, el botón cambia de color.

Todavía quedan algunos puntos por resolver. En la función OnDeinit(), al borrar el indicador del gráfico, tenemos que habilitar el desplazamiento del gráfico en el área de la subventana y deshabilitar el seguimiento de eventos del ratón. Esto puede ser importante si se están ejecutando varios programas que utilizan el seguimiento de eventos en el gráfico al mismo tiempo.

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

Funciones para borrar objetos gráficos del programa:

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

Y finalmente, este es el motivo por el cual necesitamos un temporizador en este programa. Por ejemplo, si hay más de un programa ejecutándose en el gráfico y cada uno de los programas es necesaria para el seguimiento de los eventos del ratón, entonces cuando se elimina uno de ellos del gráfico, se deshabilitará el seguimiento en la función OnDeinit() para todos los programas. Por lo tanto, como alternativa, puede ejecutar una comprobación cada segundo para ver si está habilitado el seguimiento de los eventos del ratón:

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

  }

Se proporciona a continuación el código de la función CheckChartEventMouseMove():

A veces, puede ser más que suficiente hacer esta comprobación para un evento con el identificador CHARTEVENT_CHART_CHANGE.

A continuación puede ver el vídeo demostrativo del resultado obtenido:

 

Conclusión

Bueno, esto es todo. El indicador TestButtons.mq5 está adjunto al artículo para su descarga. Con un mayor desarrollo, este ejemplo podría convertirse en un interesante menú principal. Por ejemplo, el usuario podrá moverse a la información relevante haciendo clic en un determinado botón. Si es necesario, se podría aumentar el número de botones.