English Русский 中文 Deutsch 日本語 Português
Gráficos en la biblioteca DoEasy (Parte 76): Objeto de formulario y temas de color predeterminados

Gráficos en la biblioteca DoEasy (Parte 76): Objeto de formulario y temas de color predeterminados

MetaTrader 5Ejemplos | 4 agosto 2021, 08:34
555 0
Artyom Trishkin
Artyom Trishkin

Contenido


Concepto

En el último artículo, desarrollamos la clase del objeto de elemento gráfico básico, que supone la base para crear objetos gráficos de la biblioteca más complejos, y también creamos métodos para dibujar primitivas gráficas y textos. Usando como base este objeto de elemento gráfico, hoy crearemos su clase de objeto heredero: el objeto de formulario. El objeto de formulario ya puede constituir una unidad absolutamente independiente para el diseño y la presentación elementos de control y la visualización en los programas creados sobre la base de esta biblioteca.

Pero antes de crear un objeto de formulario, debemos hablar de la GUI y los métodos necesarios para su diseño. También tenemos que crear un conjunto inicial de temas de color y tipos de objetos gráficos.
Muchos programas que usan la representación gráfica de datos y ofrecen interacción con el mundo exterior gracias a su motor gráfico, nos permiten cambiar rápidamente la apariencia y el diseño de nuestros objetos gráficos. Para cambiar rápidamente la apariencia y el esquema de color, usaremos un conjunto de temas. Los parámetros de los temas creados se encontrarán en un archivo aparte de la biblioteca, en el que el usuario del programa o el programador podrá cambiar rápidamente varias configuraciones para la apariencia y el color de los objetos gráficos.

Hoy empezaremos a crear dos máscaras a las que iremos añadiendo gradualmente más parámetros diferentes con sus valores correspondientes a medida que desarrollemos nuevos objetos y funcionalidades en la biblioteca.
A la hora de crear nuestros propios objetos gráficos, no usaremos necesariamente los temas creados en la biblioteca, pero estos podrán servir como ejemplo sobre cómo crear exactamente este o aquel objeto para su uso posterior.


Mejorando las clases de la biblioteca

En primer lugar, y como venimos haciendo en cada ocasión, añadiremos al archivo \MQL5\Include\DoEasy\Data.mqh los índices de los nuevos mensajes:

   MSG_LIB_SYS_FAILED_ADD_SYM_OBJ,                    // Failed to add symbol
   MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ,                 // Failed to create the graphical element object
   MSG_LIB_SYS_OBJ_ALREADY_IN_LIST,                   // Such an object is already present in the list
   MSG_LIB_SYS_FAILED_GET_DATA_GRAPH_RES,             // Failed to receive graphical resource data

...

   MSG_LIB_SYS_FAILED_ADD_BUFFER,                     // Failed to add buffer object to the list
   MSG_LIB_SYS_FAILED_CREATE_BUFFER_OBJ,              // Failed to create \"Indicator buffer\" object
   MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST,                // Could not add object to the list

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

   {"Не удалось добавить символ ","Failed to add "},
   {"Не удалось создать объект-графический элемент ","Failed to create graphic element object "},
   {"Такой объект уже есть в списке","Such an object is already in the list"},
   {"Не удалось получить данные графического ресурса","Failed to get graphic resource data"},

...

   {"Не удалось добавить объект-буфер в список","Failed to add buffer object to list"},
   {"Не удалось создать объект \"Индикаторный буфер\"","Failed to create object \"Indicator buffer\""},
   {"Не удалось добавить объект в список","Failed to add object to the list"},

Adelantándonos un poco, podemos decir que hoy, creando un objeto de formulario, generaremos un espacio en blanco para la posterior creación de las sombras que el formulario proyecta sobre los objetos que se encuentran por debajo del mismo. Aquí, para dibujar un formulario, necesitaremos crear a su alrededor un pequeño espacio en el que se dibujará la sombra. Para determinar el tamaño de este espacio, deberemos crear una macrosustitución que indicará en píxeles el tamaño del lado de este espacio. Si indicamos 5 píxeles, tendremos un espacio libre alrededor de la parte superior, inferior, izquierda y derecha de cinco píxeles a cada lado.

Y una cosa más: el análisis del trabajo con el objeto de elemento gráfico ha mostrado que no necesitamos algunas de las propiedades en su lista de propiedades; estas no se utilizarán para buscar y clasificar objetos. Por consiguiente, deberemos eliminarlas de la enumeración de propiedades de tipo entero del objeto de elemento: se encontrarán en las variables de miembro de clase protegidas ordinarias.

Abrimos el archivo \MQL5\Include\DoEasy\Defines.mqh y añadimos las mejoras:

En la lista de parámetros del lienzo, añadimos el margen por un lado para las sombras:

//--- Parameters of the DOM snapshot series
#define MBOOKSERIES_DEFAULT_DAYS_COUNT (1)                        // The default required number of days for DOM snapshots in the series
#define MBOOKSERIES_MAX_DATA_TOTAL     (200000)                   // Maximum number of stored DOM snapshots of a single symbol
//--- Canvas parameters
#define PAUSE_FOR_CANV_UPDATE          (16)                       // Canvas update frequency
#define NULL_COLOR                     (0x00FFFFFF)               // Zero for the canvas with the alpha channel
#define OUTER_AREA_SIZE                (5)                        // Size of one side of the outer area around the workspace
//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+

Eliminamos las dos propiedades innecesarias de la lista de propiedades de tipo entero del elemento gráfico:

   CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,                // Active area offset from the bottom edge of the element
   CANV_ELEMENT_PROP_OPACITY,                         // Element opacity
   CANV_ELEMENT_PROP_COLOR_BG,                        // Element background color
   CANV_ELEMENT_PROP_MOVABLE,                         // Element moveability flag

Ahora, la lista de propiedades de tipo entero tendrá el aspecto siguiente:

//+------------------------------------------------------------------+
//| 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_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_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
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (21)          // Total number of integer properties
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Number of integer properties not used in sorting
//+------------------------------------------------------------------+

Reducimos el número total de propiedades de tipo entero en 2: en lugar de 23, escribimos 21.

Por consiguiente, también eliminamos las dos constantes ya innecesarias de la enumeración de posibles criterios de clasificación de los elementos gráficos en el lienzo:

   SORT_BY_CANV_ELEMENT_ACT_SHIFT_BOTTOM,             // Sort by the active area offset from the bottom edge of the element
   SORT_BY_CANV_ELEMENT_OPACITY,                      // Sort by the element opacity
   SORT_BY_CANV_ELEMENT_COLOR_BG,                     // Sort by the element background color
   SORT_BY_CANV_ELEMENT_MOVABLE,                      // Sort by the element moveability flag

La lista completa tendrá ahora el siguiente aspecto:

//+------------------------------------------------------------------+
//| 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_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_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 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
  };
//+------------------------------------------------------------------+

Todos nuestros objetos gráficos se crean a partir del objeto de elemento gráfico. Y, a su vez, este es el heredero del objeto básico de todos los objetos gráficos de la biblioteca (que a su vez se hereda de la clase básica de la biblioteca estándar CObject). Todas las propiedades de cada clase padre se transmiten "por herencia" a sus descendientes. Por consiguiente, si necesitamos alguna propiedad común a todos los objetos gráficos, deberemos ubicarla en los objetos básicos de todo el árbol de herencia. En nuestro caso, el objeto de este tipo para los objetos gráficos de la biblioteca será el objeto de clase CGBaseObj.

Necesitamos controlar la visibilidad de los objetos gráficos en el gráfico. Para conseguirlo, no necesitamos eliminar, ocultar o apartar de ninguna manera el objeto gráfico fuera de la vista; bastará con especificar las banderas necesarias para el objeto gráfico en su propiedad OBJPROP_TIMEFRAMES, y el objeto se ocultará del gráfico o se mostrará en el mismo. Además, se mostrará por encima de todos los demás. Así, podremos no solo controlar la visibilidad del objeto en el gráfico, sino también colocar el objeto requerido por encima de todos los demás: este será el objeto actual con el que trabajará el usuario del programa.

De todo el conjunto de banderas del objeto, necesitaremos las siguientes: OBJ_NO_PERIODS - para ocultar el objeto, y OBJ_ALL_PERIODS - para mostrar el objeto en el gráfico. Para mover un objeto al primer plano, solo necesitaremos ocultar y luego mostrar el objeto secuencialmente. Y el objeto se moverá al primer plano.

Vamos a añadir las nuevas propiedades y métodos al archivo del objeto básico \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh.

En la sección protegida de la clase, declaramos la variable necesaria para guardar la visibilidad del objeto:

//+------------------------------------------------------------------+
//| Class of the base object of the library graphical objects        |
//+------------------------------------------------------------------+
class CGBaseObj : public CObject
  {
private:

protected:
   string            m_name_prefix;                      // Object name prefix
   string            m_name;                             // Object name
   long              m_chart_id;                         // Chart ID
   int               m_subwindow;                        // Subwindow index
   int               m_shift_y;                          // Subwindow Y coordinate shift
   int               m_type;                             // Object type
   bool              m_visible;                          // Object visibility
   
//--- Create (1) the object structure and (2) the object from the structure
   virtual bool      ObjectToStruct(void)                      { return true; }
   virtual void      StructToObject(void){;}

public:

En la sección pública de la clase, escribimos el método para establecer la bandera de visibilidad del objeto, así como la configuración simultánea de la propiedad en sí para el objeto y el método para retornar la visibilidad del objeto en el gráfico:

public:
//--- Return the values of class variables
   string            Name(void)                          const { return this.m_name;      }
   long              ChartID(void)                       const { return this.m_chart_id;  }
   int               SubWindow(void)                     const { return this.m_subwindow; }
//--- (1) Set and (2) return the object visibility
   void              SetVisible(const bool flag)   
                       { 
                        long value=(flag ? OBJ_ALL_PERIODS : 0);
                        if(::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,value))
                           this.m_visible=flag;
                       }
   bool              IsVisible(void)                     const { return this.m_visible;   }

