
Gráficos en la biblioteca DoEasy (Parte 73): Objeto de formulario del elemento gráfico
Contenido
- Concepto
- Mejorando las clases de la biblioteca
- Clase de estado del ratón
- Clase de objeto básico de todos los elementos gráficos de la biblioteca
- Clase de objeto de formulario de los elementos gráficos
- Simulación
- ¿Qué es lo próximo?
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í:
- 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.
- 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.
*Artículo final de la serie anterior:
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/9442





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso