Desarrollo de un sistema de repetición (Parte 27): Proyecto Expert Advisor — Clase C_Mouse (I)
Introducción
En el artículo anterior "Desarrollo de un sistema de repetición (Parte 26): Proyecto Expert Advisor (I)", detallé los inicios de la construcción de la primera clase. Ahora, vamos a ampliar estas ideas para que todo sea más útil, lo que nos lleva a la creación de la clase C_Mouse. Está diseñada para permitir programar al más alto nivel posible. Sin embargo, hablar de programar a niveles altos o bajos no está relacionado con incluir palabrotas o jerga en el código. Todo lo contrario. Cuando mencionamos programación de alto o bajo nivel, nos referimos a lo fácil o difícil que es para otro programador entender el código. De hecho, la distinción entre programación de alto y bajo nivel indica lo sencillo o complejo que puede resultar el código para otro desarrollador. Así, un código se considera de alto nivel cuando se parece al lenguaje natural, y de bajo nivel cuando se parece menos, aproximándose a la forma en que el procesador interpreta las instrucciones.
Por lo tanto, mi objetivo es mantener el código de las clases al nivel más alto posible, evitando en la medida de lo posible ciertos tipos de modelado que podrían complicar la comprensión a las personas menos experimentadas. Ese es el objetivo, aunque no puedo garantizar que se cumpla plenamente.
La clase C_Mouse: iniciamos la interacción con el usuario
El ratón y el teclado son probablemente los medios más comunes de interacción entre el usuario y la plataforma. Por lo tanto, es crucial que esta interacción sea sencilla y eficiente, sin que el usuario tenga que volver a aprender a realizar acciones. El código comienza con las siguientes líneas:
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #include "C_Terminal.mqh" //+------------------------------------------------------------------+ #define def_MousePrefixName "MOUSE_" #define def_NameObjectLineH def_MousePrefixName + "H" #define def_NameObjectLineV def_MousePrefixName + "TV" #define def_NameObjectLineT def_MousePrefixName + "TT" #define def_NameObjectBitMp def_MousePrefixName + "TB" #define def_NameObjectText def_MousePrefixName + "TI" //+------------------------------------------------------------------+ #define def_Fillet "Resource\\Fillet.bmp" #resource def_Fillet //+------------------------------------------------------------------+
Hemos incluido el archivo de cabecera que contiene la clase C_Terminal. Como comentamos en el artículo anterior, este archivo de clase C_Mouse se encuentra en el mismo directorio que el archivo de clase C_Terminal, lo que nos permite utilizar esta sintaxis sin problemas. Definimos un nombre para un recurso que se incluirá en el archivo ejecutable, lo que permite transportar el ejecutable sin tener que cargar el recurso por separado. Esto es especialmente útil en muchos casos, sobre todo cuando el recurso es crítico y su presencia en el momento del uso es esencial. Solemos colocar el recurso en un directorio específico para facilitar su acceso. De esta forma, siempre se compilará junto con el archivo de cabecera. Hemos añadido un directorio llamado Resource a la carpeta donde se encuentra el archivo C_Mouse.mqh. Dentro de este directorio Resource hay un archivo llamado Fillet.bmp. Si se cambia la estructura de los directorios manteniendo el mismo modelado, el compilador sabrá exactamente dónde encontrar el archivo Fillet.bmp. Una vez compilado el código, puede cargar el archivo ejecutable sin preocuparse de que no se encuentre el recurso, ya que estará incrustado en el propio ejecutable.
En este punto, definimos primero un nombre, en realidad un prefijo, para otros nombres que definiremos más adelante. El uso de definiciones simplifica considerablemente el desarrollo y el mantenimiento y es una práctica habitual en el código profesional. El programador define varios nombres y elementos que se utilizarán en todo el código, lo que suele hacerse en un archivo llamado Defines.mqh o similar, lo que facilita el cambio de definiciones. Sin embargo, como estas definiciones sólo existen dentro de este archivo, no es necesario declararlas en ningún otro lugar.
#undef def_MousePrefixName #undef def_NameObjectLineV #undef def_NameObjectBitMp #undef def_NameObjectLineH #undef def_NameObjectLineT #undef def_NameObjectText #undef def_Fillet
Este fragmento indica al compilador que haga invisibles todos los símbolos y nombres definidos y visibles por este archivo C_Mouse.mqh a partir de este punto. Normalmente, no es común ni recomendable eliminar o modificar definiciones en otros archivos. Por eso declaramos los nombres donde realmente aparecen y empiezan a utilizarse. Al final, estas definiciones se eliminan. Modificar o eliminar definiciones sin criterio no es una buena práctica. Cuando una definición deba utilizarse en varios archivos, es mejor crear un archivo específico para ella.
Ahora, vamos a explorar las primeras líneas del código de la clase. Aquí es donde las cosas empiezan a ponerse interesantes. Todo comienza con el siguiente fragmento:
class C_Mouse : public C_Terminal { protected: enum eEventsMouse {ev_HideMouse, ev_ShowMouse}; enum eBtnMouse {eKeyNull = 0x01, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10}; struct st_Mouse { struct st00 { int X, Y; double Price; datetime dt; }Position; uint ButtonStatus; };
En este fragmento, observamos que la clase C_Terminal es heredada de forma pública por la clase C_Mouse. Esto significa que utilizando la clase C_Mouse, tendremos acceso a todos los procedimientos públicos de la clase C_Terminal. De esta forma, la clase C_Mouse gana muchas más capacidades de las que tendría si se limitara únicamente al código del archivo C_Mouse.mqh. Sin embargo, la herencia no sólo aporta esta ventaja. Hay otras ventajas en el uso de la herencia para hacer las clases más potentes, que serán exploradas en futuros artículos. Siguiendo con el fragmento, dentro de la cláusula protegida, tenemos dos declaraciones de enumeración que nos permiten programar a un nivel ligeramente superior del que sería posible sin ellas. La primera enumeración es bastante sencilla y sigue el mismo concepto y las mismas reglas que se trataron en el artículo anterior. La segunda enumeración, en cambio, puede parecer un poco confusa y complicada. Sin embargo, vamos a explorar el motivo de su complejidad y, sobre todo, la razón de su existencia.
Esta enumeración nos proporciona una capacidad que de otro modo sería mucho más difícil de mantener, es decir, nos ahorraría mucho trabajo. Lo que hace esta enumeración en particular es crear definiciones de nombres, equivalentes al comando de preprocesamiento `#define`. Sin embargo, en lugar de utilizar definiciones, optamos por la enumeración. Esto nos permite adoptar una técnica ligeramente diferente, pero al mismo tiempo mucho más sencilla de entender en el código. Cuando se utiliza esta enumeración, verás cómo el código se vuelve mucho más legible. En código complejo, esto se vuelve crucial. Pero pensar que esta enumeración tiene una declaración que es, a primera vista, muy confusa y complicada se debe al hecho de que puede que no entiendas del todo cómo funcionan las enumeraciones. Desde el punto de vista del compilador, una enumeración no es más que una secuencia de definiciones, donde, por defecto, el primer elemento comienza en el índice cero. Sin embargo, nada te impide definir cuál será el índice inicial de la enumeración y, a partir de ese momento, el compilador comenzará a incrementar los valores de los índices subsiguientes. Esto es extremadamente útil en muchos escenarios en los que el valor de un índice específico sirve como valor inicial de una secuencia. No es infrecuente encontrar largas listas de enumeraciones en las que los valores de error se establecen en función de algún criterio específico. Si defines un nombre y le asignas un valor específico, el compilador incrementará automáticamente los valores de todos los nombres siguientes. Esto simplifica enormemente la creación de grandes listas de definiciones, sin correr el riesgo de duplicar valores en algún momento.
En realidad, es sorprendente que muchos programadores no utilicen esta técnica, dado que puede ayudar significativamente a evitar errores garrafales a la hora de programar ciertos tipos de proyectos. Ahora que has comprendido esto, puedes experimentar y darte cuenta de que el uso de enumeraciones simplifica considerablemente el proceso a la hora de crear una gran lista de elementos correlacionados, ya sea de forma secuencial o no. El enfoque que vamos a explorar pretende elevar el nivel de programación, haciendo el código más legible y fácil de entender.
El siguiente elemento en el código es una estructura responsable de decirle al resto del código lo que está pasando con el ratón. Muchos podrían esperar una declaración de variables en este punto. Sin embargo, no se considera una buena práctica de programación declarar variables dentro de una clase fuera de la cláusula privada. Del mismo modo, algunos podrían pensar que sería más apropiado colocar estas declaraciones en una cláusula pública. Sin embargo, yo prefiero empezar con un nivel de acceso más restringido, permitiendo el acceso público sólo como último recurso. Deberíamos asegurarnos de que sólo las funciones o procedimientos tienen acceso público, excepto aquellos de interés directo para la clase. Por lo demás, siempre es recomendable empezar concediendo los mínimos privilegios a los elementos que creemos.
Siguiendo con esta idea, podemos ver en el siguiente fragmento las variables presentes en nuestra clase C_Mouse:
private : enum eStudy {eStudyNull, eStudyCreate, eStudyExecute}; struct st01 { st_Mouse Data; color corLineH, corTrendP, corTrendN; eStudy Study; }m_Info; struct st_Mem { bool CrossHair; }m_Mem;
Es especialmente interesante observar que ya tenemos una enumeración que no será visible para ninguna otra parte del código fuera de la clase. Esto se debe a que esta enumeración sólo es útil dentro de esta clase, y no tiene sentido que otras partes del código conozcan su existencia. Este concepto se conoce como encapsulación, que se basa en el principio de reservarse el derecho a que ninguna otra parte del código sepa cómo funciona o se utiliza realmente el código responsable de realizar una determinada tarea. Este tipo de enfoque es muy valorado por los desarrolladores de bibliotecas, que proporcionan medios para que otros programadores accedan a los procedimientos sin revelar, sin embargo, cómo funciona realmente el código de la biblioteca.
Siguiendo adelante, encontramos una estructura. Ésta hace uso de otra estructura a la que se puede acceder fuera del cuerpo de la clase, que se detallará durante la explicación de los procedimientos y funciones de acceso. Por ahora, lo importante es darse cuenta de que esta variable se refiere a una estructura privada de la clase. También existe otra estructura, y en este caso prefiero adoptar ese enfoque. Esto deja claro que el contenido es especial y que sólo se accederá a él en puntos muy concretos del código. Sin embargo, nada impediría declarar estos mismos datos en la estructura anterior. Sólo habría que tener cuidado durante la programación para evitar modificar estos datos, ya que los incluidos en la primera estructura son de uso general y pueden cambiarse en cualquier momento.
Con las partes relativas a las variables ya presentadas, podemos pasar a analizar los procedimientos y las funciones. Empecemos por los primeros códigos, que se analizarán poco después:
inline void CreateObjectBase(const string szName, const ENUM_OBJECT obj, const color cor) { ObjectCreate(GetInfoTerminal().ID, szName, obj, 0, 0, 0); ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_TOOLTIP, "\n"); ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BACK, false); ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, cor); }
Este código facilita la reutilización del software, ya que a lo largo del desarrollo de la clase C_Mouse será necesario crear diversos elementos que deben seguir una cierta estandarización. Por tanto, centralizar esta creación en un único procedimiento simplifica el proceso. Una práctica que notarás a menudo en las declaraciones, especialmente cuando el rendimiento es un factor crítico, es el uso de una palabra reservada específica. Esta elección está motivada por mi deseo de que el compilador incluya el código directamente en el lugar donde se está declarando, funcionando de forma similar a una macro. Es cierto que esto puede dar lugar a un aumento del tamaño del archivo ejecutable, pero a cambio hay una mejora, por leve que sea, del rendimiento general durante la ejecución.A veces, la ganancia de rendimiento es mínima debido a diversos factores, que pueden no justificar el aumento del tamaño del ejecutable.
En este caso, nos encontramos con una situación que puede parecer insignificante para muchos, pero que se convertirá en un aspecto recurrente a lo largo del código. Esta función hace referencia a una estructura declarada en la clase C_Terminal. Sin embargo, el compilador no la interpreta como una función, sino como si fuera una variable constante. ¿Cómo es posible? ¿Cómo puede el compilador percibir una declaración, que a mis ojos parece una función, como si fuera una variable constante? A primera vista, no tiene mucho sentido. Sin embargo, cuando analizamos el código de esta llamada y su implementación en la clase C_Terminal con más detalle, vemos lo siguiente:
inline const st_Terminal GetInfoTerminal(void) const { return m_Infos; }
El código presentado está devolviendo una referencia a una estructura que pertenece a la clase C_Terminal. La variable a la que estamos devolviendo la referencia es privada de la clase y, en ningún caso, sus valores deben ser modificados por otro código que no sea el presente en la clase C_Terminal. Para asegurarnos de que, al referenciar esta variable privada, el código no realiza ningún cambio sobre ella, hemos incluido la declaración específica. De esta forma, el compilador se asegurará de que cualquier código que reciba esta referencia constante no pueda modificar su valor. Esta medida se toma para evitar cambios accidentales o errores de programación. Así, si incluso dentro de la clase C_Terminal se intenta modificar un valor dentro de la función de forma inadecuada, el compilador lo reconocerá como un error porque, según el compilador, allí no se podría modificar ninguna información. Esto se debe a que no es el lugar correcto o apropiado para realizar tal cambio.
Este tipo de programación, aunque más laboriosa, confiere al código un nivel de robustez y fiabilidad difícil de alcanzar de otro modo. Sin embargo, en este contexto hay un fallo que, en el momento de escribir este artículo, se tratará más adelante. Esto se debe a que explicar este fallo ahora complicaría la interpretación. Sigamos y veamos el siguiente procedimiento en la clase C_Mouse:
inline void CreateLineH(void) { CreateObjectBase(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH); }
Este procedimiento es el encargado de crear la línea horizontal que representa la línea de precio. Es importante notar que al delegar toda la complejidad a otro procedimiento, todo lo que necesitamos hacer es codificar una sola línea. Este tipo de enfoque suele sustituirse por algún tipo de macro. Sin embargo, prefiero intentar hacerlo sin recurrir a tal recurso. Otra forma sería insertar el mismo contenido, considerando que se trata de una sola línea, en los lugares donde se produciría la llamada. Personalmente, no recomiendo esta práctica, no porque sea incorrecta, sino porque requiere cambiar todas las líneas si hay que modificar una sola. Esto puede ser una tarea tediosa y propensa a errores. Por tanto, aunque parezca más práctico colocar el código directamente en los puntos donde se hace referencia, es más seguro hacerlo utilizando una macro o código con la palabra `inline` en su declaración.
Puede que los procedimientos que veremos a continuación no tengan mucho sentido ahora, pero es importante conocerlos antes de pasar a explicarlos. El primer procedimiento es el siguiente:
void CreateStudy(void) { CreateObjectBase(def_NameObjectLineV, OBJ_VLINE, m_Info.corLineH); CreateObjectBase(def_NameObjectLineT, OBJ_TREND, m_Info.corLineH); CreateObjectBase(def_NameObjectBitMp, OBJ_BITMAP, clrNONE); CreateObjectBase(def_NameObjectText, OBJ_TEXT, clrNONE); ObjectSetString(GetInfoTerminal().ID, def_NameObjectText, OBJPROP_FONT, "Lucida Console"); ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectText, OBJPROP_FONTSIZE, 10); ObjectSetString(GetInfoTerminal().ID, def_NameObjectBitMp, OBJPROP_BMPFILE, "::" + def_Fillet); ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineT, OBJPROP_WIDTH, 2); m_Info.Study = eStudyCreate; }
Este procedimiento está diseñado para crear los objetos que necesitamos para realizar estudios en el gráfico, lo que nos permite crear nuestro propio estilo de análisis. Esto facilita destacar la información que se considere más relevante y necesaria para un análisis eficaz. El modelo de estudio presentado aquí es bastante simple, pero es posible desarrollar una metodología más diversificada que sea más rápida y no contamine visualmente el gráfico. En la metodología del ejemplo, que es la más básica, creamos un estudio para comprobar el número de puntos entre un precio y otro, indicando visualmente si el valor es negativo o positivo. Aunque no es un sistema sofisticado, sirve de base para desarrollar modelos más elaborados.
Muchos estudios implican una gran variedad de objetos y combinaciones entre ellos, que a veces requieren cálculos o la búsqueda de una posición en un rango de precios (estudios basados en máximos y mínimos). Llevar esto a cabo manualmente no sólo es lento, sino también agotador, debido a la necesidad de añadir y eliminar constantemente objetos del gráfico. De lo contrario, el gráfico puede resultar sobrecargado y confuso, dificultando la identificación de la información necesaria. Así que utiliza este método como base para crear algo más refinado y adaptado a tus necesidades. Sin embargo, no hay prisa; habrá oportunidades de mejorarlo más adelante.
El único cuidado que hay que tener es asegurarse de que cuando se quiera añadir texto -lo que es muy probable- sea el último objeto de la secuencia de creación. Esto es evidente en el fragmento mencionado, donde el texto que muestra la información es el último objeto que se crea, lo que impide que quede oculto por otros objetos. Es habitual crear varios objetos al principio y que uno de ellos acabe ocultando el que realmente quieres destacar. Así que recuerda: el objeto que más te interese debe ser siempre el último en la cola de creación.
El hecho de que los colores de los objetos se fijen inicialmente en clrNONE no debe ser motivo de preocupación, ya que estos colores se modificarán posteriormente a medida que se desarrolle el estudio. Si el procedimiento anterior es el responsable de la creación del estudio, el procedimiento siguiente es el que realmente realiza el estudio en el gráfico.
void ExecuteStudy(const double memPrice) { if (CheckClick(eClickLeft)) { ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 1, m_Info.Data.Position.dt, m_Info.Data.Position.Price); ObjectMove(GetInfoTerminal().ID, def_NameObjectBitMp, 0, m_Info.Data.Position.dt, m_Info.Data.Position.Price); ObjectMove(GetInfoTerminal().ID, def_NameObjectText, 0, m_Info.Data.Position.dt, m_Info.Data.Position.Price); ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineT, OBJPROP_COLOR, (memPrice > m_Info.Data.Position.Price ? m_Info.corTrendN : m_Info.corTrendP)); ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectText, OBJPROP_COLOR, (memPrice > m_Info.Data.Position.Price ? m_Info.corTrendN : m_Info.corTrendP)); ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectBitMp, OBJPROP_ANCHOR, (memPrice > m_Info.Data.Position.Price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER)); ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectText, OBJPROP_ANCHOR, (memPrice > m_Info.Data.Position.Price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER)); ObjectSetString(GetInfoTerminal().ID, def_NameObjectText, OBJPROP_TEXT, StringFormat("%." + (string)GetInfoTerminal().nDigits + "f ", m_Info.Data.Position.Price - memPrice)); } else { m_Info.Study equal eStudyNull; ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName + "T"); } m_Info.Data.ButtonStatus equal eKeyNull; }
Ejecutar el estudio sobre el gráfico puede parecer inútil a primera vista, sobre todo si sólo consideramos el código de forma aislada. Sin embargo, cuando se examina el código de interacción, su finalidad resulta mucho más clara y comprensible. Pero tratemos de entender lo que ocurre aquí. A diferencia del código utilizado para crear los objetos del estudio, este segmento contiene elementos que puede ser interesante comprender para añadir más detalles a su trabajo. Básicamente, identificamos dos segmentos principales en el código: uno en el que comprobamos una condición -e incluso sin conocimientos de programación, es posible entender lo que se comprueba, ya que cuanto más se parece el código al lenguaje natural, mayor es su nivel-; y otro en el que concluimos el estudio. Ambos segmentos son sencillos y se entienden rápidamente. En este punto, movemos los objetos en la pantalla para que indiquen el intervalo del estudio, un procedimiento clásico. En las líneas siguientes, cambiamos los colores de los objetos para indicar si el movimiento es bajista o alcista, aunque esto suele ser obvio. Sin embargo, puede haber situaciones, como el uso de algún tipo de curva, en las que determinar si los valores son positivos o negativos sólo por observación se complica. Es importante recordar que se pueden generar estudios de varias maneras, basándose en diferentes criterios. Quizás la parte más crucial es la línea donde presentamos los valores basados en algún cálculo o análisis. Aquí, tienes la libertad de presentar mucha información diferente de distintas maneras, dependiendo de cada caso.
De hecho, necesitamos un método para terminar correctamente la presentación del estudio, que se realiza en el segundo segmento del código. Aunque este segmento no parece especialmente destacable a primera vista, hay un aspecto que merece especial atención: el uso de la función `ObjectsDeleteAll`. ¿Por qué este momento es crucial y exige atención? La respuesta está en la clase C_Terminal. Si observamos el constructor de la clase C_Terminal, nos damos cuenta de la siguiente línea:
ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, 0, true);
Esta línea indica a la plataforma que cada vez que un objeto es eliminado del gráfico, debe emitir un evento notificando qué objeto ha sido eliminado. Al utilizar la función `ObjectsDeleteAll`, estamos eliminando todos los elementos u objetos utilizados en el estudio en cuestión. Esto hace que MetaTrader 5 genere un evento por cada objeto eliminado del gráfico. Esencialmente, la plataforma hará justamente eso, y depende de nuestro código decidir si estos objetos se vuelven a crear o no. El problema surge cuando puede producirse una caída, en la que los objetos no se eliminan (porque el código los está recreando) o se eliminan sin que el código reciba notificación alguna. En esta situación, la propiedad `CHART_EVENT_OBJECT_DELETE` se configuraría como `false`. Aunque esto no ocurre inicialmente, a medida que el código se expande, hay ocasiones en las que esta propiedad puede ser cambiada inadvertidamente, y puede olvidarse reactivarla. Como consecuencia, la plataforma no generaría el evento que notifica a nuestro código la eliminación de objetos del gráfico, lo que podría dar lugar a discrepancias y errores en la gestión de objetos dentro del estudio.
Analicemos el constructor de la clase C_Mouse.
C_Mouse(color corH, color corP, color corN) :C_Terminal() { m_Info.corLineH = corH; m_Info.corTrendP = corP; m_Info.corTrendN = corN; m_Info.Study = eStudyNull; m_Mem.CrossHair = (bool)ChartGetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL); ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, true); ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, false); CreateLineH(); }
En este punto, hacemos una llamada explícita al constructor de la clase C_Terminal. Es imprescindible que este constructor sea invocado antes de cualquier otra ejecución dentro de la clase C_Mouse, asegurando que los valores necesarios de la clase C_Terminal ya están correctamente inicializados. Tras esta inicialización, procedemos a configurar algunos aspectos y almacenar el estado de otros. Sin embargo, la atención se centra en dos líneas concretas: aquellas en las que informamos a la plataforma de nuestro interés en recibir eventos relacionados con el ratón y nuestra intención de no utilizar la línea de estudio estándar que ofrece la plataforma. Con estas definiciones aplicadas, la plataforma cumplirá nuestros requisitos, informando de los eventos relacionados con ratones según lo solicitado. Por otra parte, cuando intentemos utilizar la línea de estudio estándar del ratón, nos corresponderá a nosotros, a través de nuestro código, proporcionar los medios para llevar a cabo dichos estudios.
La siguiente pieza de código que veremos es el destructor de la clase. Es fundamental implementar el destructor para que podamos restaurar el funcionamiento original de la plataforma cuando terminemos de utilizar la clase C_Mouse.
~C_Mouse() { ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, 0, false); ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, false); ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair); ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName); }
Esta cuestión, aunque pueda parecer contraintuitiva, es esencial. La razón de incluir esta línea de código en concreto es que, al intentar eliminar los objetos creados por la clase C_Mouse -en particular la línea de precio-, la plataforma genera un evento notificándonos que el objeto ha sido eliminado del gráfico. Nuestro código intentará entonces volver a colocar ese objeto en el gráfico, aunque esté en proceso de ser eliminado. Para evitar que la plataforma genere un evento de este tipo, tenemos que hacer explícito que no queremos que esto ocurra. Te preguntarás: "¿Pero la clase C_Terminal no se encargaría de esto diciéndonos que ya no queremos recibir eventos relacionados con la eliminación de objetos del gráfico?". Sí, la clase C_Terminal haría esto, pero como todavía necesitamos algunos datos presentes en la clase C_Terminal, dejamos que la llamada al destructor de la clase C_Terminal sea realizada implícitamente por el compilador, ocurriendo sólo después de la ejecución de la última línea del destructor de la clase C_Mouse. Sin añadir la línea de código resaltada, la plataforma seguiría generando el evento y, de este modo, aunque se elimine inicialmente la línea de precio, se podría volver a poner antes de finalizar completamente el código. El resto de líneas del destructor son más sencillas, ya que lo único que hacemos es devolver el gráfico a su estado original.
Llegamos a las últimas funciones tratadas en este artículo.
inline bool CheckClick(const eBtnMouse value) { return (m_Info.Data.ButtonStatus & value) == value; }
La línea de código mencionada introduce una forma interesante de utilizarla, pero por ahora basta con entender lo siguiente: utilizando la enumeración que definimos para los eventos recibidos del ratón, comprobamos si el valor proporcionado por la plataforma se corresponde con lo que espera un punto concreto del código. Si se confirma la coincidencia, la función devolverá true; en caso contrario, devolverá false. Aunque esto pueda parecer trivial por el momento, esta comprobación resultará extremadamente útil cuando empecemos a interactuar más intensamente con el sistema. Hay un truco específico en la declaración de esta función que facilita su uso, pero como no es esencial por el momento, no entraré en ello ahora.
La siguiente función es tan sencilla como la anterior y sigue el mismo principio que la función `GetInfoTerminal` de la clase C_Terminal.
inline const st_Mouse GetInfoMouse(void) const { return m_Info.Data; }
El objetivo es devolver la información contenida en la variable que almacena la estructura de datos del ratón, asegurando que estos datos no puedan ser alterados sin autorización explícita de la clase C_Mouse. Como este método ya ha sido comentado, no veo necesario reiterar su explicación. En esencia, ambos operan de forma similar.
Por último, estamos a punto de llegar a la culminación del código de la clase C_Mouse. Sin embargo, creo que es importante reservar una última función, presente en la clase C_Mouse, para discutirla en el próximo artículo. La razón de ello se explicará entonces.
Conclusión
Aunque estamos montando algo muy sugerente, dista mucho de ser definitivo. Nos vemos en el próximo artículo.
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/11337
- 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