//--- The virtual method returning the object type
   virtual int       Type(void)                          const { return this.m_type;      }

El método que establece la visibilidad del objeto primero comprueba el valor de la bandera y, dependiendo del valor transmitido (true o false), envía una solicitud para asignar el valor al objeto: o bien OBJ_ALL_PERIODS para mostrar el objeto en el gráfico, o bien 0 para ocultarlo. Si la solicitud se coloca correctamente en la cola de eventos del gráfico, en la variable m_visible se escribe el valor de la bandera transmitido al método; dicho valor se puede encontrar usando el método IsVisible(), que retorna el valor de esta variable.

En la lista de inicialización del constructor de la clase, inicializamos una nueva variable con el valor false:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CGBaseObj::CGBaseObj() : m_shift_y(0), m_type(0),m_visible(false), m_name_prefix(::MQLInfoString(MQL_PROGRAM_NAME)+"_")
  {
  }
//+------------------------------------------------------------------+


Mejoramos la clase de objeto de elemento gráfico en el archivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh.

En la sección protegida de la clase, declaramos la variable de bandera en la que se indicará la presencia/ausencia de la sombra proyectada por el objeto, así como la variable para guardar el color de fondo del gráfico, que necesitaremos posteriormente al dibujar las sombras:

//+------------------------------------------------------------------+
//| Class of the base object of the library graphical objects        |
//+------------------------------------------------------------------+
class CGCnvElement : public CGBaseObj
  {
protected:
   CCanvas           m_canvas;                                 // CCanvas class object
   CPause            m_pause;                                  // Pause class object
   bool              m_shadow;                                 // Shadow presence
   color             m_chart_color_bg;                         // Chart background color
//--- Return the cursor position relative to the (1) entire element and (2) the element's active area
   bool              CursorInsideElement(const int x,const int y);
   bool              CursorInsideActiveArea(const int x,const int y);
//--- Create (1) the object structure and (2) the object from the structure
   virtual bool      ObjectToStruct(void);
   virtual void      StructToObject(void);

private:

Como ya hemos eliminado dos constantes de la enumeración de propiedades enteras del objeto, ahora deberemos guardarlas en las variables de la clase.
Vamos a declararlas en la sección privada
:

   long              m_long_prop[ORDER_PROP_INTEGER_TOTAL];    // Integer properties
   double            m_double_prop[ORDER_PROP_DOUBLE_TOTAL];   // Real properties
   string            m_string_prop[ORDER_PROP_STRING_TOTAL];   // String properties
   
   ENUM_TEXT_ANCHOR  m_text_anchor;                            // Current text alignment
   color             m_color_bg;                               // Element background color
   uchar             m_opacity;                                // Element opacity
   
//--- Return the index of the array the order's (1) double and (2) string properties are located at

Ahora escribiremos las propiedades "color de fondo del elemento" y "opacidad del elemento" en estas variables.

Para conformar el aspecto externo de los objetos gráficos, necesitaremos un método que nos permita cambiar la claridad del color.
Este es uno de los componentes del modelo de color HSL:

HSL, HLS o HSI (del inglés, hue, saturation, lightness (intensity)) — es un modelo de color en el que las coordenadas de color son el tono, la saturación y la claridad. Debemos notar que HSV y HSL son dos modelos de color distintos (lightness indica la claridad, que se distingue del brillo).

Al dibujar las primitivas gráficas, necesitaremos aclarar las partes del dibujo iluminadas de forma condicional y oscurecer las partes ensombrecidas de forma condicional. En este caso, además, no deberemos tocar el color de la imagen. Para ello, aplicaremos un método que transforma el modelo de color ARGB en HSL, y también cambiaremos el brillo de los píxeles de la parte necesaria de la imagen.

Declaramos este método en la sección privada de la clase:

//--- Update the coordinates (shift the canvas)
   bool              Move(const int x,const int y,const bool redraw=false);

//--- Change the color lightness by the specified amount
   uint              ChangeColorLightness(const uint clr,const double change_value);

protected:

Como el objeto de elemento gráfico supondrá el objeto principal para crear otros objetos gráficos más complejos que serán sus herederos, entonces, teniendo en cuenta el concepto de construcción de los objetos de la biblioteca (según el cual la clase padre tiene un constructor protegido en el que se indican los parámetros del objeto heredado creado), necesitaremos crear un constructor paramétrico protegido para el objeto del elemento. A este se transmitirán los parámetros sobre el tipo de objeto heredero que se creará usando como base el elemento gráfico (hoy será un objeto de formulario).

Declaramos el nuevo constructor paramétrico protegido en la sección protegida de la clase:

protected:
//--- Protected constructor
                     CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                  const long    chart_id,
                                  const int     wnd_num,
                                  const string  name,
                                  const int     x,
                                  const int     y,
                                  const int     w,
                                  const int     h);
public:

A este transmitiremos solo los parámetros básicos para crear el objeto. Estableceremos el resto de parámetros en el objeto después de que este se cree con éxito. Realizaremos esto en la clase de colección de objetos gráficos de la biblioteca, que aún no hemos comenzado a crear, pero que comenzaremos en el futuro.

En el constructor predeterminado (no paramétrico), en su lista de inicialización, escribimos la inicialización de la bandera sobre la existencia de sombra y color de fondo del gráfico:

public:
//--- Parametric constructor
                     CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                  const int     element_id,
                                  const int     element_num,
                                  const long    chart_id,
                                  const int     wnd_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=true,
                                  const bool    activity=true,
                                  const bool    redraw=false);
//--- Default constructor/Destructor
                     CGCnvElement() : m_shadow(false),m_chart_color_bg((color)::ChartGetInteger(::ChartID(),CHART_COLOR_BACKGROUND)) {;}


En el bloque de acceso simplificado a los parámetros del objeto, añadimos los nuevos métodos para establecer las propiedades del objeto:

//+------------------------------------------------------------------+
//| Methods of simplified access to object properties                |
//+------------------------------------------------------------------+
//--- Set the (1) X, (2) Y coordinates, (3) element width, (4) height, (5) right (6) and bottom edge,
   bool              SetCoordX(const int coord_x);
   bool              SetCoordY(const int coord_y);
   bool              SetWidth(const int width);
   bool              SetHeight(const int height);
   void              SetRightEdge(void)                        { this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge());           }
   void              SetBottomEdge(void)                       { this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.BottomEdge());         }
//--- Set the shift of the (1) left, (2) top, (3) right, (4) bottom edge of the active area relative to the element,
//--- (5) all shifts of the active area edges relative to the element, (6) the element background color and (7) the element opacity
   void              SetActiveAreaLeftShift(const int value)   { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,fabs(value));       }
   void              SetActiveAreaRightShift(const int value)  { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,fabs(value));      }
   void              SetActiveAreaTopShift(const int value)    { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,fabs(value));        }
   void              SetActiveAreaBottomShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,fabs(value));     }
   void              SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift);
   void              SetColorBackground(const color colour)    { this.m_color_bg=colour;                                               }
   void              SetOpacity(const uchar value,const bool redraw=false);

//--- Set the flag of (1) object moveability, (2) activity, (3) element ID, (4) element index in the list and (5) shadow presence
   void              SetMovable(const bool flag)               { this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,flag);                     }
   void              SetActive(const bool flag)                { this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,flag);                      }
   void              SetID(const int id)                       { this.SetProperty(CANV_ELEMENT_PROP_ID,id);                            }
   void              SetNumber(const int number)               { this.SetProperty(CANV_ELEMENT_PROP_NUM,number);                       }
   void              SetShadow(const bool flag);
   
//--- Return the shift (1) of the left, (2) right, (3) top and (4) bottom edge of the element active area

Los métodos encargados de retornar el color de fondo y la opacidad ahora devuelven los valores escritos en las nuevas variables declaradas:

//--- Return (1) the background color, (2) the opacity, coordinate (3) of the right and (4) bottom element edge
   color             ColorBackground(void)               const { return this.m_color_bg;                                               }
   uchar             Opacity(void)                       const { return this.m_opacity;                                                }
   int               RightEdge(void)                     const { return this.CoordX()+this.m_canvas.Width();                           }
   int               BottomEdge(void)                    const { return this.CoordY()+this.m_canvas.Height();                          }
//--- Return the (1) X, (2) Y coordinates, (3) element width and (4) height,

Al final de la lista, añadimos el método para retornar la bandera de dibujado de la sombra proyectada por el objeto, el método para retornar el color de fondo del gráfico,
y el método que desplaza el objeto al primer plano (por encima de todos los demás objetos gráficos en el gráfico):

//--- Return (1) the element ID, (2) element index in the list, (3) flag of the form shadow presence and (4) the chart background color
   int               ID(void)                            const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ID);                   }
   int               Number(void)                        const { return (int)this.GetProperty(CANV_ELEMENT_PROP_NUM);                  }
   bool              IsShadow(void)                      const { return this.m_shadow;                                                 }
   color             ChartColorBackground(void)          const { return this.m_chart_color_bg;                                         }
//--- Set the object above all
   void              BringToTop(void)                          { CGBaseObj::SetVisible(false); CGBaseObj::SetVisible(true);            }
//+------------------------------------------------------------------+

Como podemos ver, para colocar un objeto por encima de todos los demás, bastará con ocultarlo y mostrarlo de nuevo inmediatamente con ayuda de los métodos de la clase padre que hemos analizado anteriormente.

Quitamos del constructor paramétrico las líneas que estabecen las propiedades ahora remotas:

      this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,0);                    // Active area offset from the bottom edge of the element
      this.SetProperty(CANV_ELEMENT_PROP_OPACITY,opacity);                       // Element opacity
      this.SetProperty(CANV_ELEMENT_PROP_COLOR_BG,colour);                       // Element color
      this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,movable);                       // Element moveability flag

