English Русский 中文 Deutsch 日本語 Português
Gráficos en la biblioteca DoEasy (Parte 73): Objeto de formulario del elemento gráfico

Gráficos en la biblioteca DoEasy (Parte 73): Objeto de formulario del elemento gráfico

MetaTrader 5Ejemplos | 14 julio 2021, 12:47
670 0
Artyom Trishkin
Artyom Trishkin

Contenido


Concepto

Los programas modernos, especialmente los programas analíticos, pueden usar en sus cálculos grandes cantidades de datos cuya visualización resulta imprescindible para la comprensión general del situación. También resulta bastante difícil usar el programa al completo si este no ofrece al usuario una interfaz clara y conveniente para la interacción interactiva con él. Naturalmente, la capacidad de trabajar con gráficos tiene que encontrarse obligatoriamente en esta biblioteca. Por eso, hoy vamos a inaugurar un gran apartado sobre el trabajo con elementos gráficos.

Nuestro objetivo es construir una funcionalidad adecuada para crear una amplia gama de diferentes objetos gráficos, enseñar a las principales clases de la biblioteca a trabajar interactivamente con los gráficos y sus objetos gráficos, y también a crear objetos gráficos de cualquier complejidad en la jerarquía de sus componentes.

Comenzaremos a trabajar con los objetos gráficos basados ​​en la clase de la biblioteca estándar CCanvas. Esta clase nos permite crear fácilmente dibujos personalizados y usarlos como "bloques de construcción" para crear objetos más complejos. Existe una variante de uso de imágenes preparadas previamente, pero existe una oportunidad mucho más interesante de dibujar estas imágenes de forma independiente en el lienzo creado. Es esta segunda posibilidad la que explotaremos ampliamente para diseñar nuestros objetos gráficos.

La jerarquía de un objeto siempre será así:

  1. El objeto básico de todos los elementos gráficos de la biblioteca basados ​​en la clase CObject. Este objeto declarará un objeto de la clase CCanvas y contendrá todos los parámetros comunes a los elementos gráficos, como la anchura, la altura, las coordenadas en el gráfico, los bordes derecho e inferior del objeto, etcétera.
  2. El objeto de formulario del elemento gráfico puede suponer la base (lienzo) de cualquier objeto gráfico; los demás elementos del objeto compuesto se colocarán en él y, usando sus parámetros, podremos establecer los parámetros para el objeto gráfico al completo. Aquí, declararemos un objeto de la clase que proporcione los métodos para trabajar con el estado del ratón: las coordenadas del cursor y los botones presionados.

Esta es la composición mínima del elemento básico de todos los objetos gráficos de la biblioteca basada en la clase CCanvas. Todos los demás objetos se crearán usando este objeto como base y heredarán sus propiedades básicas del mismo.

Pero, en primer lugar, modificaremos levemente las clases de biblioteca listas para usar y añadiremos nuevos datos para los objetos creados hoy.


Mejorando las clases de la biblioteca

En el archivo \MQL5\Include\DoEasy\Defines.mqh, añadimos el nuevo subapartado con los parámetros del lienzo y añadimos la macrosustitución con la frecuencia de actualización correspondiente:

//--- Parameters of the DOM snapshot series
#define MBOOKSERIES_DEFAULT_DAYS_COUNT (1)                        // The default required number of days for DOM snapshots in the series
#define MBOOKSERIES_MAX_DATA_TOTAL     (200000)                   // Maximum number of stored DOM snapshots of a single symbol
//--- Canvas parameters
#define PAUSE_FOR_CANV_UPDATE          (16)                       // Canvas update frequency
//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+

El hecho es que necesitemos actualizar (redibujar) los objetos basados ​​en el lienzo no más de 16 milisegundos, para deshacernos del redibujado innecesario de la pantalla, que ya resulta invisible para el ojo humano, pero que aun así genera una carga innecesaria en el sistema. Por consiguiente, antes de actualizar un objeto basado en el lienzo, primero comprobaremos cuántos milisegundos han pasado desde su anterior actualización. Estableciendo aquí el retraso óptimo, podemos conseguir una visualización aceptable de la pantalla con objetos gráficos.

Para definir el estado de los botones del ratón y las teclas Shift y Ctrl, crearemos la clase de objeto de estado del ratón. Para ello, necesitaremos dos enumeraciones: una lista de posibles estados de los botones del ratón y las teclas Shift y Ctrl y una lista de posibles estados del ratón en relación con el formulario. Vamos a añadirlas al final del listado del archivo:

//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Data for working with mouse                                      |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| The list of possible mouse buttons, Shift and Ctrl keys states   |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_BUTT_KEY_STATE
  {
   MOUSE_BUTT_KEY_STATE_NONE              = 0,        // Nothing is clicked
//--- Mouse buttons
   MOUSE_BUTT_KEY_STATE_LEFT              = 1,        // The left mouse button is clicked
   MOUSE_BUTT_KEY_STATE_RIGHT             = 2,        // The right mouse button is clicked
   MOUSE_BUTT_KEY_STATE_MIDDLE            = 16,       // The middle mouse button is clicked
   MOUSE_BUTT_KEY_STATE_WHELL             = 128,      // Scrolling the mouse wheel
   MOUSE_BUTT_KEY_STATE_X1                = 32,       // The first additional mouse button is clicked
   MOUSE_BUTT_KEY_STATE_X2                = 64,       // The second additional mouse button is clicked
   MOUSE_BUTT_KEY_STATE_LEFT_RIGHT        = 3,        // The left and right mouse buttons clicked
//--- Keyboard keys
   MOUSE_BUTT_KEY_STATE_SHIFT             = 4,        // Shift is being held
   MOUSE_BUTT_KEY_STATE_CTRL              = 8,        // Ctrl is being held
   MOUSE_BUTT_KEY_STATE_CTRL_CHIFT        = 12,       // Ctrl and Shift are being held
//--- Left mouse button combinations
   MOUSE_BUTT_KEY_STATE_LEFT_WHELL        = 129,      // The left mouse button is clicked and the wheel is being scrolled
   MOUSE_BUTT_KEY_STATE_LEFT_SHIFT        = 5,        // The left mouse button is clicked and Shift is being held
   MOUSE_BUTT_KEY_STATE_LEFT_CTRL         = 9,        // The left mouse button is clicked and Ctrl is being held
   MOUSE_BUTT_KEY_STATE_LEFT_CTRL_CHIFT   = 13,       // The left mouse button is clicked, Ctrl and Shift are being held
//--- Right mouse button combinations
   MOUSE_BUTT_KEY_STATE_RIGHT_WHELL       = 130,      // The right mouse button is clicked and the wheel is being scrolled
   MOUSE_BUTT_KEY_STATE_RIGHT_SHIFT       = 6,        // The right mouse button is clicked and Shift is being held
   MOUSE_BUTT_KEY_STATE_RIGHT_CTRL        = 10,       // The right mouse button is clicked and Ctrl is being held
   MOUSE_BUTT_KEY_STATE_RIGHT_CTRL_CHIFT  = 14,       // The right mouse button is clicked, Ctrl and Shift are being held
//--- Middle mouse button combinations
   MOUSE_BUTT_KEY_STATE_MIDDLE_WHEEL      = 144,      // The middle mouse button is clicked and the wheel is being scrolled
   MOUSE_BUTT_KEY_STATE_MIDDLE_SHIFT      = 20,       // The middle mouse button is clicked and Shift is being held
   MOUSE_BUTT_KEY_STATE_MIDDLE_CTRL       = 24,       // The middle mouse button is clicked and Ctrl is being held
   MOUSE_BUTT_KEY_STATE_MIDDLE_CTRL_CHIFT = 28,       // The middle mouse button is clicked, Ctrl and Shift are being held
  };
