Português
preview
Del básico al intermedio: Eventos en Objetos (III)

Del básico al intermedio: Eventos en Objetos (III)

MetaTrader 5Ejemplos |
23 0
CODE X
CODE X

Introducción

En el artículo anterior, Del básico al intermedio: Eventos en Objetos (II), se vio cómo sería posible implementar un tipo de aplicación muy interesante e incluso bastante curiosa, cuyo objetivo sería hacer posible editar el texto de un objeto OBJ_LABEL directamente en el gráfico, sin necesidad de abrir la ventana de propiedades de los objetos y editar allí el texto que se mostrará.

Aunque aquello fue algo muy interesante, e incluso bastante bueno, desde el punto de vista práctico y educativo, podemos hacer algo todavía más interesante que lo que se vio en el artículo anterior. Creo que tú, mi querido lector, debes haberlo probado e incluso haberte preguntado si sería posible ampliar aquella misma aplicación, a fin de que fuera posible abarcar cualquier objeto de tipo OBJ_LABEL, y no solo aquel que estaríamos creando con nuestra aplicación, decidí mostrar cómo podríamos hacer esto. Ya que puede llegar a ser algo realmente interesante, precisamente por otra cuestión que abordaremos después.

Como de costumbre, pasemos al primer tema de este artículo. Así que es hora de alejarse de las distracciones y centrarse en lo que se verá en este artículo, porque aquí trataremos varias cosas muy interesantes.


Mejorando lo que ya era bueno

Bien, en el artículo anterior vimos que era posible hacer lo que se muestra en la siguiente animación.

Aminación 01

Por lo tanto, sí es posible implementar un mecanismo para editar directamente en el gráfico el texto presente en un objeto de tipo OBJ_LABEL. Pero, si intentaste añadir un nuevo OBJ_LABEL de forma manual, viste que lo que se muestra en la animación 01 no sería posible. ¿Pero por qué? El motivo son los filtros que se utilizan durante el manejo de los eventos. En cierto modo, al principio, la simple eliminación de los filtros ya resolvería el problema. Al principio, claro. Ya que, si intentas hacerlo, notarás que la aplicación se vuelve un poco loca.

Por esta razón, voy a mostrar, de forma rápida, cómo deberías modificar el código fuente que vimos en el artículo anterior, de manera que pudieras utilizar el mecanismo visto en la animación 01 para editar cualquier objeto de tipo OBJ_LABEL.

Puede parecer algo complicado, pero, en realidad, es mucho más simple de lo que parece. Siempre y cuando, claro, tomes ciertos cuidados al modificar el código. Como gran parte de lo que hay que hacer ya se explicó en los artículos anteriores, vamos a centrarnos solo en lo que realmente importa. Esto puede verse a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. int OnInit()
05. {
06.     return INIT_SUCCEEDED;
07. };
08. //+------------------------------------------------------------------+
09. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
10. {
11.     return rates_total;
12. };
13. //+------------------------------------------------------------------+
14. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
15. {
16.     switch (id)
17.     {
18.         case CHARTEVENT_OBJECT_CLICK    :
19.             if ((ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE) == OBJ_LABEL) Swap_ObjLabel_ObjEdit(sparam);
20.             break;
21.         case CHARTEVENT_OBJECT_ENDEDIT  :
22.             Swap_ObjLabel_ObjEdit(sparam);
23.             break;
24.     }
25.     ChartRedraw();
26. };
27. //+------------------------------------------------------------------+
28. void Swap_ObjLabel_ObjEdit(const string szName)
29. {
30.     struct st_Mem
31.     {
32.         string  font, text;
33.         int     x, y, w, h, fontSize;
34.         color   cor;
35.     }mem;
36.     ENUM_OBJECT type = (ENUM_OBJECT)ObjectGetInteger(0, szName, OBJPROP_TYPE) == OBJ_EDIT ? OBJ_LABEL : OBJ_EDIT;
37. 
38.     mem.font        = ObjectGetString(0, szName, OBJPROP_FONT);
39.     mem.x           = (int)ObjectGetInteger(0, szName, OBJPROP_XDISTANCE);
40.     mem.y           = (int)ObjectGetInteger(0, szName, OBJPROP_YDISTANCE);
41.     mem.w           = (int)ObjectGetInteger(0, szName, OBJPROP_XSIZE);
42.     mem.h           = (int)ObjectGetInteger(0, szName, OBJPROP_YSIZE);
43.     mem.fontSize    = (int)ObjectGetInteger(0, szName, OBJPROP_FONTSIZE);
44.     mem.cor         = (color)ObjectGetInteger(0, szName, OBJPROP_COLOR);
45.     mem.text        = ObjectGetString(0, szName, OBJPROP_TEXT);
46. 
47.     ObjectDelete(0, szName);
48.     ChartRedraw();
49.     ObjectCreate(0, szName, type, 0, 0, 0);
50. 
51.     ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, (type != OBJ_EDIT ? true : false));
52.     ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, mem.x);
53.     ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, mem.y);
54.     ObjectSetInteger(0, szName, OBJPROP_XSIZE, mem.w);
55.     ObjectSetInteger(0, szName, OBJPROP_YSIZE, mem.h);
56.     ObjectSetInteger(0, szName, OBJPROP_COLOR, mem.cor);
57.     ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, mem.fontSize);
58.     ObjectSetString(0, szName, OBJPROP_FONT, mem.font);
59.     ObjectSetString(0, szName, OBJPROP_TEXT, mem.text);
60.     if (type == OBJ_EDIT)
61.     {
62.         ObjectSetInteger(0, szName, OBJPROP_BGCOLOR, clrWhite);
63.         ObjectSetInteger(0, szName, OBJPROP_READONLY, false);
64.     }
65. }
66. //+------------------------------------------------------------------+