Ahora, tanto estas propiedades como las nuevas se escribirán en las nuevas variables:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           const int      element_id,
                           const int      element_num,
                           const long     chart_id,
                           const int      wnd_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=true,
                           const bool     activity=true,
                           const bool     redraw=false) : m_shadow(false)

  {
   this.m_chart_color_bg=(color)::ChartGetInteger(chart_id,CHART_COLOR_BACKGROUND);
   this.m_name=this.m_name_prefix+name;
   this.m_chart_id=chart_id;
   this.m_subwindow=wnd_num;
   this.m_type=element_type;
   this.SetFont("Calibri",8);
   this.m_text_anchor=0;
   this.m_color_bg=colour;
   this.m_opacity=opacity;
   if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,colour,opacity,redraw))
     {
      this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Graphical resource name
      this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID());         // Chart ID
      this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,CGBaseObj::SubWindow());        // Chart subwindow index
      this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,CGBaseObj::Name());            // Element object name
      this.SetProperty(CANV_ELEMENT_PROP_TYPE,element_type);                     // Graphical element type
      this.SetProperty(CANV_ELEMENT_PROP_ID,element_id);                         // Element ID
      this.SetProperty(CANV_ELEMENT_PROP_NUM,element_num);                       // Element index in the list
      this.SetProperty(CANV_ELEMENT_PROP_COORD_X,x);                             // Element's X coordinate on the chart
      this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,y);                             // Element's Y coordinate on the chart
      this.SetProperty(CANV_ELEMENT_PROP_WIDTH,w);                               // Element width
      this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,h);                              // Element height
      this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,0);                      // Active area offset from the left edge of the element
      this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,0);                       // Active area offset from the upper edge of the element
      this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,0);                     // Active area offset from the right edge of the element
      this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,0);                    // Active area offset from the bottom edge of the element
      this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,movable);                       // Element moveability flag
      this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,activity);                       // Element activity flag
      this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge());                // Element right border
      this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.BottomEdge());              // Element bottom border
      this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.ActiveAreaLeft());     // X coordinate of the element active area
      this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.ActiveAreaTop());      // Y coordinate of the element active area
      this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.ActiveAreaRight());      // Right border of the element active area
      this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.ActiveAreaBottom());    // Bottom border of the element active area
     }
   else
     {
      ::Print(CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.m_name);
     }
  }
//+------------------------------------------------------------------+

El nuevo constructor paramétrico protegido prácticamente no se distingue del que hemos visto antes:

//+------------------------------------------------------------------+
//| Protected constructor                                            |
//+------------------------------------------------------------------+
CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           const long    chart_id,
                           const int     wnd_num,
                           const string  name,
                           const int     x,
                           const int     y,
                           const int     w,
                           const int     h) : m_shadow(false)
  {
   this.m_chart_color_bg=(color)::ChartGetInteger(chart_id,CHART_COLOR_BACKGROUND);
   this.m_name=this.m_name_prefix+name;
   this.m_chart_id=chart_id;
   this.m_subwindow=wnd_num;
   this.m_type=element_type;
   this.SetFont("Calibri",8);
   this.m_text_anchor=0;
   this.m_color_bg=NULL_COLOR;
   this.m_opacity=0;
   if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,this.m_color_bg,this.m_opacity,false))
     {
      this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Graphical resource name
      this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID());         // Chart ID
      this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,CGBaseObj::SubWindow());        // Chart subwindow index
      this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,CGBaseObj::Name());            // Element object name
      this.SetProperty(CANV_ELEMENT_PROP_TYPE,element_type);                     // Graphical element type
      this.SetProperty(CANV_ELEMENT_PROP_ID,0);                                  // Element ID
      this.SetProperty(CANV_ELEMENT_PROP_NUM,0);                                 // Element index in the list
      this.SetProperty(CANV_ELEMENT_PROP_COORD_X,x);                             // Element's X coordinate on the chart
      this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,y);                             // Element's Y coordinate on the chart
      this.SetProperty(CANV_ELEMENT_PROP_WIDTH,w);                               // Element width
      this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,h);                              // Element height
      this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,0);                      // Active area offset from the left edge of the element
      this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,0);                       // Active area offset from the upper edge of the element
      this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,0);                     // Active area offset from the right edge of the element
      this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,0);                    // Active area offset from the bottom edge of the element
      this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,false);                         // Element moveability flag
      this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,false);                          // Element activity flag
      this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge());                // Element right border
      this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.BottomEdge());              // Element bottom border
      this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.ActiveAreaLeft());     // X coordinate of the element active area
      this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.ActiveAreaTop());      // Y coordinate of the element active area
      this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.ActiveAreaRight());      // Right border of the element active area
      this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.ActiveAreaBottom());    // Bottom border of the element active area
     }
   else
     {
      ::Print(CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.m_name);
     }
  }
//+------------------------------------------------------------------+

A este se transmiten menos valores, el color de fondo del elemento se establece como blanco transparente; asimismo, se indica la transparencia total del elemento.

Eliminamos las líneas del objeto ahora innecesarias del método para crear la estructura del objeto:

   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.opacity=(uchar)this.GetProperty(CANV_ELEMENT_PROP_OPACITY);                // Element opacity
   this.m_struct_obj.color_bg=(color)this.GetProperty(CANV_ELEMENT_PROP_COLOR_BG);              // Element background color
   this.m_struct_obj.movable=(bool)this.GetProperty(CANV_ELEMENT_PROP_MOVABLE);                 // Element moveability flag

y añadimos más abajo el almacenamiento de estos parámetros desde las variables nuevas:

//+------------------------------------------------------------------+
//| 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);                       // Eleemnt 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.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
//--- 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))
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY),(string)::GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

Lo mismo hacemos en el método de creación de un objeto desde la estructura:

//+------------------------------------------------------------------+
//| 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_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
//--- 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
  }
//+------------------------------------------------------------------+

En el método que crea un objeto de elemento gráfico, ahora borraremos completamente el fondo del objeto, rellenándolo de blanco transparente:

//+------------------------------------------------------------------+
//| Create the graphical element object                              |
//+------------------------------------------------------------------+
bool CGCnvElement::Create(const long chart_id,     // Chart ID
                          const int wnd_num,       // Chart subwindow
                          const string name,       // Element name
                          const int x,             // X coordinate
                          const int y,             // Y coordinate
                          const int w,             // Width
                          const int h,             // Height
                          const color colour,      // Background color
                          const uchar opacity,     // Opacity
                          const bool redraw=false) // Flag indicating the need to redraw
                         
  {
   if(this.m_canvas.CreateBitmapLabel(chart_id,wnd_num,name,x,y,w,h,COLOR_FORMAT_ARGB_NORMALIZE))
     {
      this.Erase(NULL_COLOR);
      this.m_canvas.Update(redraw);
      this.m_shift_y=(int)::ChartGetInteger(chart_id,CHART_WINDOW_YDISTANCE,wnd_num);
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

En el método que establece la opacidad de un elemento, ahora, en lugar de escribir en la propiedad remota del objeto, añadiremos la opacidad en la variable:

//+------------------------------------------------------------------+
//| Set the element opacity                                          |
//+------------------------------------------------------------------+
void CGCnvElement::SetOpacity(const uchar value,const bool redraw=false)
  {
   this.m_canvas.TransparentLevelSet(value);
   this.m_opacity=value;
   this.m_canvas.Update(redraw);
  }
//+------------------------------------------------------------------+

Nuevo método que cambia la claridad del color en la magnitud indicada:

//+------------------------------------------------------------------+
//| Change the color lightness by the specified amount               |
//+------------------------------------------------------------------+
uint CGCnvElement::ChangeColorLightness(const uint clr,const double change_value)
  {
   if(change_value==0.0)
      return clr;
   double a=GETRGBA(clr);
   double r=GETRGBR(clr);
   double g=GETRGBG(clr);
   double b=GETRGBB(clr);
   double h=0,s=0,l=0;
   CColors::RGBtoHSL(r,g,b,h,s,l);
   double nl=l+change_value;
   if(nl>1.0) nl=1.0;
   if(nl<0.0) nl=0.0;
   CColors::HSLtoRGB(h,s,nl,r,g,b);
   return ARGB(a,r,g,b);
  }
//+------------------------------------------------------------------+

Aquí:
Comprobamos el valor transmitido al método (el valor que usamos para cambiar la luminosidad), y si se transmite cero, no será necesario cambiar nada: retornaremos el color sin cambios .
A continuación, obtenemos por separado cada uno de los componentes de color ARGB transmitidos ​al método y convertimos los componentes RGB al modelo de color HSL.
Después de la transformación, los valores de cada uno de los componentes del modelo HSL se escribirán en las variables correspondientes (necesitamos el 1-er componente).
Añadimos el valor transmitido al método (los valores de change_value pueden estar entre -1.0 y 1.0) y lo ajustamos cuando se superen los intervalos de valores permitidos .
Luego convertimos el modelo HSL de nuevo a RGB y retornamos el modelo ARGB obtenido de los nuevos componentes de color formados a partir de la conversión de HSL a RGB.


Temas de color y tipos de formulario

La biblioteca ofrecerá soporte a la creación de varios objetos: elementos gráficos, formas basadas en ellos, ventanas, etc. Cada formulario, cada ventana, cada imagen (marcos, separadores, listas desplegables, etc.) pueden tener estilos de visualización completamente distintos. Pero resultaría extraño tener en un mismo programa distintos objetos que contengan diferentes estilos de dibujado, colores y tipos de diseño.
Para facilitar la escritura de objetos idénticos en aspecto y diseño, y que además pertenezcan al mismo programa, introduciremos estilos de dibujado, tipos de objetos y esquemas de color. Esto permitirá al usuario final elegir el estilo necesario y el tema de color en los ajustes del programa, mientras que ​​el programador no tendrá que hacerse demasiadas preguntas a este respecto: los temas y estilos seleccionados reconstruirán inmediatamente todos los objetos según un mismo criterio. Bastará con realizar los cambios y adiciones necesarios al archivo de configuración gráfica, que enumerará todos los colores y parámetros necesarios de los objetos y primitivas.

Ya hemos pasado por una práctica similar, al crear la clase de mensajes de la biblioteca: tenemos una lista de índices de mensajes y una matriz de textos que se corresponden con los índices de mensajes. En casi todos los artículos nuevos, lo primero que hacemos es ingresar los nuevos datos allí.
Entonces, el archivo de ajustes gráficos se organizará de la misma manera: tendremos una enumeración con los temas de color y los estilos de objetos, y las matrices correspondientes, en las que introduciremos gradualmente los nuevos parámetros y sus valores para cada propiedad recién añadida, o para cada tema y colores recién creados.

En la carpeta raíz de la biblioteca \MQL5\Include\DoEasy\, creamos el nuevo archivo de inclusión GraphINI.mqh e introducimos el número de temas de color en él:

//+------------------------------------------------------------------+
//|                                                     GraphINI.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
//+------------------------------------------------------------------+
//| Macro substitutions                                              |
//+------------------------------------------------------------------+
#define TOTAL_COLOR_THEMES             (2)      // Number of color schemes
//+------------------------------------------------------------------+

Como ejemplo de uso de este archivo de configuración, dos temas de color serán más que suficientes. Aumentaremos su número posteriormente.

A continuación, introducimos los índices de los temas de color y los índices de los parámetros de un tema; cada tema tendrá el mismo número de parámetros:

//+------------------------------------------------------------------+
//|                                                     GraphINI.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
//+------------------------------------------------------------------+
//| Macro substitutions                                              |
//+------------------------------------------------------------------+
#define TOTAL_COLOR_THEMES             (2)      // Number of color schemes
//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| List of color scheme indices                                     |
//+------------------------------------------------------------------+
enum ENUM_COLOR_THEMES
  {
   COLOR_THEME_BLUE_STEEL,                      // Blue steel
   COLOR_THEME_LIGHT_CYAN_GRAY,                 // Light cyan gray
  };
//+------------------------------------------------------------------+
//| List of indices of color scheme parameters                       |
//+------------------------------------------------------------------+
enum ENUM_COLOR_THEME_COLORS
  {
   COLOR_THEME_COLOR_FORM_BG,                   // Form background color
   COLOR_THEME_COLOR_FORM_FRAME,                // Form frame color
   COLOR_THEME_COLOR_FORM_FRAME_OUTER,          // Form outer frame color
   COLOR_THEME_COLOR_FORM_SHADOW,               // Form shadow color
  };
#define TOTAL_COLOR_THEME_COLORS       (4)      // Number of parameters in the color theme
//+------------------------------------------------------------------+

Según los nombres de las constantes de estas enumeraciones, resultará conveniente recurrir a cada tema de color específico y su parámetro requerido.

Más abajo, escribimos una matriz bidimensional que contendrá los temas de color en la primera dimensión y los índices de los parámetros de color para representar varias propiedades de los objetos en la segunda dimensión:

//+------------------------------------------------------------------+
//|                                                     GraphINI.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
//+------------------------------------------------------------------+
//| Macro substitutions                                              |
//+------------------------------------------------------------------+
#define TOTAL_COLOR_THEMES             (2)      // Number of color schemes
//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| List of color scheme indices                                     |
//+------------------------------------------------------------------+
enum ENUM_COLOR_THEMES
  {
   COLOR_THEME_BLUE_STEEL,                      // Blue steel
   COLOR_THEME_LIGHT_CYAN_GRAY,                 // Light cyan gray
  };
//+------------------------------------------------------------------+
//| List of indices of color scheme parameters                       |
//+------------------------------------------------------------------+
enum ENUM_COLOR_THEME_COLORS
  {
   COLOR_THEME_COLOR_FORM_BG,                   // Form background color
   COLOR_THEME_COLOR_FORM_FRAME,                // Form frame color
   COLOR_THEME_COLOR_FORM_FRAME_OUTER,          // Form outer frame color
   COLOR_THEME_COLOR_FORM_SHADOW,               // Form shadow color
  };
#define TOTAL_COLOR_THEME_COLORS       (4)      // Number of parameters in the color theme
//+------------------------------------------------------------------+
//| The array containing color schemes                               |
//+------------------------------------------------------------------+
color array_color_themes[TOTAL_COLOR_THEMES][TOTAL_COLOR_THEME_COLORS]=
  {
//--- Parameters of the "Blue steel" color scheme
   {
      C'134,160,181',                           // Form background color
      C'134,160,181',                           // Form frame color
      clrDimGray,                               // Form outer frame color
      C'46,85,117',                             // Form shadow color
   },
//--- Parameters of the "Light cyan gray" color scheme
   {
      C'181,196,196',                           // Form background color
      C'181,196,196',                           // Form frame color
      clrGray,                                  // Form outer frame color
      C'130,147,153',                           // Form shadow color
   },
  };
//+------------------------------------------------------------------+

En esta matriz iremos añadiendo gradualmente nuevos colores para cada parámetro recién agregado del objeto gráfico, cuyo color debe depender del esquema de color seleccionado.

A continuación, introducimos las enumeraciones de los tipos de suavizado al dibujar las primitivas, los estilos de marco, los tipos y estilos de formulario, y luego las propias enumeraciones de los índices con las propiedades de los estilos de formulario y sus parámetros, al igual que para los temas de color:

//+------------------------------------------------------------------+
//| Smoothing types                                                  |
//+------------------------------------------------------------------+
enum ENUM_SMOOTHING_TYPE
  {
   SMOOTHING_TYPE_NONE,                         // No smoothing
   SMOOTHING_TYPE_AA,                           // Anti-aliasing
   SMOOTHING_TYPE_WU,                           // Wu
   SMOOTHING_TYPE_THICK,                        // Thick
   SMOOTHING_TYPE_DUAL,                         // Dual
  };
//+------------------------------------------------------------------+
//| Frame styles                                                     |
//+------------------------------------------------------------------+
enum ENUM_FRAME_STYLE
  {
   FRAME_STYLE_SIMPLE,                          // Simple frame
   FRAME_STYLE_FLAT,                            // Flat frame
   FRAME_STYLE_BEVEL,                           // Embossed (convex)
   FRAME_STYLE_STAMP,                           // Embossed (concave)
  };
//+------------------------------------------------------------------+
//| Form types                                                       |
//+------------------------------------------------------------------+
enum ENUM_FORM_TYPE
  {
   FORM_TYPE_SQUARE,                            // Rectangular
  };
//+------------------------------------------------------------------+
//| Form styles                                                      |
//+------------------------------------------------------------------+
enum ENUM_FORM_STYLE
  {
   FORM_STYLE_FLAT,                             // Flat form
   FORM_STYLE_BEVEL,                            // Embossed form
  };
#define TOTAL_FORM_STYLES
//+------------------------------------------------------------------+
//| List of form style parameter indices                             |
//+------------------------------------------------------------------+
enum ENUM_FORM_STYLE_PARAMS
  {
   FORM_STYLE_FRAME_WIDTH_LEFT,                 // Form frame width to the left
   FORM_STYLE_FRAME_WIDTH_RIGHT,                // Form frame width to the right
   FORM_STYLE_FRAME_WIDTH_TOP,                  // Form frame width on top
   FORM_STYLE_FRAME_WIDTH_BOTTOM,               // Form frame width below
   FORM_STYLE_FRAME_SHADOW_OPACITY,             // Shadow opacity
  };
#define TOTAL_FORM_STYLE_PARAMS        (5)      // 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
   {
      3,                                        // Form frame width to the left
      3,                                        // Form frame width to the right
      3,                                        // Form frame width on top
      3,                                        // Form frame width below
      80,                                       // Shadow opacity
   },
//--- "Embossed form" style parameters
   {
      4,                                        // Form frame width to the left
      4,                                        // Form frame width to the right
      4,                                        // Form frame width on top
      4,                                        // Form frame width below
      100,                                      // Shadow opacity
   },
  };
//+------------------------------------------------------------------+

En esta segunda matriz, cuya lógica de construcción resulta idéntica a la matriz de temas de color, también añadiremos gradualmente nuevos parámetros para construir elementos, formas, ventanas y otros objetos cuyos parámetros deben depender del estilo de construcción elegido para la apariencia de los objetos.

Para permitirnos elegir el estilo deseado de construcción de objetos y el tema de color en nuestros programas, introduciremos nuevas enumeraciones para los parámetros de entrada de los programas en el archivo \MQL5\Include\DoEasy\InpData.mqh. Al principio, añadimos el archivo GraphINI.mqh recién creado:

//+------------------------------------------------------------------+
//|                                                      InpData.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "GraphINI.mqh"
//+------------------------------------------------------------------+

Y después, en el bloque de código para la compilación en inglés y ruso, añadimos las nuevas enumeraciones de los parámetros de entrada para seleccionar el tema de color:

//+------------------------------------------------------------------+
//|                                                      InpData.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "GraphINI.mqh"
//+------------------------------------------------------------------+
//| Macro substitutions                                              |
//+------------------------------------------------------------------+
#define COMPILE_EN // Comment out the string for compilation in Russian 
//+------------------------------------------------------------------+
//| Input enumerations                                               |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| English language inputs                                          |
//+------------------------------------------------------------------+
#ifdef COMPILE_EN
//+------------------------------------------------------------------+
//| Modes of working with symbols                                    |
//+------------------------------------------------------------------+
enum ENUM_SYMBOLS_MODE
  {
   SYMBOLS_MODE_CURRENT,                              // Work only with the current Symbol
   SYMBOLS_MODE_DEFINES,                              // Work with a given list of Symbols
   SYMBOLS_MODE_MARKET_WATCH,                         // Working with Symbols from the "Market Watch" window
   SYMBOLS_MODE_ALL                                   // Work with a complete list of Symbols
  };
//+------------------------------------------------------------------+
//| Mode of working with timeframes                                  |
//+------------------------------------------------------------------+
enum ENUM_TIMEFRAMES_MODE
  {
   TIMEFRAMES_MODE_CURRENT,                           // Work only with the current timeframe
   TIMEFRAMES_MODE_LIST,                              // Work with a given list of timeframes
   TIMEFRAMES_MODE_ALL                                // Work with a complete list of timeframes
  };
//+------------------------------------------------------------------+
//| "Yes"/"No"                                                       |
//+------------------------------------------------------------------+
enum ENUM_INPUT_YES_NO
  {
   INPUT_NO  = 0,                                     // No
   INPUT_YES = 1                                      // Yes
  };
//+------------------------------------------------------------------+
//| Select color themes                                              |
//+------------------------------------------------------------------+
enum ENUM_INPUT_COLOR_THEME
  {
   INPUT_COLOR_THEME_BLUE_STEEL,                      // Blue steel
   INPUT_COLOR_THEME_LIGHT_CYAN_GRAY,                 // Light cyan gray
  };
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Russian language inputs                                          |
//+------------------------------------------------------------------+
#else  
//+------------------------------------------------------------------+
//| Modes of working with symbols                                    |
//+------------------------------------------------------------------+
enum ENUM_SYMBOLS_MODE
  {
   SYMBOLS_MODE_CURRENT,                              // Работа только с текущим символом
   SYMBOLS_MODE_DEFINES,                              // Работа с заданным списком символов
   SYMBOLS_MODE_MARKET_WATCH,                         // Работа с символами из окна "Обзор рынка"
   SYMBOLS_MODE_ALL                                   // Работа с полным списком символов
  };
//+------------------------------------------------------------------+
//| Mode of working with timeframes                                  |
//+------------------------------------------------------------------+
enum ENUM_TIMEFRAMES_MODE
  {
   TIMEFRAMES_MODE_CURRENT,                           // Работа только с текущим таймфреймом
   TIMEFRAMES_MODE_LIST,                              // Работа с заданным списком таймфреймов
   TIMEFRAMES_MODE_ALL                                // Работа с полным списком таймфреймов
  };
//+------------------------------------------------------------------+
//| "Да"/"Нет"                                                       |
//+------------------------------------------------------------------+
enum ENUM_INPUT_YES_NO
  {
   INPUT_NO  = 0,                                     // Нет
   INPUT_YES = 1                                      // Да
  };
//+------------------------------------------------------------------+
//| Select color themes                                              |
//+------------------------------------------------------------------+
enum ENUM_COLOR_THEME
  {
   COLOR_THEME_BLUE_STEEL,                            // Голубая сталь
   COLOR_THEME_LIGHT_CYAN_GRAY,                       // Светлый серо-циановый
  };
//+------------------------------------------------------------------+
#endif 
//+------------------------------------------------------------------+

Esto nos permitirá elegir el esquema de color deseado al iniciar el programa. En el futuro, añadiremos aquí y la selección de los estilos para dibujar los objetos y sus tipos de construcción.

Clase de objeto "formulario"

Un objeto de formulario es una versión más avanzada de un objeto de elemento gráfico. El formulario nos permitirá dibujar marcos "con volumen" y otras primitivas, adjuntando para ello otros elementos. Naturalmente, podemos dibujar en el elemento lo que queramos "manualmente", pero el formulario nos permitirá automatizar este proceso.

En la carpeta E:\MetaQuotes\MetaTrader 5\MQL5\Include\DoEasy\Objects\Graph\, crearemos un nuevo archivo Form.mqh de la clase CForm. La clase debe heredarse del objeto del elemento gráfico de la forma correspondiente, y también debemos incluir el archivo del objeto de elemento:

//+------------------------------------------------------------------+
//|                                                         Form.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "GCnvElement.mqh"
//+------------------------------------------------------------------+
//| Form object class                                                |
//+------------------------------------------------------------------+
class CForm : public CGCnvElement
  {
  }

En la sección privada de la clase, declararemos los objetos necesarios para la trabajar, las variables y los métodos auxiliares de la clase:

//+------------------------------------------------------------------+
//| Form object class                                                |
//+------------------------------------------------------------------+
class CForm : public CGCnvElement
  {
private:
   CArrayObj         m_list_elements;                          // List of attached elements
   CGCnvElement     *m_shadow_obj;                             // Pointer to the shadow object
   color             m_color_frame;                            // Form frame color
   color             m_color_shadow;                           // Form shadow color
   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);

//--- 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);
   
public:

La sección pública de la clase contiene los métodos estándar para los objetos de la biblioteca, así como varios constructores: tanto por defecto como otros que permiten crear un objeto de formulario en el gráfico especificado y la subventana especificada, en la subventana especificada del gráfico actual y en el gráfico actual en la ventana principal:

public:
   //--- Constructors
                     CForm(const long chart_id,
                           const int subwindow,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CForm(const int subwindow,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CForm(const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CForm() { this.Initialize(); }
//--- Destructor
                    ~CForm();
                           
//--- Supported form properties (1) integer and (2) string ones
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property)  { return true; }
   
//--- Return (1) the list of attached objects and (2) the shadow object
   CArrayObj        *GetList(void)                                            { return &this.m_list_elements;  }
   CGCnvElement     *GetShadowObj(void)                                       { return this.m_shadow_obj;      }

A continuación, declaramos los métodos para trabajar con el objeto de formulario:

//--- Set the form (1) color scheme and (2) style
   virtual void      SetColorTheme(const ENUM_COLOR_THEMES theme,const uchar opacity);
   virtual void      SetFormStyle(const ENUM_FORM_STYLE style,
                                  const ENUM_COLOR_THEMES theme,
                                  const uchar opacity,
                                  const bool shadow=false,
                                  const bool redraw=false);
   
//--- Create a new attached element
   bool              CreateNewElement(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              CreateShadow(const uchar opacity);
//--- Draw an object shadow
   void              DrawShadow(const uchar opacity);

//--- Draw the form frame
   void              DrawFormFrame(const int wd_top,                          // Frame upper segment width
                                   const int wd_bottom,                       // Frame lower segment width
                                   const int wd_left,                         // Frame left segment width
                                   const int wd_right,                        // Frame right segment width
                                   const color colour,                        // Frame color
                                   const uchar opacity,                       // Frame opacity
                                   const ENUM_FRAME_STYLE style);             // Frame style
//--- Draw a simple frame
   void              DrawFrameSimple(const int x,                             // X coordinate relative to the form
                                     const int y,                             // Y coordinate relative to the form
                                     const int width,                         // Frame width
                                     const int height,                        // Frame height
                                     const int wd_top,                        // Frame upper segment width
                                     const int wd_bottom,                     // Frame lower segment width
                                     const int wd_left,                       // Frame left segment width
                                     const int wd_right,                      // Frame right segment width
                                     const color colour,                      // Frame color
                                     const uchar opacity);                    // Frame opacity
//--- Draw a flat frame
   void              DrawFrameFlat(const int x,                               // X coordinate relative to the form
                                   const int y,                               // Y coordinate relative to the form
                                   const int width,                           // Frame width
                                   const int height,                          // Frame height
                                   const int wd_top,                          // Frame upper segment width
                                   const int wd_bottom,                       // Frame lower segment width
                                   const int wd_left,                         // Frame left segment width
                                   const int wd_right,                        // Frame right segment width
                                   const color colour,                        // Frame color
                                   const uchar opacity);                      // Frame opacity

//--- Draw an embossed (convex) frame
   void              DrawFrameBevel(const int x,                              // X coordinate relative to the form
                                    const int y,                              // Y coordinate relative to the form
                                    const int width,                          // Frame width
                                    const int height,                         // Frame height
                                    const int wd_top,                         // Frame upper segment width
                                    const int wd_bottom,                      // Frame lower segment width
                                    const int wd_left,                        // Frame left segment width
                                    const int wd_right,                       // Frame right segment width
                                    const color colour,                       // Frame color
                                    const uchar opacity);                     // Frame opacity

//--- Draw an embossed (concave) frame
   void              DrawFrameStamp(const int x,                              // X coordinate relative to the form
                                    const int y,                              // Y coordinate relative to the form
                                    const int width,                          // Frame width
                                    const int height,                         // Frame height
                                    const int wd_top,                         // Frame upper segment width
                                    const int wd_bottom,                      // Frame lower segment width
                                    const int wd_left,                        // Frame left segment width
                                    const int wd_right,                       // Frame right segment width
                                    const color colour,                       // Frame color
                                    const uchar opacity);                     // Frame opacity

//--- Draw a simple field
   void              DrawFieldFlat(const int x,                               // X coordinate relative to the form
                                   const int y,                               // Y coordinate relative to the form
                                   const int width,                           // Field width
                                   const int height,                          // Field height
                                   const color colour,                        // Field color
                                   const uchar opacity);                      // Field opacity

//--- Draw an embossed (convex) field
   void              DrawFieldBevel(const int x,                              // X coordinate relative to the form
                                    const int y,                              // Y coordinate relative to the form
                                    const int width,                          // Field width
                                    const int height,                         // Field height
                                    const color colour,                       // Field color
                                    const uchar opacity);                     // Field opacity

//--- Draw an embossed (concave) field
   void              DrawFieldStamp(const int x,                              // X coordinate relative to the form
                                    const int y,                              // Y coordinate relative to the form
                                    const int width,                          // Field width
                                    const int height,                         // Field height
                                    const color colour,                       // Field color
                                    const uchar opacity);                     // Field opacity
   
//+------------------------------------------------------------------+
//| Methods of simplified access to object properties                |
//+------------------------------------------------------------------+
//--- (1) Set and (2) get the form frame color
   void              SetColorFrame(const color colour)                        { this.m_color_frame=colour;  }
   color             ColorFrame(void)                                   const { return this.m_color_frame;  }
//--- (1) Set and (2) return the form shadow color
   void              SetColorShadow(const color colour)                       { this.m_color_shadow=colour; }
   color             ColorShadow(void)                                  const { return this.m_color_shadow; }

  };
//+------------------------------------------------------------------+

Vamos a analizar con más detalle los métodos declarados.

Constructor con indicación del identificador del gráfico y la subventana:

//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CForm::CForm(const long chart_id,
             const int subwindow,
             const string name,
             const int x,
             const int y,
             const int w,
             const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,chart_id,subwindow,name,x,y,w,h)
  {
   this.Initialize();
  }
//+------------------------------------------------------------------+

Transmitimos al constructor el identificador del gráfico, el número de la subventana en la que se debe crear el objeto de formulario, su nombre, las coordenadas de la esquina superior izquierda del formulario y sus dimensiones. En la lista de inicialización, llamamos al constructor de la clase del objeto de elemento, indicando el tipo de objeto "Formulario". En el cuerpo de la clase, llamamos al método de inicialización.

Constructor en el gráfico actual con indicación de la subventana:

//+------------------------------------------------------------------+
//| Current chart constructor specifying the subwindow               |
//+------------------------------------------------------------------+
CForm::CForm(const int subwindow,
             const string name,
             const int x,
             const int y,
             const int w,
             const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,::ChartID(),subwindow,name,x,y,w,h)
  {
   this.Initialize();
  }
//+------------------------------------------------------------------+

Transmitimos al constructor el número de la subventana en la que deseamos crear el objeto de formulario (el gráfico es el actual), el nombre del objeto de formulario, las coordenadas de la esquina superior izquierda del formulario y sus dimensiones. En la lista de inicialización, llamamos al constructor de la clase del objeto de elemento, indicando el tipo de objeto "Formulario" y el identificador del gráfico actual. En el cuerpo de la clase, llamamos al método de inicialización.

Constructor en el gráfico actual en la ventana principal del gráfico:

//+------------------------------------------------------------------+
//| Constructor on the current chart in the main chart window        |
//+------------------------------------------------------------------+
CForm::CForm(const string name,
             const int x,
             const int y,
             const int w,
             const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,::ChartID(),0,name,x,y,w,h)
  {
   this.Initialize();
  }
//+------------------------------------------------------------------+

Transmitimos al constructor el nombre del objeto de formulario, las coordenadas de la esquina superior izquierda del formulario y sus dimensiones. En la lista de inicialización, llamamos al constructor de la clase del objeto de elemento, indicando el tipo de objeto "Formulario", el identificador del gráfico actual y el número de la ventana principal (0). En el cuerpo de la clase, llamamos al método de inicialización.

En el destructor de la clase, verificamos la validez del puntero al objeto de sombra y borramos el objeto, si existe:

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CForm::~CForm()
  {
   if(m_shadow_obj!=NULL)
      delete m_shadow_obj;
  }
//+------------------------------------------------------------------+

Método de inicialización de variables:

//+------------------------------------------------------------------+
//| Initialize the variables                                         |
//+------------------------------------------------------------------+
void CForm::Initialize(void)
  {
   this.m_list_elements.Clear();
   this.m_list_elements.Sort();
   this.m_shadow_obj=NULL;
   this.m_shadow=false;
   this.m_frame_width_right=2;
   this.m_frame_width_left=2;
   this.m_frame_width_top=2;
   this.m_frame_width_bottom=2;
  }
//+------------------------------------------------------------------+

Aquí, borramos la lista de elementos adjuntos al formulario, le asignamos la bandera de lista clasificada e indicamos los valores por defecto para el puntero al objeto de sombra (NULL), el indicador para dibujar la sombra (false) y el tamaño del marco del formulario (2 píxeles en cada lado).

Método privado que crea un nuevo objeto gráfico:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CForm::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                      const int obj_num,
                                      const string obj_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)
  {
   int pos=::StringLen(::MQLInfoString(MQL_PROGRAM_NAME));
   string pref=::StringSubstr(NameObj(),pos+1);
   string name=pref+"_"+obj_name;
   CGCnvElement *element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity);
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name);
   return element;
  }
