English Русский 中文 Deutsch 日本語 Português
preview
Mejore sus gráficos comerciales con una GUI interactiva basada en MQL5 (Parte I): Interfaz móvil (I)

Mejore sus gráficos comerciales con una GUI interactiva basada en MQL5 (Parte I): Interfaz móvil (I)

MetaTrader 5Trading | 21 septiembre 2023, 10:57
424 0
Kailash Bai Mina
Kailash Bai Mina

Introducción

¡Bienvenido al apasionante mundo de las interfaces gráficas móviles en MQL5! Esta guía está diseñada para ofrecerle los conocimientos necesarios para crear una GUI dinámica e interactiva que mejorará sus estrategias comerciales. Comenzaremos con el concepto básico de eventos gráficos: el mecanismo que controla la interactividad de nuestra GUI. Una vez establecida dicha base, lo guiaremos en la creación de su primera GUI móvil.

En la siguiente parte, crearemos múltiples GUI en un solo gráfico (sin copiar y pegar elementos en plan básico) y mejoraremos nuestra GUI añadiendo y personalizando varios elementos, adaptándolos a sus necesidades únicas. Para aquellos que quieran sumergirse a fondo en el tema y crear una interfaz móvil, hemos creado una guía simplificada que ofrece las etapas rápidas.

Al final de este viaje, habrá adquirido la valiosa habilidad de crear y gestionar una interfaz gráfica móvil en MQL5, una potente herramienta en el arsenal de cualquier tráder. Si tiene poco tiempo, disponemos de una sección de tutoriales rápidos para aquellos que quieran comenzar directamente a crear una GUI móvil.

El plan de acción será el siguiente:


Decodificación de eventos gráficos: Bloques de construcción de una GUI móvil

Por el momento, el código del asesor se verá así (es decir, este es un asesor absolutamente básico):