//+------------------------------------------------------------------+
//| The list of possible mouse states relative to the form           |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_FORM_STATE
  {
   MOUSE_FORM_STATE_NONE = 0,                         // Undefined state
//--- Outside the form
   MOUSE_FORM_STATE_OUTSIDE_NOT_PRESSED,              // The cursor is outside the form, the mouse buttons are not clicked
   MOUSE_FORM_STATE_OUTSIDE_PRESSED,                  // The cursor is outside the form, any mouse button is clicked
   MOUSE_FORM_STATE_OUTSIDE_WHEEL,                    // The cursor is outside the form, the mouse wheel is being scrolled
//--- Within the form
   MOUSE_FORM_STATE_INSIDE_NOT_PRESSED,               // The cursor is inside the form, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_PRESSED,                   // The cursor is inside the form, any mouse button is clicked
   MOUSE_FORM_STATE_INSIDE_WHEEL,                     // The cursor is inside the form, the mouse wheel is being scrolled
//--- Within the window header area
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED,   // The cursor is inside the active area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED,       // The cursor is inside the active area,  any mouse button is clicked
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL,         // The cursor is inside the active area, the mouse wheel is being scrolled
//--- Within the window scrolling area
   MOUSE_FORM_STATE_INSIDE_SCROLL_NOT_PRESSED,        // The cursor is within the window scrolling area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_SCROLL_PRESSED,            // The cursor is within the window scrolling area, any mouse button is clicked
   MOUSE_FORM_STATE_INSIDE_SCROLL_WHEEL,              // The cursor is within the window scrolling area, the mouse wheel is being scrolled
  };
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Data for handling graphical elements                             |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| The list of graphical element types                              |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_FORM,                           // Simple form
   GRAPH_ELEMENT_TYPE_WINDOW,                         // Window
  };
//+------------------------------------------------------------------+

La lista de tipos de elementos gráficos la añadiremos como una "reserva de asiento" para las clases posteriores basadas en las creadas hoy; estas listas se rellenarán y usarán en artículos futuros.

En la lista de posibles estados de los botones del ratón y las teclas Shift y Ctrl, tenemos los eventos básicos del ratón y las teclas, así como algunas de sus combinaciones que pueden ser necesarias con mayor frecuencia.
Básicamente, los estados del ratón son un conjunto simple de banderas de bits descritas en la guía ayuda para el evento CHARTEVENT_MOUSE_MOVE.

El recuadro contiene los bits y los estados correspondientes de los botones del ratón y las teclas Shift y Ctrl:

Bit
 Descripción Valor
0
 Estado del botón izquierdo del ratón
 1
1
 Estado del botón derecho del ratón
 2
2
 Estado de la tecla SHIFT
 4
3
 Estado de la tecla CTRL
 8
4
 Estado del botón central del ratón
 16
5
 Estado del primer botón adicional del ratón
 32
6
 Estado del segundo botón adicional del ratón
 64

De acuerdo con este recuadro, los valores de los eventos del ratón se pueden determinar según el número escrito en la variable que almacena los bits de los estados del ratón:

  • Si solo presionamos el botón izquierdo, el valor de la variable será 1
  • Si solo presionamos el botón derecho, el valor de la variable será igual a 2
  • Si presionamos los botones izquierdo y derecho, el valor de la variable será 1 + 2 = 3
  • Si solo presionamos el botón izquierdo y mantenemos presionada la tecla Shift, el valor de la variable será 1 + 4 = 5

Es por esta razón que los valores en la enumeración ENUM_MOUSE_BUTT_KEY_STATE se establecen exactamente según el cálculo mostrado de los valores de las variables con los indicadores establecidos, descritos por las constantes de esta enumeración.

La enumeración ENUM_MOUSE_FORM_STATE se utiliza para indicar la posición del cursor del ratón en relación con el formulario cuando los botones del ratón están presionados/sin presionar. Necesitaremos los valores de las constantes de esta enumeración para determinar la posición relativa del cursor del ratón, sus botones y el objeto con el que debemos interactuar.

Podemos guardar estas dos enumeraciones en dos bytes de una variable ushort y comprender de inmediato según su valor la imagen completa de lo que está sucediendo con el ratón y el objeto de su interacción. El recuadro contiene el mapa de bits completo de esta variable:

 Bit  Byte Estado
Valor
0
0
 botón izquierdo del ratón
1
1
0
 botón derecho del ratón
2
2
0
 tecla SHIFT
4
3
0
 tecla CTRL
8
4
0
 botón central del ratón
16
5
0
 primer botón adicional del ratón
32
6
0
 segundo botón adicional del ratón
64
7
0
 scrolling de la ruleta
128
8 (0)
1
 cursor dentro del formulario
256
9 (1)
1
 cursor dentro de la zona activa del formulario
512
10 (2)
1
 cursor en la zona de control de la ventana (ocultar/desplegar/cerrar, etc.)
1024
11 (3)
1
 cursor en la zona de scrolling
2048
12 (4)
1
 cursor en el borde izquierdo del formulario
409
13 (5)
1
 cursor en el borde inferior del formulario
8192
14 (6)
1
 cursor en el borde derecho del formulario
16384
15 (7)
1
 cursor en el borde superior del formulario
32768

Por ahora, nos bastará disponer de estas banderas de los estados del ratón y la posición del cursor respecto al objeto de formulario y el objeto de ventana basado en el formulario.

Vamos a modificar ligeramente el objeto de clase de pausa en el archivo \MQL5\Include\DoEasy\Services\Pause.mqh.
Su método SetTimeBegin(), además de establecer el nuevo tiempo de cuenta atrás para la pausa, también registra el tiempo transmitido al método en la variable m_time_begin
Esto solo es necesario para enviar información al diario, no lo necesitamos si solo queremos calcular una pausa en algún lugar dentro del método. No resulta tan costoso transmitir cualquier tiempo al método (aunque sea cero), pero hemos decidido sobrecargar el método sin especificar el tiempo:

//--- Set the new (1) countdown start time and (2) pause in milliseconds
   void              SetTimeBegin(const ulong time)         { this.m_time_begin=time; this.SetTimeBegin();              }
   void              SetTimeBegin(void)                     { this.m_start=this.TickCount();                            }
   void              SetWaitingMSC(const ulong pause)       { this.m_wait_msc=pause;                                    }

Ahora, podemos crear la clase de objeto de estado del ratón.


Clase de estado del ratón

En la carpeta de funciones y clases de servicio \MQL5\Include\DoEasy\Services\, creamos la nueva clase CMouseState en el archivo MouseState.mqh.

En la sección privada de la clase, declaramos las variables para almacenar los parámetros del objeto, los dos métodos para establecer las banderas de los estados de los botones y teclas del ratón y dejamos una nota sobre la ubicación de los bits de las banderas en la variable ushort para almacenar las banderas de bits del estado del ratón:

//+------------------------------------------------------------------+
//|                                                   MouseState.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "DELib.mqh"
//+------------------------------------------------------------------+
//| Mouse status class                                               |
//+------------------------------------------------------------------+
class CMouseState
  {
private:
   int               m_coord_x;                             // X coordinate
   int               m_coord_y;                             // Y coordinate
   int               m_delta_wheel;                         // Mouse wheel scroll value
   int               m_window_num;                          // Subwindow index
   long              m_chart_id;                            // Chart ID
   ushort            m_state_flags;                         // Status flags
   
//--- Set the status of mouse buttons, as well as of Shift and Ctrl keys
   void              SetButtonKeyState(const int id,const long lparam,const double dparam,const ushort flags);
//--- Set the mouse buttons and keys status flags
   void              SetButtKeyFlags(const short flags);

//--- Data location in the ushort value of the button status
   //-----------------------------------------------------------------
   //   bit    |    byte   |            state            |    dec    |
   //-----------------------------------------------------------------
   //    0     |     0     | left mouse button           |     1     |
   //-----------------------------------------------------------------
   //    1     |     0     | right mouse button          |     2     |
   //-----------------------------------------------------------------
   //    2     |     0     | SHIFT button                |     4     |
   //-----------------------------------------------------------------
   //    3     |     0     | CTRL button                 |     8     |
   //-----------------------------------------------------------------
   //    4     |     0     | middle mouse button         |    16     |
   //-----------------------------------------------------------------
   //    5     |     0     | 1 add. mouse button         |    32     |
   //-----------------------------------------------------------------
   //    6     |     0     | 2 add. mouse button         |    64     |
   //-----------------------------------------------------------------
   //    7     |     0     | scrolling the wheel         |    128    |
   //-----------------------------------------------------------------
   //-----------------------------------------------------------------
   //    0     |     1     | cursor inside the form      |    256    |
   //-----------------------------------------------------------------
   //    1     |     1     | cursor inside active area   |    512    |
   //-----------------------------------------------------------------
   //    2     |     1     | cursor in the control area  |   1024    |
   //-----------------------------------------------------------------
   //    3     |     1     | cursor in the scrolling area|   2048    |
   //-----------------------------------------------------------------
   //    4     |     1     | cursor at the left edge     |   4096    |
   //-----------------------------------------------------------------
   //    5     |     1     | cursor at the bottom edge   |   8192    |
   //-----------------------------------------------------------------
   //    6     |     1     | cursor at the right edge    |   16384   |
   //-----------------------------------------------------------------
   //    7     |     1     | cursor at the top edge      |   32768   |
   //-----------------------------------------------------------------
      
public:

En la sección pública de la clase, escribimos los métodos que retornan los valores de las propiedades del objeto:

public:
//--- Reset the states of all buttons and keys
   void              ResetAll(void);
//--- Set (1) the subwindow index and (2) the chart ID
   void              SetWindowNum(const int wnd_num)           { this.m_window_num=wnd_num;        }
   void              SetChartID(const long id)                 { this.m_chart_id=id;               }
//--- Return the variable with the mouse status flags
   ushort            GetMouseFlags(void)                       { return this.m_state_flags;        }
//--- Return (1-2) the cursor coordinates, (3) scroll wheel value, (4) status of the mouse buttons and Shift/Ctrl keys
   int               CoordX(void)                        const { return this.m_coord_x;            }
   int               CoordY(void)                        const { return this.m_coord_y;            }
   int               DeltaWheel(void)                    const { return this.m_delta_wheel;        }
   ENUM_MOUSE_BUTT_KEY_STATE ButtKeyState(const int id,const long lparam,const double dparam,const string flags);

//--- Return the flag of the clicked (1) left, (2) right, (3) middle, (4) first and (5) second additional mouse buttons
   bool              IsPressedButtonLeft(void)           const { return this.m_state_flags==1;     }
   bool              IsPressedButtonRight(void)          const { return this.m_state_flags==2;     }
   bool              IsPressedButtonMiddle(void)         const { return this.m_state_flags==16;    }
   bool              IsPressedButtonX1(void)             const { return this.m_state_flags==32;    }
   bool              IsPressedButtonX2(void)             const { return this.m_state_flags==64;    }
//--- Return the flag of the pressed (1) Shift, (2) Ctrl, (3) Shift+Ctrl key and the flag of scrolling the mouse wheel
   bool              IsPressedKeyShift(void)             const { return this.m_state_flags==4;     }
   bool              IsPressedKeyCtrl(void)              const { return this.m_state_flags==8;     }
   bool              IsPressedKeyCtrlShift(void)         const { return this.m_state_flags==12;    }
   bool              IsWheel(void)                       const { return this.m_state_flags==128;   }

//--- Return the flag indicating the status of the left mouse button and (1) the mouse wheel, (2) Shift, (3) Ctrl, (4) Ctrl+Shift
   bool              IsPressedButtonLeftWheel(void)      const { return this.m_state_flags==129;   }
   bool              IsPressedButtonLeftShift(void)      const { return this.m_state_flags==5;     }
   bool              IsPressedButtonLeftCtrl(void)       const { return this.m_state_flags==9;     }
   bool              IsPressedButtonLeftCtrlShift(void)  const { return this.m_state_flags==13;    }
//--- Return the flag indicating the status of the right mouse button and (1) the mouse wheel, (2) Shift, (3) Ctrl, (4) Ctrl+Shift
   bool              IsPressedButtonRightWheel(void)     const { return this.m_state_flags==130;   }
   bool              IsPressedButtonRightShift(void)     const { return this.m_state_flags==6;     }
   bool              IsPressedButtonRightCtrl(void)      const { return this.m_state_flags==10;    }
   bool              IsPressedButtonRightCtrlShift(void) const { return this.m_state_flags==14;    }
//--- Return the flag indicating the status of the middle mouse button and (1) the mouse wheel, (2) Shift, (3) Ctrl, (4) Ctrl+Shift
   bool              IsPressedButtonMiddleWheel(void)    const { return this.m_state_flags==144;   }
   bool              IsPressedButtonMiddleShift(void)    const { return this.m_state_flags==20;    }
   bool              IsPressedButtonMiddleCtrl(void)     const { return this.m_state_flags==24;    }
   bool              IsPressedButtonMiddleCtrlShift(void)const { return this.m_state_flags==28;    }

//--- Constructor/destructor
                     CMouseState();
                    ~CMouseState();
  };
//+------------------------------------------------------------------+

Aquí implementamos los métodos que retornan los valores de las variables de clase y algunos métodos que retornan los estados predefinidos de los botones del ratón y las teclas Ctrl y Shift.