Código 01

Ahora quiero que mires este código 01 y me digas: ¿Cuál es la parte complicada de este código? Bien, si vienes siguiendo esta serie de artículos y también estudiando y practicando lo que se ha mostrado, posiblemente tu respuesta sea: No hay nada complicado aquí. Todo es muy simple y claro para mí. Mi querido lector, este código 01 es tan simple y fácil de entender que no veo necesidad de explicar cómo funciona. Sin embargo, aquí tenemos un pequeño fallo. Nada demasiado complicado. Pero, antes de mostrar cómo corregirlo, veamos qué ocurre cuando este código 01 esté presente en un gráfico cualquiera. Para ello, usaremos una pequeña secuencia de pasos, empezando por el primero que podemos ver a continuación.

Aminación 02

En esta animación 02, solo añadimos el código 01 al gráfico. Ten en cuenta que no ocurrió nada, ya que el objetivo no es crear algo, sino manipularlo. Justo después, añadiremos algunos objetos de tipo OBJ_LABEL. Para ello, usamos lo que se ve en la siguiente imagen.

Imagen 01

Esta imagen muestra cómo añadir objetos del tipo correcto en el gráfico. Añade cuantos quieras. En este caso, añadiré solo dos, ya que el objetivo es mostrar precisamente el fallo que existe. Así, el gráfico queda como se muestra a continuación.

Imagen 02

Muy bien, ahora viene la parte en la que las cosas empiezan a tener sentido. Una vez que la aplicación esté en ejecución en el gráfico y tengamos algún objeto del tipo correcto, ya no podremos hacer las cosas como antes. Esto ocurre porque, al hacer clic en uno de los objetos de tipo OBJ_LABEL, este se transformará en un objeto de tipo OBJ_EDIT, lo que nos permitirá escribir directamente el texto que se mostrará. Todo ello sin necesidad de abrir la ventana de propiedades del objeto de tipo OBJ_LABEL.

Aminación 03

Ten en cuenta que, a partir de lo que se ve en la animación 03, realmente podemos editar estos objetos como se prometió y se demostró en el artículo anterior. Pero no solo eso, ahora podemos hacerlo con cualquiera de los objetos de tipo OBJ_LABEL. Además, podemos hacer muchas más cosas, aunque de una forma un poco diferente. Para ello, necesitas acceder a la ventana de lista de objetos. Así, podrás cambiar algunas propiedades, como el color, el tipo de fuente, el tamaño de la fuente y también el nombre del propio objeto.

Sin embargo, estas propiedades que manipularemos de ese modo son propiedades extra. Y, de cualquier manera, tanto el objeto de tipo OBJ_EDIT como el objeto de tipo OBJ_LABEL compartirán las mismas propiedades. Ya que el procedimiento de la línea 28, que se ve en el código 01, se encargará de compartir esas propiedades por nosotros.

Entonces, ¿qué fallo es ese que mencioné al principio del tema? Bien, mi querido lector, no es que se trate de un fallo terrible ni alarmante. Es más bien un problema de selección y liberación. Cuando usamos la aplicación que se ve en el código 01, muchas veces querremos que solo un único objeto de tipo OBJ_EDIT esté presente en el gráfico. Esto se debe a que, en algunas situaciones, no tiene mucho sentido que existan varios objetos de tipo OBJ_EDIT presentes en el gráfico. Pero, debido a un fallo en el proceso de selección y liberación, mira lo que ocurre.

Aminación 04

Ten en cuenta que, originalmente, teníamos dos objetos de tipo OBJ_LABEL y pasamos a tener dos de tipo OBJ_EDIT. Esto puede corregirse. No estoy diciendo que sea algo totalmente incorrecto. Pero no tiene sentido que tengamos una situación como la que se ve en la animación 04, si el objetivo es simplemente poder editar el texto de OBJ_LABEL directamente en el gráfico.

Corregir este tipo de fallo es muy fácil y directo. Todo lo que necesitamos hacer es cambiar el código de manejo de eventos para resolver esta cuestión. En nuestro caso específico, bastará con cambiar el código como se muestra a continuación.

                   .
                   .
                   .
13. //+------------------------------------------------------------------+
14. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
15. {
16.     static string szOldEdit = "";
17. 
18.     switch (id)
19.     {
20.         case CHARTEVENT_OBJECT_CLICK    :
21.             if ((ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE) == OBJ_LABEL)
22.             {
23.                 if (szOldEdit != "") Swap_ObjLabel_ObjEdit(szOldEdit);
24.                 Swap_ObjLabel_ObjEdit(szOldEdit = sparam);
25.             }
26.             break;
27.         case CHARTEVENT_OBJECT_ENDEDIT  :
28.             Swap_ObjLabel_ObjEdit(sparam);
29.             szOldEdit = "";
30.             break;
31.     }
32.     ChartRedraw();
33. };
34. //+------------------------------------------------------------------+
                   .
                   .
                   .