Se preguntará ¿por qué un asesor y no un indicador? La razón es que los asesores son más fáciles de entender para la mayoría de las personas, mientras que los indicadores pueden resultar confusos, pero se pueden seguir los mismos pasos en un indicador.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
   {
//---
    
//---
    return(INIT_SUCCEEDED);
   }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
   {
//---

   }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
   {
//---

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

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

Para comprender mejor OnChartEvent, lo compararemos con otra función predefinida que ya conocemos del asesor básico anterior con OnChartEvent().

Funciones predefinidas Propósito
OnInit()
Se ejecuta tras la inicialización, es decir, el asesor se inicializa (adjunta) al gráfico. Se ejecuta solo una vez
OnTick()
Se activa con un tick entrante, es decir, cuando un símbolo en el gráfico recibe un tick del bróker. Tick ​​significa la actualización del precio
OnDeinit() Se inicia tras la desinicialización, es decir, cuando el asesor se desinicializa (elimina) del gráfico. Se ejecuta solo una vez

Al igual que la función anterior, OnChartEvent() se ejecuta cuando suceden ciertos eventos. ¿De qué eventos hablamos?

Hay 9 eventos predefinidos (excluyendo 2 personalizados):

  1. CHARTEVENT_KEYDOWN
  2. CHARTEVENT_MOUSE_MOVE 
  3. CHARTEVENT_OBJECT_CREATE 
  4. CHARTEVENT_OBJECT_CHANGE
  5. CHARTEVENT_OBJECT_DELETE 
  6. CHARTEVENT_CLICK 
  7. CHARTEVENT_OBJECT_CLICK 
  8. CHARTEVENT_OBJECT_DRAG 
  9. CHARTEVENT_OBJECT_ENDEDIT

Aquí tenemos una breve panorámica de los eventos utilizados en el artículo:

  1. CHARTEVENT_KEYDOWN

    Cuando la ventana del gráfico está en el foco (simplemente clique en cualquier lugar de la ventana del gráfico para colocarlo en el foco), la función OnChartEvent() se ejecutará cada vez que presionemos cualquier tecla en el teclado.

    Mantenerla pulsada significará clicar en la tecla una y otra vez a un ritmo de 30 clics por segundo.

    ¿Qué podemos hacer cuando se pulsa una tecla? Por ahora, no mucho. Primero, necesitaremos averiguar qué tecla se ha presionado. ¿Cómo lograremos esto? Los parámetros OnChartEvent() nos ayudarán en la tarea.

    ¿De qué parámetros estamos hablando? Existen 4 parámetros que obtenemos al ejecutar OnChartEvent().

    1. id -> integer
    2. lparam -> long
    3. dparam -> double
    4. sparam -> string

    En términos simples, estos son algunos datos sobre los eventos para los cuales se llama a OnChartEvent(), y podemos usar estos datos dentro de la función OnChartEvent().

    Por ejemplo, en el caso del evento CHARTEVENT_KEYDOWN,

    • La identificación contiene el propio CHARTEVENT_KEYDOWN para que podamos determinar qué evento OnChartEvent() se llama y gestionar otros parámetros en consecuencia.
    • lparam contiene el código de la tecla presionada.
    • dparam contiene el número de pulsaciones de teclas generadas mientras se ha mantenido presionada la tecla. Cuando mantenemos presionada la tecla, dparam hace 30 clics por segundo. Este valor será siempre 1.
    • sparam contiene una máscara de bits. En términos simples, el parámetro describe el estado de una tecla que se presiona o mantiene presionada y muestra 2 valores diferentes para una tecla específica (consulte el ejemplo a continuación).


    Por ejemplo, si presionamos/mantenemos presionada la tecla A en el teclado, luego OnChartEvent() se ejecutará con

    • id =  CHARTEVENT_KEYDOWN
    • lparam = 65
    • dparam = 1
    • sparam = 30 para el primer clic y 16414 para los clics posteriores continuos mientras se mantiene presionado A con una velocidad de 30 clics por segundo.


    Ahora que tenemos la información, podremos usar algunas declaraciones if y hacer algo cuando el usuario presione o mantenga presionada la tecla A.



  2. CHARTEVENT_MOUSE_MOVE

    En primer lugar, los nombres de las propiedades del gráfico booleano CHART_EVENT_MOUSE_MOVE deberán establecerse en True. Esto se puede lograr con una simple línea de código:

    //Set Chart property CHART_EVENT_MOUSE_DOWN to true 
    ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);

    Por lo general, le recomendamos hacer esto en OnInit(). Una vez hayamos hecho esto, podremos usar CHARTEVENT_MOUSE_MOVE

    Cada vez que el ratón se mueva sobre el gráfico, OnChartEvent() se ejecutará con la siguiente información en los parámetros:

    • id =  CHARTEVENT_MOUSE_MOVE
    • lparam = coordenada X
    • dparam = coordenada Y
    • sparam = valor de máscara de bits que describe el estado de los botones del ratón

     La máscara de bits contendrá los siguientes valores -> 

    • Botón izquierdo del ratón --> 1
    • Botón derecho del ratón --> 2
    • Botón central del ratón --> 16
    • Primera tecla X del ratón --> 32
    • Segunda tecla X del ratón --> 64
    • Tecla Shift --> 4
    • Tecla de control --> 8

    La ventana de gráfico estará en el cuarto cuadrante, es decir, la coordenada X (lparam) estará a la izquierda de la ventana de gráfico, mientras que la coordenada Y (dparam) estará en la parte superior de la ventana de gráfico. Ahora, con toda esta información, ya estamos listos para usar CHARTEVENT_MOUSE_MOVE. Lo usaremos a continuación para hacer que la GUI resulte fluida.


  3. CHARTEVENT_CLICK

    No requiere ninguna propiedad especial, podremos usarlo directamente, es decir, cada vez que se clique con el ratón en el gráfico, OnChartEvent() se ejecutará con los siguientes parámetros.

    • id =  CHARTEVENT_CLICK
    • lparam = coordenada X
    • dparam = coordenada Y
    • sparam = "" es decir, una cadena vacía que significará que no contiene ninguna información útil


    Ahora que tenemos la información anterior, podremos usar algunas declaraciones if y hacer algo cuando el usuario clique en cualquier parte del gráfico.