En el constructor de la clase, llamamos al método que restablece los estados de las banderas de los botones y las teclas y restablece la magnitud del desplazamiento de la ruleta:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CMouseState::CMouseState() : m_delta_wheel(0),m_coord_x(0),m_coord_y(0),m_window_num(0)
  {
   this.ResetAll();
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CMouseState::~CMouseState()
  {
  }
//+------------------------------------------------------------------+
//| Reset the states of all buttons and keys                         |
//+------------------------------------------------------------------+
void CMouseState::ResetAll(void)
  {
   this.m_delta_wheel = 0;
   this.m_state_flags = 0;
  }
//+------------------------------------------------------------------+

Método que establece el estado de los botonoes del ratón y las teclas Shift y Ctrl:

//+------------------------------------------------------------------+
//| Set the status of mouse buttons, as well as of Shift/Ctrl keys   |
//+------------------------------------------------------------------+
void CMouseState::SetButtonKeyState(const int id,const long lparam,const double dparam,const ushort flags)
  {
   //--- Reset the values of all mouse status bits
   this.ResetAll();
   //--- If a chart or an object is left-clicked
   if(id==CHARTEVENT_CLICK || id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Write the appropriate chart coordinates and set the bit of 0
      this.m_coord_x=(int)lparam;
      this.m_coord_y=(int)dparam;
      this.m_state_flags |=(0x0001);
     }
   //--- otherwise
   else
     {
      //--- in case of a mouse wheel scrolling
      if(id==CHARTEVENT_MOUSE_WHEEL)
        {
         //--- get the cursor coordinates and the total scroll value (the minimum of +120 or -120)
         this.m_coord_x=(int)(short)lparam;
         this.m_coord_y=(int)(short)(lparam>>16);
         this.m_delta_wheel=(int)dparam;
         //--- Call the method of setting flags indicating the states of the mouse buttons and Shift/Ctrl keys
         this.SetButtKeyFlags((short)(lparam>>32));
         //--- and set the bit of 8
         this.m_state_flags &=0xFF7F;
         this.m_state_flags |=(0x0001<<7);
        }
      //--- If this is a cursor movement, write its coordinates and
      //--- call the method of setting flags indicating the states of the mouse buttons and Shift/Ctrl keys
      if(id==CHARTEVENT_MOUSE_MOVE)
        {
         this.m_coord_x=(int)lparam;
         this.m_coord_y=(int)dparam;
         this.SetButtKeyFlags(flags);
        }
     }
  }
//+------------------------------------------------------------------+

Aquí, verificamos qué evento del gráfico estamos procesando.
Primero, ponemos a cero todos los bits de la variable que guarda las banderas de bits del estado del ratón.
Luego, al clicar en el gráfico o el objeto, establecemos el bit 0 de la variable que guarda las banderas de bits.
Si se da el evento de desplazamiento de la ruleta del ratón, el parámetro de tipo entero lparam contendrá los datos sobre las coordenadas del cursor, la magnitud del desplazamiento y las banderas de bits del estado de los botones y las teclas Ctrl y Shift. Extraemos todos los datos de la variable lparam y los escribimos en las variables (guardando las coordenadas del cursor), y en nuestra propia variable con las banderas de bits para que se respete el orden de los bits descritos en la sección privada de la clase. A continuación, configuramos el bit 8, indicando el desplazamiento de la ruleta del ratón.
Al mover el cursor sobre el gráfico, escribimos las coordenadas del cursor en las variables y llamamos al método para establecer las banderas de bits sobre el estado de los botones del ratón y las teclas Ctrl y Shift.

Método que establece las banderas de los estados de los botones y las teclas del ratón:

//+------------------------------------------------------------------+
//| Set the mouse buttons and keys status flags                      |
//+------------------------------------------------------------------+
void CMouseState::SetButtKeyFlags(const short flags)
  {
//--- Left mouse button status
   if((flags & 0x0001)!=0) this.m_state_flags |=(0x0001<<0);
//--- Right mouse button status
   if((flags & 0x0002)!=0) this.m_state_flags |=(0x0001<<1);
//--- SHIFT status
   if((flags & 0x0004)!=0) this.m_state_flags |=(0x0001<<2);
//--- CTRL status
   if((flags & 0x0008)!=0) this.m_state_flags |=(0x0001<<3);
//--- Middle mouse button status
   if((flags & 0x0010)!=0) this.m_state_flags |=(0x0001<<4);
//--- The first additional mouse button status
   if((flags & 0x0020)!=0) this.m_state_flags |=(0x0001<<5);
//--- The second additional mouse button status
   if((flags & 0x0040)!=0) this.m_state_flags |=(0x0001<<6);
  }
//+------------------------------------------------------------------+

Aquí, todo es simple: transmitimos al método la variable con las banderas del estado del ratón. Por turno, le superponemos una máscara de bits con el bit establecido comprobado. El valor obtenido tras aplicar la máscara de bits usando el "Y" bit a bit será verdadero solo si ambos bits comprobados están establecidos (1). Si la variable con la máscara superpuesta no es igual a cero (el bit comprobado ha sido establecido), entonces escribimos el bit correspondiente en la variable para almacenar los indicadores de bit.

Método que retorna el estado de los botones del ratón y las teclas Shift y Ctrl:

//+------------------------------------------------------------------+
//| Return the mouse buttons and Shift/Ctrl keys states              |
//+------------------------------------------------------------------+
ENUM_MOUSE_BUTT_KEY_STATE CMouseState::ButtKeyState(const int id,const long lparam,const double dparam,const string flags)
  {
   this.SetButtonKeyState(id,lparam,dparam,(ushort)flags);
   return (ENUM_MOUSE_BUTT_KEY_STATE)this.m_state_flags;
  }
//+------------------------------------------------------------------+

Aquí, primero llamamos al método que verifica y establece todas las banderas de estado del ratón y las teclas Ctrl y Shift, y luego retornamos el valor de la variable m_state_flags como enumeración ENUM_MOUSE_BUTT_KEY_STATE. En esta enumeración, los valores de todas las constantes se corresponden con el valor obtenido por el conjunto de bits establecidos de la variable. En consecuencia, retornamos inmediatamente uno de los valores de la enumeración, que procesaremos a continuación en las clases donde sea necesario para obtener el estado del ratón, sus botones y las teclas Ctrl y Shift. Este método se llama desde el manejador OnChartEvent().


Clase de objeto básico de todos los elementos gráficos de la biblioteca

De la misma forma que las clases principales de la biblioteca que heredamos de la clase básica de la biblioteca estándar, todas las clases de objetos de elementos gráficos deben heredar de ella. Dicha herencia nos permitirá trabajar con cada objeto gráfico como si fuera un objeto MQL5 estándar, es decir, para nosotros es importante poder trabajar con diferentes tipos de objetos gráficos de la misma forma que con un objeto de la clase CObject. Para conseguirlo, necesitaremos crear un nuevo objeto básico que heredará del objeto CObject y que contendrá las variables y métodos comunes para cada objeto gráfico (y cualquier objeto) de la biblioteca.

Las propiedades generales propias de cada objeto gráfico, e incluidas en el objeto gráfico básico, serán:

  • las coordenadas de ubicación del objeto en el gráfico;
  • la anchura y altura del elemento (lienzo) en el que se ubicarán los otros elementos de los objetos compuestos (que tendrán exactamente las mismas propiedades comunes a todos los objetos);
  • las coordenadas de los bordes derecho e inferior del lienzo (los bordes izquierdo y superior se corresponden con las coordenadas);
  • diferentes identificadores de objetos (su tipo, nombre e identificador de gráfico y subventana);
  • algunas banderas adicionales que especificarán el comportamiento del objeto al interactuar con él.

La clase será muy simple, contendrá: las variables privadas, los métodos protegidos de ajuste y los métodos públicos para retornar sus valores.
La clase heredará de la clase básica de la biblioteca estándar CObject.

En el directorio de la biblioteca \MQL5\Include\DoEasy\Objects\, creamos una nueva carpeta Graph\, y en ella, un nuevo archivo GBaseObj.mqh de la clase CGBaseObj:

//+------------------------------------------------------------------+
//|                                                     GBaseObj.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\..\Services\DELib.mqh"
//+------------------------------------------------------------------+
//| Class of the base object of the library graphical objects        |
//+------------------------------------------------------------------+
class CGBaseObj : public CObject
  {
private:
   int               m_type;                                // Object type
   string            m_name_obj;                            // Object name
   long              m_chart_id;                            // Chart ID
   int               m_wnd_num;                             // Chart subwindow index
   int               m_coord_x;                             // Canvas X coordinate
   int               m_coord_y;                             // Canvas Y coordinate
   int               m_width;                               // Width
   int               m_height;                              // Height
   bool              m_movable;                             // Object movability flag
   bool              m_selectable;                          // Object selectability flag

protected:
//--- Set the values to class variables
   void              SetNameObj(const string name)             { this.m_name_obj=name;                            }
   void              SetChartID(const long chart_id)           { this.m_chart_id=chart_id;                        }
   void              SetWindowNum(const int wnd_num)           { this.m_wnd_num=wnd_num;                          }
   void              SetCoordX(const int coord_x)              { this.m_coord_x=coord_x;                          }
   void              SetCoordY(const int coord_y)              { this.m_coord_y=coord_y;                          }
   void              SetWidth(const int width)                 { this.m_width=width;                              }
   void              SetHeight(const int height)               { this.m_height=height;                            }
   void              SetMovable(const bool flag)               { this.m_movable=flag;                             }
   void              SetSelectable(const bool flag)            { this.m_selectable=flag;                          }
   
public:
//--- Return the values of class variables
   string            NameObj(void)                       const { return this.m_name_obj;                          }
   long              ChartID(void)                       const { return this.m_chart_id;                          }
   int               WindowNum(void)                     const { return this.m_wnd_num;                           }
   int               CoordX(void)                        const { return this.m_coord_x;                           }
   int               CoordY(void)                        const { return this.m_coord_y;                           }
   int               Width(void)                         const { return this.m_width;                             }
   int               Height(void)                        const { return this.m_height;                            }
   int               RightEdge(void)                     const { return this.m_coord_x+this.m_width;              }
   int               BottomEdge(void)                    const { return this.m_coord_y+this.m_height;             }
   bool              Movable(void)                       const { return this.m_movable;                           }
   bool              Selectable(void)                    const { return this.m_selectable;                        }
   
//--- The virtual method returning the object type
   virtual int       Type(void)                          const { return this.m_type;                              }

//--- Constructor/destructor
                     CGBaseObj();
                    ~CGBaseObj();
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CGBaseObj::CGBaseObj() : m_chart_id(::ChartID()),
                         m_type(WRONG_VALUE),
                         m_wnd_num(0),
                         m_coord_x(0),
                         m_coord_y(0),
                         m_width(0),
                         m_height(0),
                         m_movable(false),
                         m_selectable(false)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CGBaseObj::~CGBaseObj()
  {
  }
//+------------------------------------------------------------------+

La clase de objeto básico CObject implementa un método virtual Type() que retorna el tipo de objeto (para identificar los objetos según su tipo). El método original siempre retorna cero:

   //--- method of identifying the object
   virtual int       Type(void)                                    const { return(0);      }

Redefiniendo este método en los descendientes, retornaremos desde cada objeto el tipo establecido para el mismo en la variable m_type.
Los tipos de objetos gráficos se establecerán en artículos posteriores, al crear las clases de estos objetos. Entre tanto, el método retornará -1 (este valor lo establecemos en la lista de inicialización del constructor de clases).


Clase de objeto de formulario de los elementos gráficos

Y ahora, llegamos a la creación de la clase del objeto de formulario. El objeto de formulario será la base para crear el resto de clases de los elementos gráficos de la biblioteca basados ​​en la clase CCanvas. Actuará como un "lienzo" sobre el que dibujaremos los datos necesarios para los diferentes objetos y colocaremos el resto de elementos, cuya adición finalmente mostrará el objeto terminado.

Pero, por el momento (por hoy), supondrá solo un formulario simple con parámetros básicos y una funcionalidad básica (la capacidad de establecer un área activa que sirva para interactuar con el cursor); asimismo, poseerá la capacidad de ser desplazado por el gráfico.

En la carpeta de la biblioteca \MQL5\Include\DoEasy\Objects\Graph\, creamos un nuevo archivo Form.mqh de la clase CForm.
La clase deberá heredar del objeto básico de todos los gráficos de la biblioteca. En consecuencia, deberán estar conectados a él los archivos de clase del objeto gráfico básico y la clase del objeto de propiedades del ratón:

//+------------------------------------------------------------------+
//|                                                         Form.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Canvas\Canvas.mqh>
#include "GBaseObj.mqh"
#include "..\..\Services\MouseState.mqh"
//+------------------------------------------------------------------+
//| Class of the base object of the library graphical objects        |
//+------------------------------------------------------------------+
class CForm : public CGBaseObj
  {
  }

En la sección protegida de la clase , declaramos el objeto de la biblioteca estándar CCanvas, los objetos de la biblioteca CPause y CMouseState, la variable para guardar el valor de los estados del ratón, la variable para guardar las banderas de bits del estado del ratón y las variables para guardar las propiedades del objeto:

//+------------------------------------------------------------------+
//| Class of the base object of the library graphical objects        |
//+------------------------------------------------------------------+
class CForm : public CGBaseObj
  {
protected:
   CCanvas              m_canvas;                              // CCanvas class object
   CPause               m_pause;                               // Pause class object
   CMouseState          m_mouse;                               // "Mouse status" class object
   ENUM_MOUSE_FORM_STATE m_mouse_state;                        // Mouse status relative to the form
   ushort               m_mouse_state_flags;                   // Mouse status flags
   
   int                  m_act_area_left;                       // Left border of the active area (offset from the left border inward)
   int                  m_act_area_right;                      // Right border of the active area (offset from the right border inward)
   int                  m_act_area_top;                        // Upper border of the active area (offset from the upper border inward)
   int                  m_act_area_bottom;                     // Lower border of the active area (offset from the lower border inward)
   uchar                m_opacity;                             // Opacity
   int                  m_shift_y;                             // Y coordinate shift for the subwindow
   
private:

En la sección privada de la clase, declaramos los métodos auxiliares para el funcionamiento de la clase:

private:
//--- Set and return the flags indicating the states of mouse buttons and Shift/Ctrl keys
   ENUM_MOUSE_BUTT_KEY_STATE MouseButtonKeyState(const int id,const long lparam,const double dparam,const string sparam)
                       {
                        return this.m_mouse.ButtKeyState(id,lparam,dparam,sparam);
                       }
//--- Return the cursor position relative to the (1) form and (2) active area
   bool              CursorInsideForm(const int x,const int y);
   bool              CursorInsideActiveArea(const int x,const int y);

public:

El método MouseButtonKeyState() retorna el valor devuelto por el método del mismo nombre desde el objeto de clase de estados del ratón; los otros dos métodos son necesarios para determinar la posición del cursor del ratón respecto al formulario y al área activa del formulario. Los analizaremos un poco más tarde.

La sección pública de la clase contiene los métodos necesarios para crear un formulario y establecer y retornar sus parámetros:

public:
//--- Create a form
   bool              CreateForm(const long chart_id,
                                const int wnd_num,
                                const string name,
                                const int x,
                                const int y,
                                const int w,
                                const int h,
                                const color colour,
                                const uchar opacity,
                                const bool movable=true,
                                const bool selectable=true);
                                
//--- Return the pointer to a canvas object
   CCanvas          *CanvasObj(void)                           { return &this.m_canvas;                           }
//--- Set (1) the form update frequency, (2) the movability flag and (3) selectability flag for interaction
   void              SetFrequency(const ulong value)           { this.m_pause.SetWaitingMSC(value);               }
   void              SetMovable(const bool flag)               { CGBaseObj::SetMovable(flag);                     }
   void              SetSelectable(const bool flag)            { CGBaseObj::SetSelectable(flag);                  }
//--- Update the form coordinates (shift the form)
   bool              Move(const int x,const int y,const bool redraw=false);
   
//--- Return the mouse status relative to the form
   ENUM_MOUSE_FORM_STATE MouseFormState(const int id,const long lparam,const double dparam,const string sparam);
//--- Return the flag of the clicked (1) left, (2) right, (3) middle, (4) first and (5) second additional mouse buttons
   bool              IsPressedButtonLeftOnly(void)             { return this.m_mouse.IsPressedButtonLeft();       }
   bool              IsPressedButtonRightOnly(void)            { return this.m_mouse.IsPressedButtonRight();      }
   bool              IsPressedButtonMiddleOnly(void)           { return this.m_mouse.IsPressedButtonMiddle();     }
   bool              IsPressedButtonX1Only(void)               { return this.m_mouse.IsPressedButtonX1();         }
   bool              IsPressedButtonX2Only(void)               { return this.m_mouse.IsPressedButtonX2();         }
//--- Return the flag of the pressed (1) Shift and (2) Ctrl key
   bool              IsPressedKeyShiftOnly(void)               { return this.m_mouse.IsPressedKeyShift();         }
   bool              IsPressedKeyCtrlOnly(void)                { return this.m_mouse.IsPressedKeyCtrl();          }
   
//--- Set the shift of the (1) left, (2) top, (3) right, (4) bottom edge of the active area relative to the form,
//--- (5) all shifts of the active area edges relative to the form and (6) the form opacity
   void              SetActiveAreaLeftShift(const int value)   { this.m_act_area_left=fabs(value);                }
   void              SetActiveAreaRightShift(const int value)  { this.m_act_area_right=fabs(value);               }
   void              SetActiveAreaTopShift(const int value)    { this.m_act_area_top=fabs(value);                 }
   void              SetActiveAreaBottomShift(const int value) { this.m_act_area_bottom=fabs(value);              }
   void              SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift);
   void              SetOpacity(const uchar value)             { this.m_opacity=value;                            }
//--- Return the coordinate (1) of the left, (2) right, (3) top and (4) bottom edge of the form active area
   int               ActiveAreaLeft(void)                const { return this.CoordX()+this.m_act_area_left;       }
   int               ActiveAreaRight(void)               const { return this.RightEdge()-this.m_act_area_right;   }
   int               ActiveAreaTop(void)                 const { return this.CoordY()+this.m_act_area_top;        }
   int               ActiveAreaBottom(void)              const { return this.BottomEdge()-this.m_act_area_bottom; }
//--- Return (1) the form opacity, coordinate (2) of the right and (3) bottom form edge
   uchar             Opacity(void)                       const { return this.m_opacity;                           }
   int               RightEdge(void)                     const { return CGBaseObj::RightEdge();                   }
   int               BottomEdge(void)                    const { return CGBaseObj::BottomEdge();                  }

//--- Event handler
   void              OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);

//--- Constructors/Destructor
                     CForm(const long chart_id,
                           const int wnd_num,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h,
                           const color colour,
                           const uchar opacity,
                           const bool movable=true,
                           const bool selectable=true);
                     CForm(){;}
                    ~CForm();
  };
