Del básico al intermedio: Eventos en Objetos (IV)
Introducción
En el artículo anterior, Del básico al intermedio: Eventos en Objetos (III), se mostró la primera parte de algo aún mayor, que veremos aquí en este artículo, donde concluiremos lo que se inició en el artículo anterior.
Como el objetivo de estos artículos es demostrar cómo tú, mi querido lector, puedes controlar la forma en que MetaTrader 5 va a funcionar para implementar alguna aplicación, espero que estés estudiando y practicando lo que se ha mostrado aquí. Ya que, en ningún caso, ninguno de los códigos vistos en estos artículos debe considerarse una aplicación terminada ni utilizarse sin el debido cuidado y criterio.
Lo que estamos viendo aquí, en este momento, para muchos podría considerarse una especie de delirio de mi parte. Porque mucha gente no cree que una aplicación pueda siquiera tener como objetivo hacer lo que estoy mostrando aquí. Pero la gracia de programar algo no está en hacer lo obvio, sino en buscar nuevas posibilidades dentro de aquello que pocos logran ver. Por esta razón, decidí crear esta secuencia, que busca precisamente mostrar que puedes lograr casi todo, siempre que entiendas ciertos conceptos. Así que ha llegado el momento de hacer que lo visto en el artículo anterior realmente cobre sentido.
Mejorando la interactividad
Lo que vimos en el artículo anterior está lejos de tener un sentido lógico, al menos si solo observas la parte referente al código actual, que se dejó en el archivo adjunto para que pudieras estudiar y practicar, a fin de entender cómo podía generarse esa interacción con muy poco código.
Ese código que quedó en el archivo adjunto del artículo anterior todavía no ha alcanzado su verdadera madurez. Es decir, aunque ya muestra algunas cosas que podemos implementar con un objetivo primario bastante curioso y de forma muy simple, todavía no nos permite interactuar con los objetos como queremos. En resumen: ese código solo genera lo que puede verse en la animación 01, a continuación.

Animación 01
Aunque parezca algo tonto e incluso sin mucho propósito, esta animación nos muestra el potencial que ya contiene ese código, ya que nos permite crear un contorno sobre un objeto seleccionado. Sin embargo, nuestro objetivo no es solo crear este contorno, sino cambiar las dimensiones del objeto seleccionado. Aquí es donde todo empieza a volverse algo confuso. Aquí es donde la cosa empieza a volverse algo confusa, sobre todo para quienes no vienen practicando y estudiando lo que muestro en mis artículos.
En mis artículos, hasta este momento, he mostrado que podemos interactuar con MetaTrader 5 para conseguir algún resultado. Gran parte del trabajo, sin embargo, quedaba en manos de MetaTrader 5, y solo una parte la hacíamos nosotros durante la implementación del código. No obstante, aquí tenemos un pequeño problema. Puede resumirse de la siguiente manera: MetaTrader 5 puede hacer buena parte del trabajo necesario para que nuestra aplicación controle las dimensiones de un objeto seleccionado. Pero, y este es el punto clave, no podremos hacer esto sin trabajo adicional. Lo digo porque, usando el código original, que se ve completo a continuación, NO TENDREMOS UNA INTERACCIÓN 100% ADECUADA, ya que el resultado de la ejecución se ve en la animación 01.
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 01
Pero ¿por qué estás diciendo que la interacción no será 100% adecuada? Para mí, esto no tiene sentido, ya que el mecanismo aparentemente está funcionando. En efecto, mi querido lector, el mecanismo está funcionando. Sin embargo, hay un pequeño problema, y está destacado en la siguiente imagen.

Imagen 01
Tal vez esta imagen 01 no te diga nada. Pero destaco un punto en ella precisamente para llamar tu atención sobre un detalle. La línea de contorno NO TIENE ELEMENTOS DE INTERACCIÓN, es decir, no tenemos cómo indicar a MetaTrader 5, ni al usuario, cómo mover esta línea. Ahora, presta atención. En el código 01, la línea 84 sirve precisamente para impedir que el usuario pueda seleccionar cualquier objeto cuyo nombre tenga el prefijo indicado en la línea cuatro de ese mismo código. ¿Y qué quiere decir esto? Bien, esto explica por qué no podemos seleccionar el objeto OBJ_BUTTON, ya que este objeto tiene en su nombre el prefijo mencionado. Esto puede verse en la imagen 02, a continuación.

Imagen 02
Observa que estoy marcando en verde precisamente el prefijo que nos impide seleccionar ese objeto. Aunque, a primera vista, esto no parezca tan relevante, este simple detalle ya es suficiente para que la comprobación de la línea 84 nos impida seleccionarlo. Bien, pero no necesitamos seleccionar las líneas en verde, que se usan como contorno. Podemos hacerlo mejor si seleccionamos el propio objeto del tipo OBJ_RECTANGLE_LABEL y dejamos que MetaTrader 5 haga todo lo demás por nosotros. Sí, en parte esto es cierto. Para conseguirlo, sería necesario hacer un simple cambio en el código 01. Este cambio puede verse en el fragmento de código a continuación.
. . . 034. //+----------------+ 035. public : 036. void CreateBoxResize(const string szArg, const uchar adjust = 2) 037. { 038. isBack = (bool)ObjectGetInteger(0, szArg, OBJPROP_BACK); 039. ObjectSetInteger(0, szArg, OBJPROP_BACK, true); 040. ObjectSetInteger(0, szArg, OBJPROP_SELECTED, false); 041. x = (ushort)ObjectGetInteger(0, szArg, OBJPROP_XDISTANCE) - adjust; 042. y = (ushort)ObjectGetInteger(0, szArg, OBJPROP_YDISTANCE) - adjust; 043. w = (ushort)ObjectGetInteger(0, szArg, OBJPROP_XSIZE) + adjust; 044. h = (ushort)ObjectGetInteger(0, szArg, OBJPROP_YSIZE) + adjust; 045. for (uchar c = 0; c < 4; c++) 046. { 047. CreateEdge(c); 048. ObjectSetInteger(0, szObjects[c], OBJPROP_WIDTH, 3); 049. ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTABLE, true); 050. ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTED, true); 051. } 052. } 053. //+----------------+ . . .
Código 02
Lo que este fragmento del código 02 producirá en el código 01 se muestra en la animación 02, a continuación.

Animación 02
Muy bien, en esta animación 02 vemos algo. Vamos a observarlo con un poco más de calma. Para ello, observemos la imagen 03.

Imagen 03
Este punto que destaco en esta imagen 03 es precisamente el punto clave que podemos usar para arrastrar el objeto en la ventana. Para ello, usamos MetaTrader 5 sin necesidad de preocuparnos por absolutamente nada. Sin embargo, si observas, notarás que solo tenemos 3 puntos visibles. ¿Dónde está el cuarto punto? Bien, este es nuestro primer problema. El cuarto punto está junto con otro punto, precisamente en este lugar que destaco en la imagen 03. Pero, si ese fuera el único problema real, no pasaría nada. No habría mucho que hacer al respecto. No obstante, hay un problema un poco más molesto que este. Ese problema se muestra en la animación a continuación.

Animación 03
Ahora no entiendo. ¿Por qué esto que vemos en la animación 03 sería un problema, si es algo que forma parte del funcionamiento normal de MetaTrader 5? Bien, mi querido lector, creo que no entendiste lo que queremos hacer. Déjame aclararlo un poco más. Lo que estamos intentando hacer sería algo parecido a lo que se ve en la animación 03, pero sin que las líneas verdes pierdan contacto entre sí. Es decir, en el momento en que se mueva el punto de anclaje, toda línea conectada a él también deberá moverse en consecuencia, de modo que se delimite una nueva región y se muestren así cuáles serían las nuevas dimensiones del objeto seleccionado. Ese es nuestro objetivo, y no eso que puede verse en la animación 03. ¿Entiendes ahora dónde está el problema?
Bien, en este punto, muchos ya se rendirían y empezarían a decir: "Eso que quieres hacer es imposible." ¿Será eso realmente cierto, mi querido lector? Bien, piensa un poco. Sabemos cómo MetaTrader 5 informa a nuestra aplicación sobre los eventos que ocurren con los objetos. También vimos cómo ciertas decisiones de implementación pueden influir en el tipo de resultado que podemos obtener. Entonces, pregunto:
¿Es realmente imposible lo que quiero hacer?
La respuesta es no. Sin embargo, la mayoría, por tener menos conocimientos y experiencia, acaba creyendo que sí, ya que, como puede verse en la animación 03, las cosas no ocurrieron como se esperaba. Pero, una vez más, debo resaltar lo siguiente:
No te aferres al código. Procura siempre entender los conceptos implicados y cómo se utilizaron.
Eso es precisamente lo que hacen los programadores. Toman un problema y, usando sus conocimientos de programación, encuentran y elaboran una solución. La forma en que cada uno elaborará esa solución variará, con toda seguridad, de un programador a otro. No obstante, lo que realmente importa es el resultado final. Por eso, distintas aplicaciones pueden ser más o menos interactivas, y puede ser más o menos fácil aprender a usarlas. Sin embargo, en todos los casos, lo que realmente importa y nos interesa es el resultado final.
Bien, entonces vamos a detenernos y pensar un poco en esto. Sabemos que NO PODEMOS SELECCIONAR ninguno de los objetos cuyo nombre tenga el prefijo definido en el código. Esto se debe precisamente a la línea 84 del código 01. Pero ahora sabemos que no necesitamos seleccionar esos objetos. Podemos, durante su creación, indicar a MetaTrader 5 que el objeto ya estará seleccionado. Esto se debe a las líneas 49 y 50, que se ven en el fragmento del código 02 y constituyen precisamente la modificación que implementamos en el código 01.
Debido a este hecho, conseguimos que MetaTrader 5 diera al usuario la posibilidad de mover objetos que antes no podían moverse porque no podíamos seleccionarlos. No sé si estás siguiendo la línea de razonamiento que intento mostrarte, mi querido lector. Pero, si no es así, procura revisar lo que se explicó en los artículos anteriores para entender a dónde quiero llegar. Esto es lo que intento mostrarte será precisamente el concepto que adoptaremos para alcanzar nuestro objetivo. Es decir, redimensionar de forma totalmente interactiva un objeto presente en el gráfico.
Está bien, hagamos lo siguiente: como quizá muchos no sepan cómo incorporar el fragmento que se ve en el código 02 al código 01 para conseguir lo que se ve en la animación 03, crearé un nuevo código, de modo que, en el archivo adjunto, tendrás acceso al código 02 completo. Así podrás estudiarlo con calma y aprender cómo integrar un código en otro ya existente. Pues haremos esto muchas veces. Saber cómo hacerlo podrá ayudarte a estudiar y practicar cosas que, de otro modo, no serían posibles.
Bien, entonces, partiendo del código 01, con la modificación que se ve en el fragmento de código 02, vamos a crear un nuevo código. Este puede verse a continuación.
. . . 013. string szObjects[6]; 014. //+----------------+ 015. void CreateEdge(const char what) 016. { 017. ushort p1 = x, p2 = y, p3 = w, p4 = h; 018. 019. switch (what) 020. { 021. case 0: p4 = 1; break; 022. case 1: p3 = 1; break; 023. case 2: p2 = y + h; p4 = 1; break; 024. case 3: p1 = x + w; p3 = 1; break; 025. case 5: p1 += p3; p2 += p4; 026. case 4: p3 = p4 = 1; break; 027. } . . . 035. } 036. //+----------------+ 037. public : 038. void CreateBoxResize(const string szArg, const uchar adjust = 2) 039. { . . . 047. for (uchar c = 0; c < szObjects.Size(); c++) 048. { 049. CreateEdge(c); 050. if (c > 3) 051. { 052. ObjectSetInteger(0, szObjects[c], OBJPROP_WIDTH, 3); 053. ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTABLE, true); 054. ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTED, true); 055. } 056. } 057. } 058. //+----------------+ . . .
Código 03
Ahora, observa lo siguiente, mi querido lector: en este fragmento de código 03, hay un cambio simple. Sin embargo, es bastante significativo. Observa que, en la línea 13, cambiamos la cantidad de elementos del array. En el comando switch de la línea 19, además, tenemos dos puntos más de interés, que pueden verse en las líneas 25 y 26. Ahora, hagamos una breve pausa para que pueda explicarte otra cosa, que es muy importante.
En el artículo Del básico al intermedio: Comando SWITCH, había una imagen en la que se mencionaba la presencia de algunas líneas rojas. Esta imagen puede volver a verse a continuación.

Imagen 04
La cuestión aquí está precisamente en esas líneas rojas que se ven en la imagen 04. En este comando switch de la línea 19, se utilizan esas líneas rojas. Esto es importante, ya que esto permite que la ejecución “caiga” en el siguiente case. Vaya, ¿y yo que imaginaba que las cosas no podían volverse aún más confusas! Y me dices que un código puede pasar a otro bloque de código. ¿Y que eso se hace de manera intencional? Creo que esto de programar es mucho más complicado de lo que imaginaba. Ahora estoy pensando en desistir y buscar otra cosa que hacer.
Calma, mi querido lector, no hay motivo para que desistas ahora. Justo ahora que todo va a ponerse aún más interesante, ¿vas a desistir? No, acompáñame a entender en qué consiste ese paso del que te hablo.
Observa la secuencia de los comandos case. Notarás que están casi alineados, hasta que, de repente, el orden cambia: del tres al cinco y después al cuatro. Qué cosa tan extraña. No había prestado atención a eso. Ahora que lo mencionas, realmente parece extraño. Pero ¿por qué haces eso? Bien, la razón es que lo hice a propósito. ¿Por qué? Precisamente para explicarte esa línea roja que puede verse en la imagen 04.
Entonces, presta atención: aunque sea algo simple, suele confundir seriamente a muchos buenos programadores, y no solo a principiantes. Observa que, a propósito, en cada línea entre la 21 y la 26, en el fragmento del código 03, hay un comando break al final de la línea. En todas menos en una, la línea 25. ¿Por qué? El motivo es que, cuando el case de la línea 25 coincida, esa línea se ejecutará. Pero, y aquí es donde entra la cuestión de la caída al siguiente case, NO QUIERO QUE LA EJECUCIÓN TERMINE AHÍ. Quiero que se ejecute la rutina presente en el siguiente case sin que se evalúe ese case.
Virgen santa María. Que DIOS tenga misericordia. No entendí nada de lo que dijiste. Pero espera un momento, déjame pensar con calma en lo que dijiste. Hasta donde logro entender, quieres que, en el momento en que se compruebe la línea 25 y se ejecute la rutina del comando case, también se ejecute la rutina del comando case de la línea 26. ¿Es eso? Sí, mi querido lector. Pero hay algo que no entendí. ¿Cómo se va a ejecutar la rutina del comando case presente en la línea 26 sin que se analice el comando case? Eso no tiene sentido.
Es precisamente en este punto donde se produce la caída al siguiente case. Observa lo siguiente, mi querido lector. Como el comando case siempre termina en un comando break, tal como puedes ver en la imagen 04, si NO TENEMOS EL COMANDO BREAK, la ejecución continuará en la rutina del siguiente case, SIN PASAR POR LA COMPROBACIÓN DEL SIGUIENTE CASE. Y ahí es donde entra en acción la línea roja.
Fíjate en que, cuando estemos creando el objeto número cuatro, pondremos sus dimensiones iguales a uno. Cuando estemos creando el objeto cinco, también pondremos sus dimensiones iguales a uno. Sin embargo, en el caso del objeto cinco, cambiaremos su posición inicial para que quede en la esquina inferior derecha, a diferencia del objeto cuatro, que queda en la esquina superior izquierda. Todo esto se hace aprovechando esa caída al siguiente case que puedes observar en este comando switch de la línea 19.
Sin embargo, observa lo siguiente: en la línea 50, comprobamos qué objeto estamos creando. Cuando hayamos dibujado el cuadrado que delimita el objeto seleccionado, pasamos a dibujar los dos últimos objetos de la misma manera que se hacía en el fragmento mostrado en el código 02. No obstante, con un objetivo y un resultado diferentes, como puedes observar en la siguiente imagen.

Imagen 05
Observa que ahora, en este caso, tenemos dos puntos, que es todo lo que necesitamos. Sin embargo, aún no hemos terminado. Esto se debe a que, aunque los puntos ya se hayan creado, conviene ver qué ocurre cuando interactuamos con ellos. Esto puede verse en la animación a continuación.

Animación 04
Mmm, interesante. En cierto modo, ahora, a mi entender, tenemos algo relativamente prometedor. Entonces, ¿cuál sería nuestro siguiente paso? Bien, mi querido lector, las cosas no siempre son tan simples. La cuestión aquí no es exactamente cuál es el siguiente paso. La cuestión principal, y esto es lo que necesito de ti, es que entiendas lo que estamos haciendo aquí. Este tipo de manipulación que estoy mostrando exige cierto cuidado y la creación de pruebas para asegurar que todo no se salga de control. Sin eso, quedas a merced de que algún fallo termine convirtiendo tu día en algo muy desagradable.
Entonces, una vez más: no te aferres al código. Procura entender los conceptos que se aplican. Intenta crear una solución que puedas entender. Porque solo así conseguirás resolver los problemas que surgirán durante la vida útil de tu aplicación.
Una vez hecha esta advertencia, que no me canso de repetir, podemos pasar al siguiente punto. En este caso, tendremos que modificar el código de manejo de eventos, así como otra cosa que veremos después. Pero, primero, el código de manejo de eventos. Este puede verse a continuación.
. . . 079. //+------------------------------------------------------------------+ 080. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 081. { 082. static string szNameObj = NULL; 083. 084. switch (id) 085. { 086. case CHARTEVENT_KEYDOWN : 087. if ((szNameObj != NULL) && (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE))) 088. { 089. gl_RZ.RemoveBoxResize(szNameObj); 090. szNameObj = NULL; 091. } 092. break; 093. case CHARTEVENT_OBJECT_CLICK : 094. Print((ushort)lparam, " x ", (ushort)dparam, " ->> ", sparam); 095. if (StringFind(sparam, def_Prefix) != INVALID_HANDLE) break; 096. if (szNameObj != NULL) gl_RZ.RemoveBoxResize(szNameObj); 097. if (szNameObj != sparam) gl_RZ.CreateBoxResize(szNameObj = sparam); else szNameObj = NULL; 098. break; 099. case CHARTEVENT_OBJECT_DRAG : 100. Print("CHARTEVENT_OBJECT_DRAG ->> ", sparam); 101. break; 102. } 103. ChartRedraw(); 104. }; 105. //+------------------------------------------------------------------+ . . .
Código 04
En este fragmento, que se ve en el código 04, hacemos una simple modificación para mostrarte algo que, a mi juicio, es importante, aunque ya se haya visto en artículos anteriores. Aun así, siempre es bueno reforzar ciertos conceptos hasta que realmente los hayas comprendido, mi querido lector.
Observa que, en las líneas 94 y 100, indicamos que se imprima algún mensaje en el terminal. Así, veamos el resultado de una primera ejecución, digamos, normal del código. Esta puede verse en la animación a continuación.

Animación 05
Ahora, observa un comportamiento que queremos evitar. Este se muestra en la animación 06, a continuación.

Animación 06
En ambas animaciones, tanto en la 05 como en la 06, vemos en acción el comportamiento estándar de MetaTrader 5. Sin embargo, podemos convivir con el comportamiento que se ve en la animación 05, pero no con el que se ve en la animación 06. En el caso de la animación 06, el usuario activa o desactiva la selección de un objeto que no debería poder seleccionar. Solo debería poder mover el objeto. Sin embargo, no debería poder activar ni desactivar el estado de seleccionado del objeto.
Entonces, vamos a resolver esto por pasos, y el primer paso consiste en impedir que el estado de selección del objeto se modifique por el simple hecho de que el usuario haga clic en él. Esto puede hacerse con el código que se muestra a continuación.
. . . 007. //+------------------------------------------------------------------+ 008. struct st_BoxResize 009. { . . . 036. //+----------------+ 037. public : . . . 065. //+----------------+ 066. void Block(const string szArg) 067. { 068. for (uchar c = 0; c < szObjects.Size(); c++) 069. if (szObjects[c] == szArg) 070. ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTED, true); 071. } 072. //+----------------+ 073. }gl_RZ; 074. //+------------------------------------------------------------------+ . . . 086. //+------------------------------------------------------------------+ 087. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 088. { 089. static string szNameObj = NULL; 090. 091. switch (id) 092. { 093. case CHARTEVENT_KEYDOWN : 094. if ((szNameObj != NULL) && (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE))) 095. { 096. gl_RZ.RemoveBoxResize(szNameObj); 097. szNameObj = NULL; 098. } 099. break; 100. case CHARTEVENT_OBJECT_CLICK : 101. Print((ushort)lparam, " x ", (ushort)dparam, " ->> ", sparam); 102. if (StringFind(sparam, def_Prefix) != INVALID_HANDLE) 103. { 104. gl_RZ.Block(sparam); 105. break; 106. } 107. if (szNameObj != NULL) gl_RZ.RemoveBoxResize(szNameObj); 108. if (szNameObj != sparam) gl_RZ.CreateBoxResize(szNameObj = sparam); else szNameObj = NULL; 109. break; 110. case CHARTEVENT_OBJECT_DRAG : 111. Print("CHARTEVENT_OBJECT_DRAG ->> ", sparam); 112. break; 113. } 114. ChartRedraw(); 115. }; 116. //+------------------------------------------------------------------+ . . .
Código 05
Observa lo fácil que es implementar la solución de este primer problema. Lo único que fue necesario hacer fue incorporar un nuevo procedimiento dentro de la estructura, que puede verse en la línea 66. Después, fue necesario añadir una llamada a este nuevo procedimiento, y eso se hace en la línea 104. ¿El resultado? Bien, míralo por ti mismo en la animación a continuación.

Animación 07
Ahora viene la segunda parte de la cuestión que necesitamos resolver. Pero, antes, quiero recordarte que la solución que mostraré aquí está orientada únicamente a fines didácticos. No es necesariamente la única que podemos implementar. Sin embargo, precisamente por el propósito de estos artículos, mostraré la solución más simple de todas.
Aun así, nada te impide implementar una solución mucho más adecuada. Incluso podría ser una solución en la que cada movimiento del mouse, con el fin de cambiar las dimensiones, se refleje de inmediato en las dimensiones del objeto que intentamos redimensionar de manera interactiva. Quizá, más adelante, te muestre cómo hacerlo. Es algo que, en principio, también puede ser bastante educativo e incluso interesante en varios casos que tú, mi querido lector, puedas planear implementar en el futuro.
De cualquier manera, vayamos a la solución más simple de todas. Básicamente, implica dos pasos, y el primero puede verse implementado en el siguiente fragmento.
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. enum E_Elment { 009. TOP, 010. LEFT, 011. BOTTOM, 012. RIGHT, 013. TL, 014. BR 015. }; 016. //+------------------------------------------------------------------+ 017. struct st_BoxResize 018. { 019. private : 020. ushort x, y, w, h; 021. bool isBack; 022. string szObjects[6]; 023. //+----------------+ 024. void SetPosition(const E_Elment what) 025. { 026. ushort p1 = x, p2 = y, p3 = w, p4 = h; 027. 028. switch (what) 029. { 030. case TOP: p4 = 1; break; 031. case LEFT: p3 = 1; break; 032. case BOTTOM: p2 = y + h; p4 = 1; break; 033. case RIGHT: p1 = x + w; p3 = 1; break; 034. case BR: p1 += p3; p2 += p4; 035. case TL: p3 = p4 = 1; break; 036. } 037. ObjectSetInteger(0, szObjects[what], OBJPROP_XDISTANCE, p1); 038. ObjectSetInteger(0, szObjects[what], OBJPROP_YDISTANCE, p2); 039. ObjectSetInteger(0, szObjects[what], OBJPROP_XSIZE, p3); 040. ObjectSetInteger(0, szObjects[what], OBJPROP_YSIZE, p4); 041. } 042. //+----------------+ 043. void CreateEdge(const E_Elment what) 044. { 045. szObjects[what] = macro_NameObject; 046. ObjectCreate(0, szObjects[what], OBJ_RECTANGLE_LABEL, 0, 0, 0); 047. ObjectSetInteger(0, szObjects[what], OBJPROP_BGCOLOR, clrLime); 048. SetPosition(what); 049. } 050. //+----------------+ 051. public : . . . 072. //+----------------+ 073. void UpdateBoxSize(const string szArg) 074. { 075. for (E_Elment c = 0; c < (E_Elment)szObjects.Size(); c++) 076. if (szObjects[c] == szArg) 077. { 078. x = (ushort)ObjectGetInteger(0, szObjects[TL], OBJPROP_XDISTANCE); 079. y = (ushort)ObjectGetInteger(0, szObjects[TL], OBJPROP_YDISTANCE); 080. w = (ushort)ObjectGetInteger(0, szObjects[BR], OBJPROP_XDISTANCE) - x; 081. h = (ushort)ObjectGetInteger(0, szObjects[BR], OBJPROP_YDISTANCE) - y; 082. for (E_Elment i = 0; i < (E_Elment)4; i++) 083. SetPosition(i); 084. } 085. } 086. //+----------------+ . . . 100. //+----------------+ 101. }gl_RZ; 102. //+------------------------------------------------------------------+ . . . 114. //+------------------------------------------------------------------+ 115. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 116. { 117. static string szNameObj = NULL; 118. 119. switch (id) 120. { . . . 137. case CHARTEVENT_OBJECT_DRAG : 138. if (StringFind(sparam, def_Prefix) != INVALID_HANDLE) 139. gl_RZ.UpdateBoxSize(sparam); 140. break; 141. } 142. ChartRedraw(); 143. }; 144. //+------------------------------------------------------------------+ . . .
Código 06
Sé que este código 06 está bastante fragmentado. Pero es intencional, porque aquí quiero dar énfasis solo a las partes que realmente era necesario implementar. Observa que, para simplificar varias cosas, opté por crear una enumeración en la línea ocho. Esta, a su vez, facilitará la implementación de estos dos últimos pasos. También dividí en dos bloques la parte de la construcción de los objetos que crean el cuadro delimitador. Uno es el procedimiento SetPosition, que puedes ver en la línea 24. El otro es la creación propiamente dicha de los lados del cuadro delimitador. En este caso, este procedimiento está en la línea 43.
Lo importante aquí es precisamente la aparición de este procedimiento UpdateBoxSize, que se ve en la línea 73. Este procedimiento reconstruirá básicamente el cuadro delimitador en una nueva posición cuando el evento CHARTEVENT_OBJECT_DRAG dispare la llamada que se ve en la línea 139.
De cualquier modo, al final, el objetivo que queremos alcanzar puede verse a continuación.

Animación 08
Este punto del código, tal como muestra la animación 08, es clave. Aquí, puedes cambiar la forma en que se obtiene el resultado de la animación 08. Pero lo importante es que se obtenga de la manera en que se muestra en esta animación 08. Sin embargo, además de este objetivo, en la animación 08 hay algo que podemos cambiar, según lo que cada programador en particular considere necesario o deseable. Podemos dar por cerrado aquí el proceso de redimensionamiento, tal como se muestra en la animación 08, o podemos hacerlo de otra manera, que es precisamente el camino que seguiremos. El motivo es simple: mantener la didáctica lo más cerca posible de lo que considero ideal.
Así, el siguiente paso puede verse en el código a continuación. Sin embargo, en este caso, dejaré el código completo para que puedas apreciar toda la belleza que puede crearse con un nivel de programación muy sencillo y básico, pero que hace un uso correcto de diversos conceptos y enseñanzas mostrados hasta este momento. Solo admira y disfruta el resultado. Te lo mereces, mi querido lector, después de tanto esperar e imaginar cuál sería la solución que daría al problema de redimensionar interactivamente un objeto en el gráfico.
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. enum E_Elment { 009. TOP, 010. LEFT, 011. BOTTOM, 012. RIGHT, 013. TL, 014. BR 015. }; 016. //+------------------------------------------------------------------+ 017. struct st_BoxResize 018. { 019. private : 020. ushort x, y, w, h; 021. bool isBack; 022. string szObjects[6]; 023. //+----------------+ 024. void SetPosition(const E_Elment what) 025. { 026. ushort p1 = x, p2 = y, p3 = w, p4 = h; 027. 028. switch (what) 029. { 030. case TOP: p4 = 1; break; 031. case LEFT: p3 = 1; break; 032. case BOTTOM: p2 = y + h; p4 = 1; break; 033. case RIGHT: p1 = x + w; p3 = 1; break; 034. case BR: p1 += p3; p2 += p4; 035. case TL: p3 = p4 = 1; break; 036. } 037. ObjectSetInteger(0, szObjects[what], OBJPROP_XDISTANCE, p1); 038. ObjectSetInteger(0, szObjects[what], OBJPROP_YDISTANCE, p2); 039. ObjectSetInteger(0, szObjects[what], OBJPROP_XSIZE, p3); 040. ObjectSetInteger(0, szObjects[what], OBJPROP_YSIZE, p4); 041. } 042. //+----------------+ 043. void CreateEdge(const E_Elment what) 044. { 045. szObjects[what] = macro_NameObject; 046. ObjectCreate(0, szObjects[what], OBJ_RECTANGLE_LABEL, 0, 0, 0); 047. ObjectSetInteger(0, szObjects[what], OBJPROP_BGCOLOR, clrLime); 048. SetPosition(what); 049. } 050. //+----------------+ 051. public : 052. void CreateBoxResize(const string szArg, const uchar adjust = 2) 053. { 054. isBack = (bool)ObjectGetInteger(0, szArg, OBJPROP_BACK); 055. ObjectSetInteger(0, szArg, OBJPROP_BACK, true); 056. ObjectSetInteger(0, szArg, OBJPROP_SELECTED, false); 057. x = (ushort)ObjectGetInteger(0, szArg, OBJPROP_XDISTANCE) - adjust; 058. y = (ushort)ObjectGetInteger(0, szArg, OBJPROP_YDISTANCE) - adjust; 059. w = (ushort)ObjectGetInteger(0, szArg, OBJPROP_XSIZE) + adjust; 060. h = (ushort)ObjectGetInteger(0, szArg, OBJPROP_YSIZE) + adjust; 061. for (E_Elment c = 0; c < (E_Elment)szObjects.Size(); c++) 062. { 063. CreateEdge(c); 064. if ((c == TL) || (c == BR)) 065. { 066. ObjectSetInteger(0, szObjects[c], OBJPROP_WIDTH, 3); 067. ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTABLE, true); 068. ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTED, true); 069. } 070. } 071. } 072. //+----------------+ 073. void UpdateBoxSize(const string szArg) 074. { 075. for (E_Elment c = 0; c < (E_Elment)szObjects.Size(); c++) 076. if (szObjects[c] == szArg) 077. { 078. x = (ushort)ObjectGetInteger(0, szObjects[TL], OBJPROP_XDISTANCE); 079. y = (ushort)ObjectGetInteger(0, szObjects[TL], OBJPROP_YDISTANCE); 080. w = (ushort)ObjectGetInteger(0, szObjects[BR], OBJPROP_XDISTANCE) - x; 081. h = (ushort)ObjectGetInteger(0, szObjects[BR], OBJPROP_YDISTANCE) - y; 082. for (E_Elment i = 0; i < (E_Elment)4; i++) 083. SetPosition(i); 084. } 085. } 086. //+----------------+ 087. void RemoveBoxResize(const string szArg, const bool update) 088. { 089. ObjectSetInteger(0, szArg, OBJPROP_BACK, isBack); 090. ObjectSetInteger(0, szArg, OBJPROP_SELECTED, false); 091. if (update) 092. { 093. ObjectSetInteger(0, szArg, OBJPROP_XDISTANCE, x); 094. ObjectSetInteger(0, szArg, OBJPROP_YDISTANCE, y); 095. ObjectSetInteger(0, szArg, OBJPROP_XSIZE, w); 096. ObjectSetInteger(0, szArg, OBJPROP_YSIZE, h); 097. } 098. ObjectsDeleteAll(0, def_Prefix); 099. } 100. //+----------------+ 101. void Block(const string szArg) 102. { 103. for (uchar c = 0; c < szObjects.Size(); c++) 104. if (szObjects[c] == szArg) 105. ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTED, true); 106. } 107. //+----------------+ 108. }gl_RZ; 109. //+------------------------------------------------------------------+ 110. int OnInit() 111. { 112. IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix); 113. 114. return INIT_SUCCEEDED; 115. }; 116. //+------------------------------------------------------------------+ 117. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 118. { 119. return rates_total; 120. }; 121. //+------------------------------------------------------------------+ 122. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 123. { 124. static string szNameObj = NULL; 125. 126. switch (id) 127. { 128. case CHARTEVENT_KEYDOWN : 129. if ((szNameObj != NULL) && (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE))) 130. { 131. gl_RZ.RemoveBoxResize(szNameObj, false); 132. szNameObj = NULL; 133. } 134. break; 135. case CHARTEVENT_OBJECT_CLICK : 136. if (StringFind(sparam, def_Prefix) != INVALID_HANDLE) 137. { 138. gl_RZ.Block(sparam); 139. break; 140. } 141. if (szNameObj != NULL) gl_RZ.RemoveBoxResize(szNameObj, true); 142. if (szNameObj != sparam) gl_RZ.CreateBoxResize(szNameObj = sparam); else szNameObj = NULL; 143. break; 144. case CHARTEVENT_OBJECT_DRAG : 145. if (StringFind(sparam, def_Prefix) != INVALID_HANDLE) 146. gl_RZ.UpdateBoxSize(sparam); 147. break; 148. } 149. ChartRedraw(); 150. }; 151. //+------------------------------------------------------------------+ 152. void OnDeinit(const int reason) 153. { 154. ObjectsDeleteAll(0, def_Prefix); 155. ChartRedraw(); 156. }; 157. //+------------------------------------------------------------------+
Código 07
En este caso, no explicaré lo que ocurre en este código 07. Quiero que procures estudiarlo y así comprender cómo este código, aparentemente sin mucho sentido, y que para muchos parece algo salido de la cabeza de algún loco genial, consigue hacer lo que se muestra en las animaciones a continuación. Primero, el redimensionamiento estándar.

Animación 09
Ahora, un redimensionamiento combinado con otro que se cancelará después.

Animación 10
Consideraciones finales
Programar no es algo aburrido, difícil ni agotador. En realidad, es algo agradable, emocionante y divertido. El gran detalle es: ¿Entiendes lo que deseas hacer? ¿Consigues entender conceptos básicos y aplicarlos en un conjunto lógico de instrucciones, donde le dices a la máquina qué hacer, cuándo hacerlo y por qué hacerlo? ¿O simplemente te quedas escribiendo un montón de cosas totalmente sin sentido, esperando que la máquina consiga comprender qué es lo que deseas que se haga?
Mucha gente cree que programar es solo para grandes y talentosos profesionales. Digo que incluso puede llegar a ser así. Pero nada impide que tú, mi querido y estimado lector entusiasta de la programación, puedas pensar en una forma de resolver un problema, ponerlo en una hoja de papel, analizar qué conceptos deben adoptarse y después implementarlo en forma de código.
El talento es para pocos. Pero el empeño, la dedicación, el estudio y la disciplina son para quienes realmente saben a dónde quieren llegar y buscan formas de alcanzar sus objetivos.
Piensa en ello. Aprovecha el contenido que se pone a tu disposición para estudiar y practicar. No dejes para mañana lo que podría haberse hecho ayer. Pero nunca es tarde para empezar y dedicarse a algo que te dé placer hacer.
Tal vez, en el próximo artículo, llegue a mostrar otras cosas que podemos implementar para mejorar aún más este código que vimos aquí y que estará disponible en el archivo adjunto. Todo dependerá de que el material que se muestre aporte o no algo a lo que se vio aquí. De cualquier manera, no esperes que yo te muestre cómo mejorar las cosas. Busca tú mismo una solución y, si no consigues implementar las cosas desde el principio, procura estudiar lo que ya se vio hasta aquí. Hay mucho material por asimilar, y eso solo se consigue con la práctica y con el tiempo.
| Archivo MQ5 | Descripción |
|---|---|
| Code 01 | Demostración de eventos en objetos |
| Code 02 | Demostración de eventos en objetos |
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/16094
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Simulación de mercado: Position View (I)
Aprendizaje automático y Data Science (Parte 38): Aprendizaje por transferencia de IA en los mercados de divisas
Particularidades del trabajo con números del tipo double en MQL4
Simulación de mercado: Iniciando SQL en MQL5 (V)
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso