English Русский 中文 Deutsch 日本語 Português
preview
DoEasy. Elementos de control (Parte 2): Continuamos trabajando con la clase CPanel

DoEasy. Elementos de control (Parte 2): Continuamos trabajando con la clase CPanel

MetaTrader 5Ejemplos | 16 junio 2022, 08:45
268 0
Artyom Trishkin
Artyom Trishkin

Contenido


Concepto

Con el último artículo, iniciamos un amplio tema sobre la creación de controles al estilo de Windows Forms. Pero todavía tenemos algunos fallos y errores que "arrastramos" desde que empezamos a trabajar con los objetos gráficos. Sin duda, el lector atento ya los habrá notado. Por ejemplo, nunca hemos comprobado cómo se comportan los objetos gráficos al cambiar de marco temporal. Y resulta que, simplemente, no aparecen en el gráfico. En este caso, además, en el diario de registro aparecen mensajes informando de que tales objetos ya han sido creados. En consecuencia, ni se crean ni se dibujan. De la misma forma, por ejemplo, solo los objetos de formulario pueden interactuar con el ratón. El objeto gráfico, que es el objeto padre del formulario, carece de funcionalidad para el ratón, lo cual es correcto en principio, pues es el objeto gráfico mínimo de la biblioteca que se puede usar para cualquier construcción gráfica. Pero si necesitamos una interacción activa, el objeto gráfico mínimo deberá ser un objeto de formulario. No obstante, su heredero, el control "Panel" que empezamos a desarrollar en el último artículo, tampoco responde al ratón, aunque debería. Y este es, digamos, el precio de añadir constantemente tanto objetos como la funcionalidad de los mismos a la biblioteca.

Hoy vamos a corregir algunos bugs y errores. También seguiremos añadiendo funcionalidad al objeto de control "Panel". 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. Por ejemplo, digamos que tenemos un objeto de clase CPanel. Dicho objeto permitirá que se le adjunten otros controles. Y si el control adjunto contiene algún texto como parte de él, heredará por defecto la configuración de la fuente del panel al que está adjunto. El panel, a su vez, como elemento gráfico independiente, también estará dotado de la capacidad de dibujar cualquier texto dentro de sí mismo (como todos los elementos gráficos de la biblioteca), y estos textos también utilizarán estas opciones de fuente por defecto. Sin embargo, al dibujar un nuevo texto en un elemento gráfico, nada nos impide establecer diferentes parámetros de fuente justo antes de mostrarlo: el texto se generará con estos parámetros explícitos.

Además, cualquier panel, como control, deberá tener la capacidad de mostrar un marco que encuadre el panel, junto con la capacidad de controlar los parámetros de dicho marco. Vamos a implementar el dibujado de un marco con valores por defecto y parámetros indicados explícitamente, o bien la ausencia del mismo.


Mejorando las clases de la biblioteca

Para poder establecer rápidamente diferentes estilos de visualización para los elementos gráficos, hemos creado el archivo \MQL5\Include\DoEasy\GraphINI.mqh.
En él se encuentran (podemos añadir posteriormente los nuestros, siguiendo el ejemplo de los existentes) los parámetros para los diferentes esquemas de color, tipos y estilos de visualización de varios elementos gráficos.

Para visualizar con mayor claridad las opciones de estilo del formulario, cambiaremos ligeramente el orden de los índices y los valores de estilo correspondientes.

Basta con desplazar los parámetros que aparecen en primer lugar en la lista al final de la misma y firmar su pertenencia:

//+------------------------------------------------------------------+
//| List of form style parameter indices                             |
//+------------------------------------------------------------------+
enum ENUM_FORM_STYLE_PARAMS
  {
   //--- CForm
   FORM_STYLE_FRAME_SHADOW_OPACITY,             // Shadow opacity
   FORM_STYLE_FRAME_SHADOW_BLUR,                // Shadow blur
   FORM_STYLE_DARKENING_COLOR_FOR_SHADOW,       // Form shadow color darkening
   FORM_STYLE_FRAME_SHADOW_X_SHIFT,             // Shadow X axis shift
   FORM_STYLE_FRAME_SHADOW_Y_SHIFT,             // Shadow Y axis shift
   //--- CPanel
   FORM_STYLE_FRAME_WIDTH_LEFT,                 // Panel frame width to the left
   FORM_STYLE_FRAME_WIDTH_RIGHT,                // Panel frame width to the right
   FORM_STYLE_FRAME_WIDTH_TOP,                  // Panel frame width on top
   FORM_STYLE_FRAME_WIDTH_BOTTOM,               // Panel frame width below
  };
#define TOTAL_FORM_STYLE_PARAMS        (9)      // Number of form style parameters
//+------------------------------------------------------------------+
//| Array containing form style parameters                           |
//+------------------------------------------------------------------+
int array_form_style[TOTAL_FORM_STYLES][TOTAL_FORM_STYLE_PARAMS]=
  {
//--- "Flat form" style parameters
   {
      //--- CForm
      80,                                       // Shadow opacity
      4,                                        // Shadow blur
      80,                                       // Form shadow color darkening
      2,                                        // Shadow X axis shift
      2,                                        // Shadow Y axis shift
      //--- CPanel
      3,                                        // Panel frame width to the left
      3,                                        // Panel frame width to the right
      3,                                        // Panel frame width on top
      3,                                        // Panel frame width below
   },
//--- "Embossed form" style parameters
   {
      //--- CForm
      80,                                       // Shadow opacity
      4,                                        // Shadow blur
      80,                                       // Form shadow color darkening
      2,                                        // Shadow X axis shift
      2,                                        // Shadow Y axis shift
      //--- CPanel
      3,                                        // Panel frame width to the left
      3,                                        // Panel frame width to the right
      3,                                        // Panel frame width on top
      3,                                        // Panel frame width below
   },
  };
//+------------------------------------------------------------------+

Naturalmente, esta no es en absoluto una mejora vital, pero unos parámetros correctamente estructurados facilitarán su lectura posterior.

En el archivo \MQL5\Include\DoEasy\Data.mqh, escribimos los índices de los nuevos mensajes:

//--- CGraphElementsCollection
   MSG_GRAPH_ELM_COLLECTION_ERR_OBJ_ALREADY_EXISTS,   // Error. A chart control object already exists with chart id 
   MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_CREATE_CTRL_OBJ,// Failed to create chart control object with chart ID 
   MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_GET_CTRL_OBJ,  // Failed to get chart control object with chart ID 
   MSG_GRAPH_ELM_COLLECTION_ERR_GR_OBJ_ALREADY_EXISTS,// Such graphical object already exists: 
   MSG_GRAPH_ELM_COLLECTION_ERR_EMPTY_OBJECT,         // Error! Empty object
   MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_GET_ELEMENT,   // Failed to get a graphical element from the list

y los textos de los mensajes que se corresponden con los índices nuevamente añadidos:

//--- CGraphElementsCollection
   {"Ошибка. Уже существует объект управления чартами с идентификатором чарта ","Error. A chart control object already exists with chart id "},
   {"Не удалось создать объект управления чартами с идентификатором чарта ","Failed to create chart control object with chart id "},
   {"Не удалось получить объект управления чартами с идентификатором чарта ","Failed to get chart control object with chart id "},
   {"Такой графический объект уже существует: ","Such a graphic object already exists: "},
   {"Ошибка! Пустой объект","Error! Empty object"},
   {"Не удалось получить графический элемент из списка","Failed to get graphic element from list"},