//+------------------------------------------------------------------+

Vamos a echar un vistazo a los métodos de la clase.

En el constructor paramétrico, creamos un objeto de formulario con los parámetros transmitidos ​al constructor:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CForm::CForm(const long chart_id,
             const int wnd_num,
             const string name,
             const int x,
             const int y,
             const int w,
             const int h,
             const color colour,
             const uchar opacity,
             const bool movable=true,
             const bool selectable=true) : m_act_area_bottom(0),
                                           m_act_area_left(0),
                                           m_act_area_right(0),
                                           m_act_area_top(0),
                                           m_mouse_state(0),
                                           m_mouse_state_flags(0)
                                          
  {
   if(this.CreateForm(chart_id,wnd_num,name,x,y,w,h,colour,opacity,movable,selectable))
     {
      this.m_shift_y=(int)::ChartGetInteger(chart_id,CHART_WINDOW_YDISTANCE,wnd_num);
      this.SetWindowNum(wnd_num);
      this.m_pause.SetWaitingMSC(PAUSE_FOR_CANV_UPDATE);
      this.m_pause.SetTimeBegin();
      this.m_mouse.SetChartID(chart_id);
      this.m_mouse.SetWindowNum(wnd_num);
      this.m_mouse.ResetAll();
      this.m_mouse_state_flags=0;
      CGBaseObj::SetMovable(movable);
      CGBaseObj::SetSelectable(selectable);
      this.SetOpacity(opacity);
     }
  }