//+------------------------------------------------------------------+

Transmitimos al método todos los parámetros necesarios para crear un nuevo objeto: el tipo, el número en la lista de objetos adjuntos, el nombre, las coordenadas y las dimensiones, el color, la opacidad y las banderas de movilidad y actividad del objeto.

En el cuerpo de la clase, extraemos la terminación del nombre del objeto (el nombre consta del nombre del programa y el nombre del objeto al crear el mismo). Necesitamos extraer el nombre del objeto al crearse este y añadir el nombre transmitido al método.
Así, por ejemplo, de este nombre "Nombre_del_programa_Formulario01", extraemos la sublínea "Formulario01" y añadimos a esta línea el nombre transmitido al método. Si creamos un objeto de sombra y transmitimos el nombre "Sombra", entonces el nombre del objeto será "Formulario01_ Sombra", y el nombre final del objeto creado será así: "Nombre_del_programa_Formulario01_Sombra".

A continuación, creamos un nuevo objeto indicando su tipo y los parámetros del gráfico sobre el que crearemos el objeto de formulario actual, el nombre creado para él y el resto de parámetros transmitidos ​​al método. Retorna al método el puntero al objeto creado, o NULL en caso de fallo.

Método que crea un nuevo elemento adjunto:

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CForm::CreateNewElement(const int element_num,
                             const string element_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)
  {
   CGCnvElement *obj=this.CreateNewGObject(GRAPH_ELEMENT_TYPE_ELEMENT,element_num,element_name,x,y,w,h,colour,opacity,movable,activity);
   if(obj==NULL)
      return false;
   this.m_list_elements.Sort(SORT_BY_CANV_ELEMENT_NAME_OBJ);
   int index=this.m_list_elements.Search(obj);
   if(index>WRONG_VALUE)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_OBJ_ALREADY_IN_LIST),": ",obj.NameObj());
      delete obj;
      return false;
     }
   if(!this.m_list_elements.Add(obj))
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST),": ",obj.NameObj());
      delete obj;
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