Más arriba hemos analizado tres eventos que llaman a la función OnChartEvent(): CHARTEVENT_KEYDOWN, CHART_EVENT_MOUSE_MOVE, CHARTEVENT_CLICK.

Si usted no ha utilizado antes la función OnChartEvent(), todo esto podría parecerle un poco confuso. Bueno, todos hemos pasado por esa etapa, pero la hemos superado aprendiendo y practicando más. Usaremos el conocimiento anterior para hacer que la GUI sea móvil. Muy pronto usted se sentirá más seguro.

Lea la documentación o escriba un comentario si necesita detalles sobre otros eventos que llaman a OnChartEvent().


Desarrollo de una interfaz gráfica móvil: Guía paso a paso

Ahora que todo lo aburrido ya está hecho, podemos centrarnos en el aprendizaje real, es decir, en aplicar la teoría a cosas reales.

Nuestro objetivo es crear una GUI muy simple, por ejemplo, un formulario rectangular blanco vacío. Que no le sorprenda comenzar con cosas tan simples. Todo parte siempre de lo básico y rápidamente se vuelve más complejo.

Lea el artículo hasta el final y juzgue por sí mismo lo difícil que le ha resultado el camino. Llamaremos a nuestra interfaz gráfica panel (dashboard). Pronto realmente será un panel.

Primero, crearemos un formulario rectangular básico de 200x200 (XSize x YSize) con una distancia de 100 píxeles desde la izquierda (XDistance) y 100 píxeles desde la parte superior (YDistance). 

int OnInit()
   {
    //---
    //Set the name of the rectangle as "TestRectangle"
    string name = "TestRectangle";
    //Create a Rectangle Label Object at (time1, price1)=(0,0)
    ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
    //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window
    ObjectSetInteger(0, name,OBJPROP_XDISTANCE, 100);
    //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window
    ObjectSetInteger(0, name,OBJPROP_YDISTANCE, 100);
    //Set XSize to 200px i.e. Width of Rectangle Label
    ObjectSetInteger(0, name,OBJPROP_XSIZE, 200);
    //Set YSize to 200px i.e. Height of Rectangle Label
    ObjectSetInteger(0, name,OBJPROP_YSIZE, 200);
    //---
    return(INIT_SUCCEEDED);
   }


Ahora, al ejecutar el asesor en el gráfico, deberíamos ver nuestro formulario rectangular creado:


Fig. 1. Formulario rectangular simple

Fig. 1. Formulario rectangular simple



Si somos capaces de mover el formulario con el ratón, podremos hacer muchas otras cosas, en concreto mover un panel de herramientas múltiple muy avanzado en la ventana del gráfico, lo cual puede ser muy útil para los asesores/indicadores. Un ejemplo de una aplicación de este tipo es Trade Assistant EA.