Para gestionar el estilo de dibujado de la fuente del texto, existe un conjunto de banderas. Sin embargo, para poder seleccionar el estilo de fuente deseado y los diferentes tipos de grosor de fuente de la lista, deberá existir una enumeración. Entonces, si transmitimos una enumeración a cualquier método como el estilo de fuente, o el tipo de grosor de la misma, al escribir el código, podremos seleccionar el estilo y el tipo de una lista desplegable, lo cual resulta mucho más cómodo que recordar los nombres de estas banderas.
Además, también podemos tener un panel desplegado con un marco alrededor del objeto, en su mayor parte. Así que necesitaremos tener una macrosustitución con el grosor del marco del panel por defecto.

En el archivo \MQL5\Include\DoEasy\Defines.mqh, , creamos una macrosustitución que indique la anchura de todos los lados del marco del panel por defecto:

//--- Canvas parameters
#define PAUSE_FOR_CANV_UPDATE          (16)                       // Canvas update frequency
#define CLR_CANV_NULL                  (0x00FFFFFF)               // Zero for the canvas with the alpha channel
#define CLR_FORE_COLOR                 (C'0x2D,0x43,0x48')        // Default color for texts of objects on canvas
#define DEF_FONT                       ("Calibri")                // Default font
#define DEF_FONT_SIZE                  (8)                        // Default font size
#define OUTER_AREA_SIZE                (16)                       // Size of one side of the outer area around the form workspace
#define DEF_FRAME_WIDTH_SIZE           (3)                        // Default form/panel/window frame width

y el listado de estilos de fuente y los tipos de grosor de la misma al final del listado del archivo:

//+------------------------------------------------------------------+
//| Font style list                                                  |
//+------------------------------------------------------------------+
enum ENUM_FONT_STYLE
  {
   FONT_STYLE_NORMAL=0,                               // Normal
   FONT_STYLE_ITALIC=FONT_ITALIC,                     // Italic
   FONT_STYLE_UNDERLINE=FONT_UNDERLINE,               // Underline
   FONT_STYLE_STRIKEOUT=FONT_STRIKEOUT                // Strikeout
  };
//+------------------------------------------------------------------+
//| FOnt width type list                                             |
//+------------------------------------------------------------------+
enum ENUM_FW_TYPE
  {
   FW_TYPE_DONTCARE=FW_DONTCARE,
   FW_TYPE_THIN=FW_THIN,
   FW_TYPE_EXTRALIGHT=FW_EXTRALIGHT,
   FW_TYPE_ULTRALIGHT=FW_ULTRALIGHT,
   FW_TYPE_LIGHT=FW_LIGHT,
   FW_TYPE_NORMAL=FW_NORMAL,
   FW_TYPE_REGULAR=FW_REGULAR,
   FW_TYPE_MEDIUM=FW_MEDIUM,
   FW_TYPE_SEMIBOLD=FW_SEMIBOLD,
   FW_TYPE_DEMIBOLD=FW_DEMIBOLD,
   FW_TYPE_BOLD=FW_BOLD,
   FW_TYPE_EXTRABOLD=FW_EXTRABOLD,
   FW_TYPE_ULTRABOLD=FW_ULTRABOLD,
   FW_TYPE_HEAVY=FW_HEAVY,
   FW_TYPE_BLACK=FW_BLACK
  };
//+------------------------------------------------------------------+

Como podemos ver, aquí simplemente asignamos a cada constante enum al valor de la bandera correspondiente según su nombre. Pero para el estilo de fuente, hemos introducido una nueva constante para el tipo de letra "normal", no cursiva, subrayada o tachada. Y este valor es igual a cero, lo cual restablecerá las banderas de estilo adicionales establecidas previamente para la fuente.


En la clase de objetos de formulario, tenemos una clase de animación para los elementos gráficos en el lienzo y un objeto de sombra del formulario. Ambos objetos se crean a través del operador new, y cuando se completan, son eliminados en el destructor de la clase. De esta forma, estos objetos son siempre controlados y eliminados a tiempo.
Sin embargo, ha surgido un problema al heredar de una clase de objeto de formulario. La clase de objeto de panel es heredera del objeto de formulario, y nos hemos encontrado con que los citados objetos no se borran, provocando una fuga de memoria. Podemos comprobarlo ejecutando el asesor del artículo anterior. Al eliminar este del gráfico, el registro mostrará una pérdida de 4 objetos y una fuga de memoria de 512 bytes:

 4 undeleted objects left
 1 object of type CAnimations left
 3 objects of type CArrayObj left
 512 bytes of leaked memory

Para ser honestos, hemos buscado durante mucho tiempo el motivo de este fracaso por parte de los objetos, y... No ha habido forma de encontrar la causa... Así que lo pondremos todo en el subsistema del terminal.

Para ello, solo tenemos que crear un objeto de la clase CArrayObj y poner en él los objetos creados en la clase CForm. Cuando el terminal finalice, eliminará por sí mismo la memoria de todos los objetos de esta lista.

Vamos a abrir el archivo \MQL5\Include\DoEasy\Objects\Graph\Form.mqh y declarar esta lista.
También vamos a desplazar las variables para almacenar la anchura de cada lado del marco del formulario a una sección protegida de la clase:

//+------------------------------------------------------------------+
//| Form object class                                                |
//+------------------------------------------------------------------+
class CForm : public CGCnvElement
  {
private:
   CArrayObj         m_list_tmp;
   CArrayObj         m_list_elements;                          // List of attached elements
   CAnimations      *m_animations;                             // Pointer to the animation object
   CShadowObj       *m_shadow_obj;                             // Pointer to the shadow object
   CMouseState       m_mouse;                                  // "Mouse status" class object
   ENUM_MOUSE_FORM_STATE m_mouse_form_state;                   // Mouse status relative to the form
   ushort            m_mouse_state_flags;                      // Mouse status flags
   color             m_color_frame;                            // Form frame color
   int               m_offset_x;                               // Offset of the X coordinate relative to the cursor
   int               m_offset_y;                               // Offset of the Y coordinate relative to the cursor
   
//--- Reset the array size of (1) text, (2) rectangular and (3) geometric animation frames
   void              ResetArrayFrameT(void);
   void              ResetArrayFrameQ(void);
   void              ResetArrayFrameG(void);
   
//--- Return the name of the dependent object
   string            CreateNameDependentObject(const string base_name)  const
                       { return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+1)+"_"+base_name;   }
  
//--- Create a new graphical object
   CGCnvElement     *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                      const int element_num,
                                      const string name,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool movable,
                                      const bool activity);

//--- Create a shadow object
   void              CreateShadowObj(const color colour,const uchar opacity);
   
protected:
   int               m_frame_width_left;                       // Form frame width to the left
   int               m_frame_width_right;                      // Form frame width to the right
   int               m_frame_width_top;                        // Form frame width at the top
   int               m_frame_width_bottom;                     // Form frame width at the bottom
//--- Initialize the variables
   void              Initialize(void);
   void              Deinitialize(void);
   
public:

Necesitaremos acceder a estas variables desde las clases herederas, por lo que las variables deberán estar en una sección protegida: visibles en esta clase y en las clases herederas.

En el método de inicialización, eliminamos la nueva lista y establecemos para ella la bandera de lista clasificada. Para la anchura de cada uno de los lados del marco del formulario, establecemos el valor de la nueva macrosustitución, y añadimos el objeto de animación a la lista:

//+------------------------------------------------------------------+
//| Initialize the variables                                         |
//+------------------------------------------------------------------+
void CForm::Initialize(void)
  {
   this.m_list_elements.Clear();
   this.m_list_elements.Sort();
   this.m_list_tmp.Clear();
   this.m_list_tmp.Sort();
   this.m_shadow_obj=NULL;
   this.m_shadow=false;
   this.m_frame_width_right=DEF_FRAME_WIDTH_SIZE;
   this.m_frame_width_left=DEF_FRAME_WIDTH_SIZE;
   this.m_frame_width_top=DEF_FRAME_WIDTH_SIZE;
   this.m_frame_width_bottom=DEF_FRAME_WIDTH_SIZE;
   this.m_mouse_state_flags=0;
   this.m_offset_x=0;
   this.m_offset_y=0;
   CGCnvElement::SetInteraction(false);
   this.m_animations=new CAnimations(CGCnvElement::GetObject());
   this.m_list_tmp.Add(m_animations);
  }
//+------------------------------------------------------------------+


En el método que crea el objeto de sombra, después de crear con éxito el objeto, lo añadimos a la nueva lista:

//+------------------------------------------------------------------+
//| Create the shadow object                                         |
//+------------------------------------------------------------------+
void CForm::CreateShadowObj(const color colour,const uchar opacity)
  {
//--- ...

//--- ...
   
//--- Create a new shadow object and set the pointer to it in the variable
   this.m_shadow_obj=new CShadowObj(this.ChartID(),this.SubWindow(),this.CreateNameDependentObject("Shadow"),x,y,w,h);
   if(this.m_shadow_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ));
      return;
     }
   this.m_list_tmp.Add(m_shadow_obj);
//--- ...

//--- Move the form object to the foreground
   this.BringToTop();
  }
//+------------------------------------------------------------------+

Esta mejora nos evitará fugas de memoria incontrolables y difíciles de detectar.


Vamos a continuar con la clase de objeto del control WinForms CPanel.

Implementaremos la configuración de la fuente para el panel y su marco.

En el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh, en la sección privada de la clase, declaramos una variable para almacenar el tipo de grosor de la fuente y el método que retorna las banderas establecidas para la fuente:

//+------------------------------------------------------------------+
//| Panel object class of WForms controls                            |
//+------------------------------------------------------------------+
class CPanel : public CForm
  {
private:
   color             m_fore_color;                                   // Default text color for all panel objects
   ENUM_FW_TYPE      m_bold_type;                                    // Font width type
   ENUM_FRAME_STYLE  m_border_style;                                 // Panel frame style
   bool              m_autoscroll;                                   // Auto scrollbar flag
   int               m_autoscroll_margin[2];                         // Array of fields around the control during an auto scroll
   bool              m_autosize;                                     // Flag of the element auto resizing depending on the content
   ENUM_CANV_ELEMENT_AUTO_SIZE_MODE m_autosize_mode;                 // Mode of the element auto resizing depending on the content
   ENUM_CANV_ELEMENT_DOCK_MODE m_dock_mode;                          // Mode of binding element borders to the container
   int               m_margin[4];                                    // Array of gaps of all sides between the fields of the current and adjacent controls
   int               m_padding[4];                                   // Array of gaps of all sides inside controls
//--- Return the font flags
   uint              GetFontFlags(void);
public:

Al establecer el valor del tipo de anchura de la fuente en , escribiremos este valor en la variable m_bold_type .
El método que retorna las banderas de la fuente devuelve todos los parámetros que se establecen para ella: el nombre, el tamaño, las banderas y la inclinación. Como solo necesitamos operar con banderas, para evitar tener que declarar en cada método las variables locales en las que se escribirán todos estos valores establecidos para la fuente, llamaremos a este método que retorna solo las banderas obtenidas de las propiedades de la fuente de la clase CCanvas.

En la sección pública de la clase, , declaramos los métodos para manejar las banderas de los estilos de fuente y los tipos de grosor:

public:
//--- (1) Set and (2) return the default text color of all panel objects
   void              ForeColor(const color clr)                      { this.m_fore_color=clr;               }
   color             ForeColor(void)                           const { return this.m_fore_color;            }

//--- (1) Set and (2) return the Bold font flag
   void              Bold(const bool flag);
   bool              Bold(void);
//--- (1) Set and (2) return the Italic font flag
   void              Italic(const bool flag);
   bool              Italic(void);
//--- (1) Set and (2) return the Strikeout font flag
   void              Strikeout(const bool flag);
   bool              Strikeout(void);
//--- (1) Set and (2) return the Underline font flag
   void              Underline(const bool flag);
   bool              Underline(void);
//--- (1) Set and (2) return the font style
   void              FontDrawStyle(ENUM_FONT_STYLE style);
   ENUM_FONT_STYLE   FontDrawStyle(void);
//--- (1) Set and (2) return the font width type
   void              FontBoldType(ENUM_FW_TYPE type);
   ENUM_FW_TYPE      FontBoldType(void)                        const { return this.m_bold_type;             }

//--- (1) Set and (2) return the frame style

...

y escribimos los métodos para establecer y retornar las propiedades del marco del panel:

//--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields inside the control
   int               PaddingLeft(void)                         const { return this.m_padding[0];            }
   int               PaddingTop(void)                          const { return this.m_padding[1];            }
   int               PaddingRight(void)                        const { return this.m_padding[2];            }
   int               PaddingBottom(void)                       const { return this.m_padding[3];            }
   
//--- Set the width of the form frame (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides of the control
   void              FrameWidthLeft(const int value)                 { this.m_frame_width_left=value;       }
   void              FrameWidthTop(const int value)                  { this.m_frame_width_top=value;        }
   void              FrameWidthRight(const int value)                { this.m_frame_width_right=value;      }
   void              FrameWidthBottom(const int value)               { this.m_frame_width_bottom=value;     }
   void              FrameWidthAll(const int value)
                       {
                        this.FrameWidthLeft(value); this.FrameWidthTop(value); this.FrameWidthRight(value); this.FrameWidthBottom(value);
                       }
//--- Return the width of the form frame (1) to the left, (2) at the top, (3) to the right and (4) at the bottom
   int               FrameWidthLeft(void)                      const { return this.m_frame_width_left;      }
   int               FrameWidthTop(void)                       const { return this.m_frame_width_top;       }
   int               FrameWidthRight(void)                     const { return this.m_frame_width_right;     }
   int               FrameWidthBottom(void)                    const { return this.m_frame_width_bottom;    }
   
//--- Constructors


En cada constructor, escribimos la configuración del tipo de grosor de la fuente por defecto:

                     CPanel(const string name) : CForm(::ChartID(),0,name,0,0,0,0)
                       {
                        CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL);
                        this.m_type=OBJECT_DE_TYPE_GWF_PANEL; 
                        this.m_fore_color=CLR_FORE_COLOR;
                        this.m_bold_type=FW_TYPE_NORMAL;
                        this.MarginAll(3);
                        this.PaddingAll(0);
                        this.Initialize();
                       }
//--- Destructor
                    ~CPanel();
  };
//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CPanel::CPanel(const long chart_id,
               const int subwindow,
               const string name,
               const int x,
               const int y,
               const int w,
               const int h) : CForm(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL);
   this.m_type=OBJECT_DE_TYPE_GWF_PANEL;
   this.m_fore_color=CLR_FORE_COLOR;
   this.m_bold_type=FW_TYPE_NORMAL;
   this.MarginAll(3);
   this.PaddingAll(0);
   this.Initialize();
  }
//+------------------------------------------------------------------+
//| Current chart constructor specifying the subwindow               |
//+------------------------------------------------------------------+
CPanel::CPanel(const int subwindow,
               const string name,
               const int x,
               const int y,
               const int w,
               const int h) : CForm(::ChartID(),subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL);
   this.m_type=OBJECT_DE_TYPE_GWF_PANEL; 
   this.m_fore_color=CLR_FORE_COLOR;
   this.m_bold_type=FW_TYPE_NORMAL;
   this.MarginAll(3);
   this.PaddingAll(0);
   this.Initialize();
  }
//+------------------------------------------------------------------+
//| Constructor on the current chart in the main chart window        |
//+------------------------------------------------------------------+
CPanel::CPanel(const string name,
               const int x,
               const int y,
               const int w,
               const int h) : CForm(::ChartID(),0,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL);
   this.m_type=OBJECT_DE_TYPE_GWF_PANEL; 
   this.m_fore_color=CLR_FORE_COLOR;
   this.m_bold_type=FW_TYPE_NORMAL;
   this.MarginAll(3);
   this.PaddingAll(0);
   this.Initialize();
  }
//+------------------------------------------------------------------+

Este tipo será el predeterminado para las fuentes del panel.

Método privado que retorna las banderas de la fuente:

//+------------------------------------------------------------------+
//| Return the font flags                                            |
//+------------------------------------------------------------------+
uint CPanel::GetFontFlags(void)
  {
   string name;
   int size;
   uint flags;
   uint angle;
   CGCnvElement::GetFont(name,size,flags,angle);
   return flags;
  }
//+------------------------------------------------------------------+

Como el método GetFont() de la clase de elemento gráfico debe recibir mediante un enlace las variables en las que se escribirán los valores obtenidos de los parámetros de fuente en la clase CCanvas, aquí, declararemos todas las variables necesarias, obtendremos sus valores llamando al método GetFont() y retornaremos solo las banderas obtenidas.

Método que establece la bandera de fuente Bold:

//+------------------------------------------------------------------+
//| Set the Bold font flag                                           |
//+------------------------------------------------------------------+
void CPanel::Bold(const bool flag)
  {
   uint flags=this.GetFontFlags();
   if(flag)
     {
      this.m_bold_type=FW_TYPE_BOLD;
      CGCnvElement::SetFontFlags(flags | FW_BOLD);
     }
   else
      this.m_bold_type=FW_TYPE_NORMAL;
  }
//+------------------------------------------------------------------+

Aquí, obtenemos las banderas usando el método GetFontFlags() analizado anteriormente, si la bandera transmitida en los argumentos del método está establecida, escribimos el valor de Bold en la variable m_bold_type, que almacena el tipo de grosor de la fuente, y establecemos otra bandera para las banderas de fuente: FW_BOLD.
Si la bandera transmitida en los argumentos del método no se establece, en la variable m_bold_type se escribirá el valor por defecto .

Método que retorna la bandera de fuente Bold:

//+------------------------------------------------------------------+
//| Return the Bold font flag                                        |
//+------------------------------------------------------------------+
bool CPanel::Bold(void)
  {
   uint flags=this.GetFontFlags();
   return(flags &FW_BOLD)==FW_BOLD;
  }
//+------------------------------------------------------------------+

Aquí, obtenemos las banderas usando GetFontFlags() y retornamos el resultado de la comprobación sobre la presencia de la bandera FW_BOLD en la variable.


Los métodos para establecer y retornar las otras banderas de fuentes son ligeramente diferentes: no requieren que el valor de la bandera se establezca en una variable.
Por lo demás, los métodos son idénticos a los anteriores:

//+------------------------------------------------------------------+
//| Set the Italic font flag                                         |
//+------------------------------------------------------------------+
void CPanel::Italic(const bool flag)
  {
   uint flags=this.GetFontFlags();
   if(flag)
      CGCnvElement::SetFontFlags(flags | FONT_ITALIC);
  }
//+------------------------------------------------------------------+
//| Return the Italic font flag                                      |
//+------------------------------------------------------------------+
bool CPanel::Italic(void)
  {
   uint flags=this.GetFontFlags();
   return(flags &FONT_ITALIC)==FONT_ITALIC;
  }
//+------------------------------------------------------------------+
//| Set the Strikeout font flag                                      |
//+------------------------------------------------------------------+
void CPanel::Strikeout(const bool flag)
  {
   uint flags=this.GetFontFlags();
   if(flag)
      CGCnvElement::SetFontFlags(flags | FONT_STRIKEOUT);
  }
//+------------------------------------------------------------------+
//| Return the Strikeout font flag                                   |
//+------------------------------------------------------------------+
bool CPanel::Strikeout(void)
  {
   uint flags=this.GetFontFlags();
   return(flags &FONT_STRIKEOUT)==FONT_STRIKEOUT;
  }
//+------------------------------------------------------------------+
//| Set the Underline font flag                                      |
//+------------------------------------------------------------------+
void CPanel::Underline(const bool flag)
  {
   uint flags=this.GetFontFlags();
   if(flag)
      CGCnvElement::SetFontFlags(flags | FONT_UNDERLINE);
  }
//+------------------------------------------------------------------+
//| Return the Underline font flag                                   |
//+------------------------------------------------------------------+
bool CPanel::Underline(void)
  {
   uint flags=this.GetFontFlags();
   return(flags &FONT_UNDERLINE)==FONT_UNDERLINE;
  }
//+------------------------------------------------------------------+

A nuestro juicio, estos métodos resultan claros y se explican por sí mismos.


Método que establece el estilo de la fuente:

//+------------------------------------------------------------------+
//| Set the font style                                               |
//+------------------------------------------------------------------+
void CPanel::FontDrawStyle(ENUM_FONT_STYLE style)
  {
   switch(style)
     {
      case FONT_STYLE_ITALIC     :  this.Italic(true);      break;
      case FONT_STYLE_UNDERLINE  :  this.Underline(true);   break;
      case FONT_STYLE_STRIKEOUT  :  this.Strikeout(true);   break;
      default: break;
     }
  }
//+------------------------------------------------------------------+

Dependiendo del estilo de dibujado de la fuente (cursiva, subrayado, tachado) transmitido al método, llamamos al método de establecimiento que corresponda al estilo.

Método que retorna el estilo de la fuente:

//+------------------------------------------------------------------+
//| Return the font style                                            |
//+------------------------------------------------------------------+
ENUM_FONT_STYLE CPanel::FontDrawStyle(void)
  {
   return
     (
      this.Italic()     ?  FONT_STYLE_ITALIC    :
      this.Underline()  ?  FONT_STYLE_UNDERLINE :
      this.Strikeout()  ?  FONT_STYLE_UNDERLINE :
      FONT_STYLE_NORMAL
     );
  }
//+------------------------------------------------------------------+

Dependiendo del estilo establecido para la fuente y retornado por los métodos correspondientes, retornamos el mismo estilo para la fuente de contorno.
Si no se establece ninguno de los tres estilos, retornaremos Normal.


Método que establece el grosor del tipo de fuente:

//+------------------------------------------------------------------+
//| Set the font width type                                          |
//+------------------------------------------------------------------+
void CPanel::FontBoldType(ENUM_FW_TYPE type)
  {
   this.m_bold_type=type;
   uint flags=this.GetFontFlags();
   switch(type)
     {
      case FW_TYPE_DONTCARE   : CGCnvElement::SetFontFlags(flags | FW_DONTCARE);    break;
      case FW_TYPE_THIN       : CGCnvElement::SetFontFlags(flags | FW_THIN);        break;
      case FW_TYPE_EXTRALIGHT : CGCnvElement::SetFontFlags(flags | FW_EXTRALIGHT);  break;
      case FW_TYPE_ULTRALIGHT : CGCnvElement::SetFontFlags(flags | FW_ULTRALIGHT);  break;
      case FW_TYPE_LIGHT      : CGCnvElement::SetFontFlags(flags | FW_LIGHT);       break;
      case FW_TYPE_REGULAR    : CGCnvElement::SetFontFlags(flags | FW_REGULAR);     break;
      case FW_TYPE_MEDIUM     : CGCnvElement::SetFontFlags(flags | FW_MEDIUM);      break;
      case FW_TYPE_SEMIBOLD   : CGCnvElement::SetFontFlags(flags | FW_SEMIBOLD);    break;
      case FW_TYPE_DEMIBOLD   : CGCnvElement::SetFontFlags(flags | FW_DEMIBOLD);    break;
      case FW_TYPE_BOLD       : CGCnvElement::SetFontFlags(flags | FW_BOLD);        break;
      case FW_TYPE_EXTRABOLD  : CGCnvElement::SetFontFlags(flags | FW_EXTRABOLD);   break;
      case FW_TYPE_ULTRABOLD  : CGCnvElement::SetFontFlags(flags | FW_ULTRABOLD);   break;
      case FW_TYPE_HEAVY      : CGCnvElement::SetFontFlags(flags | FW_HEAVY);       break;
      case FW_TYPE_BLACK      : CGCnvElement::SetFontFlags(flags | FW_BLACK);       break;
      default                 : CGCnvElement::SetFontFlags(flags | FW_NORMAL);      break;
     }
  }
//+------------------------------------------------------------------+

Aquí:  En la variable m_bold_type, escribimos el valor transmitido al método. Asimismo, obtenemos las banderas de fuente usando el método GetFontFlags().
Dependiendodel tipo de anchura de fuente transmitido al método, añadimos en la variable obtenida con las banderas de fuente otra bandera correspondiente al tipo de fuente indicado.
Como resultado, la variable m_bold_type contendrá el valor de enumeración transmitido al método, y las banderas de fuente contendrán la bandera con el tipo de anchura de fuente correspondiente al valor de la enumeración ENUM_FW_TYPE.


Al añadir un elemento gráfico a una lista de colección, primero se comprueba que está presente en la lista. Si dicho objeto ya existe, podremos no añadirlo e informar sobre ello en el registro y retornar un error de adición, o hacer lo mismo, pero devolver un puntero a un objeto existente en lugar de un error. Esto puede resultar útil al crear objetos dinámicos, para que un objeto creado dinámicamente pero oculto sea retornado por un método y se muestre en el gráfico al intentar crear exactamente el mismo objeto.

Para que podamos devolver diferentes códigos de retorno del método de adición de un elemento gráfico a la lista, vamos a crear una enumeración para ello.

En el archivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh de la clase de colección, escribimos la enumeración antes de la declaración de la clase:

//+------------------------------------------------------------------+
//| Collection of graphical objects                                  |
//+------------------------------------------------------------------+
#resource "\\"+PATH_TO_EVENT_CTRL_IND;          // Indicator for controlling graphical object events packed into the program resources
enum ENUM_ADD_OBJ_RET_CODE                      // Enumerate the codes of returning the method for adding an object to the list
  {
   ADD_OBJ_RET_CODE_SUCCESS,                    // Successful
   ADD_OBJ_RET_CODE_EXIST,                      // Object exists in the collection list
   ADD_OBJ_RET_CODE_ERROR,                      // Failed to add to the collection list
  };
class CGraphElementsCollection : public CBaseObj

Aquí hay tres códigos de retorno:

  1. el objeto se ha añadido con éxito a la lista,
  2. el objeto ya existe en la lista de colección,
  3. se ha producido un error al añadir el objeto a la lista de colección.

Ahora podremos retornar de forma flexible el resultado deseado. Esto solo será erróneo si el objeto no se ha colocado en la lista. Otros resultados indican la posibilidad de continuar el trabajo: el objeto se ha creado y se ha añadido a la colección, o dicho objeto ya está presente y podemos manejarlo.

Para poder crear elementos gráficos directamente desde una clase de colección, necesitaremos anteponer a su nombre el nombre de un programa con guión bajo al final. En la sección privada de la clase, declaramos una variable para almacenar el prefijo de los objetos gráficos:

class CGraphElementsCollection : public CBaseObj
  {
private:
//--- ...

   bool              m_is_graph_obj_event;      // Event flag in the list of graphical objects
   int               m_total_objects;           // Number of graphical objects
   int               m_delta_graph_obj;         // Difference in the number of graphical objects compared to the previous check
   string            m_name_prefix;             // Object name prefix
   
//--- Return the flag indicating the graphical element class object presence in the collection list of graphical elements

También añadiremos dos métodos privados: un método para crear un nuevo elemento gráfico o retornar el identificador de uno existente,
y otro método para retornar el índice del elemento gráfico indicado en la lista de la colección:

//--- Reset all interaction flags for all forms except the specified one
   void              ResetAllInteractionExeptOne(CGCnvElement *form);
//--- Add the element to the collection list
   bool AddCanvElmToCollection(CGCnvElement *element);
//--- Add the element to the collectionl ist or return the existing one
   ENUM_ADD_OBJ_RET_CODE AddOrGetCanvElmToCollection(CGCnvElement *element,int &id);
//--- Return the graphical elemnt index in the collection list
   int               GetIndexGraphElement(const long chart_id,const string name);


También debemos corregir el método que retorna una lista de elementos gráficos según el ID del gráfico y el nombre del objeto. La cuestión es que los nombres de todos los objetos gráficos de la biblioteca comienzan por el nombre del programa. Este nombre se escribe en el prefijo de los nombres de los objetos gráficos. Si transmitimos solo el nombre del objeto en el método de búsqueda, el objeto no se encontrará. La razón de ello es que se buscará el nombre transmitido al método, y los objetos gráficos también tienen un prefijo. Así que tenemos que comprobar si existe un prefijo de nombre en el nombre buscado y, si no lo hay, añadirlo al nombre del objeto buscado. En este caso, la búsqueda siempre funcionará correctamente: si el prefijo ya está en el nombre buscado, no se añadirá nada al nombre y se buscará el valor tal y como se transmite al método. Si no hay prefijo, se añadirá un prefijo al nombre, y la búsqueda se realizará de esta forma.

En la sección pública de la clase, buscamos el método que retorna una lista de elementos gráficos según el ID del gráfico y el nombre del objeto, y le añadimos las mejoras descritas anteriormente:

//--- Return the list of graphical elements by chart ID and object name
   CArrayObj        *GetListCanvElementByName(const long chart_id,const string name)
                       {
                        string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                        CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL);
                        return CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                       }

Aquí, declararemos el nombre a buscar, comprobaremos si el nombre contiene un prefijo y, si no, añadiremos un prefijo al nombre; de lo contrario, no añadimos nada.
A continuación, obtenemos la lista de elementos gráficos según el ID del gráfico y retornamos la lista filtrada según el nombre del objeto buscado. Si no se encuentra ningún objeto, el método retornará NULL.

Inmediatamente después de este método, escribimos otro método que retorna el elemento gráfico según su ID y nombre:

//--- Return the graphical element by chart ID and name
   CGCnvElement     *GetCanvElement(const long chart_id,const string name)
                       {
                        CArrayObj *list=this.GetListCanvElementByName(chart_id,name);
                        return(list!=NULL ? list.At(0) : NULL);
                       }

//--- Constructor

Aquí, obtenemos una lista de elementos gráficos según el ID del gráfico y el nombre del objeto que se han indicado. Si se ha obtenido la lista, retornaremos un puntero al único objeto de la lista. En caso contrario, retornaremos NULL.

Para poder volver a crear todos los elementos de la interfaz gráfica de usuario al cambiar de marco temporal, deberemos borrar la lista de elementos gráficos. Pero como el destructor de la clase en el que ya se ha escrito el borrado completo de la lista solo se llama cuando el programa se quita del gráfico, tendremos que implementar el borrado en el manejador OnDeinit(). Lo declaramos:

//--- Update the list of (1) all graphical objects, (2) on the specified chart, fill in the data on the number of new ones and set the event flag
   void              Refresh(void);
   void              Refresh(const long chart_id);
//--- Event handlers
   void              OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
   void              OnDeinit(void);
private:
//--- Move all objects on canvas to the foreground
   void              BringToTopAllCanvElm(void);


Ahora, en cada método que crea un elemento gráfico, reemplazamos este bloque de código:

                        if(!this.AddCanvElmToCollection(obj))
                          {
                           delete obj;
                           return WRONG_VALUE;
                          }

por uno de estos:

//--- Create a graphical element object on canvas on a specified chart and subwindow
   int               CreateElement(const long chart_id,
                                   const int subwindow,
                                   const string name,
                                   const int x,
                                   const int y,
                                   const int w,
                                   const int h,
                                   const color clr,
                                   const uchar opacity,
                                   const bool movable,
                                   const bool activity,
                                   const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,activity,redraw);
                        ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id);
                        if(res==ADD_OBJ_RET_CODE_ERROR)
                           return WRONG_VALUE;
                        if(res==ADD_OBJ_RET_CODE_EXIST)
                           obj.SetID(id);
                        obj.Erase(clr,opacity,redraw);
                        return obj.ID();
                       }
//--- Create a graphical element object on canvas on a specified chart and subwindow with the vertical gradient filling

Qué vemos aquí: primero obtenemos el código de retorno del método que crea un nuevo elemento gráfico, o retornamos el identificador de uno existente. Luego, si el método retorna un error, devolveremos -1, si el código de retorno dice que el objeto existe, asignaremos a las propiedades del objeto recién creado el identificador obtenido del método AddOrGetCanvElmToCollection(). El asunto es que inicialmente establecemos el valor máximo para el nuevo ID del objeto: el número de objetos en la lista, mientras que el ID para un objeto ya existente debe ser distinto. En este caso, el ID se establecerá en la variable transmitida por enlace al método que añade un objeto a la lista o retorna un objeto existente.
De este modo, o bien añadimos un nuevo objeto a la lista, o bien obtenemos el puntero a un objeto existente y escribimos en él su identificador.

Para los métodos de creación de objetos de formulario y el método de creación de paneles,este bloque de código será ligeramente distinto:

//--- Create a graphical object form object on canvas on a specified chart and subwindow
   int               CreateForm(const long chart_id,
                                const int subwindow,
                                const string name,
                                const int x,
                                const int y,
                                const int w,
                                const int h,
                                const color clr,
                                const uchar opacity,
                                const bool movable,
                                const bool activity,
                                const bool shadow=false,
                                const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h);
                        ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id);
                        if(res==ADD_OBJ_RET_CODE_ERROR)
                           return WRONG_VALUE;
                        obj.SetID(id);
                        obj.SetActive(activity);
                        obj.SetMovable(movable);
                        obj.SetColorBackground(clr);
                        obj.SetColorFrame(clr);
                        obj.SetOpacity(opacity,false);
                        obj.SetShadow(shadow);
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity());
                        obj.Done();
                        obj.Erase(clr,opacity,redraw);
                        return obj.ID();
                       }
//--- Create a graphical object form object on canvas on a specified chart and subwindow with the vertical gradient filling

Aquí es más fácil: al obtener un error, retornaremos -1, y no habrá necesidad de restaurar el identificador, porque se asignará en el constructor de la clase para un nuevo objeto, sin especificarse en su método de creación.

En el método para crear un objeto de panel, añadimos a los argumentos la anchura del marco del panel y tres marcos:

//--- Create graphical object WinForms Panel object on canvas on a specified chart and subwindow
   int               CreatePanel(const long chart_id,
                                 const int subwindow,
                                 const string name,
                                 const int x,
                                 const int y,
                                 const int w,
                                 const int h,
                                 const color clr,
                                 const uchar opacity,
                                 const bool movable,
                                 const bool activity,
                                 const int  frame_width=-1,
                                 ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL,
                                 const bool shadow=false,
                                 const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CPanel *obj=new CPanel(chart_id,subwindow,name,x,y,w,h);
                        ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id);
                        if(res==ADD_OBJ_RET_CODE_ERROR)

                           return WRONG_VALUE;

                        obj.SetID(id);
                        obj.SetActive(activity);
                        obj.SetMovable(movable);
                        obj.SetColorBackground(clr);
                        obj.SetColorFrame(clr);
                        obj.SetOpacity(opacity,false);
                        obj.SetShadow(shadow);
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity());
                        obj.Erase(clr,opacity,redraw);
                        if(frame_width>0)
                           obj.FrameWidthAll(frame_width);
                        obj.SetActiveAreaShift(obj.FrameWidthLeft(),obj.FrameWidthBottom(),obj.FrameWidthRight(),obj.FrameWidthTop());
                        obj.DrawFormFrame(obj.FrameWidthTop(),obj.FrameWidthBottom(),obj.FrameWidthLeft(),obj.FrameWidthRight(),obj.ColorFrame(),obj.Opacity(),frame_style);
                        obj.Done();
                        return obj.ID();
                       }

En el cuerpo del método, rellenamos el panel con un color; si el grosor del marco es superior a cero, establecemos el grosor de todos los lados del marco en las propiedades del panel, establecemos el área activa del panel dentro del marco, dibujamos un marco y registramos el aspecto del panel.


En el constructor de la clase, establecemos el valor del prefijo para los nombres de los objetos gráficos, limpiamos la lista de todos los elementos gráficos y establecemos la bandera de lista clasificada en ella:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CGraphElementsCollection::CGraphElementsCollection()
  {
   this.m_type=COLLECTION_GRAPH_OBJ_ID;
   this.m_name_prefix=this.m_name_program+"_";
   ::ChartSetInteger(::ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ::ChartSetInteger(::ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
   this.m_list_all_graph_obj.Type(COLLECTION_GRAPH_OBJ_ID);
   this.m_list_all_graph_obj.Sort(SORT_BY_CANV_ELEMENT_ID);
   this.m_list_all_graph_obj.Clear();
   this.m_list_charts_control.Sort();
   this.m_list_charts_control.Clear();
   this.m_total_objects=0;
   this.m_is_graph_obj_event=false;
   this.m_list_deleted_obj.Clear();
   this.m_list_deleted_obj.Sort();
   this.m_list_all_canv_elm_obj.Clear();
   this.m_list_all_canv_elm_obj.Sort();
  }
//+------------------------------------------------------------------+


Método que añade un elemento gráfico al lienzo en la colección:

//+------------------------------------------------------------------+
//| Add the graphical element on canvas to the collection            |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::AddCanvElmToCollection(CGCnvElement *element)
  {
   if(!this.m_list_all_canv_elm_obj.Add(element))
     {
      CMessage::ToLog(DFUN+element.Name()+": ",MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

Si no hemos podido añadir el elemento a la lista, informamos sobre ello en el registro y retornamos false. En caso contrario, retornaremos true.


Método que añade un elemento a una lista de colección o retorna un elemento existente:

//+------------------------------------------------------------------+
//| Add the element to the collectionl ist or return the existing one|
//+------------------------------------------------------------------+
ENUM_ADD_OBJ_RET_CODE CGraphElementsCollection::AddOrGetCanvElmToCollection(CGCnvElement *element,int &id)
  {
//--- If an invalid pointer is passed, notify of that and return the error code
   if(element==NULL)
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_ELM_COLLECTION_ERR_EMPTY_OBJECT);
      return ADD_OBJ_RET_CODE_ERROR;
     }
//--- If the graphical element with a specified chart ID and name is already present, 
   if(this.IsPresentCanvElmInList(element.ChartID(),element.Name()))
     {
      //--- inform of the object existence,
      CMessage::ToLog(DFUN+element.Name()+": ",MSG_LIB_SYS_OBJ_ALREADY_IN_LIST);
      //--- get the element from the collection list.
      element=this.GetCanvElement(element.ChartID(),element.Name());
      //--- If failed to get the object, inform of that and return the error code
      if(element==NULL)
        {
         CMessage::ToLog(DFUN,MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_GET_ELEMENT);
         return ADD_OBJ_RET_CODE_ERROR;
        }
      //--- set the ID of the object obtained from the list to the value returned by the link
      //--- and return the object existence code in the list
      id=element.ID();
      return ADD_OBJ_RET_CODE_EXIST;
     }
//--- If failed to add the object to the list, remove it and return the error code
   if(!this.AddCanvElmToCollection(element))
     {
      delete element;
      return ADD_OBJ_RET_CODE_ERROR;
     }
//--- All is successful
   return ADD_OBJ_RET_CODE_SUCCESS;
  }
//+------------------------------------------------------------------+

Cada línea del método se explica claramente en los comentarios, y esperamos que todo quede claro aquí. Resumiendo: transmitimos al método el puntero a un objeto recién creado y, si tal objeto existe en la lista, asignamos al puntero transmitido al método el puntero a un objeto existente en la lista. El identificador del objeto existente se escribe en una variable transmitida al método por enlace: asignaremos esta variable como el identificador del objeto desde el exterior. Si el objeto no está en la lista, lo añadiremos y retornaremos una bandera que indique el éxito de la operación.


Método que retorna el índice de un elemento gráfico en una lista de colección:

//+------------------------------------------------------------------+
//| Return the graphical elemnt index in the collection list         |
//+------------------------------------------------------------------+
int CGraphElementsCollection::GetIndexGraphElement(const long chart_id,const string name)
  {
   for(int i=0;i<this.m_list_all_canv_elm_obj.Total();i++)
     {
      CGCnvElement *obj=this.m_list_all_canv_elm_obj.At(i);
      if(obj==NULL)
         continue;
      if(obj.ChartID()==chart_id && obj.Name()==name)
         return i;
     }
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+

Aquí, en un bucle a través de todos los elementos gráficos de la colección, obtenemos el siguiente objeto y, si su ID del gráfico y su nombre son iguales a los transmitidos al método, retornaremos el índice del ciclo. Cuando el ciclo se complete, retornaremos -1.
Aquí nos gustaría aclarar por qué no usamos la búsqueda rápida utilizando la clase de la biblioteca CSelect. La cuestión es que estamos buscando específicamente el índice del objeto en la lista completa de toda la colección. Sin embargo, al filtrar una lista según las propiedades, se crean nuevas listas, y obtenemos el índice del objeto de la lista filtrada, en lugar de la lista de la colección. Naturalmente, sus índices no coincidirán en la mayoría de los casos.

Por el mismo motivo, corregiremos el error en el método que encuentra un objeto dentro de la colección, pero no en el gráfico, y retorna el puntero al objeto y el índice del objeto en la lista:

//+------------------------------------------------------------------+
//|Find an object present in the collection but not on a chart       |
//| Return the pointer to the object and its index in the list.      |
//+------------------------------------------------------------------+
CGStdGraphObj *CGraphElementsCollection::FindMissingObj(const long chart_id,int &index)
  {
   index=WRONG_VALUE;
   for(int i=0;i<this.m_list_all_graph_obj.Total();i++)
     {
      CGStdGraphObj *obj=this.m_list_all_graph_obj.At(i);
      if(obj==NULL)
         continue;
      if(!this.IsPresentGraphObjOnChart(obj.ChartID(),obj.Name()))
        {
         index=i;
         return obj;
        }
     }
   return NULL;
  }
//+------------------------------------------------------------------+

Antes, primero obteníamos una lista de objetos según el ID del gráfico, y realizábamos un ciclo en la lista obtenida. Eso no está bien.
Ahora, implementaremos un ciclo a través de toda la lista de colección, y por lo tanto, obtendremos el índice correcto del objeto en la lista.


Bien, ahora hablaremos sobre el hecho de que en el asesor del último artículo solo podíamos interactuar con el ratón con objetos de formulario.

Los objetos gráficos no deben interactuar automáticamente con el ratón, pero todos los objetos herederos de un objeto de formulario también deberán heredar los eventos de ratón de este. El error se hallaba en que estábamos comprobando rígidamente el tipo del elemento gráfico en el manejador de eventos. Y, si no se trataba de un formulario, no procesaba los eventos.

Ahora cambiaremos esta comprobación: si el tipo de elemento gráfico es un formulario o cualquier otro elemento a lo largo de la jerarquía de herencia, dichos objetos deberán procesarse en el controlador de eventos.

Vamos a corregir esto.

En el método GetFormUnderCursor(), escribimos el cambio:

//--- If managed to obtain the list and it is not empty,
   if(list!=NULL && list.Total()>0)
     {
      //--- Get the only graphical element there
      elm=list.At(0);
      //--- If the element is a form object or its descendants
      if(elm.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_FORM)
        {
         //--- Assign the pointer to the element for the form object pointer
         form=elm;
         //--- Get the mouse status relative to the form
         mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
         //--- If the cursor is within the form, return the pointer to the form
         if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
            return form;
        }
     }
//--- If there is no a single form object with a specified interaction flag,
//--- in the loop by all graphical element collection class objects
   int total=this.m_list_all_canv_elm_obj.Total();
   for(int i=0;i<total;i++)
     {
      //--- get the next element
      elm=this.m_list_all_canv_elm_obj.At(i);
      if(elm==NULL)
         continue;
      //--- if the obtained element is a form object or its descendants
      if(elm.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_FORM)
        {
         //--- Assign the pointer to the element for the form object pointer
         form=elm;
         //--- Get the mouse status relative to the form
         mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
         //--- If the cursor is within the form, return the pointer to the form
         if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
            return form;
        }
     }
//--- If there is no a single form object from the collection list
//--- Get the list of extended standard graphical objects

Anteriormente había una comparación de igualdad ("=="). Ahora se ha convertido en "más o menos". Como los valores de las constantes para enumerar los tipos de elementos gráficos están en orden ascendente, todos los tipos siguientes tendrán un valor constante superior al valor de la constante GRAPH_ELEMENT_TYPE_FORM.


En el método que resetea para todos los formularios las banderas de interacción salvo la especificada, también haremos un cambio:

//+--------------------------------------------------------------------+
//| Reset all interaction flags for all forms except the specified one |
//+--------------------------------------------------------------------+
void CGraphElementsCollection::ResetAllInteractionExeptOne(CGCnvElement *form_exept)
  {
   //--- In the loop by all graphical element collection class objects
   int total=this.m_list_all_canv_elm_obj.Total();
   for(int i=0;i<total;i++)
     {
      //--- get the pointer to the object
      CGCnvElement *obj=this.m_list_all_canv_elm_obj.At(i);
      //--- if failed to receive the pointer, or it is not a form or its descendants, or it is not a form whose pointer has been passed to the method, move on
      if(obj==NULL || obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_FORM || (obj.Name()==form_exept.Name() && obj.ChartID()==form_exept.ChartID()))
         continue;
      //--- Reset the interaction flag for the current form in the loop
      obj.SetInteraction(false);
     }
  }
//+------------------------------------------------------------------+

Antes, había una comparación de desigualdad ("!=") y, en consecuencia, se omitían todos los objetos que no fueran de formulario. Ahora todos los objetos de la jerarquía de herencia por debajo del objeto de formulario serán omitidos.


En el método SetZOrderMAX(), vamos a cambiar ligeramente el texto de prueba mostrado en el objeto gráfico (porque el asesor de prueba también cambiará estos textos), y a corregir el error que impide que los objetos interactúen con el ratón:

//+------------------------------------------------------------------+
//| Set ZOrde to the specified element                               |
//| and adjust it in other elements                                  |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::SetZOrderMAX(CGCnvElement *obj)
  {
//--- ...

//--- 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,form.TypeElementDescription()+": ID "+(string)form.ID()+", ZD "+(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);
//--- ...

//--- ...


//--- 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,form.TypeElementDescription()+": ID "+(string)form.ID()+", ZD "+(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;
  }
//+------------------------------------------------------------------+


Asimismo, escribiremos un manejador de eventos de desinicialización:

//+------------------------------------------------------------------+
//| Deinitialization event handler                                   |
//+------------------------------------------------------------------+
void CGraphElementsCollection::OnDeinit(void)
  {
   this.m_list_all_canv_elm_obj.Clear();
  }
//+------------------------------------------------------------------+

Aquí, todo es simple: limpiamos la lista de colección de elementos gráficos.

En consecuencia, este método deberá ser llamado desde el objeto principal de la biblioteca CEngine en el archivo \MQL5\Include\DoEasy\Engine.mqh.

Abrimos el archivo de esta clase e insertamos en su manejador OnDeinit() una llamada a este método desde la clase de colección de elementos gráficos:

//+------------------------------------------------------------------+
//| Deinitialize library                                             |
//+------------------------------------------------------------------+
void CEngine::OnDeinit(void)
  {
   this.m_indicators.GetList().Clear();
   this.m_graph_objects.OnDeinit();
  }
//+------------------------------------------------------------------+

Estas son todas las mejoras de la biblioteca hasta ahora. Vamos a poner a prueba lo que tenemos.


Simulación

Para la simulación, tomaremos el asesor del artículo anterior y lo guardaremos en la nueva carpeta \MQL5\Experts\TestDoEasy\Part102\ con el nuevo nombre TestDoEasyPart102.mq5.

No realizaremos ningún cambio significativo. Lo único que haremos es cambiar las coordenadas de los objetos gráficos, para que la distancia entre ellos resulte ligeramente menor, y aumentar el tamaño del panel. Para crear el panel, usaremos este código en el manejador OnInit():

//--- Create WinForms Panel object
   CPanel *pnl=NULL;
   obj_id=engine.GetGraphicObjCollection().CreatePanel(ChartID(),0,"WFPanel",elm.RightEdge()+20,50,230,150,array_clr[0],200,true,true);
   list=engine.GetListCanvElementByID(ChartID(),obj_id);
   pnl=list.At(0);
   if(pnl!=NULL)
     {
      pnl.FontDrawStyle(FONT_STYLE_NORMAL);
      pnl.Bold(true);
      Print(DFUN,EnumToString(pnl.FontDrawStyle()));
      Print(DFUN,EnumToString(pnl.FontBoldType()));
      pnl.SetFontSize(10);
      pnl.TextOnBG(0,pnl.TypeElementDescription()+": ID "+(string)pnl.ID()+", ZD "+(string)pnl.Zorder(),pnl.Width()/2,pnl.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',pnl.Opacity());
      pnl.Update(true);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Si el panel se ha creado con éxito, estableceremos para sus textos el tipo de dibujado en "Normal", el tipo de fuente en "negrita" y mostraremos una descripción del estilo de fuente y el tipo de grosor en el diario de registro.

Todos los textos que se mostrarán en todos los objetos de elementos gráficos ahora reflejarán automáticamente el tipo de objeto, su identificador y ZOrder. Simplemente lo harán en un formato ligeramente distinto al del último artículo.

Podrá analizar todos estos cambios en los archivos adjuntos al artículo.

Vamos a compilar el asesor y a ejecutarlo en el gráfico:


Como podemos ver, todos los objetos necesarios interactúan con el ratón, el panel tiene ahora un marco, y la fuente se muestra en negrita, tal y como se ha configurado. Al cambiar de gráfico, ahora no desaparecerán los objetos del gráfico como antes, pero tampoco conservarán su nueva ubicación. Para solucionarlo, habrá que escribir los datos de los objetos en un archivo y luego leerlos cuando sea necesario. Implementaremos todo esto, pero más tarde, con todos los objetos en su jerarquía de herencia prevista.


¿Qué es lo próximo?

En el próximo artículo, continuaremos desarrollando la clase de objeto de panel.

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:

DoEasy. Elementos de control (Parte 1): Primeros pasos


Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/10697

Archivos adjuntos |
MQL5.zip (4384.12 KB)
DoEasy. Elementos de control (Parte 3): Creando controles vinculados DoEasy. Elementos de control (Parte 3): Creando controles vinculados
En este artículo veremos la creación de controles subordinados vinculados a un control básico y creados directamente a partir de la funcionalidad del control básico. Además de la tarea mencionada, tocaremos también el objeto de sombra de un elemento gráfico, ya que todavía existen algunos errores lógicos no resueltos a la hora de utilizarlo con cualquiera de los objetos que permiten tener una sombra.
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.
Aprendiendo a diseñar un sistema comercial basado en CCI Aprendiendo a diseñar un sistema comercial basado en CCI
En este nuevo artículo de nuestra serie sobre el diseño de sistemas comerciales, hablaremos del Índice del Canal de Mercaderías (CCI), estudiaremos sus entresijos y crearemos juntos un sistema comercial basado en este indicador.
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
Hoy vamos a hacer un poco de "limpieza": para ello, eliminaremos los defectos que surgen al trabajar con los objetos gráficos extendidos (y estándar) y los objetos de formulario simultáneamente en el lienzo, y también arreglaremos los errores detectados durante las pruebas en el artículo anterior. Y así concluirá esta sección de la descripción de la biblioteca.