Código 02

Observa lo simple que es aplicar la solución, ya que solo hay que modificar estos puntos que se muestran en el fragmento del código 02. El resultado puede verse en la animación 05, que aparece a continuación.

Animación 05

Bien, creo que ya puedo entender lo importante que es practicar y estudiar mejor las cosas antes de ir por ahí diciendo algo que todos repiten solo porque parece correcto. Pero tengo una duda sobre esta aplicación, que tan amablemente mostraste cómo crear. ¿No existe una forma de poder manipular más cosas sin necesidad de abrir la ventana en la que podemos ver la lista de objetos? Lo digo porque, al parecer, no tenemos forma de manipular la posición en la que se mostrará el objeto. Una vez colocado y con la aplicación en ejecución en el gráfico, resulta prácticamente imposible mover el objeto en el gráfico.

Entonces, ¿habría alguna forma de resolver este tipo de situación? Porque esto me incomoda bastante, aunque me parece muy interesante este tipo de manipulación que mostraste cómo hacer.

Sí, mi querido lector, sí existen formas de resolver esto. Por eso es importante que entiendas muy bien ciertas cosas. Procura siempre entender el concepto implicado, y no necesariamente el código que se utilizó. Esto se debe a que el código puede modificarse y manipularse, pero el concepto no.

Existen varias formas de hacer lo que sugieres, es decir, de manipular la posición del objeto cuando la aplicación que se muestra aquí esté en ejecución en el gráfico. Una de las más simples sería cambiar la forma en que funciona el objeto de tipo OBJ_EDIT.

En el artículo anterior, mencioné que, para poder editar el contenido de un objeto de tipo OBJ_EDIT, necesitamos que una de sus propiedades tenga un determinado valor. Si esa misma propiedad contiene otro valor, la edición queda bloqueada. Pero ¿por qué es importante esto para nosotros? ¿No era la idea crear una forma de mover el objeto en el gráfico? ¿Cómo puede ayudarnos a resolver la cuestión del movimiento esta información sobre una determinada propiedad y el modo en que afecta al objeto de tipo OBJ_EDIT? No logro entenderlo. ¿Podrías explicarlo mejor?

Claro que puedo explicar mejor a dónde quiero llegar con esto. Si lo recuerdas, en estos dos últimos artículos mostré cómo nuestro código puede capturar los eventos de los objetos cuando necesitamos saber qué ocurre. Como no queremos un trabajo adicional demasiado grande, hasta el punto de tener que indicar a los objetos cómo deben moverse, dejamos que MetaTrader 5 se encargue de hacerlo. Bien, esta es la parte fácil de entender. Tal vez la parte complicada, y de ahí la necesidad de entender muy bien cómo funcionan los objetos, venga de que, cuando un objeto de tipo OBJ_EDIT no puede editarse porque está seleccionado, podemos dejar en manos de MetaTrader 5 la responsabilidad de mover el objeto.

La parte más difícil de entender, para la gran mayoría de las personas que están empezando a estudiar programación, es que nuestro código 01, incluso después de añadir el fragmento que vimos en el código 02, seguirá transformando un objeto de tipo OBJ_LABEL en un objeto de tipo OBJ_EDIT. Esto se hace para editar el texto que después se mostrará. Sin embargo, ¿qué ocurre si vuelves a hacer clic en el objeto de tipo OBJ_EDIT? Según lo que estamos tratando en el código, la respuesta es: NADA. Pero piensa un instante: cuando terminamos de mover un objeto, MetaTrader 5 disparará un evento. Este notificará a nuestra aplicación que el usuario ha soltado el objeto y que ahora se encuentra en una nueva posición.

Así, si usamos un poco de estrategia, podemos forzar el objeto de tipo OBJ_EDIT a una condición que permitirá que MetaTrader 5 controle el movimiento. De este modo, el usuario podrá mover el objeto en el gráfico. Cuando lo suelte, liberamos la edición o simplemente podemos transformar el objeto de tipo OBJ_EDIT en un objeto de tipo OBJ_LABEL. Depende de ti, mi querido lector, ver cuál sería la alternativa que mejor se adapta a lo que deseas hacer.

Tal vez, explicado de esta forma, esto pueda parecer un poco complicado. Sin embargo, cuando convertimos las ideas en código, entender las cosas se vuelve mucho más simple. No obstante, antes de hacerlo, necesito mostrarte algo, mi querido lector. Será algo simple, pero hará mucho más clara la comprensión de un detalle crucial para nosotros.

Cuando dije, en el artículo anterior, que necesitábamos definir la propiedad OBJPROP_SELECTABLE de un objeto de tipo OBJ_EDIT con el valor false, no llegué a mostrar por qué. Aunque, en algunos casos, podemos definir OBJPROP_SELECTABLE como true y, aun así, seguir siendo posible editar el texto en el objeto de tipo OBJ_EDIT. Pero mostrar uno u otro caso en el que podemos hacer esto sin duda vuelve las cosas mucho más complicadas. Para mostrar en la práctica lo que se dijo, mira el código a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. int OnInit()
05. {
06.     return INIT_SUCCEEDED;
07. };
08. //+------------------------------------------------------------------+
09. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
10. {
11.     return rates_total;
12. };
13. //+------------------------------------------------------------------+
14. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
15. {
16.     static string szOldEdit = "";
17. 
18.     switch (id)
19.     {
20.         case CHARTEVENT_OBJECT_CLICK    :
21.             if ((ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE) == OBJ_LABEL)
22.             {
23.                 if (szOldEdit != "") Swap_ObjLabel_ObjEdit(szOldEdit);
24.                 Swap_ObjLabel_ObjEdit(szOldEdit = sparam);
25.             }
26.             break;
27.         case CHARTEVENT_OBJECT_ENDEDIT  :
28.             Swap_ObjLabel_ObjEdit(sparam);
29.             szOldEdit = "";
30.             break;
31.     }
32.     ChartRedraw();
33. };
34. //+------------------------------------------------------------------+
35. void Swap_ObjLabel_ObjEdit(const string szName, bool Selectable = true)
36. {
37.     struct st_Mem
38.     {
39.         string  font, text;
40.         int     x, y, w, h, fontSize;
41.         color   cor;
42.     }mem;
43.     ENUM_OBJECT type = (ENUM_OBJECT)ObjectGetInteger(0, szName, OBJPROP_TYPE) == OBJ_EDIT ? OBJ_LABEL : OBJ_EDIT;
44. 
45.     mem.font        = ObjectGetString(0, szName, OBJPROP_FONT);
46.     mem.x           = (int)ObjectGetInteger(0, szName, OBJPROP_XDISTANCE);
47.     mem.y           = (int)ObjectGetInteger(0, szName, OBJPROP_YDISTANCE);
48.     mem.w           = (int)ObjectGetInteger(0, szName, OBJPROP_XSIZE);
49.     mem.h           = (int)ObjectGetInteger(0, szName, OBJPROP_YSIZE);
50.     mem.fontSize    = (int)ObjectGetInteger(0, szName, OBJPROP_FONTSIZE);
51.     mem.cor         = (color)ObjectGetInteger(0, szName, OBJPROP_COLOR);
52.     mem.text        = ObjectGetString(0, szName, OBJPROP_TEXT);
53. 
54.     ObjectDelete(0, szName);
55.     ChartRedraw();
56.     ObjectCreate(0, szName, type, 0, 0, 0);
57. 
58.     ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, Selectable);
59.     ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, mem.x);
60.     ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, mem.y);
61.     ObjectSetInteger(0, szName, OBJPROP_XSIZE, mem.w);
62.     ObjectSetInteger(0, szName, OBJPROP_YSIZE, mem.h);
63.     ObjectSetInteger(0, szName, OBJPROP_COLOR, mem.cor);
64.     ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, mem.fontSize);
65.     ObjectSetString(0, szName, OBJPROP_FONT, mem.font);
66.     ObjectSetString(0, szName, OBJPROP_TEXT, mem.text);
67.     if (type == OBJ_EDIT)
68.     {
69.         ObjectSetInteger(0, szName, OBJPROP_BGCOLOR, clrWhite);
70.         ObjectSetInteger(0, szName, OBJPROP_READONLY, false);
71.     }
72. }
73. //+------------------------------------------------------------------+

Código 03

Este código 03 sería el mismo código 02, donde vimos solo un fragmento del conjunto, pero esta vez se muestra de forma más íntegra. Esta se ve en la línea 35. Y este valor, mostrado en el segundo argumento, se usa en un solo punto. Es decir, en la línea 58. Ahora, presta atención, porque esto puede llegar a volverse un poco confuso, dependiendo del tipo de configuración que estés implementando en tu código. Tal como está, este código 03 tendrá el siguiente comportamiento, que puede verse en la siguiente animación.

Animación 06

Un momento. Espera un poco. Este código no funciona como lo hacía el código 02. ¿Cómo puede ser? ¿El simple hecho de haber cambiado una sola propiedad cambió el código de esta manera? Esto es muy extraño. Pero ¿no podemos permitir la edición al mismo tiempo que permitimos que el objeto pueda seleccionarse? En algunos casos, sí, mi querido lector. Sin embargo, como hay algunas cuestiones algo engorrosas de explicar, ya que es necesario entrar en detalles sobre el funcionamiento de algunas propiedades, prefiero adoptar otro enfoque. Porque así resulta más simple explicarlo y demostrarlo sin que te confundas.

Bien, entonces, creo que has podido notar lo siguiente: si la propiedad OBJPROP_SELECTABLE está activada en un objeto de tipo OBJ_EDIT, debes asumir que no podrás editar el texto directamente en el gráfico. En cambio, sí será perfectamente posible mover el objeto.

Así que ahora, con estos hechos en mente, podemos modificar el código 03 para obtener el comportamiento que esperamos. Esto se hace modificando el código como se muestra a continuación.

                   .
                   .
                   .