El método crea un nuevo objeto de elemento gráfico usando el método anterior y lo añade a la lista de objetos adjuntos al objeto de formulario. Si no hemos podido crear un nuevo objeto, o bien no hemos podido añadirlo a la lista de objetos adjuntos, se mostrará un mensaje de error y se retornará false. Tras crear con éxito un nuevo elemento y añadirlo a la lista, se retornará true.

Método que crea el objeto de sombra:

//+------------------------------------------------------------------+
//| Create the shadow object                                         |
//+------------------------------------------------------------------+
void CForm::CreateShadow(const uchar opacity)
  {
//--- If the shadow flag is disabled, exit
   if(!this.m_shadow)
      return;
//--- Calculate the shadow object coordinates according to the offset from the top and left
   int x=this.CoordX()-OUTER_AREA_SIZE;
   int y=this.CoordY()-OUTER_AREA_SIZE;
//--- Calculate the width and height in accordance with the top, bottom, left and right offsets
   int w=this.Width()+OUTER_AREA_SIZE*2;
   int h=this.Height()+OUTER_AREA_SIZE*2;
//--- Create a new element object and set the pointer to it in the variable
   this.m_shadow_obj=this.CreateNewGObject(GRAPH_ELEMENT_TYPE_ELEMENT,-1,"Shadow",x,y,w,h,this.m_chart_color_bg,opacity,Movable(),false);
   if(this.m_shadow_obj==NULL)
      return;
//--- Move the form object to the foreground
   this.BringToTop();
  }
//+------------------------------------------------------------------+

Comentamos la lógica del método en su listado. En resumen: como el objeto de elemento sobre el que deseamos dibujar la sombra debe ser más grande que el objeto de formulario para el que se ha creado (necesitamos espacio libre por arriba, por abajo, a la izquierda y a la derecha para dibujar la sombra), el tamaño del nuevo objeto se calculará según los valores de la macrosustitución OUTER_AREA_SIZE.
Después de crear correctamente el objeto, este se elevará automáticamente por encima del objeto de formulario en el que se ha creado. Por consiguiente, deberemos desplazar forzosamente el objeto de formulario al primer plano, lo cual haremos al final del método.

Método para dibujar la sombra:

//+------------------------------------------------------------------+
//| Draw the shadow                                                  |
//+------------------------------------------------------------------+
void CForm::DrawShadow(const uchar opacity)
  {
//--- If the shadow flag is disabled, exit
   if(!this.m_shadow)
      return;
//--- Calculate rectangle coordinates relative to the shadow object borders
   int x=OUTER_AREA_SIZE+1;
   int y=OUTER_AREA_SIZE+1;
//--- Draw a filled rectangle starting from the calculated coordinates and having the size of the current form object
   m_shadow_obj.DrawRectangleFill(x,y,x+Width(),y+Height(),this.ColorShadow(),opacity);
//--- Update the shadow object for displaying changes
   m_shadow_obj.Update();
   return;
  }
//+------------------------------------------------------------------+

Comentamos la lógica del método en su código. Este método supone actualmente solo una plantilla para crear un método completo para dibujar las sombras de los objetos. Actualmente, el método solo dibuja un rectángulo sencillo desplazado hacia la parte inferior derecha "por debajo" del objeto actual en el objeto de elemento creado para dibujar sombras.

Método que establece el esquema de color:

//+------------------------------------------------------------------+
//| Set a color scheme                                               |
//+------------------------------------------------------------------+
void CForm::SetColorTheme(const ENUM_COLOR_THEMES theme,const uchar opacity)
  {
   this.SetOpacity(opacity);
   this.SetColorBackground(array_color_themes[theme][COLOR_THEME_COLOR_FORM_BG]);
   this.SetColorFrame(array_color_themes[theme][COLOR_THEME_COLOR_FORM_FRAME]);
   this.SetColorShadow(array_color_themes[theme][COLOR_THEME_COLOR_FORM_SHADOW]);
  }
//+------------------------------------------------------------------+

El método se usa para establecer el tema de color indicado en el objeto. Transmitimos al método el tema necesario y el valor de opacidad del objeto de formulario. A continuación, establecemos el valor de opacidad del formulario, el color de fondo del formulario, el color del marco del formulario y el color de la sombra del formulario partiendo de los valores registrados en la matriz de temas de color que creamos anteriormente.

Método para configurar el estilo del formulario:

//+------------------------------------------------------------------+
//| Set the form style                                               |
//+------------------------------------------------------------------+
void CForm::SetFormStyle(const ENUM_FORM_STYLE style,
                         const ENUM_COLOR_THEMES theme,
                         const uchar opacity,
                         const bool shadow=false,
                         const bool redraw=false)
  {
//--- Set opacity parameters and the size of the form frame side
   this.m_shadow=shadow;
   this.m_frame_width_top=array_form_style[style][FORM_STYLE_FRAME_WIDTH_TOP];
   this.m_frame_width_bottom=array_form_style[style][FORM_STYLE_FRAME_WIDTH_BOTTOM];
   this.m_frame_width_left=array_form_style[style][FORM_STYLE_FRAME_WIDTH_LEFT];
   this.m_frame_width_right=array_form_style[style][FORM_STYLE_FRAME_WIDTH_RIGHT];
//--- Set a color scheme
   this.SetColorTheme(theme,opacity);
//--- Create the shadow object and draw a simple distinct shadow
   this.CreateShadow((uchar)array_form_style[style][FORM_STYLE_FRAME_SHADOW_OPACITY]);
   this.DrawShadow((uchar)array_form_style[style][FORM_STYLE_FRAME_SHADOW_OPACITY]);
//--- Fill in the form background with color and opacity
   this.Erase(this.ColorBackground(),this.Opacity());
//--- Depending on the selected form style, draw the corresponding form frame and the outer bounding frame
   switch(style)
     {
      case FORM_STYLE_BEVEL   :
        this.DrawFormFrame(this.m_frame_width_top,this.m_frame_width_bottom,this.m_frame_width_left,this.m_frame_width_right,this.ColorFrame(),this.Opacity(),FRAME_STYLE_BEVEL);
        this.DrawRectangle(0,0,Width()-1,Height()-1,array_color_themes[theme][COLOR_THEME_COLOR_FORM_FRAME_OUTER],this.Opacity());
        break;
      //---FORM_STYLE_FLAT
      default:
        this.DrawFormFrame(this.m_frame_width_top,this.m_frame_width_bottom,this.m_frame_width_left,this.m_frame_width_right,this.ColorFrame(),this.Opacity(),FRAME_STYLE_FLAT);
        this.DrawRectangle(0,0,Width()-1,Height()-1,array_color_themes[theme][COLOR_THEME_COLOR_FORM_FRAME_OUTER],this.Opacity());
        break;
     }
  }
//+------------------------------------------------------------------+

Comentamos la lógica del método en su listado.

En esencia, este método supone un ejemplo de cómo crear un objeto de formulario con los parámetros necesarios.

Método para dibujar el marco del formulario:

//+------------------------------------------------------------------+
//| Draw the form frame                                              |
//+------------------------------------------------------------------+
void CForm::DrawFormFrame(const int wd_top,              // Frame upper segment width
                          const int wd_bottom,           // Frame lower segment width
                          const int wd_left,             // Frame left segment width
                          const int wd_right,            // Frame right segment width
                          const color colour,            // Frame color
                          const uchar opacity,           // Frame opacity
                          const ENUM_FRAME_STYLE style)  // Frame style
  {
//--- Depending on the passed frame style
   switch(style)
     {
      //--- draw a dimensional (convex) frame
      case FRAME_STYLE_BEVEL :
         DrawFrameBevel(0,0,Width(),Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity);
        break;
      //--- draw a dimensional (concave) frame
      case FRAME_STYLE_STAMP :
         DrawFrameStamp(0,0,Width(),Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity);
        break;
      //--- draw a flat frame
      case FRAME_STYLE_FLAT :
         DrawFrameFlat(0,0,Width(),Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity);
        break;
      //--- draw a simple frame
      default:
        //---FRAME_STYLE_SIMPLE
         DrawFrameSimple(0,0,Width(),Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity);
        break;
     }
  }
//+------------------------------------------------------------------+

Dependiendo del estilo del marco, dibujamos el marco correspondiente del formulario.

Método que dibuja un marco simple:

//+------------------------------------------------------------------+
//| Draw a simple frame                                              |
//+------------------------------------------------------------------+
void CForm::DrawFrameSimple(const int x,           // X coordinate relative to the form
                            const int y,           // Y coordinate relative to the form
                            const int width,       // Frame width
                            const int height,      // Frame height
                            const int wd_top,      // Frame upper segment width
                            const int wd_bottom,   // Frame lower segment width
                            const int wd_left,     // Frame left segment width
                            const int wd_right,    // Frame right segment width
                            const color colour,    // Frame color
                            const uchar opacity)   // Frame opacity
  {
//--- Set rectangle coordinates
   int x1=x, y1=y;
   int x2=x1+width-1;
   int y2=y1+height-1;
//--- Draw the first rectangle
   CGCnvElement::DrawRectangle(x1,y1,x2,y2,colour,opacity);
//--- If the frame width exceeds 1 on all sides, draw the second rectangle
   if(wd_left>1 || wd_right>1 || wd_top>1 || wd_bottom>1)
      CGCnvElement::DrawRectangle(x1+wd_left-1,y1+wd_top-1,x2-wd_right+1,y2-wd_bottom+1,colour,opacity);
//--- Search for "voids" between the lines of two rectangles and fill them with color
   if(wd_left>2 && wd_right>2 && wd_top>2 && wd_bottom>2)
      this.Fill(x1+1,y1+1,colour,opacity);
   else if(wd_left>2 && wd_top>2)
      this.Fill(x1+1,y1+1,colour,opacity);
   else if(wd_right>2 && wd_bottom>2)
      this.Fill(x2-1,y2-1,colour,opacity);
   else if(wd_left<3 && wd_right<3)
     {
      if(wd_top>2)
         this.Fill(x1+1,y1+1,colour,opacity);
      if(wd_bottom>2)
         this.Fill(x1+1,y2-1,colour,opacity);
     }
   else if(wd_top<3 && wd_bottom<3)
     {
      if(wd_left>2)
         this.Fill(x1+1,y1+1,colour,opacity);
      if(wd_right>2)
         this.Fill(x2-1,y1+1,colour,opacity);
     }
  }
//+------------------------------------------------------------------+

Comentamos la lógica del método en el código. Resumiendo: dibuja dos rectángulos, uno dentro del otro. Si existen vacíos entre los rectángulos dibujados en los lugares que forman los lados del futuro marco (los lados de los rectángulos no se tocan), los rellenaremos con el mismo color en el que están dibujados los rectángulos.

Método para dibujar un marco plano:

//+------------------------------------------------------------------+
//| Draw the flat frame                                              |
//+------------------------------------------------------------------+
void CForm::DrawFrameFlat(const int x,
                          const int y,
                          const int width,
                          const int height,
                          const int wd_top,
                          const int wd_bottom,
                          const int wd_left,
                          const int wd_right,
                          const color colour,
                          const uchar opacity)
  {
//--- Draw a simple frame
   this.DrawFrameSimple(x,y,width,height,wd_top,wd_bottom,wd_left,wd_right,colour,opacity);
//--- If the width of the frame top and bottom exceeds one pixel
   if(wd_top>1 && wd_bottom>1)
     {
      //--- Darken the horizontal sides of the frame
      for(int i=0;i<width;i++)
        {
         this.m_canvas.PixelSet(x+i,y,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y),-0.05));
         this.m_canvas.PixelSet(x+i,y+height-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-1),-0.07));
        }
     }
//--- If the width of the frame left and right sides exceeds one pixel
   if(wd_left>1 && wd_right>1)
     {
      //--- Darken the vertical sides of the frame
      for(int i=1;i<height-1;i++)
        {
         this.m_canvas.PixelSet(x,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x,y+1),-0.01));
         this.m_canvas.PixelSet(x+width-1,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+width-1,y+1),-0.02));
        }
     }
  }
//+------------------------------------------------------------------+

Método para dibujar un marco con relieve (convexo):

//+------------------------------------------------------------------+
//| Draw an embossed (convex) frame                                  |
//+------------------------------------------------------------------+
void CForm::DrawFrameBevel(const int x,
                           const int y,
                           const int width,
                           const int height,
                           const int wd_top,
                           const int wd_bottom,
                           const int wd_left,
                           const int wd_right,
                           const color colour,
                           const uchar opacity)
  {
//--- Draw a simple frame
   this.DrawFrameSimple(x,y,width,height,wd_top,wd_bottom,wd_left,wd_right,colour,opacity);
//--- If the width of the frame top and bottom exceeds one pixel
   if(wd_top>1 && wd_bottom>1)
     {
      //--- Lighten and darken the required sides of the frame edges
      for(int i=0;i<width;i++)
        {
         this.m_canvas.PixelSet(x+i,y,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y),0.25));
         this.m_canvas.PixelSet(x+i,y+height-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-1),-0.2));
        }
      for(int i=wd_left;i<width-wd_right;i++)
        {
         this.m_canvas.PixelSet(x+i,y+wd_top-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+wd_top-1),-0.2));
         this.m_canvas.PixelSet(x+i,y+height-wd_bottom,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-wd_bottom),0.1));
        }
     }
//--- If the width of the frame left and right sides exceeds one pixel
   if(wd_left>1 && wd_right>1)
     {
      //--- Lighten and darken the required sides of the frame edges
      for(int i=1;i<height-1;i++)
        {
         this.m_canvas.PixelSet(x,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x,y+i),0.1));
         this.m_canvas.PixelSet(x+width-1,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+width-1,y+i),-0.1));
        }
      for(int i=wd_top;i<height-wd_bottom;i++)
        {
         this.m_canvas.PixelSet(x+wd_left-1,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+wd_left-1,y+i),-0.1));
         this.m_canvas.PixelSet(x+width-wd_right,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+width-wd_right,y+i),0.1));
        }
     }
  }
//+------------------------------------------------------------------+

Método para dibujar un marco con relieve (cóncavo):

//+------------------------------------------------------------------+
//| Draw an embossed (concave) frame                                 |
//+------------------------------------------------------------------+
void CForm::DrawFrameStamp(const int x,
                           const int y,
                           const int width,
                           const int height,
                           const int wd_top,
                           const int wd_bottom,
                           const int wd_left,
                           const int wd_right,
                           const color colour,
                           const uchar opacity)
  {
//--- Draw a simple frame
   this.DrawFrameSimple(x,y,width,height,wd_top,wd_bottom,wd_left,wd_right,colour,opacity);
//--- If the width of the frame top and bottom exceeds one pixel
   if(wd_top>1 && wd_bottom>1)
     {
      //--- Lighten and darken the required sides of the frame edges
      for(int i=0;i<width;i++)
        {
         this.m_canvas.PixelSet(x+i,y,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y),-0.25));
         this.m_canvas.PixelSet(x+i,y+height-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-1),0.2));
        }
      for(int i=wd_left;i<width-wd_right;i++)
        {
         this.m_canvas.PixelSet(x+i,y+wd_top-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+wd_top-1),0.2));
         this.m_canvas.PixelSet(x+i,y+height-wd_bottom,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-wd_bottom),-0.25));
        }
     }
//--- If the width of the frame left and right sides exceeds one pixel
   if(wd_left>1 && wd_right>1)
     {
      //--- Lighten and darken the required sides of the frame edges
      for(int i=1;i<height-1;i++)
        {
         this.m_canvas.PixelSet(x,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x,y+i),-0.1));
         this.m_canvas.PixelSet(x+width-1,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+width-1,y+i),0.2));
        }
      for(int i=wd_top;i<height-wd_bottom;i++)
        {
         this.m_canvas.PixelSet(x+wd_left-1,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+wd_left-1,y+i),0.2));
         this.m_canvas.PixelSet(x+width-wd_right,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+width-wd_right,y+i),-0.2));
        }
     }
  }
//+------------------------------------------------------------------+

Métodos que dibujan los campos (simple y con relieve):

//+------------------------------------------------------------------+
//| Draw a simple field                                              |
//+------------------------------------------------------------------+
void CForm::DrawFieldFlat(const int x,const int y,const int width,const int height,const color colour,const uchar opacity)
  {
//--- Draw a filled rectangle
   CGCnvElement::DrawRectangleFill(x,y,x+width-1,y+height-1,colour,opacity);
//--- Darken all its edges
   for(int i=0;i<width;i++)
     {
      this.m_canvas.PixelSet(x+i,y,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y),-0.05));
      this.m_canvas.PixelSet(x+i,y+height-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-1),-0.05));
     }
   for(int i=1;i<height-1;i++)
     {
      this.m_canvas.PixelSet(x,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x,y+1),-0.05));
      this.m_canvas.PixelSet(x+width-1,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+width-1,y+1),-0.05));
     }
  }
//+------------------------------------------------------------------+
//| Draw an embossed (convex) field                                  |
//+------------------------------------------------------------------+
void CForm::DrawFieldBevel(const int x,const int y,const int width,const int height,const color colour,const uchar opacity)
  {
//--- Draw a filled rectangle
   CGCnvElement::DrawRectangleFill(x,y,x+width-1,y+height-1,colour,opacity);
//--- Lighten its top and left and darken its bottom and right
   for(int i=0;i<width;i++)
     {
      this.m_canvas.PixelSet(x+i,y,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y),0.1));
      this.m_canvas.PixelSet(x+i,y+height-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-1),-0.1));
     }
   for(int i=1;i<height-1;i++)
     {
      this.m_canvas.PixelSet(x,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x,y+1),0.05));
      this.m_canvas.PixelSet(x+width-1,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+width-1,y+1),-0.05));
     }
  }
//+------------------------------------------------------------------+
//| Draw an embossed (concave) field                                 |
//+------------------------------------------------------------------+
void CForm::DrawFieldStamp(const int x,const int y,const int width,const int height,const color colour,const uchar opacity)
  {
//--- Draw a filled rectangle
   CGCnvElement::DrawRectangleFill(x,y,x+width-1,y+height-1,colour,opacity);
//--- Darken its top and left and lighten its bottom and right
   for(int i=0;i<width;i++)
     {
      this.m_canvas.PixelSet(x+i,y,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y),-0.1));
      this.m_canvas.PixelSet(x+i,y+height-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-1),0.1));
     }
   for(int i=1;i<height-1;i++)
     {
      this.m_canvas.PixelSet(x,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x,y+1),-0.05));
      this.m_canvas.PixelSet(x+width-1,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+width-1,y+1),0.05));
     }
  }
//+------------------------------------------------------------------+

La lógica de los métodos anteriormente mencionados es prácticamente idéntica, y la hemos comentado en el código de los métodos. Esperamos que no surjan dudas sobre ellos. En cualquier caso, el lector podrá plantear cualquier duda en los comentarios al artículo.

Con esto, podemos dar por finalizada la creación del objeto de formulario.


Simulación

Hoy no habrá pruebas "espectaculares" :) Simplemente crearemos dos formularios diferentes con estilos de construcción y temas de color distintos. Una vez creados los formularios, les añadiremos los campos. El formulario superior tendrá un campo cóncavo dimensional, mientras que el segundo formulario tendrá un campo cóncavo dimensional semitransparente.

Para la simulación, vamos a tomar el asesor del artículo anterior y guardarlo en la nueva carpeta \MQL5\Experts\TestDoEasy\Part76\ con el nuevo nombre TestDoEasyPart76.mq5.

Añadimos al asesor el archivo del objeto de formulario de la biblioteca y renombramos la lista de objetos de elemento como la lista de objetos de formulario:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart76.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <Arrays\ArrayObj.mqh>
#include <DoEasy\Services\Select.mqh>
#include <DoEasy\Objects\Graph\Form.mqh>
//--- defines
#define        FORMS_TOTAL (2)   // Number of created forms
//--- input parameters
sinput   bool  InpMovable  = true;  // Movable flag
//--- global variables
CArrayObj      list_forms;
//+------------------------------------------------------------------+

En el manejador OnInit(), creamos dos formularios y dibujamos campos cóncavos en ellos:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set the permissions to send cursor movement and mouse scroll events
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
//--- Set EA global variables

//--- Create the specified number of form objects
   list_forms.Clear();
   int total=FORMS_TOTAL;
   for(int i=0;i<total;i++)
     {
      //--- When creating an object, pass all the required parameters to it
      CForm *form=new CForm("Form_0"+(string)(i+1),300,40+(i*80),100,70);
      if(form==NULL)
         continue;
      //--- Set activity and moveability flags for the form
      form.SetActive(true);
      form.SetMovable(false);
      //--- Set the form ID equal to the loop index 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 full opacity for the top form and the partial opacity for the bottom one
      uchar opacity=(i==0 ? 255 : 250);
      //--- Set the form style and its color theme depending on the loop index
      ENUM_FORM_STYLE style=(ENUM_FORM_STYLE)i;
      ENUM_COLOR_THEMES theme=(ENUM_COLOR_THEMES)i;
      //--- Set the form style and theme
      form.SetFormStyle(style,theme,opacity,true);
      //--- If this is the first (top) form
      if(i==0)
        {
         //--- Draw a concave field slightly shifted from the center of the form downwards
         form.DrawFieldStamp(3,10,form.Width()-6,form.Height()-13,form.ColorBackground(),form.Opacity());
         form.Update(true);
        }
      //--- If this is the second (bottom) form
      if(i==1)
        {
         //--- Draw a concave semi-transparent "tainted glass" field in the center
         form.DrawFieldStamp(10,10,form.Width()-20,form.Height()-20,clrWheat,200);
         form.Update(true);
        }
      //--- Add objects to the list
      if(!list_forms.Add(form))
        {
         delete form;
         continue;
        }
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

A continuación, eliminamos del manejador OnChartEvent() el procesamiento completo de los clics del ratón sobre los objetos:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- If clicking on an object
   if(id==CHARTEVENT_OBJECT_CLICK)
     {

     }
  }
//+------------------------------------------------------------------+

Compilamos el asesor y lo iniciamos en el gráfico:


Como podemos ver, con solo indicar el estilo necesario y el tema de color, hemos creado dos formularios diferentes, con colores de componentes y estilo de dibujado distintos.

¿Qué es lo próximo?

En el próximo artículo, continuaremos desarrollando el objeto de formulario y completando su funcionalidad.

Más abajo se adjuntan todos los archivos de la versión actual de la biblioteca y el archivo del asesor de prueba para MQL5. Puede 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 73): Objeto de formulario del elemento gráfico
Gráficos en la biblioteca DoEasy (Parte 74): Elemento gráfico básico sobre la clase CCanvas
Gráficos en la biblioteca DoEasy (Parte 75): Métodos de trabajo con primitivas y texto en el elemento gráfico básico

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

Archivos adjuntos |
MQL5.zip (3980.07 KB)
Gráficos en la biblioteca DoEasy (Parte 77): Clase de objeto Sombra Gráficos en la biblioteca DoEasy (Parte 77): Clase de objeto Sombra
En el presente artículo, vamos a crear la clase para el objeto de sombra, que es heredero del objeto de elemento gráfico. Asimismo, añadiremos la posibilidad de rellenar el fondo del objeto con relleno en gradiente.
Análisis de clústeres (Parte I): Usando la inclinación de las líneas de indicador Análisis de clústeres (Parte I): Usando la inclinación de las líneas de indicador
El análisis de clústeres es uno de los elementos más importantes de la inteligencia artificial. En este artículo, trataremos de aplicar el análisis de inclinación del clúster del indicador para obtener valores de umbral que nos ayuden a determinar la naturaleza plana o de tendencia del mercado.
Patrones con ejemplos (Parte I): Pico múltiple Patrones con ejemplos (Parte I): Pico múltiple
El artículo inicia un ciclo de análisis de patrones de reversión en el marco del trading algorítmico. Comenzaremos la idea examinando la primera y más interesante familia entre estos patrones, originada a partir de los patrones Double Top y Double Bottom.
Gráficos en la biblioteca DoEasy (Parte 75): Métodos de trabajo con primitivas y texto en el elemento gráfico básico Gráficos en la biblioteca DoEasy (Parte 75): Métodos de trabajo con primitivas y texto en el elemento gráfico básico
En el presente artículo, continuaremos el desarrollo de la clase de elemento gráfico de todos los elementos gráficos de la biblioteca creados sobre la base de la Biblioteca Estándar CCanvas. En concreto, crearemos los métodos para dibujar las primitivas gráficas y los métodos para mostrar el texto en un objeto de elemento gráfico.