Gráficos en la biblioteca DoEasy (Parte 86): Colección de objetos gráficos - controlando la modificación de propiedades
Contenido
- Concepto
- Monitoreando la modificación de las propiedades de los objetos gráficos
- Monitoreando la eliminación de los objetos gráficos
- Simulación
- ¿Qué es lo próximo?
Concepto
En el último artículo, logramos implementar el almacenamiento de los objetos gráficos estándar recién creados en la clase de colección de objetos gráficos: monitoreamos la aparición de nuevos objetos en el gráfico, creamos para ellos los objetos de clase correspondientes al tipo de objeto en el gráfico y los añadimos a la lista de colección. Sin embargo, para gestionar de forma completa los objetos gráficos, esto no resultará suficiente. Necesitamos controlar todos los cambios en las propiedades de los objetos gráficos en el gráfico, así como su eliminación o cambio de nombre.
Como las funciones de la serie ObjectGetXXX se usan para leer las propiedades de los objetos gráficos, no podemos comprobar constantemente los valores de cada propiedad de cada objeto gráfico en el temporizador, porque dichas funciones son síncronas, lo cual significa que están esperando que se ejecute el comando, hecho que puede consumir muchos recursos si tenemos una gran cantidad de objetos gráficos.
Aquí nos enfrentamos a un dilema: o bien usamos un temporizador para sondear cada propiedad de cada objeto gráfico con todas las consecuencias derivadas de la sincronización de las funciones de solicitud de propiedades, o bien elegimos usar el modelo de eventos, reaccionando al evento en el manejador OnChartEvent(), que, desafortunadamente, no funciona en el simulador de estrategias (recordemos que el procesamiento de las operaciones del temporizador en el simulador lo organizamos en la biblioteca a través de los manejadores OnTick() y OnCalculate()).
Después de sopesar todos los pros y los contras, decidimos organizar el seguimiento de los cambios en las propiedades de los objetos gráficos en el manejador de eventos del gráfico. Para ello, usaremos el modelo por eventos, que simplifica la escritura del código, pero ofrece restricciones para trabajar en el simulador. No obstante, al menos por ahora, no podemos trabajar con objetos gráficos en el simulador (añadir estos a la ventana del simulador y luego cambiar sus propiedades de alguna forma). En consecuencia, deberíamos trabajar con objetos gráficos solo en los gráficos "en vivo" donde se ejecuta el manejador de eventos.
En esta ocasión, crearemos una versión de prueba que se encargará de procesar los eventos de los objetos gráficos solo para el gráfico actual en el que se está ejecutando el programa. En cuanto entendamos que todo funciona correctamente, crearemos manejadores de eventos completos para cada gráfico abierto, que enviarán eventos al gráfico principal del programa, donde la biblioteca los recopilará y procesará en su colección de objetos gráficos.
Monitoreando la modificación de las propiedades de los objetos gráficos
En el manejador OnChartEvent(), nos interesan los eventos:
- CHARTEVENT_OBJECT_CREATE — Crear un objeto gráfico (si para el gráfico se ha establecido la propiedad CHART_EVENT_OBJECT_CREATE=true);
- CHARTEVENT_OBJECT_CHANGE — Cambiar las propiedades del objeto a través de la venta de diálogo de propiedades;
- CHARTEVENT_OBJECT_DELETE — Eliminar objeto gráfico (si para el gráfico se ha establecido la propiedad CHART_EVENT_OBJECT_DELETE=true)
- CHARTEVENT_OBJECT_DRAG — Desplazar el objeto gráfico con la ayuda del ratón;
Básicamente, hemos implementado el evento de creación de un objeto gráfico del último artículo sin llamar al manejador OnChartEvent().
Necesitamos el evento de cambio de propiedades de un objeto gráfico a través de su ventana de diálogo de propiedades en el terminal para que el usuario del terminal cliente pueda controlar manualmente el cambio en las propiedades del objeto.
También hemos implementado el evento de eliminación de un objeto gráfico: la biblioteca monitorea el número de objetos gráficos en todos los gráficos del terminal y dispone de banderas de eventos para cada gráfico abierto; cuando el número de objetos en el gráfico disminuye, podemos averiguar la cantidad de objetos eliminados del gráfico y procesar la situación.
Necesitamos un evento de desplazamiento del objeto gráfico para controlar los cambios en la posición del objeto gráfico en general, así como sus puntos de anclaje individuales en particular.
Sobre el evento de desplazamiento, merece la pena señalar que también se activa cuando el objeto se crea manualmente. Cuando clicamos en el gráfico para configurar el objeto, pero aún no hemos soltado el botón, el objeto es creado y la biblioteca lo ve, creando el objeto de la clase correspondiente y añadiéndolo a la colección. Al mismo tiempo, no todos los valores de las propiedades del objeto están configurados correctamente (después de todo, aún no hemos soltado el botón y podemos mover el objeto con el ratón o establecer otros puntos de anclaje si el objeto se extrae de varios puntos). Pero cuando soltamos el botón del ratón (si el objeto ya ha establecido todos sus puntos de anclaje para una construcción exitosa), se crea un evento para mover el objeto gráfico. Rastreando el evento y modificando los valores de propiedad del objeto de clase ya creado según los parámetros completamente establecidos del objeto gráfico creado, establecemos los valores correctos de todas las propiedades del objeto recién creado
Cambiar el nombre de un objeto implica tres eventos al mismo tiempo: la eliminación de un objeto, la creación de un objeto y el cambio de las propiedades del mismo. Podemos monitorear estos tres eventos para comprender si ha sucedido un cambio en el nombre de algún objeto de los existentes, pero seguiremos el camino más simple. El caso es que cuando cambiamos el nombre de un objeto, en cualquier caso, el último evento será el evento CHARTEVENT_OBJECT_CHANGE, que nosotros procesaremos. Y como todos los objetos se seleccionan en el terminal según el nombre y el identificador del gráfico, podemos verificar qué objeto ha desaparecido en la lista de colección entre aquellos que se encuentran en el gráfico. Tenemos que buscar el nombre de un objeto en el gráfico para el que no hay ningún objeto de clase en la colección (1), buscar un objeto en la colección para el que no hay ningún objeto correspondiente al nombre en el gráfico (2) y escribir este nombre en el objeto de clase que se encuentra en (1) en la lista de colección. Suena "aterrador", pero en realidad todo es simple.
Para entender qué propiedad del objeto ha sido cambiada (después de todo, recibimos un evento que contiene solo el nombre del objeto cambiado, pero no se indica la propiedad modificada), hay que comparar todas las propiedades del objeto, es decir, debemos comparar sus valores actuales con los que había antes de obtener el evento de cambio de las propiedades de un objeto. Por consiguiente, necesitamos crear tres matrices más, que se encargarán de guardar las propiedades "anteriores" del objeto.
Abrimos el archivo \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh de la clase de objeto gráfico abstracto estándar y escribimos en la sección privada las nuevas matrices para guardar las propiedades "anteriores" del objeto:
//+------------------------------------------------------------------+ //| The class of the abstract standard graphical object | //+------------------------------------------------------------------+ class CGStdGraphObj : public CGBaseObj { private: long m_long_prop[GRAPH_OBJ_PROP_INTEGER_TOTAL]; // Integer properties double m_double_prop[GRAPH_OBJ_PROP_DOUBLE_TOTAL]; // Real properties string m_string_prop[GRAPH_OBJ_PROP_STRING_TOTAL]; // String properties long m_long_prop_prev[GRAPH_OBJ_PROP_INTEGER_TOTAL]; // Integer properties before change double m_double_prop_prev[GRAPH_OBJ_PROP_DOUBLE_TOTAL]; // Real properties before change string m_string_prop_prev[GRAPH_OBJ_PROP_STRING_TOTAL]; // String properties before change //--- Return the index of the array the (1) double and (2) string properties are actually located at int IndexProp(ENUM_GRAPH_OBJ_PROP_DOUBLE property) const { return(int)property-GRAPH_OBJ_PROP_INTEGER_TOTAL; } int IndexProp(ENUM_GRAPH_OBJ_PROP_STRING property) const { return(int)property-GRAPH_OBJ_PROP_INTEGER_TOTAL-GRAPH_OBJ_PROP_DOUBLE_TOTAL; } public:
En la sección pública de la clase, añadimos los métodos para establecer y retornar las propiedades "anteriores" del objeto:
public: //--- Set object's (1) integer, (2) real and (3) string properties void SetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property,long value) { this.m_long_prop[property]=value; } void SetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property,double value) { this.m_double_prop[this.IndexProp(property)]=value; } void SetProperty(ENUM_GRAPH_OBJ_PROP_STRING property,string value) { this.m_string_prop[this.IndexProp(property)]=value; } //--- Return object’s (1) integer, (2) real and (3) string property from the properties array long GetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property) const { return this.m_long_prop[property]; } double GetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property) const { return this.m_double_prop[this.IndexProp(property)]; } string GetProperty(ENUM_GRAPH_OBJ_PROP_STRING property) const { return this.m_string_prop[this.IndexProp(property)]; } //--- Set object's previous (1) integer, (2) real and (3) string properties void SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property,long value) { this.m_long_prop_prev[property]=value; } void SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property,double value){ this.m_double_prop_prev[this.IndexProp(property)]=value;} void SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property,string value){ this.m_string_prop_prev[this.IndexProp(property)]=value;} //--- Return object’s (1) integer, (2) real and (3) string property from the previous properties array long GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property) const { return this.m_long_prop_prev[property]; } double GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property) const { return this.m_double_prop_prev[this.IndexProp(property)]; } string GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property) const { return this.m_string_prop_prev[this.IndexProp(property)]; } //--- Return itself CGStdGraphObj *GetObject(void) { return &this;}
Tenemos la propiedad "identificador del objeto" en el objeto de clase que describe el objeto gráfico. La necesitamos para establecer una cierta etiqueta única del objeto, que nos ayudará a identificar este.
Esta propiedad no debe participar en la definición de los eventos del objeto. Por consiguiente, en el método para configurar el identificador del objeto, escribiremos el valor transmitido al método en ambas propiedades a la vez: en la actual (escrita anteriormente) y en la pasada (la añadiremos ahora):
public: //+--------------------------------------------------------------------+ //|Methods of simplified access and setting graphical object properties| //+--------------------------------------------------------------------+ //--- Object index in the list int Number(void) const { return (int)this.GetProperty(GRAPH_OBJ_PROP_NUM); } void SetNumber(const int number) { this.SetProperty(GRAPH_OBJ_PROP_NUM,number); } //--- Object ID long ObjectID(void) const { return this.GetProperty(GRAPH_OBJ_PROP_ID); } void SetObjectID(const long obj_id) { CGBaseObj::SetObjectID(obj_id); this.SetProperty(GRAPH_OBJ_PROP_ID,obj_id); this.SetPropertyPrev(GRAPH_OBJ_PROP_ID,obj_id); } //--- Graphical object type
El algoritmo de reacción ante un evento será el siguiente: después de recibir el evento, necesitamos actualizar todos los datos del objeto de clase que describe el objeto gráfico para que todas sus propiedades tengan valores reales. Como no sabemos qué propiedad ha cambiado, actualizamos todas las propiedades del objeto y luego comparamos las propiedades actuales con las que tenía el objeto antes de recibir el evento de cambio. A continuación, compararemos en tres ciclos las tres matrices de propiedades del objeto con las matrices correspondientes de las propiedades anteriores. En el momento de la comparación, y si la propiedad actual (su valor) no es igual al valor anterior, mostraremos temporalmente (todavía estamos comprobando una variante de prueba) un mensaje sobre el cambio de esta propiedad en el diario de registro. Y así, para cada diferencia hallada en los valores comparados en cada matriz de propiedades del objeto. Posteriormente, cuando implementemos ya el control de los cambios de propiedad para cada uno de los gráficos abiertos, lo haremos de una manera ligeramente distinta: "añadiremos" todas las propiedades modificadas en un objeto de evento y obtendremos cada uno de esos objetos en la clase de colección para después indicar al programa de control que han cambiado las propiedades de cada uno de los objetos en cada gráfico abierto.
En la sección pública de la clase, declararemos un método para sobrescribir todas las propiedades del objeto (será necesario cuando se detecte un evento de cambio de propiedad) para revisar todas las propiedades de un objeto gráfico a la vez y añadirlas en las propiedades del objeto de clase.
Método que verifica el cambio de propiedades del objeto; el método comparará todas las propiedades actuales del objeto con su estado anterior.
En la sección privada de la clase, declararemos los tres métodos necesarios para obtener todas las propiedades del objeto gráfico y escribirlas en las propiedades del objeto de clase.
Y el método que copia las propiedades actuales en las anteriores, de forma que en la siguiente comprobación, al detectarse un evento, ya se compararán con las propiedades recién cambiadas:
//--- Return the description of the object visibility on timeframes string VisibleOnTimeframeDescription(void); //--- Re-write all graphical object properties void PropertiesRefresh(void); //--- Check object property changes void PropertiesCheckChanged(void); private: //--- Get and save (1) integer, (2) real and (3) string properties void GetAndSaveINT(void); void GetAndSaveDBL(void); void GetAndSaveSTR(void); //--- Copy the current data to the previous one void PropertiesCopyToPrevData(void); }; //+------------------------------------------------------------------+
Vamos a simplificar el constructor paramétrico protegido. Antes, en este se recibían todas las propiedades inherentes a todos los objetos gráficos desde el objeto gráfico y se anotaban en el objeto de clase:
//+------------------------------------------------------------------+ //| Protected parametric constructor | //+------------------------------------------------------------------+ CGStdGraphObj::CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type, const ENUM_GRAPH_OBJ_BELONG belong, const ENUM_GRAPH_OBJ_GROUP group, const long chart_id,const string name) { //--- Set the object (1) type, type of graphical (2) object, (3) element, (4) subwindow affiliation and (5) index, as well as (6) chart symbol Digits this.m_type=obj_type; CGBaseObj::SetChartID(chart_id); CGBaseObj::SetTypeGraphObject(CGBaseObj::GraphObjectType(obj_type)); CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_STANDARD); CGBaseObj::SetBelong(belong); CGBaseObj::SetGroup(group); CGBaseObj::SetSubwindow(chart_id,name); CGBaseObj::SetDigits((int)::SymbolInfoInteger(::ChartSymbol(chart_id),SYMBOL_DIGITS)); //--- Save integer properties //--- properties inherent in all graphical objects but not present in a graphical object this.m_long_prop[GRAPH_OBJ_PROP_CHART_ID] = CGBaseObj::ChartID(); // Chart ID this.m_long_prop[GRAPH_OBJ_PROP_WND_NUM] = CGBaseObj::SubWindow(); // Chart subwindow index this.m_long_prop[GRAPH_OBJ_PROP_TYPE] = CGBaseObj::TypeGraphObject(); // Graphical object type (ENUM_OBJECT) this.m_long_prop[GRAPH_OBJ_PROP_ELEMENT_TYPE]= CGBaseObj::TypeGraphElement(); // Graphical element type (ENUM_GRAPH_ELEMENT_TYPE) this.m_long_prop[GRAPH_OBJ_PROP_BELONG] = CGBaseObj::Belong(); // Graphical object affiliation this.m_long_prop[GRAPH_OBJ_PROP_GROUP] = CGBaseObj::Group(); // Graphical object group this.m_long_prop[GRAPH_OBJ_PROP_ID] = 0; // Object ID this.m_long_prop[GRAPH_OBJ_PROP_NUM] = 0; // Object index in the list //--- Properties inherent in all graphical objects and present in a graphical object this.m_long_prop[GRAPH_OBJ_PROP_CREATETIME] = ::ObjectGetInteger(chart_id,name,OBJPROP_CREATETIME); // Object creation time this.m_long_prop[GRAPH_OBJ_PROP_TIMEFRAMES] = ::ObjectGetInteger(chart_id,name,OBJPROP_TIMEFRAMES); // Object visibility on timeframes this.m_long_prop[GRAPH_OBJ_PROP_BACK] = ::ObjectGetInteger(chart_id,name,OBJPROP_BACK); // Background object this.m_long_prop[GRAPH_OBJ_PROP_ZORDER] = ::ObjectGetInteger(chart_id,name,OBJPROP_ZORDER); // Priority of a graphical object for receiving the event of clicking on a chart this.m_long_prop[GRAPH_OBJ_PROP_HIDDEN] = ::ObjectGetInteger(chart_id,name,OBJPROP_HIDDEN); // Disable displaying the name of a graphical object in the terminal object list this.m_long_prop[GRAPH_OBJ_PROP_SELECTED] = ::ObjectGetInteger(chart_id,name,OBJPROP_SELECTED); // Object selection this.m_long_prop[GRAPH_OBJ_PROP_SELECTABLE] = ::ObjectGetInteger(chart_id,name,OBJPROP_SELECTABLE); // Object availability this.m_long_prop[GRAPH_OBJ_PROP_TIME] = ::ObjectGetInteger(chart_id,name,OBJPROP_TIME); // First point time coordinate this.m_long_prop[GRAPH_OBJ_PROP_COLOR] = ::ObjectGetInteger(chart_id,name,OBJPROP_COLOR); // Color this.m_long_prop[GRAPH_OBJ_PROP_STYLE] = ::ObjectGetInteger(chart_id,name,OBJPROP_STYLE); // Style this.m_long_prop[GRAPH_OBJ_PROP_WIDTH] = ::ObjectGetInteger(chart_id,name,OBJPROP_WIDTH); // Line width //--- Properties belonging to different graphical objects this.m_long_prop[GRAPH_OBJ_PROP_FILL] = 0; // Object color filling this.m_long_prop[GRAPH_OBJ_PROP_READONLY] = 0; // Ability to edit text in the Edit object this.m_long_prop[GRAPH_OBJ_PROP_LEVELS] = 0; // Number of levels this.m_long_prop[GRAPH_OBJ_PROP_LEVELCOLOR] = 0; // Level line color this.m_long_prop[GRAPH_OBJ_PROP_LEVELSTYLE] = 0; // Level line style this.m_long_prop[GRAPH_OBJ_PROP_LEVELWIDTH] = 0; // Level line width this.m_long_prop[GRAPH_OBJ_PROP_ALIGN] = 0; // Horizontal text alignment in the Edit object (OBJ_EDIT) this.m_long_prop[GRAPH_OBJ_PROP_FONTSIZE] = 0; // Font size this.m_long_prop[GRAPH_OBJ_PROP_RAY_LEFT] = 0; // Ray goes to the left this.m_long_prop[GRAPH_OBJ_PROP_RAY_RIGHT] = 0; // Ray goes to the right this.m_long_prop[GRAPH_OBJ_PROP_RAY] = 0; // Vertical line goes through all windows of a chart this.m_long_prop[GRAPH_OBJ_PROP_ELLIPSE] = 0; // Display the full ellipse of the Fibonacci Arc object this.m_long_prop[GRAPH_OBJ_PROP_ARROWCODE] = 0; // Arrow code for the "Arrow" object this.m_long_prop[GRAPH_OBJ_PROP_ANCHOR] = 0; // Position of the binding point of the graphical object this.m_long_prop[GRAPH_OBJ_PROP_XDISTANCE] = 0; // Distance from the base corner along the X axis in pixels this.m_long_prop[GRAPH_OBJ_PROP_YDISTANCE] = 0; // Distance from the base corner along the Y axis in pixels this.m_long_prop[GRAPH_OBJ_PROP_DIRECTION] = 0; // Gann object trend this.m_long_prop[GRAPH_OBJ_PROP_DEGREE] = 0; // Elliott wave marking level this.m_long_prop[GRAPH_OBJ_PROP_DRAWLINES] = 0; // Display lines for Elliott wave marking this.m_long_prop[GRAPH_OBJ_PROP_STATE] = 0; // Button state (pressed/released) this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID] = 0; // Chart object ID (OBJ_CHART). this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PERIOD] = 0; // Chart object period< this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE] = 0; // Time scale display flag for the Chart object this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE] = 0; // Price scale display flag for the Chart object this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE] = 0; // Chart object scale this.m_long_prop[GRAPH_OBJ_PROP_XSIZE] = 0; // Object width along the X axis in pixels. this.m_long_prop[GRAPH_OBJ_PROP_YSIZE] = 0; // Object height along the Y axis in pixels. this.m_long_prop[GRAPH_OBJ_PROP_XOFFSET] = 0; // X coordinate of the upper-left corner of the visibility area. this.m_long_prop[GRAPH_OBJ_PROP_YOFFSET] = 0; // Y coordinate of the upper-left corner of the visibility area. this.m_long_prop[GRAPH_OBJ_PROP_BGCOLOR] = 0; // Background color for OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL this.m_long_prop[GRAPH_OBJ_PROP_CORNER] = 0; // Chart corner for binding a graphical object this.m_long_prop[GRAPH_OBJ_PROP_BORDER_TYPE] = 0; // Border type for "Rectangle border" this.m_long_prop[GRAPH_OBJ_PROP_BORDER_COLOR] = 0; // Border color for OBJ_EDIT and OBJ_BUTTON //--- Save real properties this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_PRICE)] = ::ObjectGetDouble(chart_id,name,OBJPROP_PRICE); // Price coordinate this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELVALUE)] = 0; // Level value this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_SCALE)] = 0; // Scale (property of Gann objects and Fibonacci Arcs objects) this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_ANGLE)] = 0; // Angle this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_DEVIATION)] = 0; // Deviation of the standard deviation channel //--- Save string properties this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_NAME)] = name; // Object name this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TEXT)] = ::ObjectGetString(chart_id,name,OBJPROP_TEXT); // Object description (the text contained in the object) this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TOOLTIP)] = ::ObjectGetString(chart_id,name,OBJPROP_TOOLTIP);// Tooltip text this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELTEXT)] = ""; // Level description this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_FONT)] = ""; // Font this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_BMPFILE)] = ""; // BMP file name for the "Bitmap Level" object this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL)]= ""; // Chart object symbol //--- Save basic properties in the parent object this.m_create_time=(datetime)this.GetProperty(GRAPH_OBJ_PROP_CREATETIME); this.m_back=(bool)this.GetProperty(GRAPH_OBJ_PROP_BACK); this.m_selected=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED); this.m_selectable=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE); this.m_hidden=(bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN); this.m_name=this.GetProperty(GRAPH_OBJ_PROP_NAME); } //+-------------------------------------------------------------------+
Ahora, trasladaremos todas estas líneas a métodos aparte, que se llamarán al mismo tiempo desde el método PropertiesRefresh().
Por eso, eliminamos estas líneas; el constructor quedará como sigue:
//+------------------------------------------------------------------+ //| Protected parametric constructor | //+------------------------------------------------------------------+ CGStdGraphObj::CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type, const ENUM_GRAPH_OBJ_BELONG belong, const ENUM_GRAPH_OBJ_GROUP group, const long chart_id,const string name) { //--- Set the object (1) type, type of graphical (2) object, (3) element, (4) subwindow affiliation and (5) index, as well as (6) chart symbol Digits this.m_type=obj_type; this.SetName(name); CGBaseObj::SetChartID(chart_id); CGBaseObj::SetTypeGraphObject(CGBaseObj::GraphObjectType(obj_type)); CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_STANDARD); CGBaseObj::SetBelong(belong); CGBaseObj::SetGroup(group); CGBaseObj::SetSubwindow(chart_id,name); CGBaseObj::SetDigits((int)::SymbolInfoInteger(::ChartSymbol(chart_id),SYMBOL_DIGITS)); //--- Save integer properties //--- properties inherent in all graphical objects but not present in a graphical object this.m_long_prop[GRAPH_OBJ_PROP_CHART_ID] = CGBaseObj::ChartID(); // Chart ID this.m_long_prop[GRAPH_OBJ_PROP_WND_NUM] = CGBaseObj::SubWindow(); // Chart subwindow index this.m_long_prop[GRAPH_OBJ_PROP_TYPE] = CGBaseObj::TypeGraphObject(); // Graphical object type (ENUM_OBJECT) this.m_long_prop[GRAPH_OBJ_PROP_ELEMENT_TYPE]= CGBaseObj::TypeGraphElement(); // Graphical element type (ENUM_GRAPH_ELEMENT_TYPE) this.m_long_prop[GRAPH_OBJ_PROP_BELONG] = CGBaseObj::Belong(); // Graphical object affiliation this.m_long_prop[GRAPH_OBJ_PROP_GROUP] = CGBaseObj::Group(); // Graphical object group this.m_long_prop[GRAPH_OBJ_PROP_ID] = 0; // Object ID this.m_long_prop[GRAPH_OBJ_PROP_NUM] = 0; // Object index in the list //--- Save the properties inherent in all graphical objects and present in a graphical object this.PropertiesRefresh(); //--- Save basic properties in the parent object this.m_create_time=(datetime)this.GetProperty(GRAPH_OBJ_PROP_CREATETIME); this.m_back=(bool)this.GetProperty(GRAPH_OBJ_PROP_BACK); this.m_selected=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED); this.m_selectable=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE); this.m_hidden=(bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN); //--- Save the current properties to the previous ones this.PropertiesCopyToPrevData(); } //+-------------------------------------------------------------------+
Para que el método PropertiesRefresh() funcione correctamente, deberá conocer el nombre del objeto gráfico desde el cual recibe los datos. Antes, el nombre se escribía casi al final, en el bloque para leer parámetros de tipo string. Ahora, leeremos el nombre del objeto gráfico y lo escribiremos en las propiedades del objeto de clase, justo al entrar en el constructor. Al final, después de escribir todas las propiedades en el objeto de la clase, llamaremos al método PropertiesCopyToPrevData(), que escribirá todas las propiedades del objeto ya guardadas en las matrices de propiedades "anteriores", para así controlar sus cambios.
Métodos que obtienen las propiedades de tipo entero, real y string de un objeto gráfico y las almacenan en un objeto de clase:
//+------------------------------------------------------------------+ //| Get and save the integer properties | //+------------------------------------------------------------------+ void CGStdGraphObj::GetAndSaveINT(void) { //--- Properties inherent in all graphical objects and present in a graphical object this.m_long_prop[GRAPH_OBJ_PROP_CREATETIME] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CREATETIME); // Object creation time this.m_long_prop[GRAPH_OBJ_PROP_TIMEFRAMES] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_TIMEFRAMES); // Object visibility on timeframes this.m_long_prop[GRAPH_OBJ_PROP_BACK] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BACK); // Background object this.m_long_prop[GRAPH_OBJ_PROP_ZORDER] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ZORDER); // Priority of a graphical object for receiving the event of clicking on a chart this.m_long_prop[GRAPH_OBJ_PROP_HIDDEN] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_HIDDEN); // Disable displaying the name of a graphical object in the terminal object list this.m_long_prop[GRAPH_OBJ_PROP_SELECTED] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_SELECTED); // Object selection this.m_long_prop[GRAPH_OBJ_PROP_SELECTABLE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_SELECTABLE); // Object availability this.m_long_prop[GRAPH_OBJ_PROP_TIME] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_TIME); // First point time coordinate this.m_long_prop[GRAPH_OBJ_PROP_COLOR] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_COLOR); // Color this.m_long_prop[GRAPH_OBJ_PROP_STYLE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STYLE); // Style this.m_long_prop[GRAPH_OBJ_PROP_WIDTH] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_WIDTH); // Line width //--- Properties belonging to different graphical objects this.m_long_prop[GRAPH_OBJ_PROP_FILL] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_FILL); // Fill an object with color this.m_long_prop[GRAPH_OBJ_PROP_READONLY] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_READONLY); // Ability to edit text in the Edit object this.m_long_prop[GRAPH_OBJ_PROP_LEVELS] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELS); // Number of levels this.m_long_prop[GRAPH_OBJ_PROP_LEVELCOLOR] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELCOLOR); // Level line color this.m_long_prop[GRAPH_OBJ_PROP_LEVELSTYLE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELSTYLE); // Level line style this.m_long_prop[GRAPH_OBJ_PROP_LEVELWIDTH] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELWIDTH); // Level line width this.m_long_prop[GRAPH_OBJ_PROP_ALIGN] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ALIGN); // Horizontal text alignment in the Edit object (OBJ_EDIT) this.m_long_prop[GRAPH_OBJ_PROP_FONTSIZE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_FONTSIZE); // Font size this.m_long_prop[GRAPH_OBJ_PROP_RAY_LEFT] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY_LEFT); // Ray goes to the left this.m_long_prop[GRAPH_OBJ_PROP_RAY_RIGHT] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY_RIGHT); // Ray goes to the right this.m_long_prop[GRAPH_OBJ_PROP_RAY] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY); // Vertical line goes through all windows of a chart this.m_long_prop[GRAPH_OBJ_PROP_ELLIPSE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ELLIPSE); // Display the full ellipse of the Fibonacci Arc object this.m_long_prop[GRAPH_OBJ_PROP_ARROWCODE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ARROWCODE); // Arrow code for the "Arrow" object this.m_long_prop[GRAPH_OBJ_PROP_ANCHOR] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ANCHOR); // Position of the binding point of the graphical object this.m_long_prop[GRAPH_OBJ_PROP_XDISTANCE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XDISTANCE); // Distance from the base corner along the X axis in pixels this.m_long_prop[GRAPH_OBJ_PROP_YDISTANCE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YDISTANCE); // Distance from the base corner along the Y axis in pixels this.m_long_prop[GRAPH_OBJ_PROP_DIRECTION] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DIRECTION); // Gann object trend this.m_long_prop[GRAPH_OBJ_PROP_DEGREE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DEGREE); // Elliott wave marking level this.m_long_prop[GRAPH_OBJ_PROP_DRAWLINES] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DRAWLINES); // Display lines for Elliott wave marking this.m_long_prop[GRAPH_OBJ_PROP_STATE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STATE); // Button state (pressed/released) this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CHART_ID); // Chart object ID (OBJ_CHART). this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PERIOD] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_PERIOD); // Chart object period this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DATE_SCALE); // Time scale display flag for the Chart object this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE]= ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_PRICE_SCALE);// Price scale display flag for the Chart object this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE]= ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CHART_SCALE);// Chart object scale this.m_long_prop[GRAPH_OBJ_PROP_XSIZE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XSIZE); // Object width along the X axis in pixels. this.m_long_prop[GRAPH_OBJ_PROP_YSIZE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YSIZE); // Object height along the Y axis in pixels. this.m_long_prop[GRAPH_OBJ_PROP_XOFFSET] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XOFFSET); // X coordinate of the upper-left corner of the visibility area. this.m_long_prop[GRAPH_OBJ_PROP_YOFFSET] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YOFFSET); // Y coordinate of the upper-left corner of the visibility area. this.m_long_prop[GRAPH_OBJ_PROP_BGCOLOR] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BGCOLOR); // Background color for OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL this.m_long_prop[GRAPH_OBJ_PROP_CORNER] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CORNER); // Chart corner for binding a graphical object this.m_long_prop[GRAPH_OBJ_PROP_BORDER_TYPE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_TYPE);// Border type for "Rectangle border" this.m_long_prop[GRAPH_OBJ_PROP_BORDER_COLOR]= ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_COLOR);// Border color for OBJ_EDIT and OBJ_BUTTON } //+------------------------------------------------------------------+ //| Get and save the real properties | //+------------------------------------------------------------------+ void CGStdGraphObj::GetAndSaveDBL(void) { this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_PRICE)] = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_PRICE); // Price coordinate this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELVALUE)] = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_LEVELVALUE); // Level value this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_SCALE)] = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_SCALE); // Scale (property of Gann objects and Fibonacci Arcs objects) this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_ANGLE)] = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_ANGLE); // Corner this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_DEVIATION)] = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_DEVIATION); // Deviation of the standard deviation channel } //+------------------------------------------------------------------+ //| Get and save the string properties | //+------------------------------------------------------------------+ void CGStdGraphObj::GetAndSaveSTR(void) { this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TEXT)] = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_TEXT); // Object description (the text contained in the object) this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TOOLTIP)] = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_TOOLTIP); // Tooltip text this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELTEXT)] = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_LEVELTEXT);// Level description this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_FONT)] = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_FONT); // Font this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_BMPFILE)] = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_BMPFILE); // BMP file name for the "Bitmap Level" object this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL)] = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_SYMBOL); // Chart object symbol } //+------------------------------------------------------------------+
En estos tres métodos, simplemente se trasladan las líneas eliminadas del constructor de la clase; estas ahora son reemplazadas por una llamada al método que sobrescribe todas las propiedades del objeto gráfico:
//+------------------------------------------------------------------+ //| Overwrite all graphical object properties | //+------------------------------------------------------------------+ void CGStdGraphObj::PropertiesRefresh(void) { this.GetAndSaveINT(); this.GetAndSaveDBL(); this.GetAndSaveSTR(); } //+------------------------------------------------------------------+
Aquí, los tres métodos anteriores se llaman por turno.
Método que copia las propiedades actuales del objeto de clase en las anteriores:
//+------------------------------------------------------------------+ //| Copy the current data to the previous one | //+------------------------------------------------------------------+ void CGStdGraphObj::PropertiesCopyToPrevData(void) { ::ArrayCopy(this.m_long_prop_prev,this.m_long_prop); ::ArrayCopy(this.m_double_prop_prev,this.m_double_prop); ::ArrayCopy(this.m_string_prop_prev,this.m_string_prop); } //+------------------------------------------------------------------+
Aquí, usando la función de copiado de matrices, copiamos por turno las matrices de las propiedades enteras, reales y string en las matrices correspondientes de las propiedades anteriores.
Método que busca cambios en las propiedades del objeto:
//+------------------------------------------------------------------+ //| Check object property changes | //+------------------------------------------------------------------+ void CGStdGraphObj::PropertiesCheckChanged(void) { bool changed=false; int beg=0, end=GRAPH_OBJ_PROP_INTEGER_TOTAL; for(int i=beg; i<end; i++) { ENUM_GRAPH_OBJ_PROP_INTEGER prop=(ENUM_GRAPH_OBJ_PROP_INTEGER)i; if(!this.SupportProperty(prop)) continue; if(this.GetProperty(prop)!=this.GetPropertyPrev(prop)) { changed=true; ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop)); } } beg=end; end+=GRAPH_OBJ_PROP_DOUBLE_TOTAL; for(int i=beg; i<end; i++) { ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i; if(!this.SupportProperty(prop)) continue; if(this.GetProperty(prop)!=this.GetPropertyPrev(prop)) { changed=true; ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop)); } } beg=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL; for(int i=beg; i<end; i++) { ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i; if(!this.SupportProperty(prop)) continue; if(this.GetProperty(prop)!=this.GetPropertyPrev(prop)) { changed=true; ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop)); } } if(changed) PropertiesCopyToPrevData(); } //+------------------------------------------------------------------+
Aquí, en tres ciclos por separado para las propiedades enteras, reales y string, obtenemos la siguiente propiedad de la matriz correspondiente y la comparamos con la misma propiedad en la matriz de propiedades anteriores. Si los valores comparados de las propiedades actuales y anteriores no son iguales, esto indicará que la propiedad ha cambiado, por lo que estableceremos la bandera de cambio en las propiedades del objeto y escribiremos un mensaje en el diario de registro.
Este es un método de prueba utilizado solo para comprobar la idea propuesta de búsqueda de cambios en las propiedades del objeto. En artículos posteriores, refinaremos este concepto para alcanzar un estado completamente funcional; asimismo, monitorearemos los cambios en todos los objetos gráficos en todos los gráficos abiertos y enviaremos eventos sobre los cambios en las propiedades del objeto al gráfico del programa de control, que la biblioteca podrá procesar más adelante.
Monitoreando la eliminación de los objetos gráficos
La modificación de las propiedades del objeto gráfico se monitorea en la clase de este objeto, ya que las propiedades del objeto gráfico pertenecen a este objeto, se escriben en la clase de objeto y se pueden verificar en él. No obstante, todos los métodos creados anteriormente para la actualización de las propiedades del objeto y la comprobación de sus cambios se llamarán desde la clase de colección de objetos gráficos. Minetras tanto, la adición y la eliminación de los objetos gráficos en el gráfico, solo podemos monitorearlas en la clase de colección de objetos gráficos; esta clase controla la lista completa de todos los objetos en todos los gráficos abiertos y realiza un seguimiento de ellos en su propia lista de colección.
Ahora, abrimos el archivo de la clase de colección de objetos gráficos \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh y añadimos todas las modificaciones necesarias.
En la clase de control de objetos de gráficos, que también se encuentra en el mismo archivo, declaramos un método privado que establece el permiso para que el gráfico monitoree los eventos del ratón y los objetos gráficos. Lo necesitamos para que los permisos de monitoreo de estos eventos se establezcan para cada uno de los gráficos abiertos.
En los constructores de clase, llamamos a estos métodos para otorgarle los permisos a los gráficos para los cuales se crearán estos objetos de control. Al final del listado de la clase, declaramos el manejador de eventos (analizaremos su implementación en el próximo artículo):
//+------------------------------------------------------------------+ //| Chart object management class | //+------------------------------------------------------------------+ class CChartObjectsControl : public CObject { private: CArrayObj m_list_new_graph_obj; // List of added graphical objects ENUM_TIMEFRAMES m_chart_timeframe; // Chart timeframe long m_chart_id; // Chart ID string m_chart_symbol; // Chart symbol bool m_is_graph_obj_event; // Event flag in the list of graphical objects int m_total_objects; // Number of graphical objects int m_last_objects; // Number of graphical objects during the previous check int m_delta_graph_obj; // Difference in the number of graphical objects compared to the previous check //--- Return the name of the last graphical object added to the chart string LastAddedGraphObjName(void); //--- Set the permission to track mouse events and graphical objects void SetMouseEvent(void); public: //--- Return the variable values ENUM_TIMEFRAMES Timeframe(void) const { return this.m_chart_timeframe; } long ChartID(void) const { return this.m_chart_id; } string Symbol(void) const { return this.m_chart_symbol; } bool IsEvent(void) const { return this.m_is_graph_obj_event; } int TotalObjects(void) const { return this.m_total_objects; } int Delta(void) const { return this.m_delta_graph_obj; } //--- Create a new standard graphical object CGStdGraphObj *CreateNewGraphObj(const ENUM_OBJECT obj_type,const long chart_id, const string name); //--- Return the list of newly added objects CArrayObj *GetListNewAddedObj(void) { return &this.m_list_new_graph_obj;} //--- Check the chart objects void Refresh(void); //--- Constructors CChartObjectsControl(void) { this.m_list_new_graph_obj.Clear(); this.m_list_new_graph_obj.Sort(); this.m_chart_id=::ChartID(); this.m_chart_timeframe=(ENUM_TIMEFRAMES)::ChartPeriod(this.m_chart_id); this.m_chart_symbol=::ChartSymbol(this.m_chart_id); this.m_is_graph_obj_event=false; this.m_total_objects=0; this.m_last_objects=0; this.m_delta_graph_obj=0; this.SetMouseEvent(); } CChartObjectsControl(const long chart_id) { this.m_list_new_graph_obj.Clear(); this.m_list_new_graph_obj.Sort(); this.m_chart_id=chart_id; this.m_chart_timeframe=(ENUM_TIMEFRAMES)::ChartPeriod(this.m_chart_id); this.m_chart_symbol=::ChartSymbol(this.m_chart_id); this.m_is_graph_obj_event=false; this.m_total_objects=0; this.m_last_objects=0; this.m_delta_graph_obj=0; this.SetMouseEvent(); } //--- Compare CChartObjectsControl objects by a chart ID (for sorting the list by an object property) virtual int Compare(const CObject *node,const int mode=0) const { const CChartObjectsControl *obj_compared=node; return(this.ChartID()>obj_compared.ChartID() ? 1 : this.ChartID()<obj_compared.ChartID() ? -1 : 0); } //--- Event handler void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam); }; //+------------------------------------------------------------------+
Fuera del cuerpo de la clase, escribimos la implementación del método encargado de establecer los permisos para monitorear los eventos del ratón y los objetos gráficos:
//+------------------------------------------------------------------+ //| Set the permission | //| to track mouse and graphical object events for the chart | //+------------------------------------------------------------------+ void CChartObjectsControl::SetMouseEvent(void) { ::ChartSetInteger(this.ChartID(),CHART_EVENT_MOUSE_MOVE,true); ::ChartSetInteger(this.ChartID(),CHART_EVENT_MOUSE_WHEEL,true); ::ChartSetInteger(this.ChartID(),CHART_EVENT_OBJECT_CREATE,true); ::ChartSetInteger(this.ChartID(),CHART_EVENT_OBJECT_DELETE,true); } //+------------------------------------------------------------------+
Ahora, vamos a ocuparnos de la clase de colección de objetos gráficos.
En la sección privada de la clase, declaramos los nuevos métodos, cuya descripción indica claramente su propósito:
//+------------------------------------------------------------------+ //| Collection of graphical objects | //+------------------------------------------------------------------+ class CGraphElementsCollection : public CBaseObj { private: CArrayObj m_list_charts_control; // List of chart management objects CListObj m_list_all_canv_elm_obj; // List of all graphical elements on canvas CListObj m_list_all_graph_obj; // List of all graphical objects bool m_is_graph_obj_event; // Event flag in the list of graphical objects int m_total_objects; // Number of graphical objects int m_delta_graph_obj; // Difference in the number of graphical objects compared to the previous check //--- Return the flag indicating the graphical element object presence in the collection list of graphical elements bool IsPresentGraphElmInList(const int id,const ENUM_GRAPH_ELEMENT_TYPE type_obj); //--- Return the flag indicating the graphical element object presence in the collection list of graphical objects bool IsPresentGraphObjInList(const long chart_id,const string name); //--- Return the flag indicating the presence of a graphical object on a chart by name bool IsPresentGraphObjOnChart(const long chart_id,const string name); //--- Return the pointer to the object of managing objects of the specified chart CChartObjectsControl *GetChartObjectCtrlObj(const long chart_id); //--- Create a new object of managing graphical objects of a specified chart and add it to the list CChartObjectsControl *CreateChartObjectCtrlObj(const long chart_id); //--- Update the list of graphical objects by chart ID CChartObjectsControl *RefreshByChartID(const long chart_id); //--- Return the first free ID of the graphical (1) object and (2) element on canvas long GetFreeGraphObjID(void); long GetFreeCanvElmID(void); //--- Add a graphical object to the collection bool AddGraphObjToCollection(const string source,CChartObjectsControl *obj_control); //--- Find an object present in the collection but not on a chart CGStdGraphObj *FindMissingObj(const long chart_id); //--- Find the graphical object present on a chart but not in the collection string FindExtraObj(const long chart_id); //--- Remove the graphical object from the graphical object collection list bool DeleteGraphObjFromList(CGStdGraphObj *obj); public:
En la sección pública de la clase, declaramos el método que retorna un objeto gráfico según el nombre y el identificador del gráfico y el manejador de eventos del gráfico:
public: //--- Return itself CGraphElementsCollection *GetObject(void) { return &this; } //--- Return the full collection list of standard graphical objects "as is" CArrayObj *GetListGraphObj(void) { return &this.m_list_all_graph_obj; } //--- Return the full collection list of graphical elements on canvas "as is" CArrayObj *GetListCanvElm(void) { return &this.m_list_all_canv_elm_obj;} //--- Return the list of graphical elements by a selected (1) integer, (2) real and (3) string properties meeting the compared criterion CArrayObj *GetList(ENUM_CANV_ELEMENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode); } CArrayObj *GetList(ENUM_CANV_ELEMENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode); } CArrayObj *GetList(ENUM_CANV_ELEMENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode); } //--- Return the list of graphical objects by a selected (1) integer, (2) real and (3) string properties meeting the compared criterion CArrayObj *GetList(ENUM_GRAPH_OBJ_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,value,mode); } CArrayObj *GetList(ENUM_GRAPH_OBJ_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,value,mode); } CArrayObj *GetList(ENUM_GRAPH_OBJ_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,value,mode); } //--- Return the number of new graphical objects, (3) the flag of the occurred change in the list of graphical objects int NewObjects(void) const { return this.m_delta_graph_obj; } bool IsEvent(void) const { return this.m_is_graph_obj_event; } //--- Return a graphical object by chart name and ID CGStdGraphObj *GetStdGraphObject(const string name,const long chart_id); //--- Constructor CGraphElementsCollection(); //--- Display the description of the object properties in the journal (full_prop=true - all properties, false - supported ones only - implemented in descendant classes) virtual void Print(const bool full_prop=false,const bool dash=false); //--- Display a short description of the object in the journal virtual void PrintShort(const bool dash=false,const bool symbol=false); //--- Create the list of chart management objects and return the number of charts int CreateChartControlList(void); //--- Update the list of (1) all graphical objects, (2) on the specified chart, fill in the data on the number of new ones and set the event flag void Refresh(void); void Refresh(const long chart_id); //--- Event handler void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam); }; //+------------------------------------------------------------------+
En el método que actualiza la lista con todos los objetos gráficos, escribimos un bloque para procesar la eliminación de un objeto gráfico del gráfico:
//+------------------------------------------------------------------+ //| Update the list of all graphical objects | //+------------------------------------------------------------------+ void CGraphElementsCollection::Refresh(void) { //--- Declare variables to search for charts long chart_id=0; int i=0; //--- In the loop by all open charts in the terminal (no more than 100) while(i<CHARTS_MAX) { //--- Get the chart ID chart_id=::ChartNext(chart_id); if(chart_id<0) break; //--- Get the pointer to the object for managing graphical objects //--- and update the list of graphical objects by chart ID CChartObjectsControl *obj_ctrl=this.RefreshByChartID(chart_id); //--- If failed to get the pointer, move on to the next chart if(obj_ctrl==NULL) continue; //--- If the number of objects on the chart changes if(obj_ctrl.IsEvent()) { //--- If a graphical object is added to the chart if(obj_ctrl.Delta()>0) { //--- Get the list of added graphical objects and move them to the collection list //--- (if failed to move the object to the collection, move on to the next object) if(!AddGraphObjToCollection(DFUN_ERR_LINE,obj_ctrl)) continue; } //--- If the graphical object has been removed else if(obj_ctrl.Delta()<0) { // Find an extra object in the list CGStdGraphObj *obj=this.FindMissingObj(chart_id); if(obj!=NULL) { //--- Display a short description of a detected object deleted from a chart in the journal obj.PrintShort(); //--- Remove the class object of a removed graphical object from the collection list if(!this.DeleteGraphObjFromList(obj)) CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST); } } //--- otherwise else { } } //--- Increase the loop index i++; } } //+------------------------------------------------------------------+
En este método, en un ciclo por el número total de objetos de control del gráfico, comprobamos la presencia de un evento en cada objeto de control y, si el evento existe, verificamos la magnitud en la que ha cambiado el número de objetos en el gráfico que está controlado por el objeto de control del gráfico. Ya escribimos el procesamiento de la adición de un objeto en el último artículo; hoy implementaremos el procesamiento del valor negativo del cambio en el número de objetos en el gráfico.
Todo es simple aquí: buscamos en la lista de colección un objeto para el que no hay un objeto gráfico en el gráfico y lo eliminamos de la lista de colección.
Método que busca un objeto que está presente en la colección, pero no lo está en el gráfico:
//+------------------------------------------------------------------+ //|Find an object present in the collection but not on a chart | //+------------------------------------------------------------------+ CGStdGraphObj *CGraphElementsCollection::FindMissingObj(const long chart_id) { CArrayObj *list=CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),GRAPH_OBJ_PROP_CHART_ID,chart_id,EQUAL); if(list==NULL) return NULL; for(int i=0;i<list.Total();i++) { CGStdGraphObj *obj=list.At(i); if(obj==NULL) continue; if(!this.IsPresentGraphObjOnChart(obj.ChartID(),obj.Name())) return obj; } return NULL; } //+------------------------------------------------------------------+
Aquí, obtenemos una lista con todos los objetos cuyo identificador de gráfico es igual al indicado en los parámetros del método.
En un ciclo por la lista resultante, obtenemos otro objeto de la clase del objeto gráfico estándar y, si no hay ningún objeto con este nombre en el gráfico, retornamos el puntero a este objeto.
Al final del ciclo, retornamos NULL.
Método que busca un objeto que está presente en el gráfico, pero ausente en la colección:
//+------------------------------------------------------------------+ //|Find an object present on a chart but not in the collection | //+------------------------------------------------------------------+ string CGraphElementsCollection::FindExtraObj(const long chart_id) { int total=::ObjectsTotal(chart_id); for(int i=0;i<total;i++) { string name=::ObjectName(chart_id,i); if(!this.IsPresentGraphObjInList(chart_id,name)) return name; } return NULL; } //+------------------------------------------------------------------+
Aquí, en un ciclo a través de todos los objetos en la lista del terminal, obtenemos el nombre del siguiente objeto y, si no hay un objeto con el mismo nombre e identificador de gráfico en la lista de colección, retornamos el nombre del objeto gráfico. Al final del ciclo, retornamos NULL.
Método que retorna la bandera de presencia de un objeto de clase de objeto gráfico en la lista de colección de objetos gráficos:
//+-----------------------------------------------------------------------------------+ //| Return the flag indicating the presence of the graphical object class object | //| in the graphical object collection list | //+-----------------------------------------------------------------------------------+ bool CGraphElementsCollection::IsPresentGraphObjInList(const long chart_id,const string name) { CArrayObj *list=CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),GRAPH_OBJ_PROP_CHART_ID,chart_id,EQUAL); list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_NAME,name,EQUAL); return(list==NULL || list.Total()==0 ? false : true); } //+------------------------------------------------------------------+
Obtenemos la lista de objetos con el identificador de gráfico especificado. De la lista resultante, obtenemos el puntero al objeto cuyo nombre coincide con el necesario. Si la lista no se ha podido recuperar o está vacía, retornamos false, el objeto no ha sido encontrado; de lo contrario, retornamos true.
Método que retorna la bandera de presencia de un objeto gráfico en el gráfico según el nombre:
//+----------------------------------------------------------------------------------+ //| Return the flag indicating the presence of a graphical object on a chart by name | //+----------------------------------------------------------------------------------+ bool CGraphElementsCollection::IsPresentGraphObjOnChart(const long chart_id,const string name) { int total=::ObjectsTotal(chart_id); for(int i=0;i<total;i++) if(::ObjectName(chart_id,i)==name) return true; return false; } //+-------------------------------------------------------------------+
En un ciclo por el número total de objetos gráficos en el gráfico indicado según el identificador, obtenemos el nombre del siguiente objeto y, si el nombre coincide con el buscado, retornamos true. Al final del ciclo, retornamos false.
Método que elimina un objeto gráfico de la lista de colección de objetos gráficos:
//+---------------------------------------------------------------------+ //|Remove the graphical object from the graphical object collection list| //+---------------------------------------------------------------------+ bool CGraphElementsCollection::DeleteGraphObjFromList(CGStdGraphObj *obj) { this.m_list_all_graph_obj.Sort(); int index=this.m_list_all_graph_obj.Search(obj); return(index==WRONG_VALUE ? false : this.m_list_all_graph_obj.Delete(index)); } //+------------------------------------------------------------------+
Al método se le transmite el puntero al objeto que debe eliminarse de la lista.
Asignamos a la lista la bandera de lista clasificada (la búsqueda se realiza solo en listas clasificadas),
y obtenemos el índice del objeto usando el método Search() de la biblioteca estándar.
Si no se encuentra el índice del objeto, retornamos false;
de lo contrario, retornamos el resultado de la eliminación del objeto de la lista usando el método Delete() de la Biblioteca Estándar.
Método que retorna el puntero a un objeto gráfico según el nombre e identificador del gráfico:
//+------------------------------------------------------------------+ //| Return a graphical object by chart name and ID | //+------------------------------------------------------------------+ CGStdGraphObj *CGraphElementsCollection::GetStdGraphObject(const string name,const long chart_id) { CArrayObj *list=this.GetList(GRAPH_OBJ_PROP_CHART_ID,chart_id); list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_NAME,name,EQUAL); return(list!=NULL && list.Total()>0 ? list.At(0) : NULL); } //+------------------------------------------------------------------+
Obtenemos la lista de objetos cuyo identificador de gráfico es igual al transmitido al método. De la lista resultante, obtenemos la lista que contiene el objeto cuyo nombre coincide con el buscado (solo debe haber uno de esos objetos). Si la lista se ha obtenido con éxito y no está vacía, retornamos el puntero al primer (y único) objeto de la lista. De lo contrario, retornamos NULL.
Manejador de eventos.
Hoy crearemos una versión de prueba del manejador de eventos, que procesará los eventos de los objetos gráficos solo en el gráfico actual. El manejador procesará los eventos de cambio o desplazamiento de objetos gráficos. Este método bastará para determinar los eventos mencionados y, al mismo tiempo, para resolver el problema del rellenado incompleto de las propiedades de los objetos construidos con más de un clic del ratón. Ya hemos discutido anteriormente que el primer clic en el gráfico genera el evento de creación de un objeto, y que la biblioteca crea inmediatamente el objeto correspondiente.
En este caso, no todas las propiedades se registran correctamente en él, ya que el objeto se construye con unos pocos clics del ratón. Y ahora, una vez completada la construcción del objeto, se crea un evento de desplazamiento, al que reaccionaremos sobrescribiendo además las propiedades del objeto (queremos monitorear este evento durante el desplazamiento real, pero al crear un objeto con varios puntos de anclaje, también obtenemos un evento de desplazamiento que hará que se actualicen las propiedades del objeto y se sobrescriban con los datos correctos).
La lógica completa se describe en los comentarios al código del método. Solo analizaremos el método de autoaprendizaje:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { CGStdGraphObj *obj=NULL; if(id==CHARTEVENT_OBJECT_CHANGE || id==CHARTEVENT_OBJECT_DRAG) { //--- If the object, whose properties were changed or which was relocated, //--- is successfully received from the collection list by its name set in sparam obj=this.GetStdGraphObject(sparam,::ChartID()); if(obj!=NULL) { //--- Update the properties of the obtained object //--- and check their change obj.PropertiesRefresh(); obj.PropertiesCheckChanged(); } //--- If failed to get the object by its name, it is not on the list, //--- which means its name has been changed else { //--- Let's search the list for the object that is not on the chart obj=this.FindMissingObj(::ChartID()); if(obj==NULL) return; //--- Get the name of the renamed graphical object on the chart, which is not in the collection list string name_new=this.FindExtraObj(::ChartID()); //--- Set a new name for the collection list object, which does not correspond to any graphical object on the chart, //--- update the chart properties and check their change obj.SetName(name_new); obj.PropertiesRefresh(); obj.PropertiesCheckChanged(); } } } //+------------------------------------------------------------------+
Queda un pequeño detalle: necesitamos acceder a la colección de objetos gráficos de los programas creados a partir de la biblioteca.
Para ello, en el objeto principal de la biblioteca, crearemos en \MQL5\Include\DoEasy\Engine.mqh un método que retorna el puntero a la clase de colección de objetos gráficos de la biblioteca:
//--- Launch the new pause countdown void Pause(const ulong pause_msc,const datetime time_start=0) { this.PauseSetWaitingMSC(pause_msc); this.PauseSetTimeBegin(time_start*1000); while(!this.PauseIsCompleted() && !::IsStopped()){} } //--- Return the graphical object collection CGraphElementsCollection *GetGraphicObjCollection(void) { return &this.m_graph_objects; } //--- Constructor/destructor CEngine(); ~CEngine(); private:
Simulación
Para la simulación, vamos a tomar el asesor del artículo anterior y a guardarlo en la nueva carpeta \MQL5\Experts\TestDoEasy\Part86\ con el nuevo nombre TestDoEasyPart86.mq5.
Eliminamos del manejador OnInit() las líneas en las que se establecen los permisos de monitoreo de los eventos del ratón:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set the permissions to send cursor movement and mouse scroll events ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true); ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true); //--- Set EA global variables ArrayResize(array_clr,2); // Array of gradient filling colors array_clr[0]=C'246,244,244'; // Original ≈pale gray array_clr[1]=C'249,251,250'; // Final ≈pale gray-green //--- Create the array with the current symbol and set it to be used in the library string array[1]={Symbol()}; engine.SetUsedSymbols(array); //--- Create the timeseries object for the current symbol and period, and show its description in the journal engine.SeriesCreate(Symbol(),Period()); engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Ahora, los permisos para monitorear los eventos del ratón y los eventos de los objetos gráficos se establecen en la clase de control de gráficos, y se establecen estos permisos para todos los gráficos abiertos.
Al final del manejador OnChartEvent(), escribimos la llamada al manejador de eventos de la clase de colección de objetos gráficos:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- If working in the tester, exit if(MQLInfoInteger(MQL_TESTER)) return; //--- If the mouse is moved if(id==CHARTEVENT_MOUSE_MOVE) { CForm *form=NULL; datetime time=0; double price=0; int wnd=0; //--- If Ctrl is not pressed, if(!IsCtrlKeyPressed()) { //--- clear the list of created form objects, allow scrolling a chart with the mouse and show the context menu list_forms.Clear(); ChartSetInteger(ChartID(),CHART_MOUSE_SCROLL,true); ChartSetInteger(ChartID(),CHART_CONTEXT_MENU,true); return; } //--- If X and Y chart coordinates are successfully converted into time and price, if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price)) { //--- get the bar index the cursor is hovered over int index=iBarShift(Symbol(),PERIOD_CURRENT,time); if(index==WRONG_VALUE) return; //--- Get the bar index by index CBar *bar=engine.SeriesGetBar(Symbol(),Period(),index); if(bar==NULL) return; //--- Convert the coordinates of a chart from the time/price representation of the bar object to the X and Y coordinates int x=(int)lparam,y=(int)dparam; if(!ChartTimePriceToXY(ChartID(),0,bar.Time(),(bar.Open()+bar.Close())/2.0,x,y)) return; //--- Disable moving a chart with the mouse and showing the context menu ChartSetInteger(ChartID(),CHART_MOUSE_SCROLL,false); ChartSetInteger(ChartID(),CHART_CONTEXT_MENU,false); //--- Create the form object name and hide all objects except one having such a name string name="FormBar_"+(string)index; HideFormAllExceptOne(name); //--- If the form object with such a name does not exist yet, if(!IsPresentForm(name)) { //--- create a new form object form=bar.CreateForm(index,name,x,y,114,16); if(form==NULL) return; //--- Set activity and unmoveability flags for the form form.SetActive(true); form.SetMovable(false); //--- Set the opacity of 200 form.SetOpacity(200); //--- The form background color is set as the first color from the color array form.SetColorBackground(array_clr[0]); //--- Form outlining frame color form.SetColorFrame(C'47,70,59'); //--- Draw the shadow drawing flag form.SetShadow(true); //--- Calculate the shadow color as the chart background color converted to the monochrome one color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100); //--- If the settings specify the usage of the chart background color, replace the monochrome color with 20 units //--- Otherwise, use the color specified in the settings for drawing the shadow color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,-20) : InpColorForm3); //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes //--- Set the shadow opacity to 200, while the blur radius is equal to 4 form.DrawShadow(2,2,clr,200,3); //--- Fill the form background with a vertical gradient form.Erase(array_clr,form.Opacity()); //--- Draw an outlining rectangle at the edges of the form form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity()); //--- If failed to add the form object to the list, remove the form and exit the handler if(!list_forms.Add(form)) { delete form; return; } //--- Capture the form appearance form.Done(); } //--- If the form object exists, if(form!=NULL) { //--- draw a text with the bar type description on it and show the form. The description corresponds to the mouse cursor position form.TextOnBG(0,bar.BodyTypeDescription(),form.Width()/2,form.Height()/2-1,FRAME_ANCHOR_CENTER,C'7,28,21'); form.Show(); } //--- Redraw the chart ChartRedraw(); } } engine.GetGraphicObjCollection().OnChartEvent(id,lparam,dparam,sparam); } //+------------------------------------------------------------------+
Esto es todo lo que debíamos mejorar. Compilamos el asesor y lo ejecutamos en el gráfico. Al crear o eliminar un objeto, o cambiar las propiedades del mismo, las entradas sobre los eventos correspondientes se mostrarán en el diario del terminal de cliente.
Por ahora, solo son entradas en el diario, pero...
¿Qué es lo próximo?
En el próximo artículo, crearemos los manejadores de eventos de los objetos para cada gráfico abierto y enviaremos estos eventos al gráfico del programa de control para que este programa pueda procesarlos por completo.
Si tiene preguntas, observaciones o sugerencias, podrá concretarlas en los comentarios al artículo.
*Artículos de esta serie:
Gráficos en la biblioteca DoEasy (Parte 83): Clase de objeto gráfico abstracto estándar
Gráficos en la biblioteca DoEasy (Parte 84): Clases herederas del objeto gráfico abstracto estándar
Gráficos en la biblioteca DoEasy (Parte 85): Colección de objetos gráficos - añadiendo los objetos nuevamente creados
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/10018
- 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