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

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

MetaTrader 5Ejemplos |
39 0
CODE X
CODE X

Introducción

En el artículo anterior, Del básico al intermedio: Eventos en Objetos (I), se explicó y se demostró la primera parte básica, donde los eventos en objetos serían el foco principal. Sin embargo, quedó pendiente hablar de otros tres eventos que pueden dispararse cuando un objeto recibe algún tipo de interacción del usuario. De estos tres, uno de ellos tendría que activarse para que MetaTrader 5 realmente lo disparara. De lo contrario, aunque tu código tenga recursos para manejar este evento, jamás recibiría la notificación de que dicho evento ocurrió.

Bien, como quiero que tú, mi querido lector, entiendas cada uno de estos eventos antes de poder usarlos en conjunto, haremos lo siguiente: crearemos pequeños temas solo para explicar cómo y cuándo MetaTrader 5 dispara cada uno de estos tres últimos eventos. En cuanto los entiendas, podremos ver cómo usarlos en conjunto para implementar alguna funcionalidad que pueda resultar divertida. Entonces, empecemos.


Evento CHARTEVENT_OBJECT_DELETE

Este evento, al igual que CHARTEVENT_OBJECT_CREATE y otros que están relacionados con el ratón, deben ser activados y desactivados por tu aplicación. Esto es para que MetaTrader 5 sepa cuándo una aplicación que se ejecuta en un gráfico necesita recibir una notificación sobre la eliminación de algún objeto presente en el gráfico.

Definitivamente, este es un evento cuyo objetivo es preventivo. Esto ocurre porque, siempre que deseamos mantener nuestra aplicación dentro de una determinada configuración de objetos, podemos usar este evento para garantizar que ningún objeto sensible o esencial sea eliminado del gráfico por el usuario. Ya sea por error al eliminar objetos del gráfico o por cualquier otro motivo.

Básicamente, este evento es muy simple. Sin embargo, hay que tomar algunas precauciones en la programación o en la implementación del código. El motivo de estas precauciones es que, en algunos casos, podemos tener problemas relacionados con el esquema de colores. Esto termina dificultando, y mucho, la correcta visualización del objeto cuando la aplicación necesite recrearlo automáticamente.

Pero esto que estoy diciendo no será, en ningún caso, una preocupación aquí para nosotros. Este contenido no está orientado a explicar cómo implementar una solución genérica que consiga cubrir todos los casos posibles. Además, cada caso es distinto.

En ningún caso existe una forma totalmente integrada y genérica de recrear un objeto que haya sido eliminado indebidamente del gráfico. Tú, como programador, puedes pensar en diversas formas de garantizar esto. Personalmente, sugiero usar una pequeña estructura en la que se almacenen las propiedades de los objetos que tú, o mejor dicho, que tu aplicación esté creando. Así, cuando la aplicación necesite recrear el objeto, lo hará con un esquema de colores o un posicionamiento adecuados. Además, claro, de otras cuestiones, como la fuente, el tamaño de la fuente, la dimensión del objeto; en fin, creo que se entendió.

Bien, después de esta breve introducción, creo que ya estás listo para ver cuál será el primer código de este artículo. 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. string gl_szObjectName;
09. //+------------------------------------------------------------------+
10. int OnInit()
11. {
12.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
13.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
14. 
15.     gl_szObjectName = macro_NameObject;
16.     Proc_Object();
17. 
18.     return INIT_SUCCEEDED;
19. };
20. //+------------------------------------------------------------------+
21. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
22. {
23.     return rates_total;
24. };
25. //+------------------------------------------------------------------+
26. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
27. {
28.     switch (id)
29.     {
30.         case CHARTEVENT_MOUSE_MOVE:
31.             ObjectSetString(0, gl_szObjectName, OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam));
32.             break;        
33.         case CHARTEVENT_OBJECT_DELETE:
34.             if (sparam == gl_szObjectName) Proc_Object();
35.             break;
36.     }
37.     ChartRedraw();
38. };
39. //+------------------------------------------------------------------+
40. void OnDeinit(const int reason)
41. {
42.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
43.     ObjectsDeleteAll(0, def_Prefix);
44.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
45.     ChartRedraw();
46. };
47. //+------------------------------------------------------------------+
48. void Proc_Object(void)
49. {
50.     ObjectCreate(0, gl_szObjectName, OBJ_LABEL, 0, 0, 0);
51.     ObjectSetInteger(0, gl_szObjectName, OBJPROP_SELECTABLE, true);
52.     ObjectSetInteger(0, gl_szObjectName, OBJPROP_XDISTANCE, 50);
53.     ObjectSetInteger(0, gl_szObjectName, OBJPROP_YDISTANCE, 50);
54.     ObjectSetInteger(0, gl_szObjectName, OBJPROP_XSIZE, 150);
55.     ObjectSetInteger(0, gl_szObjectName, OBJPROP_COLOR, clrMediumBlue);
56.     ObjectSetInteger(0, gl_szObjectName, OBJPROP_FONTSIZE, 20);
57.     ObjectSetString(0, gl_szObjectName, OBJPROP_FONT, "Lucida Console");
58. }
59. //+------------------------------------------------------------------+

Código 01

Este código 01 tiene un objetivo muy simple: mostrar qué sucede cuando eliminamos un objeto del gráfico. Como necesitamos activar y desactivar la notificación para que MetaTrader 5 nos diga si un objeto fue eliminado o no, este código facilita bastante entender cómo funciona todo.

Tal vez la parte más complicada no sea activar la notificación, ya que esto es simple de hacer, y para ello basta con usar la línea 13. Normalmente, activamos la notificación justo al inicio de la ejecución de la aplicación para evitar perder algún evento de eliminación que pueda llegar a ocurrir. Sin embargo, la parte difícil es justamente saber cuándo desactivar la notificación. MetaTrader 5 funciona de una forma determinada y, según el momento en que se desactive la notificación, puedes obtener resultados bastante extraños, pero, la mayoría de las veces, completamente inofensivos.

Para entender esto, intenta cambiar de lugar el contenido que aparece en la línea 42. Esa es la responsable de desactivar las notificaciones de evento cuando eliminamos un objeto del gráfico. Como el código fuente estará en el anexo, puedes probar a intercambiar el contenido de la línea 42 con el de la línea 43. Esto hará que el resultado de retirar el indicador del gráfico sea bastante interesante. Al hacer esto, verás que no siempre podemos ejecutar estas acciones en cualquier orden. Y esto te ayudará a entender diversas cuestiones que, de otro modo, serían complicadas de explicar.

De todos modos, cuando se ejecute este código 01, podrás realizar básicamente dos tipos de acciones. La primera se ve en la siguiente animación.

Animación 01

En este caso, estamos seleccionando el objeto y eliminándolo con la tecla DELETE. Observa que el objeto se recrea inmediatamente después de pulsar DELETE. Sin embargo, como está vinculado al evento del ratón para que la información se actualice, solo después de que ocurra un movimiento o un evento del ratón la información pasa a mostrarse como se espera. La segunda situación sería un intento de eliminar el objeto usando, para ello, la ventana de lista de objetos. Esto puede verse en la animación siguiente.

Animación 02

Del mismo modo que muestra la animación 01, aquí, en la animación 02, queda claro que, en el momento exacto en que eliminamos el objeto, se recrea. Sin embargo, a pesar de ello, la ventana no lo muestra de inmediato. Hay que volver a abrir la ventana para que vuelva a mostrarse en la lista de objetos presentes en el gráfico.

Bien. Este era el evento para que se nos informara de que un objeto fue eliminado del gráfico. Ahora tenemos otros dos por ver. Sin embargo, aquí surgen algunas cuestiones bastante curiosas. Y, para explicarlo mejor, vamos a iniciar un nuevo tema.


Evento CHARTEVENT_OBJECT_CHANGE

Este evento, que veremos ahora, es bastante curioso si entiendes cómo funciona. Esto ocurre porque, según la situación, puedes implementar con él cosas muy interesantes, ya que se dispara siempre que un objeto sufre algún cambio en una de sus propiedades.

En el tema anterior, mencioné que no existe una forma de crear un mecanismo 100 % genérico para recrear un objeto que haya sido eliminado. Eso es cierto. Sin embargo, según cómo implementes el código, puede que consigas una forma adecuada de recrear casi cualquier tipo de objeto de manera genérica. Con lo que se ha mostrado hasta aquí, hacer esto es una tarea bastante desafiante. Por lo tanto, mantén la calma, porque, aunque podamos crear buenas aplicaciones con mecanismos relativamente simples, puede que en el futuro encuentres formas mejores. El conocimiento se va acumulando, al igual que la experiencia. Por eso, es bueno estudiar y practicar siempre. No tengas prisa. Pero esfuérzate por seguir aprendiendo.

Para mostrar cómo esto puede llegar a ser interesante y por qué es importante conocer lo que se está implementando, tomaremos el código 01, que vimos en el tema anterior, y lo modificaremos para poder ver en funcionamiento el evento de este tema. Esto se hace en el 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. struct st_Obj
09. {
10. //+----------------+
11.     private:
12.         string  szName,
13.                 font;
14.         bool    Selectable;
15.         int     x,
16.                 y,
17.                 w,
18.                 fontSize;
19.         color   cor;
20. //+----------------+
21.     public:
22. //+----------------+
23.         void SetDefault(const string arg)
24.         {
25.             szName      = arg;
26.             font        = "Lucida Console";
27.             Selectable  = true;
28.             x           = 50;
29.             y           = 50;
30.             w           = 150;
31.             cor         = clrMediumBlue;
32.             fontSize    = 20;
33.         }
34. //+----------------+
35.         void Create(void)
36.         {
37.             ObjectCreate(0, szName, OBJ_LABEL, 0, 0, 0);
38.             ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, Selectable);
39.             ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, x);
40.             ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, y);
41.             ObjectSetInteger(0, szName, OBJPROP_XSIZE, w);
42.             ObjectSetInteger(0, szName, OBJPROP_COLOR, cor);
43.             ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, fontSize);
44.             ObjectSetString(0, szName, OBJPROP_FONT, font);
45.         }
46. //+----------------+
47.         string GetName(void)
48.         {
49.             return szName;
50.         }
51. //+----------------+
52. }gl_Obj;
53. //+------------------------------------------------------------------+
54. int OnInit()
55. {
56.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
57.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
58. 
59.     gl_Obj.SetDefault(macro_NameObject);
60.     gl_Obj.Create();
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.     switch (id)
73.     {
74.         case CHARTEVENT_MOUSE_MOVE:
75.             ObjectSetString(0, gl_Obj.GetName(), OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam));
76.             break;        
77.         case CHARTEVENT_OBJECT_DELETE:
78.             if (sparam == gl_Obj.GetName()) gl_Obj.Create();
79.             break;
80.         case CHARTEVENT_OBJECT_CHANGE:
81.             Comment("Ocorreu um evento: CHARTEVENT_OBJECT_CHANGE no objeto ["+sparam+"]");
82.             break;
83.     }
84.     ChartRedraw();
85. };
86. //+------------------------------------------------------------------+
87. void OnDeinit(const int reason)
88. {
89.     Comment("");
90.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
91.     ObjectsDeleteAll(0, def_Prefix);
92.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
93.     ChartRedraw();
94. };
95. //+------------------------------------------------------------------+

Código 02

Cuando ejecutes este código 02, podrás ver dos cosas. La primera se muestra en la siguiente animación.

Animación 03

Observa que, en el instante exacto en que cambiamos alguna propiedad del objeto, se dispara un evento que será capturado en la línea 80. Como no estamos filtrando el origen del evento, cualquier cambio en cualquier objeto hará que la línea 81 se muestre en la esquina superior izquierda del gráfico, como puedes ver en la animación 03. Pero lo interesante es cuando ocurre un segundo evento, como puede verse en la animación 04.

Animación 04

Qué extraño. ¿Por qué el objeto no mantuvo su propiedad? Estamos llamando al procedimiento que aparece en la línea 35. En el caso del código 01, sí entiendo el motivo. Pero, en este caso, imaginé que, si el objeto fuera eliminado del gráfico, la aplicación lo crearía con los mismos atributos. No se produjo ninguna llamada a SetDefault para restablecer los valores modificados en la animación 03. Esto, a mi modo de ver, resulta bastante extraño.

Mi querido lector, muchos principiantes suelen caer en este mismo tipo de trampa, en la que crean un código, pero no consiguen entender ciertas cuestiones internas del propio código. Normalmente, esto ocurre precisamente porque están copiando el código de algún otro lugar. Pero también puede ocurrir cuando estás probando una nueva implementación, en la que todo parece estar en perfecto orden, pero, cuando vas a probarla, acabas notando que todavía faltan algunos detalles por implementar. Esto no es, en ningún caso, un error. Muy al contrario. Es una buena forma de practicar y probar nuevas soluciones que, en algún momento futuro, pueden resultar interesantes.

Perfecto. Pero entonces, ¿cuál es aquí el problema? El detalle es que tú, al usar MetaTrader 5 para actualizar alguna propiedad del objeto, no estás actualizando las propiedades con las que fue creado. Solo estás actualizando las propiedades locales del objeto en cuestión. Así, cuando se destruya y la línea 80, en este código 02, lo recree, lo hará precisamente usando los parámetros o propiedades definidos y todavía conservados dentro de la variable global.

Por eso, el objeto se recrea tal como fue configurado en el código. Para cambiar esto, necesitamos que el objeto, desde dentro de la aplicación, también se actualice con este cambio. Y, para ello, necesitamos utilizar la captura del evento, realizada precisamente en la línea 82. Por eso, no estoy filtrando el evento en este código 02. Quiero que entiendas cómo se notifica a la aplicación un cambio en la propiedad de uno de los objetos presentes en el gráfico.

Como MetaTrader solo nos informa el nombre del objeto, y no qué propiedades fueron modificadas, necesitamos capturarlas todas. Al menos las que realmente nos interesan. Aunque muchos objetos tienen propiedades semejantes, hay algunas que proceden exclusivamente de un determinado objeto y no aparecen en otro. Por eso dije que crear una solución 100 % genérica es algo muy complicado.

Bien, teniendo esto en cuenta, podemos modificar el código 02 para mantener intactas algunas de las propiedades. Al menos durante la ejecución del código. Si la aplicación se elimina del gráfico, perderá las propiedades definidas por el usuario y volverá a ser aquello que implementamos en el código. Hay varias formas de resolver esto. Pero lo veremos en otro momento.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #define def_Prefix  "Demo"
005. //+------------------------------------------------------------------+
006. #define macro_NameObject  def_Prefix + (string)(ObjectsTotal(0) + 1)
007. //+------------------------------------------------------------------+
008. struct st_Obj
009. {
010. //+----------------+
011.     private:
012.         string  szName,
013.                 font;
014.         bool    Selectable;
015.         int     x,
016.                 y,
017.                 w,
018.                 fontSize;
019.         color   cor;
020. //+----------------+
021.     public:
022. //+----------------+
023.         void SetDefault(const string arg)
024.         {
025.             szName      = arg;
026.             font        = "Lucida Console";
027.             Selectable  = true;
028.             x           = 50;
029.             y           = 50;
030.             w           = 150;
031.             cor         = clrMediumBlue;
032.             fontSize    = 20;
033.         }
034. //+----------------+
035.         void Create(void)
036.         {
037.             ObjectCreate(0, szName, OBJ_LABEL, 0, 0, 0);
038.             ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, Selectable);
039.             ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, x);
040.             ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, y);
041.             ObjectSetInteger(0, szName, OBJPROP_XSIZE, w);
042.             ObjectSetInteger(0, szName, OBJPROP_COLOR, cor);
043.             ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, fontSize);
044.             ObjectSetString(0, szName, OBJPROP_FONT, font);
045.         }
046. //+----------------+
047.         string GetName(void)
048.         {
049.             return szName;
050.         }
051. //+----------------+
052.         void Update(void)
053.         {
054.             font        = ObjectGetString(0, szName, OBJPROP_FONT);
055.             Selectable  = (int)ObjectGetInteger(0, szName, OBJPROP_SELECTABLE);
056.             x           = (int)ObjectGetInteger(0, szName, OBJPROP_XDISTANCE);
057.             y           = (int)ObjectGetInteger(0, szName, OBJPROP_YDISTANCE);
058.             w           = (int)ObjectGetInteger(0, szName, OBJPROP_XSIZE);
059.             fontSize    = (int)ObjectGetInteger(0, szName, OBJPROP_FONTSIZE);
060.             cor         = (color)ObjectGetInteger(0, szName, OBJPROP_COLOR);
061.         }
062. //+----------------+
063. }gl_Obj;
064. //+------------------------------------------------------------------+
065. int OnInit()
066. {
067.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
068.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
069. 
070.     gl_Obj.SetDefault(macro_NameObject);
071.     gl_Obj.Create();
072. 
073.     return INIT_SUCCEEDED;
074. };
075. //+------------------------------------------------------------------+
076. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
077. {
078.     return rates_total;
079. };
080. //+------------------------------------------------------------------+
081. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
082. {
083.     switch (id)
084.     {
085.         case CHARTEVENT_MOUSE_MOVE:
086.             ObjectSetString(0, gl_Obj.GetName(), OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam));
087.             break;        
088.         case CHARTEVENT_OBJECT_DELETE:
089.             if (sparam == gl_Obj.GetName()) gl_Obj.Create();
090.             break;
091.         case CHARTEVENT_OBJECT_CHANGE:
092.             if (sparam == gl_Obj.GetName()) gl_Obj.Update();
093.             break;
094.     }
095.     ChartRedraw();
096. };
097. //+------------------------------------------------------------------+
098. void OnDeinit(const int reason)
099. {
100.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
101.     ObjectsDeleteAll(0, def_Prefix);
102.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
103.     ChartRedraw();
104. };
105. //+------------------------------------------------------------------+

Código 03

Este código 03 muestra qué hay que hacer. Cuando se ejecuta, el resultado puede verse en la siguiente animación.

Animación 05

Observa que ahora sí tenemos la recreación del objeto conforme a las propiedades que el usuario definió hace un momento. Aunque el objeto se elimine del gráfico, debido a su importancia, será recreado. Sin embargo, a diferencia de lo que ocurría antes, ahora no se recreará con las propiedades definidas en la aplicación, sino con las propiedades definidas por el usuario y guardadas gracias a la llamada realizada en la línea 92.

Pero fíjate en que, esta vez, sí estamos filtrando. Esto es importante por la propia naturaleza de lo que hacemos. Piensa en el siguiente escenario: Tienes dos objetos en el gráfico y decides modificar las propiedades de uno de ellos. Hasta ahí, bien. Sin embargo, cuando eliminas el objeto que utiliza tu aplicación, se recreará de inmediato. No obstante, si no se aplicara el filtrado que aparece en la línea 92, modificar un objeto distinto del que controla la aplicación haría que las propiedades de ese otro objeto se aplicaran al objeto que la aplicación recrearía, lo que causaría una gran confusión en todo lo que se hiciera en el gráfico con los objetos. Por eso, este filtrado de la línea 92 es tan importante para nosotros.

Intenta probar este mismo código 03 sin usar este filtrado de la línea 92 y observa los resultados. Hay momentos en los que ese enfoque puede resultar interesante. Por lo tanto, entender y ver qué sucede puede serte útil en algún momento, mi querido lector.

El aprendizaje no ocurre solo cuando acertamos al implementar el código.

También ocurre cuando el código no funciona como se esperaba.

En este caso, entender y saber cómo se hizo la solución es tan importante como la propia solución adoptada.

Muy bien, ahora ya tenemos elementos suficientes para ver el siguiente y último tipo de evento que puede ocurrir con un objeto. En este caso, como se trata de un objeto único y de un evento asociado específicamente a él, vamos a hacer un pequeño experimento que, a mi modo de ver, será muy divertido y bastante instructivo. Haremos algo que, por defecto, MetaTrader 5 no hace. Pero podremos crear una aplicación capaz de hacerlo. Será algo muy interesante e incluso bastante divertido, dada la naturaleza de la implementación.

Entonces, pasemos a un nuevo tema, para dejar todo debidamente separado.


Evento CHARTEVENT_OBJECT_ENDEDIT

Una de las cosas que, a mi entender, resulta bastante curiosa es que muchos usuarios no tienen idea de cuánto podemos manipular MetaTrader 5 para conseguir realizar ciertos tipos de tareas. Muchas veces, se trata de cosas que la gente insiste en decir que no se pueden hacer o que no sería posible hacer.

Bien, básicamente hay una cuestión que, por un lado, es bastante inusual. Mientras que, por otro, no tiene mucho sentido hacerla tal como se hace. Pero, paciencia. El hecho es que, a diferencia de lo que muchos piensan, MQL5 no es un lenguaje orientado a crear aplicaciones para MetaTrader 5. Aunque sirva, a grandes rasgos, para eso. En el fondo, MQL5 es un lenguaje orientado a darnos cierto control sobre cómo deberá funcionar MetaTrader 5, o sobre cómo queremos que funcione. Claro, dentro de determinadas reglas. Digo esto porque, sin entender este detalle simple y sencillo, resulta difícil entender lo que haremos en este tema.

Para la gran mayoría de los usuarios, sobre todo los principiantes, lo que se hará aquí es algo de otro mundo. Tal vez algún tipo de código creado por inteligencia artificial o por alienígenas. De todos modos, no es nada de eso. Lo que vamos a hacer es perfectamente posible con muy poco conocimiento, pero con una buena dosis de imaginación y creatividad. Además, claro, de una buena dosis de observación.

No sé si ya te has fijado en una cosa: un objeto OBJ_LABEL es muy parecido a un OBJ_EDIT. A grandes rasgos, la mayor, y quizá la única, diferencia entre ambos objetos es que OBJ_EDIT nos permite editar un texto directamente en el gráfico, mientras que el objeto OBJ_LABEL no. Pero, ¿realmente es así?

Bien, mi querido lector, lo cierto es que, aunque son muy semejantes, un objeto OBJ_EDIT no comparte las mismas características que un OBJ_LABEL, y viceversa. Pero eso, si miramos las cosas desde el punto de vista del usuario. Desde el punto de vista de la programación, en cambio, no es exactamente así. En este caso, ambos objetos son básicamente iguales, e incluso pueden trabajar en conjunto, siempre que pienses en cómo hacerlo.

Lo que haremos aquí es lo siguiente: crearemos una forma de hacer que un OBJ_LABEL se pueda editar directamente en el gráfico, sin necesidad de abrir su ventana de propiedades para escribir un texto. ¿Parece imposible? Bien, veamos cómo podría hacerse. Recuerda que aquí el objetivo es didáctico. Así que no te aferres demasiado a cómo se implementará el código. Procura entender por qué funciona lo que verás después. Esa es la parte importante.