18.     switch (id)
19.     {
20.         case CHARTEVENT_OBJECT_CLICK    :
21.             {
22.                 ENUM_OBJECT type = (ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE);
23.                 if (type == OBJ_EDIT) ObjectSetInteger(0, sparam, OBJPROP_SELECTABLE, false); 
24.                 else if (type == OBJ_LABEL)
25.                 {
26.                     if (szOldEdit != "") Swap_ObjLabel_ObjEdit(szOldEdit);
27.                     Swap_ObjLabel_ObjEdit(szOldEdit = sparam);
28.                     ObjectSetInteger(0, sparam, OBJPROP_SELECTED, true);
29.                 }
30.             }
31.             break;
                   .
                   .
                   .

Código 04

Aquí, en este código 04, nos centramos solo en la parte que realmente necesita modificarse en el código 03. Es decir, vemos únicamente el fragmento que fue necesario modificar. Ahora, cuando usemos este fragmento en el código 03, o si lo prefieres, en el código 02, el resultado será el que puedes observar a continuación, en la animación 07.

Aminación 07

Vamos, pero qué solución tan loca y absurda es esta que se está implementando aquí. Tú no debes ser normal. No puede ser, para haber pensado en esta forma de resolver la cuestión del movimiento y la edición del objeto de tipo OBJ_LABEL directamente en el gráfico. Esto es una locura.

Pero, dejando de lado la parte insana, ¿no es divertido lo que acabo de mostrar? Porque, básicamente, con muy poco trabajo, fue posible crear una forma de editar directamente un objeto de tipo OBJ_LABEL en el gráfico. Claro que esta solución no es 100% perfecta. Esto se debe a que necesitas usar la aplicación durante algún tiempo para acostumbrarte a cómo funciona. Ya que se trata de una secuencia de operaciones que deben realizarse en determinados momentos, como puede verse en la animación 07. Entonces, la línea 24 superará la comprobación y, así, convertiremos un objeto de tipo OBJ_LABEL en uno de tipo OBJ_EDIT. Pero, como queremos poder moverlo, en la línea 28 activaremos la selección del objeto. Esto permitirá mover el objeto de tipo OBJ_EDIT en el gráfico.

En cuanto volvemos a hacer clic en el mismo objeto, la comprobación de la línea 23 desactiva la posibilidad de selección. Así, cuando volvamos a hacer clic, MetaTrader 5 ya no seleccionará el objeto de tipo OBJ_EDIT, sino el texto, lo que nos permitirá modificarlo. Quedó bien, pero puede mejorarse. Sin embargo, como el objetivo NO es crear una aplicación, sino mostrar qué podemos hacer y cómo hacerlo, ya me doy por satisfecho con el resultado presentado y puedo pasar al siguiente tema.


Controlando las dimensiones directamente en el gráfico

Lo que vimos hasta este momento, puedo decir, sin falsa modestia, que fue algo muy divertido e interesante de hacer. Pero nada se compara con lo que veremos ahora. Bueno, en realidad, empezaremos a hacerlo. Esto se debe a que, según tu creatividad y hasta dónde seas capaz de imaginar cómo pueden encajar las cosas, pasarán a ser posibles más o menos cosas. Simplemente porque ya has entendido cómo interactúan MetaTrader 5 y nuestra aplicación entre sí, de manera que podamos producir cierto tipo de resultado y de modelo de aplicación.

Una de las cosas que, a mi modo de ver, es muy divertida de hacer es la posibilidad de usar MetaTrader 5 no para operaciones de mercado, como fue diseñado originalmente, sino para generar otro tipo de cosas. Como, por ejemplo, un editor gráfico, un editor de texto o incluso una pequeña plataforma de juegos simples, como rompecabezas o juegos clásicos. Al estilo de lo que se veía en las antiguas máquinas recreativas.

Muchos quizá crean que no es posible hacer este tipo de cosas, ya que, al parecer, no tenemos los mecanismos necesarios para producirlas. Sin embargo, a diferencia de lo que muchos creen y piensan saber sobre lo que podemos o no hacer con MetaTrader 5, sí tenemos posibilidades de hacer este tipo de cosas en la plataforma. Siempre que tengas imaginación y el conocimiento adecuado. Pero lo principal es que tengas los conceptos correctos y sepas cómo usarlos a tu favor. Si eso ocurre, podrás llegar a construir muchas cosas interesantes para que se ejecuten en MetaTrader 5, y no limitarte solo a mirar gráficos de cotización, aburridos y sin gracia. Incluso quizá puedas idear una forma un poco más divertida de trazar un gráfico de cotización, usando para ello elementos 3D, entre otras cosas.

Pero esto es algo que, en este momento, muy probablemente todavía no estarás preparado para visualizar. Es decir, para poder hacer algo así, hasta este momento, apenas estamos en lo más básico de todo esto. Y sí, a diferencia de lo que puedas estar imaginando, todavía seguimos en el nivel básico de lo que sería la programación en MQL5. Muy básico, de principiante incluso. Y mira cuántas cosas ya hemos conseguido hacer solo en este nivel básico.

Bien, ahora vamos a ver otra cosa, que es bastante fácil de hacer. Aunque puede ser un poco complicada de entender si no sigues y estudias los artículos. Ya que usaremos ciertas cosas que pueden parecer un tanto complicadas. Sin embargo, no quiero limitar tu imaginación al nivel básico de lo que podemos hacer. Si podemos hacer algo, vamos a hacerlo. Aunque tarde un poco en llegar a entender realmente qué está ocurriendo y qué tipo de concepto se está utilizando.

La idea en este tema es crear una forma, o manera, de cambiar las dimensiones de algún objeto en el gráfico usando, para ello, el ratón. Sin embargo, intentaremos hacerlo sin necesidad de usar directamente los eventos del ratón. Parece un poco complicado, ¿verdad? Pero la idea será seguir utilizando la ayuda de MetaTrader 5 para gestionar ciertos eventos, y nosotros los controlaremos de manera que produzcan un determinado tipo de resultado.

Esto sí parece algo muy, pero muy complicado de hacer. Algo que solo se le ocurriría a algún loco salido de un sanatorio y empeñado en volver iguales a los demás. Pero verás que es tan simple de hacer como caminar hacia delante. Tal vez incluso más simple que eso. Sin embargo, como el tema es bastante largo y necesitará más de un artículo para explicarse y comprenderse debidamente, vamos a empezar por el siguiente hecho: a diferencia de lo que ocurrió en los demás códigos, aquí ya permitiremos que tú, o cualquier otro usuario, pueda manipular las dimensiones de cualquier tipo de objeto.

Aunque, al principio, no será cualquier tipo. Este puede haber sido creado por tu aplicación o haber sido colocado por el usuario en el gráfico. No importa, trabajaremos con todos de la misma manera. Hay algunas limitaciones, pero las veremos a medida que el código se vaya implementando. Entonces, para empezar, tendremos el siguiente código que se muestra a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Prefix  "Demo"
05. //+------------------------------------------------------------------+
06. #define macro_NameObject  def_Prefix + (string)(ObjectsTotal(0) + 1)
07. //+------------------------------------------------------------------+
08. int OnInit()
09. {
10.     IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix);
11. 
12.     return INIT_SUCCEEDED;
13. };
14. //+------------------------------------------------------------------+
15. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
16. {
17.     return rates_total;
18. };
19. //+------------------------------------------------------------------+
20. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
21. {
22.     switch (id)
23.     {
24.         case CHARTEVENT_KEYDOWN         :
25.             break;
26.         case CHARTEVENT_OBJECT_CLICK    :
27.             break;
28.         case CHARTEVENT_OBJECT_DRAG     :
29.             break;
30.     }
31.     ChartRedraw();
32. };
33. //+------------------------------------------------------------------+
34. void OnDeinit(const int reason)
35. {
36.     ObjectsDeleteAll(0, def_Prefix);
37.     ChartRedraw();
38. };
39. //+------------------------------------------------------------------+

