
Gráficos en la biblioteca DoEasy (Parte 100): Solucionamos las deficiencias al trabajar con los objetos gráficos estándar extendidos
Contenido
Concepto
Durante los últimos artículos hemos desarrollado la funcionalidad para trabajar con objetos gráficos compuestos basados en objetos gráficos estándar extendidos. Poco a poco, artículo a artículo, estamos avanzando en el proceso de creación de esta funcionalidad. Incluso hemos tenido que saltar varias veces de un tema a otro: primero creamos objetos basados en la clase CCanvas, y luego cambiamos a la creación de objetos gráficos, ya que la funcionalidad planeada para implementar objetos gráficos en todos los objetos de la biblioteca necesitaba al menos una parte de la funcionalidad ya activa de los objetos gráficos estándar. Luego volvimos a saltar a los objetos de formulario basados en CCanvas, ya que los objetos gráficos extendidos requerían la mejora de las clases basadas en Canvas. Ahora tendremos que volver a cambiar de tema para seguir desarrollando los objetos en el lienzo.
Hoy vamos a «cortar» algunos flecos: eliminaremos varios fallos obvios en el manejo simultáneo de los objetos gráficos extendidos (y estándar) y los objetos de formulario en el lienzo, y también arreglaremos algunos errores detectados durante las pruebas en el artículo anterior. Y así concluirá esta sección de la descripción de la biblioteca. En el próximo artículo, inauguraremos una nueva sección donde comenzaremos a desarrollar objetos gráficos en el lienzo imitando a Windows Forms en MS Visual Studio: necesitaremos estos objetos para continuar el desarrollo de los objetos gráficos estándar extendidos y los objetos compuestos basados en ellos.
Mejorando las clases de la biblioteca
Si el gráfico contiene objetos gráficos en el lienzo para crear elementos de la GUI (elemento, formulario, ventana (aún no implementado) u otros elementos de control similares), colocar objetos gráficos estándar, así como otros objetos de biblioteca basados en ellos en el gráfico (ya sea manualmente o programáticamente), provocará que estos objetos se dibujen encima de los controles, lo cual resulta un inconveniente. Por consiguiente, tendremos que desarrollar un mecanismo para monitorear la aparición de nuevos objetos gráficos en los gráficos y desplazar todos los elementos de la GUI al primer plano. Para ello, podemos utilizar la propiedad del objeto gráfico ZOrder (La prioridad de un objeto gráfico para obtener el evento de pulsación del ratón sobre el gráfico (CHARTEVENT_CLICK)).
De la guía de referencia:
Por defecto, cuando se crea un objeto, este valor se pone a cero; pero si hace falta, se puede subir la prioridad. Cuando los objetos se aplican uno al otro, sólo uno de ellos, cuya prioridad es superior, recibirá el evento CHARTEVENT_CLICK.
Sin embargo, nosotros usaremos esta propiedad en un sentido más amplio: el valor de esta propiedad indicará el orden de los elementos de la GUI en relación con los demás, y también en relación con otros objetos gráficos.
En el archivo \MQL5\Include\DoEasy\Defines,.mqh añadiremos una nueva propiedad a la lista de propiedades enteras del elemento gráfico en el lienzo y aumentaremos el número de propiedades enteras de 23 a 24:
//+------------------------------------------------------------------+ //| Integer properties of the graphical element on the canvas | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_PROP_INTEGER { CANV_ELEMENT_PROP_ID = 0, // Element ID CANV_ELEMENT_PROP_TYPE, // Graphical element type CANV_ELEMENT_PROP_BELONG, // Graphical element affiliation CANV_ELEMENT_PROP_NUM, // Element index in the list CANV_ELEMENT_PROP_CHART_ID, // Chart ID CANV_ELEMENT_PROP_WND_NUM, // Chart subwindow index CANV_ELEMENT_PROP_COORD_X, // Form's X coordinate on the chart CANV_ELEMENT_PROP_COORD_Y, // Form's Y coordinate on the chart CANV_ELEMENT_PROP_WIDTH, // Element width CANV_ELEMENT_PROP_HEIGHT, // Element height CANV_ELEMENT_PROP_RIGHT, // Element right border CANV_ELEMENT_PROP_BOTTOM, // Element bottom border CANV_ELEMENT_PROP_ACT_SHIFT_LEFT, // Active area offset from the left edge of the element CANV_ELEMENT_PROP_ACT_SHIFT_TOP, // Active area offset from the upper edge of the element CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT, // Active area offset from the right edge of the element CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM, // Active area offset from the bottom edge of the element CANV_ELEMENT_PROP_MOVABLE, // Element moveability flag CANV_ELEMENT_PROP_ACTIVE, // Element activity flag CANV_ELEMENT_PROP_INTERACTION, // Flag of interaction with the outside environment CANV_ELEMENT_PROP_COORD_ACT_X, // X coordinate of the element active area CANV_ELEMENT_PROP_COORD_ACT_Y, // Y coordinate of the element active area CANV_ELEMENT_PROP_ACT_RIGHT, // Right border of the element active area CANV_ELEMENT_PROP_ACT_BOTTOM, // Bottom border of the element active area CANV_ELEMENT_PROP_ZORDER, // Priority of a graphical object for receiving the event of clicking on a chart }; #define CANV_ELEMENT_PROP_INTEGER_TOTAL (24) // Total number of integer properties #define CANV_ELEMENT_PROP_INTEGER_SKIP (0) // Number of integer properties not used in sorting //+------------------------------------------------------------------+
Y añadiremos esta nueva propiedad a la lista de posibles criterios para clasificar los elementos gráficos en el lienzo:
//+------------------------------------------------------------------+ //| Possible sorting criteria of graphical elements on the canvas | //+------------------------------------------------------------------+ #define FIRST_CANV_ELEMENT_DBL_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP) #define FIRST_CANV_ELEMENT_STR_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP) enum ENUM_SORT_CANV_ELEMENT_MODE { //--- Sort by integer properties SORT_BY_CANV_ELEMENT_ID = 0, // Sort by element ID SORT_BY_CANV_ELEMENT_TYPE, // Sort by graphical element type SORT_BY_CANV_ELEMENT_BELONG, // Sort by a graphical element affiliation SORT_BY_CANV_ELEMENT_NUM, // Sort by form index in the list SORT_BY_CANV_ELEMENT_CHART_ID, // Sort by chart ID SORT_BY_CANV_ELEMENT_WND_NUM, // Sort by chart window index SORT_BY_CANV_ELEMENT_COORD_X, // Sort by the element X coordinate on the chart SORT_BY_CANV_ELEMENT_COORD_Y, // Sort by the element Y coordinate on the chart SORT_BY_CANV_ELEMENT_WIDTH, // Sort by the element width SORT_BY_CANV_ELEMENT_HEIGHT, // Sort by the element height SORT_BY_CANV_ELEMENT_RIGHT, // Sort by the element right border SORT_BY_CANV_ELEMENT_BOTTOM, // Sort by the element bottom border SORT_BY_CANV_ELEMENT_ACT_SHIFT_LEFT, // Sort by the active area offset from the left edge of the element SORT_BY_CANV_ELEMENT_ACT_SHIFT_TOP, // Sort by the active area offset from the top edge of the element SORT_BY_CANV_ELEMENT_ACT_SHIFT_RIGHT, // Sort by the active area offset from the right edge of the element SORT_BY_CANV_ELEMENT_ACT_SHIFT_BOTTOM, // Sort by the active area offset from the bottom edge of the element SORT_BY_CANV_ELEMENT_MOVABLE, // Sort by the element moveability flag SORT_BY_CANV_ELEMENT_ACTIVE, // Sort by the element activity flag SORT_BY_CANV_ELEMENT_INTERACTION, // Sort by the flag of interaction with the outside environment SORT_BY_CANV_ELEMENT_COORD_ACT_X, // Sort by X coordinate of the element active area SORT_BY_CANV_ELEMENT_COORD_ACT_Y, // Sort by Y coordinate of the element active area SORT_BY_CANV_ELEMENT_ACT_RIGHT, // Sort by the right border of the element active area SORT_BY_CANV_ELEMENT_ACT_BOTTOM, // Sort by the bottom border of the element active area SORT_BY_CANV_ELEMENT_ZORDER, // Sort by the priority of a graphical object for receiving the event of clicking on a chart //--- Sort by real properties //--- Sort by string properties SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Sort by an element object name SORT_BY_CANV_ELEMENT_NAME_RES, // Sort by the graphical resource name }; //+------------------------------------------------------------------+
Ahora podremos seleccionar y clasificar los elementos gráficos del lienzo según esta propiedad.
En nuestra biblioteca, todos los objetos gráficos se heredan del objeto básico de todos los objetos gráficos de la biblioteca: los objetos gráficos estándar y los elementos gráficos del lienzo. En el archivo de clase del objeto gráfico básico \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh, vamos a hacer virtuales los métodos para trabajar con la propiedad ZOrder:
//--- Set the "Disable displaying the name of a graphical object in the terminal object list" flag bool SetFlagHidden(const bool flag,const bool only_prop) { ::ResetLastError(); if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_SELECTABLE,flag)) || only_prop) { this.m_hidden=flag; return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Set the priority of a graphical object for receiving the event of clicking on a chart virtual bool SetZorder(const long value,const bool only_prop) { ::ResetLastError(); if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_ZORDER,value)) || only_prop) { this.m_zorder=value; return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Set object visibility on all timeframes bool SetVisible(const bool flag,const bool only_prop) { long value=(flag ? OBJ_ALL_PERIODS : OBJ_NO_PERIODS); ::ResetLastError(); if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,value)) || only_prop) { this.m_visible=flag; return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Set visibility flags on timeframes specified as flags
...
//--- Return the values of class variables ENUM_GRAPH_ELEMENT_TYPE TypeGraphElement(void) const { return this.m_type_element; } ENUM_GRAPH_OBJ_BELONG Belong(void) const { return this.m_belong; } ENUM_GRAPH_OBJ_SPECIES Species(void) const { return this.m_species; } ENUM_OBJECT TypeGraphObject(void) const { return this.m_type_graph_obj; } datetime TimeCreate(void) const { return this.m_create_time; } string Name(void) const { return this.m_name; } long ChartID(void) const { return this.m_chart_id; } long ObjectID(void) const { return this.m_object_id; } virtual long Zorder(void) const { return this.m_zorder; } int SubWindow(void) const { return this.m_subwindow; } int ShiftY(void) const { return this.m_shift_y; } int VisibleOnTimeframes(void) const { return this.m_timeframes_visible; } int Digits(void) const { return this.m_digits; } int Group(void) const { return this.m_group; } bool IsBack(void) const { return this.m_back; } bool IsSelected(void) const { return this.m_selected; } bool IsSelectable(void) const { return this.m_selectable; } bool IsHidden(void) const { return this.m_hidden; } bool IsVisible(void) const { return this.m_visible; } //--- Return the graphical object type (ENUM_OBJECT) calculated from the object type (ENUM_OBJECT_DE_TYPE) passed to the method
También tendremos estos mismos métodos herederos de la clase de objeto básico de todos los objetos gráficos de la biblioteca.
También tenemos que hacer que estos métodos sean virtuales en ellos.
Vamos a mejorar la clase del objeto gráfico estándar abstracto en el archivo \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh.
//--- Background object bool Back(void) const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_BACK,0); } bool SetFlagBack(const bool flag,const bool only_prop) { if(!CGBaseObj::SetFlagBack(flag,only_prop)) return false; this.SetProperty(GRAPH_OBJ_PROP_BACK,0,flag); return true; } //--- Priority of a graphical object for receiving the event of clicking on a chart virtual long Zorder(void) const { return this.GetProperty(GRAPH_OBJ_PROP_ZORDER,0); } virtual bool SetZorder(const long value,const bool only_prop) { if(!CGBaseObj::SetZorder(value,only_prop)) return false; this.SetProperty(GRAPH_OBJ_PROP_ZORDER,0,value); return true; } //--- Disable displaying the name of a graphical object in the terminal object list bool Hidden(void) const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN,0); } bool SetFlagHidden(const bool flag,const bool only_prop) { if(!CGBaseObj::SetFlagHidden(flag,only_prop)) return false; this.SetProperty(GRAPH_OBJ_PROP_HIDDEN,0,flag); return true; } //--- Object selection
En el archivo de clase del objeto del elemento gráfico \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, añadimos exactamente los mismo métodos virtuales:
//--- (1) Show and (2) hide the element virtual void Show(void) { CGBaseObj::SetVisible(true,false); } virtual void Hide(void) { CGBaseObj::SetVisible(false,false); } //--- Priority of a graphical object for receiving the event of clicking on a chart virtual long Zorder(void) const { return this.GetProperty(CANV_ELEMENT_PROP_ZORDER); } virtual bool SetZorder(const long value,const bool only_prop) { if(!CGBaseObj::SetZorder(value,only_prop)) return false; this.SetProperty(CANV_ELEMENT_PROP_ZORDER,value); return true; } //+------------------------------------------------------------------+ //| The methods of receiving raster data | //+------------------------------------------------------------------+
Como la clase de objeto del elemento gráfico ya presenta la estructura de las propiedades del objeto para guardarlo y restaurarlo, necesitaremos añadirle un campo para la nueva propiedad entera:
//--- Create (1) the object structure and (2) the object from the structure virtual bool ObjectToStruct(void); virtual void StructToObject(void); private: struct SData { //--- Object integer properties int id; // Element ID int type; // Graphical element type int number; // Element index in the list long chart_id; // Chart ID int subwindow; // Chart subwindow index int coord_x; // Form's X coordinate on the chart int coord_y; // Form's Y coordinate on the chart int width; // Element width int height; // Element height int edge_right; // Element right border int edge_bottom; // Element bottom border int act_shift_left; // Active area offset from the left edge of the element int act_shift_top; // Active area offset from the top edge of the element int act_shift_right; // Active area offset from the right edge of the element int act_shift_bottom; // Active area offset from the bottom edge of the element uchar opacity; // Element opacity color color_bg; // Element background color bool movable; // Element moveability flag bool active; // Element activity flag bool interaction; // Flag of interaction with the outside environment int coord_act_x; // X coordinate of the element active area int coord_act_y; // Y coordinate of the element active area int coord_act_right; // Right border of the element active area int coord_act_bottom; // Bottom border of the element active area long zorder; // Priority of a graphical object for receiving the event of clicking on a chart //--- Object real properties //--- Object string properties uchar name_obj[64]; // Graphical element object name uchar name_res[64]; // Graphical resource name }; SData m_struct_obj; // Object structure
En el método de creación de una estructura de objeto , añadimos el guardado de una nueva propiedad del objeto:
//+------------------------------------------------------------------+ //| Create the object structure | //+------------------------------------------------------------------+ bool CGCnvElement::ObjectToStruct(void) { //--- Save integer properties this.m_struct_obj.id=(int)this.GetProperty(CANV_ELEMENT_PROP_ID); // Element ID this.m_struct_obj.type=(int)this.GetProperty(CANV_ELEMENT_PROP_TYPE); // Graphical element type this.m_struct_obj.number=(int)this.GetProperty(CANV_ELEMENT_PROP_NUM); // Element ID in the list this.m_struct_obj.chart_id=this.GetProperty(CANV_ELEMENT_PROP_CHART_ID); // Chart ID this.m_struct_obj.subwindow=(int)this.GetProperty(CANV_ELEMENT_PROP_WND_NUM); // Chart subwindow index this.m_struct_obj.coord_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_X); // Form's X coordinate on the chart this.m_struct_obj.coord_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_Y); // Form's Y coordinate on the chart this.m_struct_obj.width=(int)this.GetProperty(CANV_ELEMENT_PROP_WIDTH); // Element width this.m_struct_obj.height=(int)this.GetProperty(CANV_ELEMENT_PROP_HEIGHT); // Element height this.m_struct_obj.edge_right=(int)this.GetProperty(CANV_ELEMENT_PROP_RIGHT); // Element right edge this.m_struct_obj.edge_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_BOTTOM); // Element bottom edge this.m_struct_obj.act_shift_left=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT); // Active area offset from the left edge of the element this.m_struct_obj.act_shift_top=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP); // Active area offset from the top edge of the element this.m_struct_obj.act_shift_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT); // Active area offset from the right edge of the element this.m_struct_obj.act_shift_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM);// Active area offset from the bottom edge of the element this.m_struct_obj.movable=(bool)this.GetProperty(CANV_ELEMENT_PROP_MOVABLE); // Element moveability flag this.m_struct_obj.active=(bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE); // Element activity flag this.m_struct_obj.interaction=(bool)this.GetProperty(CANV_ELEMENT_PROP_INTERACTION); // Flag of interaction with the outside environment this.m_struct_obj.coord_act_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_X); // X coordinate of the element active area this.m_struct_obj.coord_act_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y); // Y coordinate of the element active area this.m_struct_obj.coord_act_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_RIGHT); // Right border of the element active area this.m_struct_obj.coord_act_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM); // Bottom border of the element active area this.m_struct_obj.color_bg=this.m_color_bg; // Element background color this.m_struct_obj.opacity=this.m_opacity; // Element opacity this.m_struct_obj.zorder=this.m_zorder; // Priority of a graphical object for receiving the on-chart mouse click event //--- Save real properties //--- Save string properties ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj);// Graphical element object name ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res);// Graphical resource name //--- Save the structure to the uchar array ::ResetLastError(); if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY,true); return false; } return true; } //+------------------------------------------------------------------+
Y en el método para restaurar un objeto desde una estructura , añadiremos al objeto la lectura de esta propiedad:
//+------------------------------------------------------------------+ //| Create the object from the structure | //+------------------------------------------------------------------+ void CGCnvElement::StructToObject(void) { //--- Save integer properties this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id); // Element ID this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type); // Graphical element type this.SetProperty(CANV_ELEMENT_PROP_NUM,this.m_struct_obj.number); // Element index in the list this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,this.m_struct_obj.chart_id); // Chart ID this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,this.m_struct_obj.subwindow); // Chart subwindow index this.SetProperty(CANV_ELEMENT_PROP_COORD_X,this.m_struct_obj.coord_x); // Form's X coordinate on the chart this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,this.m_struct_obj.coord_y); // Form's Y coordinate on the chart this.SetProperty(CANV_ELEMENT_PROP_WIDTH,this.m_struct_obj.width); // Element width this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,this.m_struct_obj.height); // Element height this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.m_struct_obj.edge_right); // Element right edge this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.m_struct_obj.edge_bottom); // Element bottom edge this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,this.m_struct_obj.act_shift_left); // Active area offset from the left edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,this.m_struct_obj.act_shift_top); // Active area offset from the upper edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,this.m_struct_obj.act_shift_right); // Active area offset from the right edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,this.m_struct_obj.act_shift_bottom); // Active area offset from the bottom edge of the element this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,this.m_struct_obj.movable); // Element moveability flag this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,this.m_struct_obj.active); // Element activity flag this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,this.m_struct_obj.interaction); // Flag of interaction with the outside environment this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.m_struct_obj.coord_act_x); // X coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.m_struct_obj.coord_act_y); // Y coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.m_struct_obj.coord_act_right); // Right border of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.m_struct_obj.coord_act_bottom); // Bottom border of the element active area this.m_color_bg=this.m_struct_obj.color_bg; // Element background color this.m_opacity=this.m_struct_obj.opacity; // Element opacity this.m_zorder=this.m_struct_obj.zorder; // Priority of a graphical object for receiving the on-chart mouse click event //--- Save real properties //--- Save string properties this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj));// Graphical element object name this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res));// Graphical resource name } //+------------------------------------------------------------------+
Necesitaremos todo lo mencionado para poder guardar correctamente el objeto en el disco y recuperarlo desde allí en el futuro, cuando leamos y escribamos objetos de la biblioteca en archivos. Esto será necesario para que, al reiniciarse el terminal, la biblioteca pueda restaurar el estado del programa y sus datos para continuar con el funcionamiento normal. Pero todo eso vendrá después. Mientras tanto, nos limitaremos a preparar la funcionalidad necesaria.
Vamos a mejorar la clase del objeto gráfico estándar abstracto en el archivo \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh.
Como en el futuro necesitaremos conocer las coordenadas mínimas y máximas de todo el objeto al construir objetos gráficos compuestos a partir de objetos gráficos estándar extendidos, ahora es el momento de añadir los métodos necesarios para encontrar y retornar las coordenadas X e Y mínimas y máximas del objeto gráfico estándar. Ya más adelante, basándonos en estos métodos, podremos obtener las coordenadas mínimas/máximas de todo el objeto gráfico compuesto.
En la sección pública de la clase , declaramos dos nuevos métodos:
//--- Return (1) the list of dependent objects, (2) dependent graphical object by index and (3) the number of dependent objects CArrayObj *GetListDependentObj(void) { return &this.m_list; } CGStdGraphObj *GetDependentObj(const int index) { return this.m_list.At(index); } int GetNumDependentObj(void) { return this.m_list.Total(); } //--- Return the name of the dependent object by index string NameDependent(const int index); //--- Add the dependent graphical object to the list bool AddDependentObj(CGStdGraphObj *obj); //--- Change X and Y coordinates of the current and all dependent objects bool ChangeCoordsExtendedObj(const int x,const int y,const int modifier,bool redraw=false); //--- Set X and Y coordinates into the appropriate pivot points of a specified subordinate object bool SetCoordsXYtoDependentObj(CGStdGraphObj *dependent_obj); //--- Return (1) the minimum and (2) maximum XY screen coordinate of the specified pivot point bool GetMinCoordXY(int &x,int &y); bool GetMaxCoordXY(int &x,int &y); //--- Return the object of data on pivot points CLinkedPivotPoint*GetLinkedPivotPoint(void) { return &this.m_linked_pivots; }
Y los implementamos fuera del cuerpo de la clase.
Método que retorna la coordenada de pantalla XY mínima de todos los puntos de pivote:
//+------------------------------------------------------------------+ //| Return the minimum XY screen coordinate | //| from all pivot points | //+------------------------------------------------------------------+ bool CGStdGraphObj::GetMinCoordXY(int &x,int &y) { //--- Declare the variables storing the minimum time and maximum price datetime time_min=LONG_MAX; double price_max=0; //--- Depending on the object type switch(this.TypeGraphObject()) { //--- Objects constructed based on screen coordinates case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : //--- Write XY screen coordinates to x and y variables and return 'true' x=this.XDistance(); y=this.YDistance(); return true; //--- Objects constructed in time/price coordinates default : //--- In the loop by all pivot points for(int i=0;i<this.Pivots();i++) { //--- Define the minimum time if(this.Time(i)<time_min) time_min=this.Time(i); //--- Define the maximum price if(this.Price(i)>price_max) price_max=this.Price(i); } //--- Return the result of converting time/price coordinates into XY screen coordinates return(::ChartTimePriceToXY(this.ChartID(),this.SubWindow(),time_min,price_max,x,y) ? true : false); } return false; } //+------------------------------------------------------------------+
Para los objetos construidos en coordenadas de pantalla, no será necesario buscar la coordenada máxima/mínima, ya que estos objetos solo tienen un punto de anclaje al gráfico, y ya se encuentra en coordenadas de pantalla: solo hay que retornarlas.
No obstante, para los objetos basados en coordenadas de tiempo/precio, necesitaremos obtener estas coordenadas con la función ChartTimePriceToXY(). Pero al hacerlo, primero deberemos encontrar la coordenada de tiempo mínima de todos los puntos de pivote disponibles para el objeto, y la coordenada de precio máximo de los mismos puntos de pivote. ¿Y por qué buscamos la coordenada máxima del precio si necesitamos la coordenada mínima del gráfico? La respuesta es simple: el precio sube de abajo hacia arriba en el gráfico, mientras que las coordenadas de los píxeles en el gráfico vienen de la esquina superior izquierda. En consecuencia, cuanto mayor sea el precio, menor será el valor en píxeles de la coordenada para ese punto de precio.
En un ciclo, encontramos para todos los puntos de pivote el tiempo mínimo y el precio máximo, que finalmente transmitimos a la función ChartTimePriceToXY() para obtener las coordenadas en píxeles del gráfico.
El mismo método se usa para obtener la coordenada XY máxima de la pantalla a partir de todos los puntos de pivote:
//+------------------------------------------------------------------+ //| Return the maximum XY screen coordinate | //| from all pivot points | //+------------------------------------------------------------------+ bool CGStdGraphObj::GetMaxCoordXY(int &x,int &y) { //--- Declare the variables that store the maximum time and minimum price datetime time_max=0; double price_min=DBL_MAX; //--- Depending on the object type switch(this.TypeGraphObject()) { //--- Objects constructed based on screen coordinates case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : //--- Write XY screen coordinates to x and y variables and return 'true' x=this.XDistance(); y=this.YDistance(); return true; //--- Objects constructed in time/price coordinates default : //--- In the loop by all pivot points for(int i=0;i<this.Pivots();i++) { //--- Define the maximum time if(this.Time(i)>time_max) time_max=this.Time(i); //--- Define the maximum price if(this.Price(i)<price_min) price_min=this.Price(i); } //--- Return the result of converting time/price coordinates into XY screen coordinates return(::ChartTimePriceToXY(this.ChartID(),this.SubWindow(),time_max,price_min,x,y) ? true : false); } return false; } //+------------------------------------------------------------------+
El método es similar al anterior, excepto que aquí buscamos el tiempo máximo y el precio mínimo.
Una vez más, necesitaremos estos métodos más adelante cuando sigamos trabajando con objetos gráficos compuestos.
En la clase de instrumental del objeto gráfico estándar extendido \MQL5\Include\DoEasy\Objects\Graph\Extend\CGStdGraphObjExtToolkit.mqh, corregimos ligeramente el método que retorna las coordenadas X e Y del punto de pivote especificado del objeto gráfico en coordenadas de pantalla. La cuestión es que tenemos que asignar los objetos gráficos a los casos del operador de conmutación para que cada caso contenga objetos gráficos según el número de puntos de pivote de esos objetos que se construyen utilizando las coordenadas de tiempo/precio. Los distribuimos en el orden correcto:
//+------------------------------------------------------------------+ //| Return the X and Y coordinates of the specified pivot point | //| of the graphical object in screen coordinates | //+------------------------------------------------------------------+ bool CGStdGraphObjExtToolkit::GetControlPointCoordXY(const int index,int &x,int &y) { //--- Declare form objects, from which we are to receive their screen coordinates CFormControl *form0=NULL, *form1=NULL; //--- Set X and Y to zero - these values will be received in case of a failure x=0; y=0; //--- Depending on the graphical object type switch(this.m_base_type) { //--- Objects drawn using screen coordinates case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : //--- Write object screen coordinates and return 'true' x=this.m_base_x; y=this.m_base_y; return true; //--- One pivot point (price only) case OBJ_HLINE : break; //--- One pivot point (time only) case OBJ_VLINE : case OBJ_EVENT : break; //--- One reference point (time/price) case OBJ_TEXT : case OBJ_BITMAP : break; //--- Two pivot points and a central one //--- Lines case OBJ_TREND : case OBJ_TRENDBYANGLE : case OBJ_CYCLES : case OBJ_ARROWED_LINE : //--- Channels case OBJ_CHANNEL : case OBJ_STDDEVCHANNEL : case OBJ_REGRESSION : //--- Gann case OBJ_GANNLINE : case OBJ_GANNGRID : //--- Fibo case OBJ_FIBO : case OBJ_FIBOTIMES : case OBJ_FIBOFAN : case OBJ_FIBOARC : case OBJ_FIBOCHANNEL : case OBJ_EXPANSION : //--- Calculate coordinates for forms on the line pivot points if(index<this.m_base_pivots) return(::ChartTimePriceToXY(this.m_base_chart_id,this.m_base_subwindow,this.m_base_time[index],this.m_base_price[index],x,y) ? true : false); //--- Calculate the coordinates for the central form located between the line pivot points else { form0=this.GetControlPointForm(0); form1=this.GetControlPointForm(1); if(form0==NULL || form1==NULL) return false; x=(form0.CoordX()+this.m_shift+form1.CoordX()+this.m_shift)/2; y=(form0.CoordY()+this.m_shift+form1.CoordY()+this.m_shift)/2; return true; } //--- Channels case OBJ_PITCHFORK : break; //--- Gann case OBJ_GANNFAN : break; //--- Elliott case OBJ_ELLIOTWAVE5 : break; case OBJ_ELLIOTWAVE3 : break; //--- Shapes case OBJ_RECTANGLE : break; case OBJ_TRIANGLE : break; case OBJ_ELLIPSE : break; //--- Arrows case OBJ_ARROW_THUMB_UP : break; case OBJ_ARROW_THUMB_DOWN : break; case OBJ_ARROW_UP : break; case OBJ_ARROW_DOWN : break; case OBJ_ARROW_STOP : break; case OBJ_ARROW_CHECK : break; case OBJ_ARROW_LEFT_PRICE : break; case OBJ_ARROW_RIGHT_PRICE : break; case OBJ_ARROW_BUY : break; case OBJ_ARROW_SELL : break; case OBJ_ARROW : break; //--- default : break; } return false; } //+------------------------------------------------------------------+
Los objetos dibujados inicialmente en coordenadas de pantalla simplemente retornan las coordenadas de pantalla de su objeto básico (al que están unidos).
Los objetos para los que se implementa actualmente el retorno de las coordenadas de pantalla tienen dos puntos de pivote. Las coordenadas de la pantalla se calculan para todos los puntos de pivote del objeto y el punto central.
El resto de losobjetos para los que aún no se ha implementado ninguna funcionalidad, simplemente se agrupan en sus propios grupos (no todos, algunos simplemente se agrupan por tipo y no por número de puntos de pivote, y se implementarán a su debido tiempo).
Ahora tenemos que asegurarnos de que todos los elementos de gestión que representan la interfaz gráfica de usuario que se encuentran en el gráfico permanecen siempre por encima de cualquiera de los objetos gráficos recién añadidos en el gráfico. No solo eso: tendremos que hacer que cuando los elementos de la GUI se reorganicen, se alineen en la misma secuencia que tenían antes su adición al gráfico.
Para que un objeto gráfico pase a primer plano, deberemos ocultarlo y volver a mostrarlo sucesivamente. Esto se hace reseteando y luego estableciendola bandera de visibilidad del objeto gráfico. Para ocultar un objeto debemos establecer con la función ObjectSetInteger() para la propiedad OBJPROP_TIMEFRAMES la bandera OBJ_NO_PERIODS. Para la visualización, marque OBJ_ALL_PERIODS. Y si hacemos esto para todos los objetos de la GUI en la secuencia en que están dispuestos en la lista de colección de elementos gráficos, perderemos la secuencia de su disposición en el gráfico. Es decir, los objetos se colocarán de forma que el primer objeto de la lista sea el más bajo del gráfico, y que el más reciente sea el más alto. Pero estos objetos pueden haber sido dispuestos en una secuencia muy diferente, que se verá alterada cuando se vuelva a dibujar. Y aquí es donde recurrimos a una nueva propiedad que hemos añadido hoy: ZOrder. Tenemos que ordenar la lista de elementos gráficos en orden ascendente de la propiedad ZOrder, y entonces los objetos se redibujarán en la secuencia correcta.
Vamos a abrir el archivo de clase de la colección de elementos gráficos \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh y hacer todas las mejoras necesarias.
En la sección privada de la clase , declaramos tres nuevos métodos:
//--- Event handler void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam); private: //--- Move all objects on canvas to the foreground void BringToTopAllCanvElm(void); //--- (1) Return the maximum ZOrder of all CCanvas elements, //--- (2) Set ZOrde to the specified element and adjust it in all the remaining elements long GetZOrderMax(void); bool SetZOrderMAX(CGCnvElement *obj); //--- Handle the removal of extended graphical objects void DeleteExtendedObj(CGStdGraphObj *obj);
Y los implementamos fuera del cuerpo de la clase.
Método que desplaza todos los objetos del lienzo al primer plano:
//+------------------------------------------------------------------+ //| Move all objects on canvas to the foreground | //+------------------------------------------------------------------+ void CGraphElementsCollection::BringToTopAllCanvElm(void) { //--- Create a new list, reset the flag of managing memory and sort by ZOrder CArrayObj *list=new CArrayObj(); list.FreeMode(false); list.Sort(SORT_BY_CANV_ELEMENT_ZORDER); //--- Declare the graphical element object CGCnvElement *obj=NULL; //--- In the loop by the total number of all graphical elements, for(int i=0;i<this.m_list_all_canv_elm_obj.Total();i++) { //--- get the next object obj=this.m_list_all_canv_elm_obj.At(i); if(obj==NULL) continue; //--- and insert it to the list in the order of sorting by ZOrder list.InsertSort(obj); } //--- In a loop by the created list sorted by ZOrder, for(int i=0;i<list.Total();i++) { //--- get the next object obj=list.At(i); if(obj==NULL) continue; //--- and move it to the foreground obj.BringToTop(); } //--- Remove the list sorted by 'new' delete list; } //+------------------------------------------------------------------+
Bien, ¿qué vemos aquí? Declaramos el nuevo objeto de lista CArrayObj reseteamos su bandera de gestión de la memoria. A continuación, asignamos a la lista la bandera de clasificación según la propiedad ZOrder. ¿Para qué necesitamos esto? Para que podamos ordenar los objetos CObject según cualquier propiedad: el valor de esa propiedad deberá estar establecido como bandera de clasificación. Entonces el método virtual (), implementado en todos los objetos derivados de la clase CObject, retornará el valor necesario para el método Sort() en cuanto a la comparación de las mismas propiedades (la propiedad establecida como modo de clasifición) de dos objetos.
Debemos restablecer la bandera de gestión de memoria para al final tener copias de los objetos de la lista de colección en la matriz creada, en lugar de punteros a los objetos de la colección. Esto es importante, porque si son punteros, cualquier cambio en ellos en la nueva lista cambiará automáticamente el objeto en la lista de la colección: después de todo, son solo punteros en dos listas al mismo objeto en la lista de la colección. Para que no haya "sorpresas", crearemos una lista de copias independiente, cuyos cambios no afectarán al original. No obstante, una vez que el método haya finalizado, deberemos borrar la lista creada para evitar fugas de memoria. Esto también se menciona en la guía de ayuda de la biblioteca estándar (FreeMode):
Configurar la bandera de gestión de memoria es un aspecto importante en el uso de la clase CArrayObj. Dado que los elementos del array son punteros a objetos dinámicos es importante determinar qué hacer con ellos al borrarlos del array.
Si se establece la bandera, al eliminar un elemento del array, el elemento se borra automáticamente por medio del operador delete. Si no se establece la bandera, se asume que todavía existe un puntero al objeto eliminado en algún lugar del programa, y el programa lo liberará a continuación.
Si el usuario reinicia la bandera de gestión de memoria, entonces debe entender que tiene que responsabilizarse de la eliminación del array antes de que finalice el programa, porque de lo contrario, la memoria será ocupada por otros elementos al crear el nuevo operador.
Si el usuario no reinicia la bandera de gestión de memoria, se puede incluso estropear el terminal al manejar grandes cantidades de datos. Si el programa de usuario no restablece el indicador de gestión de la memoria, hay otro obstáculo.
Almacenar punteros y arrays en variables locales después de borrar el array, dará lugar a errores críticos y bloqueos del programa. De forma predeterminada, queda establecida la bandera de gestión de memoria; la clase del array se responsabiliza de liberar la memoria de los elementos.
A continuación, hacemos un ciclo a través de la lista de colección de elementos gráficos e insertamos cada objeto sucesivo en una nueva lista en orden de clasificación.
Cuando el ciclo se complete, obtendremos una nueva lista, pero clasificada de forma ascendente de las propiedades ZOrder de todos los elementos gráficos.
En un ciclo a través de esta lista, recuperamos cada elemento posterior y lo trasladamos al primer plano. Al hacerlo, cada uno de los elementos sucesivos será siempre superior al anterior.
Cuando el ciclo se haya completado, nos deberemos asegurar de borrar la lista creada.
Método que retorna el máximo ZOrder de todos los elementos de CCanvas:
//+------------------------------------------------------------------+ //| Return the maximum ZOrder of all CCanvas elements | //+------------------------------------------------------------------+ long CGraphElementsCollection::GetZOrderMax(void) { this.m_list_all_canv_elm_obj.Sort(SORT_BY_CANV_ELEMENT_ZORDER); int index=CSelect::FindGraphCanvElementMax(this.GetListCanvElm(),CANV_ELEMENT_PROP_ZORDER); CGCnvElement *obj=this.m_list_all_canv_elm_obj.At(index); return(obj!=NULL ? obj.Zorder() : WRONG_VALUE); } //+------------------------------------------------------------------+
Aquí, Clasificamos la lista según la propiedad ZOrder y obtenemos el índice del elemento de la lista con el valor máximo de la propiedad.
Recuperamos el puntero a un elemento de la lista según el índice resultante y retornamos la propiedad ZOrder de ese elemento.
Si no obtenemos ningún elemento, el método retornará -1.
Si tenemos, por ejemplo, tres objetos de GUI, será lógico tener tres valores ZOrder para ellos. Todos los objetos tendrán inicialmente un valor ZOrder igual a cero: todos se encontrarán en la parte inferior. Tan pronto como tomemos un objeto, su ZOrder debería aumentar en 1. Sin embargo, primero tenemos que ver si algún otro objeto tiene un valor de ZOrder igual o mayor a 1, porque el último objeto seleccionado deberá ser mayor que todos los demás, es decir, en 1 mayor que el máximo ZOrder de todos los objetos disponibles. Obviamente, podríamos obtener el máximo ZOrder y simplemente aumentarlo en 1 y no tener que pensar en nada más. Pero esto resulta un poco desordenado, y preferimos hacer que de los tres objetos solo podamos tener valores de ZOrder entre 0 y 2.
Así que para el último objeto seleccionado deberemos aumentar ZOrder en 1, o dejarlo lo más alto posible (según el número de todos los objetos, empezando por cero), y para los otros objetos disminuir el ZOrder en uno. Pero al mismo tiempo, si el objeto se encuentra al fondo y ya tiene un ZOrder de cero, no deberemos reducirlo. De esta forma, los valores de ZOrder se modificarán "en círculo" según el número de objetos de la GUI.
Método que establece ZOrder en el elemento indicado, y corrige en los elementos restantes:
//+------------------------------------------------------------------+ //| Set ZOrde to the specified element | //| and adjust it in other elements | //+------------------------------------------------------------------+ bool CGraphElementsCollection::SetZOrderMAX(CGCnvElement *obj) { //--- Get the maximum ZOrder of all graphical elements long max=this.GetZOrderMax(); //--- If an invalid pointer to the object has been passed or the maximum ZOrder has not been received, return 'false' if(obj==NULL || max<0) return false; //--- Declare the variable for storing the method result bool res=true; //--- If the maximum ZOrder is zero, ZOrder is equal to 1, //--- if the maximum ZOrder is less than (the total number of graphical elements)-1, ZOrder will exceed it by 1, //--- otherwise, ZOrder will be equal to (the total number of graphical elements)-1 long value=(max==0 ? 1 : max<this.m_list_all_canv_elm_obj.Total()-1 ? max+1 : this.m_list_all_canv_elm_obj.Total()-1); //--- If failed to set ZOrder for an object passed to the method, return 'false' if(!obj.SetZorder(value,false)) return false; //--- Temporarily declare a form object for drawing a text for visually displaying its ZOrder CForm *form=obj; //--- and draw a text specifying ZOrder on the form form.TextOnBG(0,TextByLanguage("Тест: ID ","Test. ID ")+(string)form.ID()+", ZOrder "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true); //--- Sort the list of graphical elements by an element ID this.m_list_all_canv_elm_obj.Sort(SORT_BY_CANV_ELEMENT_ID); //--- Get the list of graphical elements without an object whose ID is equal to the ID of the object passed to the method CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_ID,obj.ID(),NO_EQUAL); //--- If failed to obtain the list and the list size exceeds one, //--- which indicates the presence of other objects in it in addition to the one sorted by ID, return 'false' if(list==NULL && this.m_list_all_canv_elm_obj.Total()>1) return false; //--- In the loop by the obtained list of remaining graphical element objects for(int i=0;i<list.Total();i++) { //--- get the next object CGCnvElement *elm=list.At(i); //--- If failed to get the object or if this is a control form for managing pivot points of an extended standard graphical object //--- or, if the object's ZOrder is zero, skip the object since there is no need in changing its ZOrder as it is the bottom one if(elm==NULL || elm.Type()==OBJECT_DE_TYPE_GFORM_CONTROL || elm.Zorder()==0) continue; //--- If failed to set the object's ZOrder to 1 less than it already is (decreasing ZOrder by 1), add 'false' to the 'res' value if(!elm.SetZorder(elm.Zorder()-1,false)) res &=false; //--- Temporarily (for the test purpose), if the element is a form, if(elm.Type()==OBJECT_DE_TYPE_GFORM) { //--- assign the pointer to the element for the form and draw a text specifying ZOrder on the form form=elm; form.TextOnBG(0,TextByLanguage("Тест: ID ","Test. ID ")+(string)form.ID()+", ZOrder "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true); } } //--- Upon the loop completion, return the result set in 'res' return res; } //+------------------------------------------------------------------+
Cada línea del método contiene comentarios que explican la lógica del mismo. Solo señalaremos que para las pruebas hemos añadido la muestra del texto en el formulario con su valor ZOrder: así podremos comprobar visualmente el cambio de estas propiedades para cada objeto del formulario durante las pruebas.
Tenemos dos casos en los que necesitaremos desplazar los objetos de GUI por encima de los objetos gráficos recién construidos:
- Al añadir manualmente un objeto gráfico estándar a un gráfico,
- Al añadir de forma programática un objeto gráfico (compuesto estándar o extendido).
Estas situaciones se tratan de forma independiente en la biblioteca.
Por consiguiente, necesitaremos añadir una llamada al método en varios lugares que lleve a los objetos de GUI a un nivel superior.
Al crear un objeto gráfico de forma programática, podemos colocar la llamada para redibujar los objetos de GUI directamente en el método privado que añade un objeto gráfico estándar creado a la lista: este será el lugar donde se determinará en última instancia el éxito de la creación de un objeto gráfico, y justo al final podemos añadir una llamada al método para traer todos los objetos de GUI al primer plano:
//--- Add a newly created standard graphical object to the list bool AddCreatedObjToList(const string source,const long chart_id,const string name,CGStdGraphObj *obj) { if(!this.m_list_all_graph_obj.Add(obj)) { CMessage::ToLog(source,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST); ::ObjectDelete(chart_id,name); delete obj; return false; } //--- Move all the forms above the created object on the chart and redraw the chart this.BringToTopAllCanvElm(); ::ChartRedraw(chart_id); return true; }
Después de llamar al método, el gráfico deberá actualizarse de inmediato para mostrar los cambios.
Al declarar objetos gráficos manualmente, este evento se definirá en el método que actualiza la lista con todos los objetos gráficos.
En el bloque de código donde se define la adición de un nuevo objeto gráfico, escribimos la llamada al método para traer los objetos de la interfaz gráfica de usuario al primer plano:
//+------------------------------------------------------------------+ //| Update the list of all graphical objects | //+------------------------------------------------------------------+ void CGraphElementsCollection::Refresh(void) { this.RefreshForExtraObjects(); //--- 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(!this.AddGraphObjToCollection(DFUN_ERR_LINE,obj_ctrl)) continue; //--- Move all the forms above the created object on the chart and redraw the chart this.BringToTopAllCanvElm(); ::ChartRedraw(obj_ctrl.ChartID()); } //--- If the graphical object has been removed else if(obj_ctrl.Delta()<0) { int index=WRONG_VALUE; //--- In the loop by the number of removed graphical objects for(int j=0;j<-obj_ctrl.Delta();j++) { // Find an extra object in the list CGStdGraphObj *obj=this.FindMissingObj(chart_id,index); if(obj!=NULL) { //--- Get the removed object parameters long lparam=obj.ChartID(); string sparam=obj.Name(); double dparam=(double)obj.TimeCreate(); //--- If this is an extended graphical object if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) { this.DeleteExtendedObj(obj); } //--- Move the graphical object class object to the list of removed objects //--- and send the event to the control program chart if(this.MoveGraphObjToDeletedObjList(index)) ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_DELETE,lparam,dparam,sparam); } } } } //--- Increase the loop index i++; } } //+------------------------------------------------------------------+
En el artículo anterior, durante las pruebas, determinamos que las coordenadas de un objeto gráfico compuesto no siempre están correctamente delimitadas por los límites del gráfico. Si las ubicaciones de sus puntos de pivote son diferentes, al llegar al borde del gráfico, en algunos casos el objeto comienza a "deformarse".
Tras las pruebas, nos dimos cuenta rápidamente del motivo:
... los desplazamientos de los puntos de pivote se calculan en relación con el punto central. Esto significa que un punto tendrá un desplazamiento positivo, mientras que el segundo tendrá un desplazamiento negativo. Al cambiar la ubicación de los puntos de pivote respecto al punto central, se produce un error al calcular los límites.
Para corregir esto, realizaremos un cambio. Para ello, calcularemos directamente las coordenadas del punto central, y luego calcularemos los desplazamientos desde los puntos de pivote del objeto gráfico hasta su punto central. Al calcular los límites de tamaño del gráfico, usaremos un valor de desplazamiento sin signo. Entonces, el signo de desplazamiento no afectará al cálculo.
En el manejador de eventos del bloque de procesamiento de formularios del objeto gráfico estándar extendido , escribimos el nuevo cálculo de las restricciones y mostramos la información de depuración en el gráfico:
//--- If the form is central for managing all pivot points of a graphical object else { //--- Get screen coordinates of all object pivot points and write them to the m_data_pivot_point structure if(this.GetPivotPointCoordsAll(ext,m_data_pivot_point)) { //--- In the loop by the number of object pivot points, for(int i=0;i<(int)this.m_data_pivot_point.Size();i++) { //--- limit the screen coordinates of the current pivot point so that they do not move beyond the chart borders //--- By X coordinate if(x+shift-::fabs(this.m_data_pivot_point[i].ShiftX)<0) x=-shift+::fabs(m_data_pivot_point[i].ShiftX); if(x+shift+::fabs(this.m_data_pivot_point[i].ShiftX)>chart_width) x=chart_width-shift-::fabs(this.m_data_pivot_point[i].ShiftX); //--- By Y coordinate if(y+shift-::fabs(this.m_data_pivot_point[i].ShiftY)<0) y=-shift+::fabs(this.m_data_pivot_point[i].ShiftY); if(y+shift+::fabs(this.m_data_pivot_point[i].ShiftY)>chart_height) y=chart_height-shift-::fabs(this.m_data_pivot_point[i].ShiftY); //--- set the calculated coordinates to the current object pivot point ext.ChangeCoordsExtendedObj(x+shift+this.m_data_pivot_point[i].ShiftX,y+shift+this.m_data_pivot_point[i].ShiftY,i); } } //--- Display debugging comments on the chart if(m_data_pivot_point.Size()>=2) { int max_x,min_x,max_y,min_y; if(ext.GetMinCoordXY(min_x,min_y) && ext.GetMaxCoordXY(max_x,max_y)) Comment ( "MinX=",min_x,", MaxX=",max_x,"\n", "MaxY=",min_y,", MaxY=",max_y ); } }
En cuanto pulsemos el botón del ratón sobre un objeto de GUI, tendremos que moverlo al primer plano y establecer el máximo ZOrder para él.
En el manejador de eventos, en el bloque de gestión del cursor dentro del área activa al presionar el botón del ratón, escribiremos un bloque de código para asignar al objeto el ZOrder máximo:
//--- 'The cursor is inside the active area, any mouse button is clicked' event handler if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED && !move) { pressed_form=true; // the flag of holding the mouse button on the form //--- If the left mouse button is pressed if(this.m_mouse.IsPressedButtonLeft()) { //--- Set flags and form parameters move=true; // movement flag form.SetInteraction(true); // flag of the form interaction with the environment form.BringToTop(); // form on the background - above all others form.SetOffsetX(this.m_mouse.CoordX()-form.CoordX()); // Cursor shift relative to the X coordinate form.SetOffsetY(this.m_mouse.CoordY()-form.CoordY()); // Cursor shift relative to the Y coordinate this.ResetAllInteractionExeptOne(form); // Reset interaction flags for all forms except the current one //--- Get the maximum ZOrder long zmax=this.GetZOrderMax(); //--- If the maximum ZOrder has been received and the form's ZOrder is less than the maximum one or the maximum ZOrder of all forms is equal to zero if(zmax>WRONG_VALUE && (form.Zorder()<zmax || zmax==0)) { //--- If the form is not a control point for managing an extended standard graphical object, //--- set the form's ZOrder above all others if(form.Type()!=OBJECT_DE_TYPE_GFORM_CONTROL) this.SetZOrderMAX(form); } } }
Aquí, estableceremos ZOrder para los objetos de formulario que no son objetos de gestión de los objetos gráficos estándar extendidos: no será necesario para ellos.
Como el manejador de eventos de la clase de colección de elementos gráficos es bastante voluminoso, no tendrá sentido presentar el código completo aquí: podrá leer información sobre sus cambios (analizados anteriormente) en los archivos adjuntos a este artículo.
En el método que retorna las coordenadas de pantalla de cada punto de pivote del objeto gráfico, cambiamos el cálculo de los desplazamientos.
Ahora calcularemos directamente las coordenadas del punto de pivote central (tras el que se mueve el objeto), y a partir de ahí ya calcularemos los desplazamientos. Para ser más exactos, los desplazamientos se calcularán desde cada punto de pivote hasta el punto central:
//+------------------------------------------------------------------+ //| Return screen coordinates | //| of each graphical object pivot point | //+------------------------------------------------------------------+ bool CGraphElementsCollection::GetPivotPointCoordsAll(CGStdGraphObj *obj,SDataPivotPoint &array_pivots[]) { //--- Central point coordinates int xc=0, yc=0; //--- If failed to increase the array of structures to match the number of pivot points, //--- inform of that in the journal and return 'false' if(::ArrayResize(array_pivots,obj.Pivots())!=obj.Pivots()) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); return false; } //--- In the loop by the number of graphical object pivot points for(int i=0;i<obj.Pivots();i++) { //--- Convert the object coordinates into screen ones. If failed, inform of that and return 'false' if(!::ChartTimePriceToXY(obj.ChartID(),obj.SubWindow(),obj.Time(i),obj.Price(i),array_pivots[i].X,array_pivots[i].Y)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_CONV_GRAPH_OBJ_COORDS_TO_XY); return false; } } //--- Depending on the graphical object type switch(obj.TypeGraphObject()) { //--- One pivot point in screen coordinates case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : break; //--- One pivot point (price only) case OBJ_HLINE : break; //--- One pivot point (time only) case OBJ_VLINE : case OBJ_EVENT : break; //--- Two pivot points and a central one //--- Lines case OBJ_TREND : case OBJ_TRENDBYANGLE : case OBJ_CYCLES : case OBJ_ARROWED_LINE : //--- Channels case OBJ_CHANNEL : case OBJ_STDDEVCHANNEL : case OBJ_REGRESSION : //--- Gann case OBJ_GANNLINE : case OBJ_GANNGRID : //--- Fibo case OBJ_FIBO : case OBJ_FIBOTIMES : case OBJ_FIBOFAN : case OBJ_FIBOARC : case OBJ_FIBOCHANNEL : case OBJ_EXPANSION : //--- Calculate the central point coordinates xc=(array_pivots[0].X+array_pivots[1].X)/2; yc=(array_pivots[0].Y+array_pivots[1].Y)/2; //--- Calculate the shifts of all pivot points from the central one and write them to the structure array array_pivots[0].ShiftX=array_pivots[0].X-xc; // X shift from 0 to the central point array_pivots[1].ShiftX=array_pivots[1].X-xc; // X shift from 1 to the central point array_pivots[0].ShiftY=array_pivots[0].Y-yc; // Y shift from 0 to the central point array_pivots[1].ShiftY=array_pivots[1].Y-yc; // Y shift from 1 to the central point return true; //--- Channels case OBJ_PITCHFORK : break; //--- Gann case OBJ_GANNFAN : break; //--- Elliott case OBJ_ELLIOTWAVE5 : break; case OBJ_ELLIOTWAVE3 : break; //--- Shapes case OBJ_RECTANGLE : break; case OBJ_TRIANGLE : break; case OBJ_ELLIPSE : break; //--- Arrows case OBJ_ARROW_THUMB_UP : break; case OBJ_ARROW_THUMB_DOWN : break; case OBJ_ARROW_UP : break; case OBJ_ARROW_DOWN : break; case OBJ_ARROW_STOP : break; case OBJ_ARROW_CHECK : break; case OBJ_ARROW_LEFT_PRICE : break; case OBJ_ARROW_RIGHT_PRICE : break; case OBJ_ARROW_BUY : break; case OBJ_ARROW_SELL : break; case OBJ_ARROW : break; //--- Graphical objects with time/price coordinates case OBJ_TEXT : break; case OBJ_BITMAP : break; //--- default: break; } return false; } //+------------------------------------------------------------------+
Todos los desplazamientos y límites deberían calcularse ahora correctamente: podremos comprobar esto en las pruebas.
Simulación
Para la simulación, tomaremos el asesor del artículo anterior y lo guardaremos en la nueva carpeta \MQL5\Experts\TestDoEasy\Part100\ con el nuevo nombre TestDoEasyPart100.mq5.
Dado que al realizar la prueba queremos ver directamente el valor de su propiedad ZOrder en cada formulario, añadiremos al manejador OnInit() el establecimiento del valor cero de esta propiedad para el formulario (el objeto abajo del todo) y añadiremos la muestra del identificador del objeto de formulario y el valor de su propiedad ZOrder:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set EA global variables ArrayResize(array_clr,2); // Array of gradient filling colors array_clr[0]=C'26,100,128'; // Original ≈Dark-azure color array_clr[1]=C'35,133,169'; // Lightened original color //--- 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 //--- Create form objects CForm *form=NULL; for(int i=0;i<FORMS_TOTAL;i++) { //--- When creating an object, pass all the required parameters to it form=new CForm("Form_0"+string(i+1),30,(form==NULL ? 100 : form.BottomEdge()+20),100,30); if(form==NULL) continue; //--- Set activity and moveability flags for the form form.SetActive(true); form.SetMovable(true); //--- Set the form ID and the index in the list of objects form.SetID(i); form.SetNumber(0); // (0 - main form object) Auxiliary objects may be attached to the main one. The main object is able to manage them //--- Set the opacity of 200 form.SetOpacity(245); //--- 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(clrDarkBlue); //--- Draw the shadow drawing flag form.SetShadow(false); //--- 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(3,3,clr,200,4); //--- Fill the form background with a vertical gradient form.Erase(array_clr,form.Opacity(),true); //--- Draw an outlining rectangle at the edges of the form form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity()); form.Done(); //--- Set ZOrder to zero, display the text describing the gradient type and update the form //--- Text parameters: the text coordinates and the anchor point in the form center //--- Create a new text animation frame with the ID of 0 and display the text on the form form.SetZorder(0,false); form.TextOnBG(0,TextByLanguage("Тест: ID ","Test: ID ")+(string)form.ID()+", ZOrder "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true); //--- Add the form to the list if(!engine.GraphAddCanvElmToCollection(form)) delete form; } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Vamos a compilar y ejecutar el asesor en el gráfico:
Como podemos ver, justo al crear los objetos de formulario, cada uno de ellos obtiene un valor ZOrder de cero, pero los objetos gráficos aún se construyen "debajo de ellos". El valor de ZOrder de cada objeto cambia "en círculos": no es superior al número de objetos de formulario en el gráfico (contando hacia abajo desde cero). Cualquier objeto gráfico construido siempre aparecerá "debajo" de los objetos de GUI, y su posición relativa no cambiará, por lo que se asignarán correctamente en la lista según los valores de su propiedad ZOrder. Por último, el objeto gráfico compuesto estará ahora correctamente delimitado en los bordes de la pantalla, y no se verá distorsionado; asimismo, al igual que otros objetos gráficos, se dibujará debajo de los objetos de formulario.
¿Qué es lo próximo?
A partir del próximo artículo, inauguraremos una amplia sección sobre la creación de objetos de GUI al estilo de Windows Forms.
Sin embargo, esto no significa que los objetos gráficos extendidos y los objetos compuestos basados en ellos no vayan a desarrollarse más: simplemente necesitaremos controles completos para seguir desarrollándolos. En consecuencia, a medida que vayamos desarrollando el siguiente apartado de la biblioteca, continuaremos desarrollando y perfeccionando gradualmente los objetos gráficos extendidos.
*Artículos de esta serie:
Gráficos en la biblioteca DoEasy (Parte 93): Preparando la funcionalidad para crear objetos gráficos compuestos
Gráficos en la biblioteca DoEasy (Parte 94): Objetos gráficos compuestos, desplazamiento y eliminación
Gráficos en la biblioteca DoEasy (Parte 95): Elementos de control de los objetos gráficos compuestos
Gráficos en la biblioteca DoEasy (Parte 96): Trabajando con los eventos del ratón y los gráficos en los objetos de formulario
Gráficos en la biblioteca DoEasy (Parte 97): Procesamiento independiente del desplazamiento de los objetos de formulario
Gráficos en la biblioteca DoEasy (Parte 98): Desplazamiento de los puntos de anclaje de los objetos gráficos estándar ampliados
Gráficos en la biblioteca DoEasy (Parte 99): Desplazando un objeto gráfico extendido con un punto de control
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/10634





- 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