Para empezar, necesitamos un código inicial. Como no quiero crear demasiadas cosas desde cero, para no perder mucho tiempo explicando lo que se está haciendo, vamos a crear una variación del código que vimos en el tema anterior. Puede verse a continuación.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #define def_Prefix  "Demo"
005. //+------------------------------------------------------------------+
006. #define macro_NameObject  def_Prefix + (string)(ObjectsTotal(0) + 1)
007. //+------------------------------------------------------------------+
008. struct st_Obj
009. {
010. //+----------------+
011.     private:
012.         string  szName,
013.                 font;
014.         bool    Selectable;
015.         int     x,
016.                 y,
017.                 w,
018.                 h,
019.                 fontSize;
020.         color   cor,
021.                 backColor;
022.         ENUM_OBJECT actual;
023. //+----------------+
024.     public:
025. //+----------------+
026.         void SetDefault(const string arg)
027.         {
028.             szName      = arg;
029.             font        = "Lucida Console";
030.             Selectable  = true;
031.             x           = 50;
032.             y           = 50;
033.             w           = 150;
034.             h           = 27;
035.             cor         = clrMediumBlue;
036.             backColor   = clrWhite;
037.             fontSize    = 20;
038.         }
039. //+----------------+
040.         void Create(const ENUM_OBJECT type)
041.         {
042.             actual = type;
043.             Recreates();
044.         }
045. //+----------------+
046.         void Recreates(void)
047.         {
048.             ObjectCreate(0, szName, actual, 0, 0, 0);
049.             ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, (actual != OBJ_EDIT ? Selectable : false));
050.             ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, x);
051.             ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, y);
052.             ObjectSetInteger(0, szName, OBJPROP_XSIZE, w);
053.             ObjectSetInteger(0, szName, OBJPROP_YSIZE, h);
054.             ObjectSetInteger(0, szName, OBJPROP_COLOR, cor);
055.             ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, fontSize);
056.             ObjectSetString(0, szName, OBJPROP_FONT, font);
057.             if (actual == OBJ_EDIT)
058.             {
059.                 ObjectSetInteger(0, szName, OBJPROP_BGCOLOR, backColor);
060.                 ObjectSetInteger(0, szName, OBJPROP_READONLY, false);
061.             }
062.         }
063. //+----------------+
064.         string GetName(void)
065.         {
066.             return szName;
067.         }
068. //+----------------+
069.         void Update(void)
070.         {
071.             font        = ObjectGetString(0, szName, OBJPROP_FONT);
072.             Selectable  = (int)ObjectGetInteger(0, szName, OBJPROP_SELECTABLE);
073.             x           = (int)ObjectGetInteger(0, szName, OBJPROP_XDISTANCE);
074.             y           = (int)ObjectGetInteger(0, szName, OBJPROP_YDISTANCE);
075.             w           = (int)ObjectGetInteger(0, szName, OBJPROP_XSIZE);
076.             h           = (int)ObjectGetInteger(0, szName, OBJPROP_YSIZE);
077.             fontSize    = (int)ObjectGetInteger(0, szName, OBJPROP_FONTSIZE);
078.             cor         = (color)ObjectGetInteger(0, szName, OBJPROP_COLOR);
079.             backColor   = (color)((ENUM_OBJECT)ObjectGetInteger(0, szName, OBJPROP_TYPE) == OBJ_LABEL ? backColor : ObjectGetInteger(0, szName, OBJPROP_BGCOLOR));
080.         }
081. //+----------------+
082. }gl_Obj;
083. //+------------------------------------------------------------------+
084. int OnInit()
085. {
086.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
087.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
088. 
089.     gl_Obj.SetDefault(macro_NameObject);
090.     gl_Obj.Create(OBJ_LABEL);
091. 
092.     return INIT_SUCCEEDED;
093. };
094. //+------------------------------------------------------------------+
095. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
096. {
097.     return rates_total;
098. };
099. //+------------------------------------------------------------------+
100. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
101. {
102.     switch (id)
103.     {
104.         case CHARTEVENT_MOUSE_MOVE      :
105.             ObjectSetString(0, gl_Obj.GetName(), OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam));
106.             break;        
107.         case CHARTEVENT_OBJECT_DELETE   :
108.             if (sparam == gl_Obj.GetName())gl_Obj.Recreates();
109.             break;
110.         case CHARTEVENT_OBJECT_CHANGE   :
111.             if (sparam == gl_Obj.GetName()) gl_Obj.Update();
112.             break;
113.     }
114.     ChartRedraw();
115. };
116. //+------------------------------------------------------------------+
117. void OnDeinit(const int reason)
118. {
119.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
120.     ObjectsDeleteAll(0, def_Prefix);
121.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
122.     ChartRedraw();
123. };
124. //+------------------------------------------------------------------+

Código 04

Aquí añadí unas pocas cosas. Sin embargo, necesitas notar lo siguiente: el funcionamiento de este código 04 es el mismo que el del código 03. Pero fíjate en que, en la línea 40, cuando llamamos al procedimiento para crear el objeto, tenemos que indicar qué tipo de objeto debe crearse. En este caso, trabajaremos con OBJ_EDIT y OBJ_LABEL. Al hacer estos cambios, empezamos a preparar el terreno para permitir otra cosa que pronto entenderás, que será precisamente manipular el objeto OBJ_LABEL para editar el texto directamente en el gráfico.

Ahora presta mucha atención al siguiente punto, que aparece en este código 04. Observa que, en la línea 49, donde realmente creamos el objeto, estamos definiendo la propiedad OBJPROP_SELECTABLE. Esta propiedad es muy importante y debemos definirla correctamente. En un objeto OBJ_LABEL, necesitamos definir esta propiedad como true si quieres poder seleccionar el objeto más adelante, como hemos hecho hasta ahora. Sin embargo, al definir esta misma propiedad como true en un objeto del tipo OBJ_EDIT, no podremos editar el texto directamente en el gráfico.

Como se dijo antes, cada objeto tiene sus propios detalles y, según el tipo de acción que queramos realizar, necesitamos que sus propiedades estén definidas de una determinada manera. Entonces, de forma resumida, cuando tengamos un objeto OBJ_EDIT con la propiedad OBJPROP_SELECTABLE en true, no podremos editar el texto. Pero sí podremos mover el objeto en la pantalla. Cuando esta misma propiedad esté configurada como false, podremos editar el texto, pero no podremos mover el objeto en la pantalla.

Bien, ahora observa que, en la línea 90, estamos indicando qué tipo de objeto queremos crear, es decir, un objeto del tipo OBJ_LABEL. Sin embargo, en la línea 108, ya no usamos la llamada Create, sino la llamada que recreará el objeto. Esto se debe a que ya sabemos qué tipo tendremos que crear. Pero la aplicación no, ya que, si utilizas el método para consultar el tipo de objeto, casi siempre obtendrás el tipo incorrecto. Esto ocurre porque MetaTrader 5 ya habrá destruido el objeto.

Cuando este evento CHARTEVENT_OBJECT_DELETE llega hasta nosotros, no nos está pidiendo permiso para destruir el objeto. MetaTrader 5 ya habrá destruido el objeto. Por eso, salvo que estemos usando un objeto de tipo cero, es decir, un OBJ_VLINE, nunca sabremos qué tipo de objeto fue destruido si se lo preguntamos a MetaTrader 5. Ya que no sabrá informarlo, porque el objeto ya no existe.

Hasta aquí, creo que todo ha sido muy simple y fácil de entender. Ahora viene la parte divertida. Vamos a tomar este código 04 y modificarlo para que varios eventos trabajen en conjunto, para obtener el resultado que queremos.

Como esto puede resultar bastante complicado si se presenta de una sola vez, vamos a hacerlo en dos pasos simples. El primero se muestra en el código que puedes ver a continuación.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #define def_Prefix  "Demo"
005. //+------------------------------------------------------------------+
006. #define macro_NameObject  def_Prefix + (string)(ObjectsTotal(0) + 1)
007. //+------------------------------------------------------------------+
008. struct st_Obj
009. {
010. //+----------------+
011.     private:
012.         string  szName,
013.                 font,
014.                 text;
015.         bool    Selectable;
016.         int     x,
017.                 y,
018.                 w,
019.                 h,
020.                 fontSize;
021.         color   cor,
022.                 backColor;
023.         ENUM_OBJECT actual;
024. //+----------------+
025.     public:
026. //+----------------+
027.         void SetDefault(const string arg)
028.         {
029.             szName      = arg;
030.             font        = "Lucida Console";
031.             Selectable  = true;
032.             x           = 50;
033.             y           = 50;
034.             w           = 250;
035.             h           = 27;
036.             cor         = clrMediumBlue;
037.             backColor   = clrWhite;
038.             fontSize    = 20;
039.         }
040. //+----------------+
041.         void Create(const ENUM_OBJECT type)
042.         {
043.             actual  = type;
044.             text    = EnumToString(type);
045.             Recreates();
046.         }
047. //+----------------+
048.         void Recreates(void)
049.         {
050.             ObjectCreate(0, szName, actual, 0, 0, 0);
051.             ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, (actual != OBJ_EDIT ? Selectable : false));
052.             ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, x);
053.             ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, y);
054.             ObjectSetInteger(0, szName, OBJPROP_XSIZE, w);
055.             ObjectSetInteger(0, szName, OBJPROP_YSIZE, h);
056.             ObjectSetInteger(0, szName, OBJPROP_COLOR, cor);
057.             ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, fontSize);
058.             ObjectSetString(0, szName, OBJPROP_FONT, font);
059.             ObjectSetString(0, szName, OBJPROP_TEXT, text);
060.             if (actual == OBJ_EDIT)
061.             {
062.                 ObjectSetInteger(0, szName, OBJPROP_BGCOLOR, backColor);
063.                 ObjectSetInteger(0, szName, OBJPROP_READONLY, false);
064.             }
065.         }
066. //+----------------+
067.         string GetName(void)
068.         {
069.             return szName;
070.         }
071. //+----------------+
072.         void Update(void)
073.         {
074.             font        = ObjectGetString(0, szName, OBJPROP_FONT);
075.             Selectable  = (int)ObjectGetInteger(0, szName, OBJPROP_SELECTABLE);
076.             x           = (int)ObjectGetInteger(0, szName, OBJPROP_XDISTANCE);
077.             y           = (int)ObjectGetInteger(0, szName, OBJPROP_YDISTANCE);
078.             w           = (int)ObjectGetInteger(0, szName, OBJPROP_XSIZE);
079.             h           = (int)ObjectGetInteger(0, szName, OBJPROP_YSIZE);
080.             fontSize    = (int)ObjectGetInteger(0, szName, OBJPROP_FONTSIZE);
081.             cor         = (color)ObjectGetInteger(0, szName, OBJPROP_COLOR);
082.             backColor   = (color)((ENUM_OBJECT)ObjectGetInteger(0, szName, OBJPROP_TYPE) == OBJ_LABEL ? backColor : ObjectGetInteger(0, szName, OBJPROP_BGCOLOR));
083.         }
084. //+----------------+
085. }gl_Obj;
086. //+------------------------------------------------------------------+
087. int OnInit()
088. {
089.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
090. 
091.     gl_Obj.SetDefault(macro_NameObject);
092.     gl_Obj.Create(OBJ_LABEL);
093. 
094.     return INIT_SUCCEEDED;
095. };
096. //+------------------------------------------------------------------+
097. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
098. {
099.     return rates_total;
100. };
101. //+------------------------------------------------------------------+
102. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
103. {
104.     switch (id)
105.     {
106.         case CHARTEVENT_OBJECT_CLICK    :
107.             if (sparam == gl_Obj.GetName())
108.             {
109.                 string sz0 = ObjectGetString(0, sparam, OBJPROP_TEXT);
110.                 if ((ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE) == OBJ_EDIT)
111.                     break;
112.                 ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
113.                 ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, true);
114.                 ObjectDelete(0, sparam);
115.                 gl_Obj.Create(OBJ_EDIT);
116.                 ObjectSetString(0, sparam, OBJPROP_TEXT, sz0);
117.             }
118.             break;
119.         case CHARTEVENT_OBJECT_CREATE   :
120.             if (sparam == gl_Obj.GetName())
121.             {
122.                 ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, false);
123.                 ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
124.             }
125.             break;
126.         case CHARTEVENT_OBJECT_DELETE   :
127.             if (sparam == gl_Obj.GetName())gl_Obj.Recreates();
128.             break;
129.         case CHARTEVENT_OBJECT_CHANGE   :
130.             if (sparam == gl_Obj.GetName()) gl_Obj.Update();
131.             break;
132.     }
133.     ChartRedraw();
134. };
135. //+------------------------------------------------------------------+
136. void OnDeinit(const int reason)
137. {
138.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
139.     ObjectsDeleteAll(0, def_Prefix);
140.     ChartRedraw();
141. };
142. //+------------------------------------------------------------------+

Código 05

Aquí, en este código 05, está ocurriendo algo muy extraño. Puedes observarlo en la siguiente animación.

Animación 06

Presta mucha atención a esta animación 06, mi querido lector. Observa que, en la línea 92, estamos pidiendo crear un objeto del tipo OBJ_LABEL. Por esta razón, en la línea 44, el texto que se nos mostrará es el tipo de objeto que usamos. Hasta ahí, normal. Sin embargo, en el momento en que hagas clic en este objeto del tipo OBJ_LABEL, se modificará a un objeto del tipo OBJ_EDIT. Esto se hace precisamente mediante el código de tratamiento del evento capturado en la línea 106. Observa que estamos centrando todo en el objeto que crearemos inicialmente.

No obstante, debido a la línea 109, el texto que esté en el objeto OBJ_LABEL se transferirá al objeto OBJ_EDIT, en la línea 116. Esto es importante para nosotros, ya que, sin esta transferencia, se colocaría otro texto en el objeto OBJ_EDIT. Y eso no es lo que queremos. Lo que realmente queremos es poder modificar el texto del objeto OBJ_LABEL directamente en el gráfico.

Ahora quiero que notes lo siguiente: dentro de este evento CHARTEVENT_OBJECT_CLICK, destruiremos el objeto del tipo OBJ_LABEL y crearemos uno del tipo OBJ_EDIT. Pero, como activamos la notificación para el evento CHARTEVENT_OBJECT_DELETE en la línea 89, necesitamos desactivarla. Esto se hace en la línea 112. Justo después, activamos otro evento, cuyo objetivo será informar a MetaTrader 5 que deseamos recibir notificaciones sobre la creación de un objeto. Esto hará que, en la línea 119, se capture el evento de creación del objeto que realiza la aplicación. En este manejador, desactivaremos la notificación de creación y volveremos a activar la notificación de destrucción.

Con esto, ahora el manejador de la línea 126 volverá a funcionar, aunque no de forma totalmente perfecta. Esto por el simple hecho de que, si destruyes el objeto OBJ_EDIT, se recreará, pero su texto ya no corresponderá al texto original que existía antes de que el objeto fuera destruido.

Esta pequeña corrección te la dejaré como ejercicio, para que practiques y busques una forma de resolver este pequeño inconveniente. Es algo muy simple de hacer. Sin embargo, recuerda que necesitarás manejar algunos datos para recuperar el texto que existía en el momento en que se creó el objeto del tipo OBJ_EDIT. En cambio, recuperar el texto que existía entre la creación del OBJ_EDIT y su destrucción exigirá bastante más trabajo. Ya que también necesitarás observar los eventos del teclado y del ratón, para mantener un texto actualizado en memoria y poder recrearlo.

Personalmente, esta segunda parte me parece bastante innecesaria y un trabajo bastante molesto. Ya que, si el usuario destruyó el texto antes de haberlo guardado dentro del objeto del tipo OBJ_LABEL, que intente recordar el texto que había en el objeto del tipo OBJ_EDIT. (RISAS).

Bien, ahora viene la parte final, que consiste precisamente en usar el evento de este tema. Para hacerlo, haremos una última modificación en el código. Esta puede verse a continuación.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #define def_Prefix  "Demo"
005. //+------------------------------------------------------------------+
006. #define macro_NameObject  def_Prefix + (string)(ObjectsTotal(0) + 1)
007. //+------------------------------------------------------------------+
008. struct st_Obj
009. {
010. //+----------------+
011.     private:
012.         string  szName,
013.                 font,
014.                 text;
015.         bool    Selectable;
016.         int     x,
017.                 y,
018.                 w,
019.                 h,
020.                 fontSize;
021.         color   cor,
022.                 backColor;
023.         ENUM_OBJECT actual;
024. //+----------------+
025.     public:
026. //+----------------+
027.         void SetDefault(const string arg)
028.         {
029.             szName      = arg;
030.             font        = "Lucida Console";
031.             Selectable  = true;
032.             x           = 50;
033.             y           = 50;
034.             w           = 250;
035.             h           = 27;
036.             cor         = clrMediumBlue;
037.             backColor   = clrWhite;
038.             fontSize    = 20;
039.         }
040. //+----------------+
041.         void Create(const ENUM_OBJECT type)
042.         {
043.             actual  = type;
044.             text    = EnumToString(type);
045.             Recreates();
046.         }
047. //+----------------+
048.         void Recreates(void)
049.         {
050.             ObjectCreate(0, szName, actual, 0, 0, 0);
051.             ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, (actual != OBJ_EDIT ? Selectable : false));
052.             ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, x);
053.             ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, y);
054.             ObjectSetInteger(0, szName, OBJPROP_XSIZE, w);
055.             ObjectSetInteger(0, szName, OBJPROP_YSIZE, h);
056.             ObjectSetInteger(0, szName, OBJPROP_COLOR, cor);
057.             ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, fontSize);
058.             ObjectSetString(0, szName, OBJPROP_FONT, font);
059.             ObjectSetString(0, szName, OBJPROP_TEXT, text);
060.             if (actual == OBJ_EDIT)
061.             {
062.                 ObjectSetInteger(0, szName, OBJPROP_BGCOLOR, backColor);
063.                 ObjectSetInteger(0, szName, OBJPROP_READONLY, false);
064.             }
065.         }
066. //+----------------+
067.         string GetName(void)
068.         {
069.             return szName;
070.         }
071. //+----------------+
072.         void Update(void)
073.         {
074.             font        = ObjectGetString(0, szName, OBJPROP_FONT);
075.             Selectable  = (int)ObjectGetInteger(0, szName, OBJPROP_SELECTABLE);
076.             x           = (int)ObjectGetInteger(0, szName, OBJPROP_XDISTANCE);
077.             y           = (int)ObjectGetInteger(0, szName, OBJPROP_YDISTANCE);
078.             w           = (int)ObjectGetInteger(0, szName, OBJPROP_XSIZE);
079.             h           = (int)ObjectGetInteger(0, szName, OBJPROP_YSIZE);
080.             fontSize    = (int)ObjectGetInteger(0, szName, OBJPROP_FONTSIZE);
081.             cor         = (color)ObjectGetInteger(0, szName, OBJPROP_COLOR);
082.             backColor   = (color)((ENUM_OBJECT)ObjectGetInteger(0, szName, OBJPROP_TYPE) == OBJ_LABEL ? backColor : ObjectGetInteger(0, szName, OBJPROP_BGCOLOR));
083.             text        = ObjectGetString(0, szName, OBJPROP_TEXT);
084.         }
085. //+----------------+
086. }gl_Obj;
087. //+------------------------------------------------------------------+
088. int OnInit()
089. {
090.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
091. 
092.     gl_Obj.SetDefault(macro_NameObject);
093.     gl_Obj.Create(OBJ_LABEL);
094. 
095.     return INIT_SUCCEEDED;
096. };
097. //+------------------------------------------------------------------+
098. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
099. {
100.     return rates_total;
101. };
102. //+------------------------------------------------------------------+
103. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
104. {
105. //+----------------+
106.     #define macro_SWAP(A)   {                                           \
107.                 string sz0 = ObjectGetString(0, sparam, OBJPROP_TEXT);  \
108.                 ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);   \
109.                 ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, true);    \
110.                 ObjectDelete(0, sparam);                                \
111.                 gl_Obj.Create(A);                                       \
112.                 ObjectSetString(0, sparam, OBJPROP_TEXT, sz0);          \
113.                 gl_Obj.Update();                                        \
114.                             }
115. //+----------------+
116.     switch (id)
117.     {
118.         case CHARTEVENT_OBJECT_CLICK    :
119.             if (sparam == gl_Obj.GetName())
120.                 if ((ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE) != OBJ_EDIT)
121.                     macro_SWAP(OBJ_EDIT);
122.             break;
123.         case CHARTEVENT_OBJECT_CREATE   :
124.             if (sparam == gl_Obj.GetName())
125.             {
126.                 ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, false);
127.                 ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
128.             }
129.             break;
130.         case CHARTEVENT_OBJECT_DELETE   :
131.             if (sparam == gl_Obj.GetName())gl_Obj.Recreates();
132.             break;
133.         case CHARTEVENT_OBJECT_CHANGE   :
134.             if (sparam == gl_Obj.GetName()) gl_Obj.Update();
135.             break;
136.         case CHARTEVENT_OBJECT_ENDEDIT  :
137.             if (sparam == gl_Obj.GetName()) macro_SWAP(OBJ_LABEL)
138.             break;
139.     }
140.     ChartRedraw();
141. //+----------------+
142.     #undef macro_SWAP
143. //+----------------+
144. };
145. //+------------------------------------------------------------------+
146. void OnDeinit(const int reason)
147. {
148.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
149.     ObjectsDeleteAll(0, def_Prefix);
150.     ChartRedraw();
151. };
152. //+------------------------------------------------------------------+

Código 06

Este código 06 ya tiene varias cosas funcionando, incluida la corrección de aquel pequeño inconveniente. Pero, a propósito, cambié parte del código para ocultar la forma en que resolví el problema. Esto para que los usuarios menos experimentados no consigan ver de inmediato cómo se obtuvo la solución.

Como este código 06 es tan simple como los demás, y quien viene estudiando y practicando podrá resolverlo sin dificultad, creo que no necesito explicar muchas cosas. Solo diré lo siguiente: cuando el objeto del tipo OBJ_EDIT termine la edición, lo que normalmente ocurre cuando pulsamos la tecla ENTER, MetaTrader 5 disparará un evento del tipo CHARTEVENT_OBJECT_ENDEDIT. Este evento será capturado por la línea 136, y esta hará que el objeto que antes era del tipo OBJ_EDIT vuelva a ser un objeto del tipo OBJ_LABEL.

El resultado de una posible ejecución puede verse a continuación.

Animación 07

Pero no te dejes engañar por esta animación 07, mi querido lector. Procura estudiar y entender lo que realmente está ocurriendo aquí. Pues lo que estás viendo en esta animación 07 es apenas la punta de un enorme iceberg. Esto ocurre porque aquí solo mostré lo más básico del asunto. Podemos ir mucho, mucho más lejos que lo que se mostró aquí.


Consideraciones finales

Tal vez muchos hayan encontrado este artículo un tanto aburrido, ya que, en todo momento, mostré todos los códigos completos y di pocas explicaciones sobre cómo funcionan realmente. Pero, personalmente, considero innecesario repetir cosas que ya fueron explicadas en otros artículos de esta misma serie. Claro que aquí hicimos algo que a muchos podría sorprenderles e incluso despertarles cierta desconfianza. Está bien, forma parte del proceso de aprendizaje.

Pero me gustaría darte un último consejo, mi querido lector, antes de terminar el artículo y pasar al siguiente. En este último tema, viste que podemos hacer muchas cosas interesantes. Si te fijas, acabarás notando que cambié las dimensiones del objeto del tipo OBJ_LABEL, para que fuera posible incluir un texto un poco más largo. Mi consejo es: intenta entender cómo se crea realmente el texto en un objeto del tipo OBJ_EDIT. Esto para que puedas cambiar las dimensiones del objeto OBJ_LABEL cuando vayas a mostrar el texto en el gráfico.

Además, intenta idear una forma de mover el objeto del tipo OBJ_LABEL en el gráfico. Esto ocurre porque siempre quedará algo fijo en el lugar donde fue creado. Observa que esto sucede precisamente porque, en el momento en que sea seleccionado, debido al evento CHARTEVENT_OBJECT_CLICK, se transformará en un objeto del tipo OBJ_EDIT. Esto nos impide cambiarlo de ubicación.

Además, hay otra cosa que también podemos hacer, pero muy probablemente la veremos en el próximo artículo. Si, por casualidad, noto que resulta poco interesante o que no aportará mucho para incluirlo en un artículo, diré de qué se trata. Pero, de cualquier forma, ahora ya sabes un poco más sobre MQL5 y MetaTrader 5 de lo que sabías al comienzo de este artículo.

Entonces, intenta estudiar y practicar con los códigos presentes en el anexo, y nos vemos en el próximo artículo.

Archivo MQ5 Descripció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/16050

Archivos adjuntos |
Anexo.zip (4.5 KB)
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.
Del básico al intermedio: Eventos en objetos (I) Del básico al intermedio: Eventos en objetos (I)
En este artículo, veré tres de los seis eventos que MetaTrader 5 puede disparar cuando algo sucede con un objeto presente en el gráfico. Estos eventos son muy útiles cuando se trata de interacción con el usuario. Esto se debe a que, sin entender estos eventos, tendrás mucho más trabajo para mantener cierta configuración en el gráfico, al intentar controlar objetos con finalidades específicas.
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.
Simulación de mercado: Iniciando SQL en MQL5 (IV) Simulación de mercado: Iniciando SQL en MQL5 (IV)
Muchos suelen infrautilizar SQL, o incluso no utilizarlo, porque no comprenden bien cómo funciona en realidad. Al consultar una base de datos SQL, no siempre buscamos una respuesta genérica; en algunos casos queremos una respuesta muy concreta y práctica. Si tú creas una base de datos con cierta estructuración y modelado, podrás introducir prácticamente cualquier tipo de información en ella.