English Русский 中文 Deutsch 日本語 Português
preview
Gráficos en la biblioteca DoEasy (Parte 100): Solucionamos las deficiencias al trabajar con los objetos gráficos estándar extendidos

Gráficos en la biblioteca DoEasy (Parte 100): Solucionamos las deficiencias al trabajar con los objetos gráficos estándar extendidos

MetaTrader 5Ejemplos | 10 junio 2022, 17:37
413 0
Artyom Trishkin
Artyom Trishkin

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:

  1. Al añadir manualmente un objeto gráfico estándar a un gráfico,
  2. 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.

Más abajo, adjuntamos todos los archivos de la versión actual de la biblioteca, así como los archivos del asesor de prueba y el indicador de control de eventos de los gráficos para MQL5. Podrá descargarlo todo y ponerlo a prueba por sí mismo. Si tiene preguntas, observaciones o sugerencias, podrá concretarlas en los comentarios al artículo.

Volver al contenido

*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

Archivos adjuntos |
MQL5.zip (4375.67 KB)
DoEasy. Elementos de control (Parte 1): Primeros pasos DoEasy. Elementos de control (Parte 1): Primeros pasos
Con este artículo, iniciamos un extenso tutorial sobre la creación de controles al estilo de Windows Forms en MQL5. Vamos a empezar el tema creando una clase de panel. Ya se está haciendo difícil manejar las cosas sin controles. Por consiguiente, crearemos todos los controles posibles al estilo de Windows Forms.
Aprendizaje automático y Data Science (Parte 01): Regresión lineal Aprendizaje automático y Data Science (Parte 01): Regresión lineal
Es hora de que los tráders entrenemos nuestros sistemas y aprendamos a tomar nuestras propias decisiones en función de lo que muestren los números. En este proceso, evitaremos los métodos visuales o intuitivos que usa todo el mundo. Marcharemos perpendicularmente a la dirección general.
DoEasy. Elementos de control (Parte 2): Continuamos trabajando con la clase CPanel DoEasy. Elementos de control (Parte 2): Continuamos trabajando con la clase CPanel
En este artículo, eliminaremos algunos errores que surgen al trabajar con los elementos gráficos y continuaremos desarrollando el control CPanel. Estos métodos servirán para establecer por defecto los parámetros de fuente usados para todos los objetos de texto en el panel, que a su vez podrán ser colocados en él en el futuro.
Aprendiendo a diseñar un sistema comercial basado en RSI Aprendiendo a diseñar un sistema comercial basado en RSI
En este artículo, hablaremos sobre otro indicador popular y de uso común: RSI. Asimismo, aprenderemos a desarrollar un sistema comercial basado en las lecturas de este indicador.