Código 05

Este código 05 es nuestro esqueleto base. Ten en cuenta que tenemos tres eventos indicados para ser tratados en este código 05. Esto se debe a que no vamos a crear una aplicación fantabulosa (sería la unión de la palabra fantástica con la palabra fabulosa). Aquí solo vamos a hacer algo simple y fácil de entender, que será manipular las dimensiones y, quizá, también la posición de cualquier objeto. Y esto directamente en el gráfico. Con respecto a la posición, todavía estoy pensando si vale o no la pena implementarla.

Bien, tú eres el jefe aquí. Entonces, ¿cuál es nuestro siguiente paso? Bien, mi querido lector, el siguiente paso es usar el evento CHARTEVENT_OBJECT_CLICK para capturar algunos datos sobre el objeto que vamos a manipular. Básicamente, trabajaremos solo con esta información, que es el nombre del objeto. Ya que esta es la única información realmente importante para nosotros. Porque todo lo demás podemos obtenerlo a partir de ella. Así, ahora nos centraremos en el procedimiento OnChartEvent. Para no tener que mostrar todo el código de nuevo. Así, los primeros cambios pueden verse a continuación.

                   .
                   .
                   .
19. //+------------------------------------------------------------------+
20. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
21. {
22.     static string szNameObj = NULL;
23. 
24.     switch (id)
25.     {
26.         case CHARTEVENT_KEYDOWN         :
27.             if ((szNameObj != NULL) && (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE))) szNameObj = NULL;
28.             break;
29.         case CHARTEVENT_OBJECT_CLICK    :
30.             if (StringFind(sparam, def_Prefix) != INVALID_HANDLE) break;
31.             if (szNameObj != NULL);
32.             if (szNameObj != sparam); else szNameObj = NULL;
33.             break;
34.         case CHARTEVENT_OBJECT_DRAG     :
35.             break;
36.     }
37.     ChartRedraw();
38. };
39. //+------------------------------------------------------------------+
                   .
                   .
                   .

Código 06

Todavía no sabemos qué se hará. ¿Puedes adelantar un poco el código, por favor? Me estoy poniendo un poco ansioso. Bien, mi querido lector. Tú lo pediste. (RISAS). Pero, antes de hacerlo, mira la lógica incorporada en este fragmento que se muestra en el código 06. Porque entender esta lógica es muy importante para ver lo que se hará a continuación. Así, lo siguiente que debe hacerse se muestra en el código a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Prefix  "Demo"
05. //+------------------------------------------------------------------+
06. #define macro_NameObject  def_Prefix + (string)(ObjectsTotal(0) + 1)
07. //+------------------------------------------------------------------+
08. struct st_BoxResize
09. {
10.     private :
11.         ushort  x, y, w, h;
12.         bool    isBack;
13.         string  szObjects[4];
14. //+----------------+
15.         void CreateEdge(const char what)
16.         {
17.             ushort p1 = x, p2 = y, p3 = w, p4 = h;
18. 
19.             switch (what)
20.             {
21.                 case 0: p4 = 1; break;
22.                 case 1: p3 = 1; break;
23.                 case 2: p2 = y + h; p4 = 1; break;
24.                 case 3: p1 = x + w; p3 = 1; break;
25.             }
26.             szObjects[what] = macro_NameObject;
27.             ObjectCreate(0, szObjects[what], OBJ_RECTANGLE_LABEL, 0, 0, 0);
28.             ObjectSetInteger(0, szObjects[what], OBJPROP_BGCOLOR, clrLime);
29.             ObjectSetInteger(0, szObjects[what], OBJPROP_XDISTANCE, p1);
30.             ObjectSetInteger(0, szObjects[what], OBJPROP_YDISTANCE, p2);
31.             ObjectSetInteger(0, szObjects[what], OBJPROP_XSIZE, p3);
32.             ObjectSetInteger(0, szObjects[what], OBJPROP_YSIZE, p4);
33.         }
34. //+----------------+
35.     public  :
36.         void CreateBoxResize(const string szArg, const uchar adjust = 2)
37.         {
38.             isBack = (bool)ObjectGetInteger(0, szArg, OBJPROP_BACK);
39.             ObjectSetInteger(0, szArg, OBJPROP_BACK, true);
40.             ObjectSetInteger(0, szArg, OBJPROP_SELECTED, false);
41.             x = (ushort)ObjectGetInteger(0, szArg, OBJPROP_XDISTANCE) - adjust;
42.             y = (ushort)ObjectGetInteger(0, szArg, OBJPROP_YDISTANCE) - adjust;
43.             w = (ushort)ObjectGetInteger(0, szArg, OBJPROP_XSIZE) + adjust;
44.             h = (ushort)ObjectGetInteger(0, szArg, OBJPROP_YSIZE) + adjust;
45.             for (uchar c = 0; c < 4; c++)
46.                 CreateEdge(c);
47.         }
48. //+----------------+
49.         void RemoveBoxResize(const string szArg)
50.         {
51.             ObjectSetInteger(0, szArg, OBJPROP_BACK, isBack);
52.             ObjectSetInteger(0, szArg, OBJPROP_SELECTED, false);
53.             ObjectsDeleteAll(0, def_Prefix);
54.         }
55. //+----------------+
56. }gl_RZ;
57. //+------------------------------------------------------------------+
58. int OnInit()
59. {
60.     IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix);
61. 
62.     return INIT_SUCCEEDED;
63. };
64. //+------------------------------------------------------------------+
65. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
66. {
67.     return rates_total;
68. };
69. //+------------------------------------------------------------------+
70. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
71. {
72.     static string szNameObj = NULL;
73. 
74.     switch (id)
75.     {
76.         case CHARTEVENT_KEYDOWN         :
77.             if ((szNameObj != NULL) && (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)))
78.             {
79.                 gl_RZ.RemoveBoxResize(szNameObj);
80.                 szNameObj = NULL;
81.             }
82.             break;
83.         case CHARTEVENT_OBJECT_CLICK    :
84.             if (StringFind(sparam, def_Prefix) != INVALID_HANDLE) break;
85.             if (szNameObj != NULL) gl_RZ.RemoveBoxResize(szNameObj);
86.             if (szNameObj != sparam) gl_RZ.CreateBoxResize(szNameObj = sparam); else szNameObj = NULL;
87.             break;
88.         case CHARTEVENT_OBJECT_DRAG     :
89.             break;
90.     }
91.     ChartRedraw();
92. };
93. //+------------------------------------------------------------------+
94. void OnDeinit(const int reason)
95. {
96.     ObjectsDeleteAll(0, def_Prefix);
97.     ChartRedraw();
98. };
99. //+------------------------------------------------------------------+

Código 07

Epa. Ve con calma, no hace falta llegar a tanto. Bien, tú lo pediste, ahí tienes el resultado. Pero, aunque parezca muy complicado, este código en sí es muy simple y extremadamente básico. Para entenderlo, basta con que observes con bastante calma cómo se están haciendo las cosas. De todos modos, voy a dar una breve explicación de lo que se está haciendo aquí. Ya que esta es la primera parte de lo que haremos después.

En la línea ocho, estamos creando una estructura cuyo objetivo será crear un marco alrededor del objeto que estemos seleccionando. Ahora, presta atención, porque esto es importante, mi querido lector. El objeto alrededor del cual crearemos ese contorno debe tener coordenadas de tipo cartesiano. Las coordenadas de tipo cotización todavía no se tratan aquí. Como tenemos pocos objetos de este tipo implementados en MetaTrader 5, y de ellos solo dos pueden llegar a ser de interés, que son los objetos de tipo OBJ_TEXT y OBJ_BITMAP, no veo motivos para trabajar con ellos todavía. Sin embargo, los demás sí son importantes para nosotros.

Observa que, en el manejador de eventos OnChartEvent, estamos haciendo referencia a algunos procedimientos dentro de la estructura. Sin embargo, la propia estructura contiene un procedimiento interno y privado. Este es el que realmente se encarga de crear las líneas que rodearán el objeto seleccionado.

En este caso, estoy usando un color bastante llamativo. Este se define en la línea 28. Si lo deseas, cámbialo por otro que te parezca más adecuado. Sin embargo, evita tocar los demás puntos del código, salvo que estés experimentando para entender cómo funciona. Muy bien, en la línea 38 capturamos el hecho de que el objeto seleccionado está en primer plano o se trata de un objeto en el plano de fondo. Esto es importante para nosotros, ya que, de todos modos, enviaremos el objeto al plano de fondo y lo desmarcaremos, para indicar así que no estará seleccionado.

¿Pero por qué estamos haciendo esto? El motivo es evitar que el objeto interfiera en lo que haremos después. Recuerda que, cuando un objeto está seleccionado, podemos usar uno de sus puntos específicos para moverlo en la ventana gráfica. Esto usando el propio MetaTrader 5. Sin embargo, en este caso, no queremos ese tipo de comportamiento. Ya que tomaremos las riendas y controlaremos esto nosotros mismos. Antes, necesito que entiendas muy bien cómo funciona este código. Pero, antes de cerrar el artículo,

¿qué te parece si vemos de qué es capaz este código 07? Bien, esto se muestra en la siguiente animación. Pero antes, observa los objetos que están presentes en el gráfico. Procura centrarte en el nombre de los objetos, porque esto es importante para entender cómo el manejador de eventos tratará cada uno de ellos. Esto puede verse en la imagen 03.

Imagen 03

Ahora vamos a la animación de demostración.

Aminación 08


Consideraciones finales

En este artículo, se demostró cómo podríamos trabajar con objetos de tipo OBJ_LABEL junto con objetos de tipo OBJ_EDIT, a fin de poder editar un texto directamente en el gráfico. Además, vimos cómo podríamos añadir una lógica capaz de permitirnos mover un objeto, aunque al principio esto no fuera posible. Porque, cuando hacíamos el cambio de un objeto de tipo OBJ_LABEL a uno de tipo OBJ_EDIT, y viceversa, acabábamos en un callejón sin salida para permitir el movimiento del objeto.

También vimos el inicio de algo que se explorará mejor en el próximo artículo. Pero, como quiero que procures entender muy bien lo que se hizo aquí, ya que será necesario para lo que se verá después, decidí adelantar brevemente algo que, a mi modo de ver, te dará varias ideas sobre cómo controlar MetaTrader 5 usando para ello MQL5.

Por lo demás, me despido por ahora, y nos vemos en el próximo artículo. Entonces, que sigas estudiando y hasta pronto.

Archivo MQ5Descripción
Code 01
 Demostración de eventos en objetos
Code 02  Demostración de eventos en objetos
Code 03  Demostración de eventos en objetos
Code 04  Demostración de eventos en objetos

Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/16075

Archivos adjuntos |
Anexo.zip (4.19 KB)
Simulación de mercado: Iniciando SQL en MQL5 (V) Simulación de mercado: Iniciando SQL en MQL5 (V)
En el artículo anterior mostré cómo debías proceder para poder añadir el mecanismo de consulta. Esto para que, dentro del código MQL5, pudieras usar SQL plenamente y obtener los resultados al usar el comando SELECT FROM de SQL. Pero faltó hablar de la última función que necesitamos implementar. Esta es la función DatabaseReadBind. Y, como para entenderla adecuadamente hace falta una explicación un poco más amplia, se decidió hacerlo no en aquel artículo anterior, sino en este. Entonces, como el tema será relativamente largo, vayamos directamente al siguiente apartado.
Herramientas personalizadas de depuración y perfilado para el desarrollo en MQL5 (Parte I): Registro avanzado Herramientas personalizadas de depuración y perfilado para el desarrollo en MQL5 (Parte I): Registro avanzado
Descubra cómo implementar un potente marco de registro personalizado para MQL5 que va más allá de las simples instrucciones Print(), ya que admite niveles de gravedad, múltiples manejadores de salida y rotación automática de archivos, todo ello configurable sobre la marcha. Integre el singleton CLogger con ConsoleLogHandler y FileLogHandler para registrar entradas de registro contextuales y con marca de tiempo tanto en la pestaña «Expertos» como en archivos persistentes. Optimice la depuración y el seguimiento del rendimiento de sus asesores expertos gracias a formatos de registro claros y personalizables y a un control centralizado.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Creación de un Panel de administración de operaciones en MQL5 (Parte XI): Interfaz moderna de funciones de comunicación (I) Creación de un Panel de administración de operaciones en MQL5 (Parte XI): Interfaz moderna de funciones de comunicación (I)
Hoy nos centramos en mejorar la interfaz de mensajería del Panel de Comunicaciones para adaptarla a los estándares de las aplicaciones de comunicación modernas y de alto rendimiento. Esta mejora se logrará actualizando la clase CommunicationsDialog. Únase a nosotros en este artículo y debate mientras exploramos ideas clave y describimos los próximos pasos para avanzar en la programación de interfaces utilizando MQL5.