//+------------------------------------------------------------------+

Aquí, primero inicializamos todas las variables en la lista de inicialización del constructor. A continuación, llamamos al método para crear el formulario, y si el formulario se ha creado con éxito, establecemos los parámetros transmitidos ​​al constructor del objeto.

En el destructor de la clase, eliminamos el objeto gráfico creado:

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CForm::~CForm()
  {
   ::ObjectsDeleteAll(this.ChartID(),this.NameObj());
  }
//+------------------------------------------------------------------+


Método que crea un objeto de formulario gráfico:

//+------------------------------------------------------------------+
//| Create the graphical form object                                 |
//+------------------------------------------------------------------+
bool CForm::CreateForm(const long chart_id,
                       const int wnd_num,
                       const string name,
                       const int x,
                       const int y,
                       const int w,
                       const int h,
                       const color colour,
                       const uchar opacity,
                       const bool movable=true,
                       const bool selectable=true)
  {
   if(this.m_canvas.CreateBitmapLabel(chart_id,wnd_num,name,x,y,w,h,COLOR_FORMAT_ARGB_NORMALIZE))
     {
      this.SetChartID(chart_id);
      this.SetWindowNum(wnd_num);
      this.SetNameObj(name);
      this.SetCoordX(x);
      this.SetCoordY(y);
      this.SetWidth(w);
      this.SetHeight(h);
      this.SetActiveAreaLeftShift(1);
      this.SetActiveAreaRightShift(1);
      this.SetActiveAreaTopShift(1);
      this.SetActiveAreaBottomShift(1);
      this.SetOpacity(opacity);
      this.SetMovable(movable);
      this.SetSelectable(selectable);
      this.m_canvas.Erase(::ColorToARGB(colour,this.Opacity()));
      this.m_canvas.Update();
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

Con la ayuda del método CreateBitmapLabel() de la clase CCanvas, creamos un recurso gráfico usando el identificador del gráfico y el número de subventana (el segundo formulario de llamada al método). Si creamos correctamente el recurso gráfico, estableceremos en el objeto de formulario todos los parámetros transmitidos ​al método; a continuación, rellenaremos el formulario con color y configuraremos la opacidad usando el método Erase(), mostrando después los cambios en la pantalla con la ayuda del método Update().

Queremos hacer algunas aclaraciones respecto al término "opacidad", o densidad de color. La clase CCanvas nos permite establecer el nivel de transparencia de sus objetos. En este caso, un valor de 0 será un color completamente transparente, mientras que un valor de 255, será un color completamente opaco. Resulta, por así decirlo, al revés. Así que hemos decidido utilizar el término "opacidad", ya que los valores 0-255 se corresponden exactamente con un aumento en la densidad del color que va desde cero (completamente transparente) a 255 (completamente opaco).

Manejador de eventos de clase CForm:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CForm::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Get the status of mouse buttons, Shift/Ctrl keys and the state of a mouse relative to the form
   ENUM_MOUSE_BUTT_KEY_STATE mouse_state=this.m_mouse.ButtKeyState(id,lparam,dparam,sparam);
   this.m_mouse_state=this.MouseFormState(id,lparam,dparam-this.m_shift_y,sparam);
//--- Initialize the difference between X and Y coordinates of the form and cursor
   static int diff_x=0;
   static int diff_y=0;
//--- In case of a chart change event, recalculate the shift by Y for the subwindow
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      this.m_shift_y=(int)::ChartGetInteger(this.ChartID(),CHART_WINDOW_YDISTANCE,this.WindowNum());
     }
//--- If the cursor is inside the form, disable chart scrolling, context menu and Crosshair tool
   if((this.m_mouse_state_flags & 0x0100)!=0)
     {
      ::ChartSetInteger(this.ChartID(),CHART_MOUSE_SCROLL,false);
      ::ChartSetInteger(this.ChartID(),CHART_CONTEXT_MENU,false);
      ::ChartSetInteger(this.ChartID(),CHART_CROSSHAIR_TOOL,false);
     }
//--- Otherwise, if the cursor is outside the form, allow chart scrolling, context menu and Crosshair tool
   else
     {
      ::ChartSetInteger(this.ChartID(),CHART_MOUSE_SCROLL,true);
      ::ChartSetInteger(this.ChartID(),CHART_CONTEXT_MENU,true);
      ::ChartSetInteger(this.ChartID(),CHART_CROSSHAIR_TOOL,true);
     }
//--- If the mouse movement event and the cursor are located in the form active area
   if(id==CHARTEVENT_MOUSE_MOVE && m_mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED)
     {
      //--- If only the left mouse button is being held and the form is moved,
      //--- set the new parameters of moving the form relative to the cursor
      if(IsPressedButtonLeftOnly() && this.Move(this.m_mouse.CoordX()-diff_x,this.m_mouse.CoordY()-diff_y))
        {
         diff_x=this.m_mouse.CoordX()-this.CoordX();
         diff_y=this.m_mouse.CoordY()-this.CoordY();
        }
     }
//--- In any other cases, set the parameters of shifting the form relative to the cursor
   else
     {
      diff_x=this.m_mouse.CoordX()-this.CoordX();
      diff_y=this.m_mouse.CoordY()-this.CoordY();
     }
//--- Test display of mouse states on the chart
   Comment(EnumToString(mouse_state),"\n",EnumToString(this.m_mouse_state));
  }
//+------------------------------------------------------------------+

La lógica al completo se explica en los comentarios del listado de códigos. El método debe llamarse desde el manejador estándar OnChartEvent() del programa y tiene exactamente los mismos parámetros.

Vamos a decir algunas palabras sobre el cálculo resaltado transmitido al método MouseFormState(). Si nuestro formulario se ubica en la ventana principal del gráfico, el valor de la variable m_shift_y será igual a cero y la expresión dparam-this.m_shift_y retornará la coordenada Y exacta del cursor. Pero si el formulario se ubica en una subventana de gráfico, el cambio en la variable m_shift_y será mayor que cero, para ajustar la coordenada Y del cursor a las coordenadas de la subventana. Por consiguiente, también necesitaremos transmitir el valor de la coordenada Y con el desplazamiento indicado en la variable m_shift_y a los métodos para calcular las coordenadas del cursor. De lo contrario, las coordenadas del objeto indicarán un lugar superior al real, igual al número de píxeles de desplazamiento especificado en esta variable.

Método que retorna la posición del cursor respecto al formulario:

//+------------------------------------------------------------------+
//| Return the cursor position relative to the form                  |
//+------------------------------------------------------------------+
bool CForm::CursorInsideForm(const int x,const int y)
  {
   return(x>=this.CoordX() && x<this.RightEdge() && y>=this.CoordY() && y<=this.BottomEdge());
  }
//+------------------------------------------------------------------+

Las coordenadas X e Y del cursor son transmitidas al método.

Si

  • (la coordenada X del cursor es mayor o igual que la coordenada X del formulario y la coordenada X del cursor es menor o igual que la coordenada del borde derecho del formulario) y
  • (la coordenada Y del cursor es mayor o igual que la coordenada Y del formulario y la coordenada Y del cursor es menor o igual que la coordenada de la parte inferior del formulario)

... se retornará true: el cursor está dentro del objeto de formulario.

Método que retorna la posición del cursor respecto al área activa del formulario:

//+------------------------------------------------------------------+
//| Return the cursor position relative to the form active area      |
//+------------------------------------------------------------------+
bool CForm::CursorInsideActiveArea(const int x,const int y)
  {
   return(x>=this.ActiveAreaLeft() && x<this.ActiveAreaRight() && y>=this.ActiveAreaTop() && y<=this.ActiveAreaBottom());
  }
//+------------------------------------------------------------------+

Las coordenadas X e Y del cursor son transmitidas al método.

Si

  • (la coordenada X del cursor es mayor o igual que la coordenada X del área activa del formulario y la coordenada X del cursor es menor o igual que la coordenada del borde derecho del área activa del formulario) y
  • (la coordenada Y del cursor es mayor o igual que la coordenada Y del área activa del formulario y la coordenada Y del cursor es menor o igual que la coordenada del borde inferior del área activa del formulario)

... se retornará true: el cursor está dentro del área activa del objeto de formulario.

Método que retorna el estado del ratón respecto al formulario:

//+------------------------------------------------------------------+
//| Return the mouse status relative to the form                     |
//+------------------------------------------------------------------+
ENUM_MOUSE_FORM_STATE CForm::MouseFormState(const int id,const long lparam,const double dparam,const string sparam)
  {
//--- Get the mouse status relative to the form, as well as the states of mouse buttons and Shift/Ctrl keys
   ENUM_MOUSE_FORM_STATE form_state=MOUSE_FORM_STATE_NONE;
   ENUM_MOUSE_BUTT_KEY_STATE state=this.MouseButtonKeyState(id,lparam,dparam,sparam);
//--- Get the mouse status flags from the CMouseState class object and save them in the variable
   this.m_mouse_state_flags=this.m_mouse.GetMouseFlags();
//--- If the cursor is inside the form
   if(this.CursorInsideForm(m_mouse.CoordX(),m_mouse.CoordY()))
     {
      //--- Set bit 8 responsible for the "cursor inside the form" flag
      this.m_mouse_state_flags |= (0x0001<<8);
      //--- If the cursor is inside the active area, set bit 9 "cursor inside the active area"
      if(CursorInsideActiveArea(m_mouse.CoordX(),m_mouse.CoordY()))
         this.m_mouse_state_flags |= (0x0001<<9);
      //--- otherwise, release the bit "cursor inside the active area"
      else this.m_mouse_state_flags &=0xFDFF;
      //--- If one of the mouse buttons is clicked, check the cursor location in the active area and
      //--- return the appropriate value of the pressed key (in the active area or the form area)
      if((this.m_mouse_state_flags & 0x0001)!=0 || (this.m_mouse_state_flags & 0x0002)!=0 || (this.m_mouse_state_flags & 0x0010)!=0)
         form_state=((m_mouse_state_flags & 0x0200)!=0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : MOUSE_FORM_STATE_INSIDE_PRESSED);
      //--- otherwise, check the cursor location in the active area and
      //--- return the appropriate value of the unpressed key (in the active area or the form area)
      else
         form_state=((m_mouse_state_flags & 0x0200)!=0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : MOUSE_FORM_STATE_INSIDE_NOT_PRESSED);
     }
   return form_state;
  }
//+------------------------------------------------------------------+

Cada línea de código se explica en los comentarios al mismo. Resumiendo: obtenemos el estado del ratón ya preparado desde el objeto de clase de estado del ratón y lo escribimos en la variable m_mouse_state_flags. Además, dependiendo de la ubicación del cursor respecto al formulario, simplemente complementaremos las banderas de bits del estado del ratón con los nuevos datos y retornaremos el estado del ratón en el formato de enumeración ENUM_MOUSE_FORM_STATE, que ya analizamos anteriormente al inicio del artículo.

Método que actualiza las coordenadas del formulario (desplaza el formulario en el gráfico):

//+------------------------------------------------------------------+
//| Update the form coordinates                                      |
//+------------------------------------------------------------------+
bool CForm::Move(const int x,const int y,const bool redraw=false)
  {
//--- If the form is not movable, leave
   if(!this.Movable())
      return false;
//--- If new values are successfully set into graphical object properties
   if(::ObjectSetInteger(this.ChartID(),this.NameObj(),OBJPROP_XDISTANCE,x) &&
      ::ObjectSetInteger(this.ChartID(),this.NameObj(),OBJPROP_YDISTANCE,y))
     {
      //--- set the new values of X and Y coordinate properties
      this.SetCoordX(x);
      this.SetCoordY(y);
      //--- If the update flag is activated, redraw the chart.
      if(redraw)
         ::ChartRedraw(this.ChartID());
      //--- Return 'true'
      return true;
     }
//--- Something is wrong...
   return false;
  }
//+------------------------------------------------------------------+

Las coordenadas se transmiten al método al que queremos desplazar el objeto de formulario. Si logramos establecer los nuevos parámetros de las coordenadas para el objeto gráfico del formulario, escribiremos estas coordenadas en las propiedades del objeto y dibujaremos de nuevo el gráfico solo si se activa la bandera de redibujado, que también se transmite al método. El redibujado según el valor de la bandera es necesario para no volver a dibujar el gráfico muchas veces si el objeto gráfico consta de muchos formularios. En este caso, primero deberemos trasladar todos los formularios del objeto y luego, cuando cada formulario haya recibido las nuevas coordenadas, actualizaremos el gráfico una vez.

Método para establecer todos los desplazamientos del área activa respecto al formulario.

//+------------------------------------------------------------------+
//| Set all shifts of the active area relative to the form           |
//+------------------------------------------------------------------+
void CForm::SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift)
  {
   this.SetActiveAreaLeftShift(left_shift);
   this.SetActiveAreaBottomShift(bottom_shift);
   this.SetActiveAreaRightShift(right_shift);
   this.SetActiveAreaTopShift(top_shift);
  }
//+------------------------------------------------------------------+

Ya tenemos los métodos necesarios para establecer los bordes del área activa. No obstante, a veces queremos establecer todos los límites en una llamada a un método. Exactamente de eso se encarga este método: establece nuevos valores para el desplazamiento de los límites del área activa desde los bordes del formulario usando las llamadas de los métodos correspondientes.

Con esto, podemos dar por finalizada la creación de la primera versión del objeto de formulario. Vamos a poner a prueba lo que hemos obtenido.

Simulación

Para la prueba, crearemos un objeto de formulario en el gráfico e intentaremos desplazarlo con el cursor. Al mismo tiempo, mostraremos el estado de los botones del ratón y las teclas Ctrl y Shift en el gráfico en los comentarios, así como el estado del cursor en relación con el formulario y los límites de su zona activa.

Creamos en la nueva carpeta \MQL5\Experts\TestDoEasy\Part73\ el nuevo archivo del asesor TestDoEasyPart73.mq5.

Al crear el archivo del asesor, indicaremos que necesitamos la variable de entrada InpMovable con el tipo bool y un valor inicial igual a true:


A continuación, indicaremos que necesitamos un manejador OnChartEvent() adicional:

Como resultado, obtendremos el siguiente asesor experto:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart73.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- input parameters
input bool     InpMovable=true;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   
  }
//+------------------------------------------------------------------+

Incluimos la clase del objeto de formulario recién creado en el archivo del asesor experto y declaramos dos variables globales, a saber, el prefijo de los nombres de los objetos y el objeto de la clase CForm:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart73.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Objects\Graph\Form.mqh>
//--- input parameters
sinput   bool  InpMovable  = true;  // Movable flag
//--- global variables
string         prefix;
CForm          form;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+

En el controlador OnInit(), activamos el permiso para enviar los eventos de desplazamiento del cursor y el scrolling de la ruleta del ratón, establecemos el valor para el prefijo de los nombres de objeto como (nombre de archivo) + "_" y creamos un objeto de formulario en el gráfico. Después de crearlo, establecemos márgenes de 10 píxeles para los bordes de la zona activa:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set the permissions to send cursor movement and mouse scroll events
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
//--- Set EA global variables
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
//--- If the form is created, set an active area for it with the offset of 10 pixels from the edges
   if(form.CreateForm(ChartID(),0,prefix+"Form_01",300,20,100,70,clrSilver,200,InpMovable))
     {
      form.SetActiveAreaShift(10,10,10,10);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Ahora, nos queda llamar desde el manejador OnChartEvent() del asesor el manejador OnChartEvent() del objeto de formulario:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   form.OnChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

Compilamos el asesor y lo ejecutamos en el gráfico del símbolo:


Como podemos ver, el estado de los botones y el cursor se muestra correctamente. El objeto de formulario se desplaza solo si lo arrastramos con el ratón por el área de su zona activa.

Cuando clicamos en los botones central y derecho del ratón dentro del formulario, el menú contextual y la herramienta de cruceta no se activan. También hay un artefacto divertido: si activamos la herramienta de cruceta fuera de la ventana y luego nos desplazamos con ella (con el botón izquierdo del ratón presionado) al área activa del formulario, entonces comenzará a desplazarse. Es un comportamiento incorrecto. Pero esto es solo el comienzo. En artículos posteriores, finalizaremos todo y añadiremos nueva funcionalidad al objeto de formulario.

¿Qué es lo próximo?

En el siguiente artículo, continuaremos desarrollando la clase de objeto de formulario.

Más abajo se adjuntan todos los archivos de la versión actual de la biblioteca y el archivo del asesor de prueba para MQL5. Puede descargarlo todo y ponerlo a prueba por sí mismo.
Si tiene preguntas, observaciones o sugerencias, podrá concretarlas en los comentarios al artículo.

Volver al contenido

*Artículo final de la serie anterior:

Otras clases en la biblioteca DoEasy (Parte 70): Seguimiento y registro de parámetros de los objetos de gráfico en la colección

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/9442

Archivos adjuntos |
MQL5.zip (3954.84 KB)
Combinatoria y teoría de la probabilidad en el trading (Parte I): Fundamentos Combinatoria y teoría de la probabilidad en el trading (Parte I): Fundamentos
En esta serie de artículos, buscaremos una aplicación práctica de la teoría de probabilidad para describir el proceso del trading y la fijación de los precios. En el primer artículo, nos familiarizaremos con los conceptos básicos de la combinatoria y la teoría de probabilidad, y analizaremos el primer ejemplo de la aplicación de fractales dentro de la teoría de probabilidad.
Otras clases en la biblioteca DoEasy (Parte 72): Seguimiento y registro de parámetros de los objetos de gráfico en la colección Otras clases en la biblioteca DoEasy (Parte 72): Seguimiento y registro de parámetros de los objetos de gráfico en la colección
En el presente artículo, finalizaremos el trabajo con las clases de los objetos de gráfico y sus colecciones. Implementaremos el seguimiento automático del cambio de las propiedades de los gráficos y sus ventanas, y también el almacenamiento de los parámetros en las propiedades del objeto. Estas mejoras nos permitirán en el futuro crear una funcionalidad de eventos para la colección de gráficos al completo.
Gráficos en la biblioteca DoEasy (Parte 74): Elemento gráfico básico sobre la clase CCanvas Gráficos en la biblioteca DoEasy (Parte 74): Elemento gráfico básico sobre la clase CCanvas
En esta ocasión, vamos a revisar el concepto de construcción de objetos gráficos del artículo anterior y a preparar una clase básica para todos los objetos gráficos de la biblioteca creados sobre la base de la clase CCanvas de la Biblioteca Estándar.
Consejos de un programador profesional (parte II): Organizando el almacenamiento y el intercambio de parámetros entre el experto, los scripts y los programas externos Consejos de un programador profesional (parte II): Organizando el almacenamiento y el intercambio de parámetros entre el experto, los scripts y los programas externos
Consejos de un programador profesional sobre métodos, técnicas y herramientas auxiliares para facilitar la programación. En esta ocasión, hablaremos de los parámetros que podemos restaurar tras reiniciar (cerrar) el terminal. Todos los ejemplos son en realidad trozos del código operativo del proyecto Cayman del propio autor.