¿Cómo hacer que un formulario sea móvil? Primero, crearemos un plan:

  • Condiciones antes del desplazamiento:
    • El ratón debe estar en el panel.

    • El botón izquierdo del ratón debe estar presionado.

  • Si movemos el ratón con el botón izquierdo pulsado el panel debería moverse.

  • Pero ¿a qué distancia? El panel debería moverse suavemente siempre que se cumplan ambas condiciones.

    Ahora escribiremos el código paso a paso:

    Según la primera condición, el ratón debería estar en el panel. Primero necesitaremos encontrar las coordenadas X e Y de la posición del ratón.


    Es hora de aplicar la teoría. Para las coordenadas X e Y del ratón, necesitaremos usar OnChartEvent().

    1. Ahora estableceremos la propiedad del gráfico CHART_EVENT_MOUSE_MOVE en True

      Luego ubicaremos el siguiente código en OnInit() para que sea verdadero al inicializar el asesor:

      //Set Chart property CHART_EVENT_MOUSE_DOWN to true
      ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);

    2. Ya podemos obtener las coordenadas del ratón en OnChartEvent().
      void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
         {
          //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
          if(id == CHARTEVENT_MOUSE_MOVE)
             {
              //Comment the X and Y Axes Coordinates
              Comment("X: ", lparam, "\nY: ", dparam);
             }
         }

      En OnChartEvent(), primero verificaremos que el evento que ha causado OnChartEvent haya sido CHARTEVENT_MOUSE_MOVE usando una declaración if simple que verificará si el ID es igual a CHARTEVENT_MOUSE_MOVE, porque solo querremos ejecutar nuestro código de comentario en ese caso.

      Luego comentaremos las coordenadas de los ejes X e Y (aparecerán en la esquina superior izquierda de la ventana del gráfico en blanco con una fuente pequeña), por ejemplo: 

      Fig. 2. Coordenadas X e Y

      Fig. 2. Coordenadas X e Y

    Volvamos ahora a nuestra lógica para saber si el ratón está en el panel (ver imagen a continuación): 

    Fig. 3. Visualización de la fórmula.

    Fig. 3. Visualización de la fórmula.





    Para saber si el ratón está en el panel,

    • X >= XDistance             --> X>=100
    • X <= XDIstance + XSize --> X<=300
    • Y >= YDistance             --> Y>=100
    • Y <= YDistance + YSize --> Y>=300

    Conversión en código:

    void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
      {
       //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
       if(id == CHARTEVENT_MOUSE_MOVE)
         {
          //define X, Y, XDistance, YDistance, XSize, YSize
          int X = (int)lparam;
          int Y = (int)dparam;
    
          string name = "TestRectangle";
          int XDistance = ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()
          int YDistance = ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()
          int XSize = ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()
          int YSize = ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()
          
          //Check Mouse on Dashboard condition
          if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize)
            {
             //Comment the X and Y Axes Coordinates and Mouse is on the dashboard
             Comment("X: ", lparam, "\nY: ", dparam, "\nMouse is on the Dashboard");
            }
          else
            {
             //Comment the X and Y Axes Coordinates and Mouse is not on the dashboard
             Comment("X: ", lparam, "\nY: ", dparam, "\nMouse is NOT on the Dashboard");
            }
    
         }
      }

    Definimos las variables X, Y, name, XDistance, YDistance, XSize y YSize. Hemos obtenido X de lparam, Y de dparam, name es solo el nombre de cadena que hemos configurado anteriormente, XDistance, YDistance, XSize, YSize usando la función ObjectGetInteger().

    Nuestro objetivo aquí será hacer que el panel se mueva sin problemas.

    Resultado:

    Fig. 4. Ratón en el panel

    Fig. 4. Ratón en el panel


    Como podemos ver, cada vez que el ratón está sobre el panel, el comentario cambia. Entonces nuestra lógica funciona. Ahora sabemos si el ratón se encuentra o no en el panel.

    Lo que necesitaremos ahora es el estado de los botones del ratón. Como recordará, si clicamos en el botón izquierdo, sparam será igual a 1. Vamos a aprovechar esto.

    void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
      {
       //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
       if(id == CHARTEVENT_MOUSE_MOVE)
         {
          //define X, Y, XDistance, YDistance, XSize, YSize
          int X = (int)lparam;
          int Y = (int)dparam;
          int MouseState = (int)sparam;
    
          string name = "TestRectangle";
          int XDistance = ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()
          int YDistance = ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()
          int XSize = ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()
          int YSize = ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()
    
          //Check Dashboard move conditions
          if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize && MouseState == 1)
            {
             //Comment that the dashboard is ready to move
             Comment("Dashboard is ready to move.");
            }
          else
            {
             //Comment that the dashboard is not ready to move
             Comment("Dashboard is NOT ready to move.");
            }
          
         }
      }

    Hemos añadido

    int MouseState = (int)sparam; //To get the mouse state: 1 -> Mouse Left Button Down (You can check the other above)
    

    a las variables, además de la condición

    if(MouseState == 1) // This insures that Mouse Left button in pressed

    al operador if. También hemos cambiado un poco los comentarios.

    Ahora, cada vez que presionemos el botón izquierdo del ratón en el panel, aparecerá el comentario "Dashboard is ready to move" (el panel está listo para moverse). De lo contrario, obtendremos el comentario "Dashboard is NOT ready to move" (el panel NO está listo para moverse).

    Veamos esto en acción:

    Fig. 5. El panel está listo para moverse.

    Fig. 5. El panel está listo para moverse.


    Observe cómo cambia el comentario al mantener presionado el botón izquierdo del ratón.


    Ahora ya estamos listos para mover nuestro panel. ¿Cómo hacemos esto?

    Sabemos que el panel se moverá usando el ratón. ¿Cómo cambiar entonces la posición del panel? Obviamente, usando las propiedades XDistance y YDistance. Entonces cambiaremos XDistance y YDistance, pero ¿en cuánto? Como el panel se mueve con el ratón, debería moverse de la misma manera que el ratón, ¿verdad?

    ¿Y hasta dónde ha progresado nuestro ratón? Todavía quedan demasiadas preguntas por responder. Vamos a crear un plan.

    Plan: 

    • Vamos a averiguar cuánto se ha movido nuestro ratón después de presionar su botón izquierdo.
    • Desplazaremos el panel exactamente en la misma magnitud que se mueve el ratón.
    • Haremos esto mientras mantengamos pulsado el botón izquierdo del ratón, tras lo cual dejaremos de mover el panel.

    Vayamos paso a paso.

    Siempre podemos obtener la posición actual correcta del ratón. ¿Qué pasará si guardamos la posición del ratón cuando presionamos por primera vez el botón izquierdo?

    Podremos averiguar si se ha presionado el botón izquierdo del ratón desde la variable MouseState creada por nosotros, que almacena sparam.

    A continuación le mostramos el código que detecta el primer clic izquierdo del ratón: 

    int previousMouseState = 0;
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
      {
       //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
       if(id == CHARTEVENT_MOUSE_MOVE)
         {
          int MouseState = (int)sparam;
          
          bool mblDownFirstTime = false;
          if(previousMouseState == 0 && MouseState == 1) {
             mblDownFirstTime = true;
          }
          
          previousMouseState = MouseState;
         }
      }
    //+------------------------------------------------------------------+

    Hemos eliminado algunas líneas para facilitar la comprensión. Este método se usa con bastante frecuencia al crear funciones de seguimiento para un asesor, al detectar una nueva barra, etc.

    Vamos a analizarlo:

    1. int previousMouseState = 0;

      Declararemos una variable int con el nombre anteriorMouseState en el espacio global y luego estableceremos su valor en 0. Esta variable almacenará el valor de MouseState desde el último evento CHARTEVENT_MOUSE_MOVE. ¿Y cómo exactamente? Lo descubrirá pronto.


    2. int previousMouseState = 0;

      Declararemos una variable int con el nombre anteriorMouseState en el espacio global y luego estableceremos su valor en 0. Esta variable almacenará el valor de MouseState desde el último evento CHARTEVENT_MOUSE_MOVE. ¿Y cómo exactamente? Lo descubrirá pronto.

    3. int MouseState = (int)sparam;   
      bool mblDownFirstTime = false;
      if(previousMouseState == 0 && MouseState == 1) {
         mblDownFirstTime = true;
      }


      En primer lugar, declararemos un MouseState y lo configuraremos en el sparam que contiene el estado del ratón; en segundo lugar, declararemos una variable booleana llamada mblDownFirstTime y estableceremos su valor predeterminado en falso.

      Luego verificaremos 2 condiciones: el valor de previousMouseState deberá ser igual a 0 (botón izquierdo del ratón liberado, botón del ratón no presionado) y (&&)MouseState deberá ser igual a 1 (botón izquierdo del ratón presionado).

      Esta condición básicamente confirmará o negará que se haya presionado el botón izquierdo del ratón por primera vez. Si es la primera vez, configuraremos mblDownFirstTime en true para poder usar esta variable más adelante.


    Nuestro primer paso ya está completo, vamos a pasar a los pasos 2 y 3.

    Hay muchas formas en que podemos seguir avanzando. A continuación detallaremos los pasos que seguiremos para conseguir un movimiento muy suave:

    1. Para ello, crearemos la variable booleana global movingState, que estableceremos en true tan pronto como el usuario clique con el botón izquierdo en el panel de herramientas y también declararemos MLB Down X, MLB Down Y, MLB Down XDistance, MLB Down YDistance (aquí MLB Down indicará que el botón izquierdo del ratón se ha soltado en primer lugar). Serán necesarios para cambiar la posición del panel de herramientas.
    2. Siempre que movingState sea true, actualizaremos la posición del panel de herramientas según el cambio en la posición del ratón con respecto al original (en el primer clic izquierdo).
    3. Siempre que movingState sea true, actualizaremos la posición del panel de herramientas según el cambio en la posición del ratón con respecto al original (en el primer clic izquierdo).


    Nuevo código:

    int previousMouseState = 0;
    int mlbDownX = 0;
    int mlbDownY = 0;
    int mlbDownXDistance = 0;
    int mlbDownYDistance = 0;
    bool movingState = false;
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
      {
    //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
       if(id == CHARTEVENT_MOUSE_MOVE)
         {
          if(previousMouseState == 0 && MouseState == 1)
            {
             mlbDownX = X;
             mlbDownY = Y;
             mlbDownXDistance = XDistance;
             mlbDownYDistance = YDistance;
    
             if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize)
               {
                movingState = true;
               }
            }
    
          if(movingState)
            {
             ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
             ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);
             ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);
             ChartRedraw(0);
            }
    
          if(MouseState == 0)
            {
             movingState = false;
             ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
            }
    
          previousMouseState = MouseState;
         }
      }
    //+------------------------------------------------------------------+

     Vamos a analizarlo en términos más simples:

    1. int mlbDownX = 0;
      int mlbDownY = 0;
      int mlbDownXDistance = 0;
      int mlbDownYDistance = 0;
      bool movingState = false;

      Hemos creado varias variables en el espacio global. Ya hemos mencionado el MouseState anterior:

      • mlbDownX -> Mantener presionada la coordenada X cuando el botón izquierdo del ratón está presionado por primera vez
      • mlbDownY -> Mantener presionada la coordenada Y cuando el botón izquierdo del ratón está presionado por primera vez
      • mlbDownXDistance -> Mantener presionada la propiedad XDistance del panel de herramientas en el primer clic del botón izquierdo del ratón
      • mlbDownYDistance -> Mantener presionada la propiedad YDistance del panel de herramientas en el primer clic del botón izquierdo del ratón    
      • movingState -> Guardar true si desplazamos el panel, y false en caso contrario

      if(previousMouseState == 0 && MouseState == 1)
        {
         mlbDownX = X;
         mlbDownY = Y;
         mlbDownXDistance = XDistance;
         mlbDownYDistance = YDistance;
      
         if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize)
          {
           movingState = true;
          }
        }



      Primero comprobaremos si este es el primer clic izquierdo del ratón. De ser así, actualizaremos mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance a las X, Y, XDistance, YDistance actuales, respectivamente. Las usaremos más tarde.

      Luego comprobaremos si se ha soltado el botón izquierdo del ratón en el panel. En caso afirmativo, estableceremos movingState en true.

    2. if(movingState)
        {
         ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
         ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);
         ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);
         ChartRedraw(0);
        }

      Si movingState es true, cambiaremos XDistance y YDistance,

      X - mlbDownX // Change in Mouse X Position form the initial click
      and 
      Y - mlbDownY // Change in Mouse X Position form the initial click

      Arriba están los cambios en la posición del ratón después del primer clic, y los añadiremos a mlbDownXDistance y mlbDownYDistance para obtener la nueva posición en el panel. Si lo piensa bien, tiene sentido.

      También estableceremos CHART_MOUSE_SCROLL en false para que el gráfico no se mueva con nuestro panel. Luego, por supuesto, redibujaremos el gráfico para conseguir un movimiento muy suave.

    3. if(MouseState == 0)
        {
         movingState = false;
         ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
        }

      Ahora, tan pronto como soltemos el botón izquierdo del ratón, MouseState pasará a ser 0.

      Luego estableceremos movingState en false y permitiremos que el gráfico se mueva nuevamente estableciendo CHART_MOUSE_SCROLL en true de nuevo.



    Nuestro código está casi completo. Puede estar orgulloso de su paciencia.

    Nuestro código completo se verá así:

    //+------------------------------------------------------------------+ //| Expert initialization function                                   | //+------------------------------------------------------------------+ int OnInit()   { //---    //Set the name of the rectangle as "TestRectangle"    string name = "TestRectangle";    //Create a Rectangle Label Object at (time1, price1)=(0,0)    ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);    //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window    ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 100);    //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window    ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 100);    //Set XSize to 200px i.e. Width of Rectangle Label    ObjectSetInteger(0, name, OBJPROP_XSIZE, 200);    //Set YSize to 200px i.e. Height of Rectangle Label    ObjectSetInteger(0, name, OBJPROP_YSIZE, 200); //Set Chart property CHART_EVENT_MOUSE_DOWN to true    ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); //---    return(INIT_SUCCEEDED);   } //+------------------------------------------------------------------+ //| Expert deinitialization function                                 | //+------------------------------------------------------------------+ void OnDeinit(const int reason)   { //---   } //+------------------------------------------------------------------+ //| Expert tick function                                             | //+------------------------------------------------------------------+ void OnTick()   { //---   } //+------------------------------------------------------------------+ //Declare some global variable that will be used in the OnChartEvent() function int previousMouseState = 0; int mlbDownX = 0; int mlbDownY = 0; int mlbDownXDistance = 0; int mlbDownYDistance = 0; bool movingState = false; //+------------------------------------------------------------------+ //|                                                                  | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)   { //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case    if(id == CHARTEVENT_MOUSE_MOVE)      {       //define X, Y, XDistance, YDistance, XSize, YSize       int X = (int)lparam;       int Y = (int)dparam;       int MouseState = (int)sparam;       string name = "TestRectangle";       int XDistance = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()       int YDistance = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()       int XSize = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()       int YSize = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()       if(previousMouseState == 0 && MouseState == 1) //Check if this was the MLB first click         {          mlbDownX = X; //Set mlbDownX (Variable that stores the initial MLB X location) equal to the current X          mlbDownY = Y; //Set mlbDownY (Variable that stores the initial MLB Y location) equal to the current Y          mlbDownXDistance = XDistance; //Set mlbDownXDistance (Variable that stores the initial XDistance i.e. Width of the dashboard) equal to the current XDistance          mlbDownYDistance = YDistance; //Set mlbDownYDistance (Variable that stores the initial YDistance i.e. Height of the dashboard) equal to the current YDistance          if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize) //Check if the click was on the dashboard            {             movingState = true; //If yes the set movingState to True            }         }       if(movingState)//if movingState is true, Update the Dashboard position         {          ChartSetInteger(0, CHART_MOUSE_SCROLL, false);//Restrict Chart to be moved by Mouse          ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);//Update XDistance to: mlbDownXDistance + (X - mlbDownX)          ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);//Update YDistance to: mlbDownYDistance + (Y - mlbDownY)          ChartRedraw(0); //Redraw Chart         }       if(MouseState == 0)//Check if MLB is not pressed         {          movingState = false;//set movingState again to false          ChartSetInteger(0, CHART_MOUSE_SCROLL, true);//allow the cahrt to be moved again         }       previousMouseState = MouseState;//update the previousMouseState at the end so that we can use it next time and copare it with new value      }   } //+------------------------------------------------------------------+

    Este código sencillo funciona.

    Resultado:

    Fig. 6. Resultado final

    Fig. 6. Resultado final



    Conclusión

    Lamento detenerme a mitad de la frase porque todavía no hemos creado algo verdaderamente único, sin embargo, en unos días se publicará la siguiente parte, en la que:

    • Crearemos varias interfaces gráficas en un gráfico (sin copiar y pegar elementos en plan básico)
    • Mejoraremos nuestra interfaz gráfica añadiendo y personalizando varios elementos, adaptándolos a nuestras necesidades únicas. Para aquellos que quieran sumergirse a fondo en el tema y crear una interfaz móvil, hemos creado una guía simplificada que ofrece las etapas rápidas.

    Al final de este viaje, habrá adquirido la valiosa habilidad de crear y gestionar una interfaz gráfica móvil en MQL5, una potente herramienta en el arsenal de cualquier tráder.


    Espero que le haya gustado el artículo y que este le haya servido de alguna ayuda.

    ¡Le deseo suerte escribiendo su código!



    Traducción del inglés realizada por MetaQuotes Ltd.
    Artículo original: https://www.mql5.com/en/articles/12751

    Archivos adjuntos |
    Desarrollo de un sistema de repetición — Simulación de mercado (Parte 15): Nacimiento del SIMULADOR (V) - RANDOM WALK Desarrollo de un sistema de repetición — Simulación de mercado (Parte 15): Nacimiento del SIMULADOR (V) - RANDOM WALK
    En este artículo, vamos a finalizar la fase en la que estamos desarrollando el simulador para nuestro sistema. El propósito principal aquí será ajustar el algoritmo visto en el artículo anterior. Este algoritmo tiene como objetivo crear el movimiento de RANDOM WALK. Por lo tanto, es fundamental comprender el contenido de los artículos anteriores para seguir lo que se explicará aquí. Si no has seguido el desarrollo del simulador, te aconsejo que veas esta secuencia desde el principio. De lo contrario, podrías perderte en lo que se explicará aquí.
    Desarrollo de un sistema de repetición — Simulación de mercado (Parte 14): Nacimiento del SIMULADOR (IV) Desarrollo de un sistema de repetición — Simulación de mercado (Parte 14): Nacimiento del SIMULADOR (IV)
    En este artículo, continuaremos con la fase de desarrollo del simulador. Sin embargo, ahora veremos cómo crear efectivamente un movimiento del tipo "RANDOM WALK" (paseo aleatorio). Este tipo de movimiento es bastante intrigante, ya que sirve de base para todo lo que sucede en el mercado de capitales. Además, comenzarás a comprender algunos conceptos esenciales para quienes realizan análisis de mercado.
    Teoría de Categorías en MQL5 (Parte 10): Grupos monoidales Teoría de Categorías en MQL5 (Parte 10): Grupos monoidales
    El presente artículo continúa la serie sobre la implementación de la teoría de categorías en MQL5. Hoy analizaremos los grupos monoidales como un medio que normaliza conjuntos de monoides y los hace más comparables entre una gama más amplia de conjuntos de monoides y tipos de datos.
    Teoría de categorías (Parte 9): Acciones de monoides Teoría de categorías (Parte 9): Acciones de monoides
    El presente artículo continúa la serie sobre la implementación de la teoría de categorías en MQL5. En este artículo examinaremos las acciones de los monoides como un medio de transformación de los monoides descritos en el artículo anterior para aumentar sus aplicaciones.