English Русский 中文 Español Deutsch 日本語
preview
DoEasy. Controles (Parte 3): Criando controles vinculados

DoEasy. Controles (Parte 3): Criando controles vinculados

MetaTrader 5Exemplos | 4 agosto 2022, 08:49
226 0
Artyom Trishkin
Artyom Trishkin

Conteúdo


Ideia

Hoje vamos analisar como criar uma funcionalidade para construir controles gráficos incorporados a outro elemento gráfico. Imaginemos que temos um controle, p. ex., um Painel. Por si só, esse elemento é apenas uma espécie de contêiner no qual outros controles podem ser colocados. Logo, ao mover este painel, todos os objetos-controles incorporados a ele também se moverão. Isto quer dizer que o painel é o objeto que serve de base para colocar os elementos GUI nele de maneira agrupada. Como não temos um construtor de interface gráfica dos programas no terminal, a concepção destes elementos será de responsabilidade do programador que está escrevendo o programa. Porém, a biblioteca facilitará a criação de tais elementos GUI, bastará escrever em que ordem os elementos gráficos necessários serão criados para colocá-los no painel. Além disso, poderemos elaborar e acrescentar elementos ao painel de forma programática.

Hoje faremos métodos, ou melhor, continuaremos a escrevê-los, pois há muito tempo temos um arranjo preliminar de um método para criar elementos dentro de outro elemento. Os métodos permitirão que um novo elemento gráfico seja criado diretamente do painel (elemento esse já incorporado ao painel), o que posteriormente possibilitará que seja manuseado como uma parte independente da GUI do programa. No que lhe toca a esses elementos criados e incorporados ao painel, também podem criar outros elementos dentro de si. Vale a pena dizer que a menor unidade que possui a funcionalidade hoje em causa será um objeto da classe forma.

Além da tarefa definida acima, trabalharemos um pouco no objeto sombra do elemento gráfico, pois ainda persistem alguns erros de lógica ao aplicá-lo a qualquer um dos objetos que permitem ter sombra. Isto porque, por exemplo, ao gerarmos uma sombra, ela só é desenhada sobre o gráfico, mas deveria ficar sobreposta ao objeto sobre o qual se encontra o objeto que a projeta. Basicamente, estaremos corrigindo pequenas falhas.


Modificando as classes da biblioteca

No arquivo \MQL5\Include\DoEasy\Defines.mqh, temos macro-substituições para especificar valores padrão para algumas propriedades dos objetos da biblioteca.

No bloco de parâmetros da tela, alteramos o nome da macro-substituição CLR_FORE_COLOR para CLR_DEF_FORE_COLOR, adicionamos um valor padrão para a opacidade dos objetos-elementos gráficos e mais alguns valores padrão para as propriedades do objeto-sombra:

//--- Canvas parameters
#define PAUSE_FOR_CANV_UPDATE          (16)                       // Canvas update frequency
#define CLR_CANV_NULL                  (0x00FFFFFF)               // Zero for the canvas with the alpha channel
#define CLR_DEF_FORE_COLOR             (C'0x2D,0x43,0x48')        // Default color for texts of objects on canvas
#define CLR_DEF_OPACITY                (200)                      // Default color non-transparency for canvas objects
#define CLR_DEF_SHADOW_COLOR           (C'0x6B,0x6B,0x6B')        // Default color for canvas object shadows
#define CLR_DEF_SHADOW_OPACITY         (127)                      // Default color non-transparency for canvas objects
#define DEF_SHADOW_BLUR                (4)                        // Default blur for canvas object shadows
#define DEF_FONT                       ("Calibri")                // Default font
#define DEF_FONT_SIZE                  (8)                        // Default font size
#define OUTER_AREA_SIZE                (16)                       // Size of one side of the outer area around the form workspace
#define DEF_FRAME_WIDTH_SIZE           (3)                        // Default form/panel/window frame width
//--- Graphical object parameters

Esses valores serão utilizados pela biblioteca nos métodos de criação de elementos gráficos. Uma vez criados, os valores padrão sempre podem ser alterados.

Vemos que nossas constantes não têm uma disposição muito lógica na lista de tipos de elementos gráficos:

//+------------------------------------------------------------------+
//| The list of graphical element types                              |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_STANDARD,                       // Standard graphical object
   GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED,              // Extended standard graphical object
   GRAPH_ELEMENT_TYPE_ELEMENT,                        // Element
   GRAPH_ELEMENT_TYPE_SHADOW_OBJ,                     // Shadow object
   GRAPH_ELEMENT_TYPE_FORM,                           // Form
   GRAPH_ELEMENT_TYPE_WINDOW,                         // Window
   //--- WinForms
   GRAPH_ELEMENT_TYPE_PANEL,                          // Windows Forms Panel
  };
//+------------------------------------------------------------------+

A constante para o objeto-sombra está localizada após a constante do objeto-elemento gráfico. Isso não é muito prático em termos de restrição de tipos de objetos em alguns métodos. Por exemplo, se precisarmos processar todos os objetos que são elementos GUI, podemos especificar que objetos do tipo elemento e acima serão processados. Mas neste caso, o objeto-sombra também será processado pelo método. Para evitar essa situação e conseguir selecionar um grupo de objetos pelo valor da constante de enumeração, trocamos de lugar as constantes especificadas para que os objetos-elementos GUI sejam categorizados segundo sua hierarquia a nível de herança:

//+------------------------------------------------------------------+
//| The list of graphical element types                              |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_STANDARD,                       // Standard graphical object
   GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED,              // Extended standard graphical object
   GRAPH_ELEMENT_TYPE_SHADOW_OBJ,                     // Shadow object
   GRAPH_ELEMENT_TYPE_ELEMENT,                        // Element
   GRAPH_ELEMENT_TYPE_FORM,                           // Form
   GRAPH_ELEMENT_TYPE_WINDOW,                         // Window
   //--- WinForms
   GRAPH_ELEMENT_TYPE_PANEL,                          // Windows Forms Panel
  };
//+------------------------------------------------------------------+

Agora podemos selecionar rapidamente os objetos necessários a serem processados nos métodos da biblioteca.


Inserimos os índices das novas mensagens da biblioteca no arquivo \MQL5\Include\DoEasy\Data.mqh:

//--- CForm
   MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT,// No shadow object. Create it using the CreateShadowObj() method
   MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ,      // Failed to create new shadow object
   MSG_FORM_OBJECT_ERR_FAILED_CREATE_PC_OBJ,          // Failed to create new pixel copier object
   MSG_FORM_OBJECT_PC_OBJ_ALREADY_IN_LIST,            // Pixel copier object with ID already present in the list 
   MSG_FORM_OBJECT_PC_OBJ_NOT_EXIST_LIST,             // No pixel copier object with ID in the list 
   MSG_FORM_OBJECT_ERR_NOT_INTENDED,                  // The method is not meant for creating such an object: 

e o texto da mensagem correspondente ao índice recém-adicionado:

//--- CForm
   {"Отсутствует объект тени. Необходимо сначала его создать при помощи метода CreateShadowObj()","There is no shadow object. You must first create it using the CreateShadowObj () method"},
   {"Не удалось создать новый объект для тени","Failed to create new object for shadow"},
   {"Не удалось создать новый объект-копировщик пикселей","Failed to create new pixel copier object"},
   {"В списке уже есть объект-копировщик пикселей с идентификатором ","There is already a pixel copier object in the list with ID "},
   {"В списке нет объекта-копировщика пикселей с идентификатором ","No pixel copier object with ID "},
   {"Метод не предназначен для создания такого объекта: ","The method is not intended to create such an object: "},


Todos os elementos GUI são criados a partir de diferentes objetos de biblioteca, sendo que o elemento mínimo é um objeto-elemento gráfico. Mas na hierarquia de herança de cada objeto que se segue, é acrescentada uma nova funcionalidade que está faltando nos objetos pai. Por isso, a funcionalidade de alguns métodos do objeto pai não é suficiente para ser totalmente implementada nos objetos descendentes. Precisamos tornar esses métodos virtuais e adicionar a funcionalidade necessária nos objetos descendentes.

Cada objeto que compõe a GUI deve "conhecer" o elemento ao qual está incorporado, de modo que possa utilizar as propriedades do objeto que serve de base para seu posicionamento, entre outras coisas. Para fazer isso, vamos introduzir um ponteiro para o objeto base na classe do objeto-elemento (este objeto é o pai de todos os elementos da GUI, então é lógico colocar o ponteiro nele).

Todos os objetos que compõem a interface gráfica neste estágio estão vinculados às coordenadas de tela do gráfico, portanto suas coordenadas são contadas a partir do canto superior esquerdo do gráfico. Mas se precisarmos posicionar um objeto dentro de outro objeto, então é lógico transferir dado sistema de coordenadas para o objeto base correspondente, onde a origem será o canto superior esquerdo do elemento gráfico base, e não o gráfico. Para fazer isso, introduzimos o conceito de coordenadas relativas e adicionamos novos métodos que retornam as coordenadas de um objeto em relação à sua base, isto é, à qual está vinculado. Daí podemos simplesmente especificar um deslocamento relativo ao canto superior esquerdo do objeto base para posicionar o objeto, em vez de ter que calcular continuamente uma nova coordenada de posicionamento quando o elemento gráfico base é movido no gráfico.

No arquivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh , em sua seção protected vamos declarar um ponteiro para o objeto base, isto é, ao qual o elemento gráfico está anexado, e na seção privada vamos declarar variáveis para armazenar o deslocamento de suas coordenadas em relação ao objeto base:

//+------------------------------------------------------------------+
//| Class of the graphical element object                            |
//+------------------------------------------------------------------+
class CGCnvElement : public CGBaseObj
  {
protected:
   CGCnvElement     *m_element_base;                           // Pointer to the parent element
   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
   uint              m_duplicate_res[];                        // Array for storing resource data copy

//--- Create (1) the object structure and (2) the object from the structure
   virtual bool      ObjectToStruct(void);
   virtual void      StructToObject(void);
   
private:
   int               m_shift_coord_x;                          // Offset of the X coordinate relative to the base object
   int               m_shift_coord_y;                          // Offset of the Y coordinate relative to the base object
   struct SData

Na seção pública, vamos escrever métodos para definir e retornar um ponteiro para o objeto base:

//--- Create the element
   bool              Create(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 redraw=false);

//--- (1) Set and (2) return the pointer to the parent control
   void              SetBase(CGCnvElement *element)                                    { this.m_element_base=element;               }
   CGCnvElement     *GetBase(void)                                                     { return this.m_element_base;                }
//--- Return the pointer to a canvas object
   CCanvas          *GetCanvasObj(void)                                                { return &this.m_canvas;                     }

Vamos tornar o método Move() virtual:

//--- Return the size of the graphical resource copy array
   uint              DuplicateResArraySize(void)                                       { return ::ArraySize(this.m_duplicate_res);  }
   
//--- Update the coordinates (shift the canvas)
   virtual bool      Move(const int x,const int y,const bool redraw=false);

//--- Save an image to the array
   bool              ImageCopy(const string source,uint &array[]);

Como um objeto-elemento gráfico pode ser incorporado a outro objeto, mas, ao mesmo tempo, não pode ter objetos anexados, então seu movimento processado pelo método Move() não requer nenhuma modificação. Além disso, os objetos herdeiros já podem ter outros elementos gráficos vinculados, e os ponteiros para eles são colocados na lista, o que significa que o método Move() também deve lidar com toda a lista de objetos vinculados, e o método Move() será chamado para cada um deles. É por esta razão que este método será virtual e implementado separadamente para cada um dos objetos herdados.

Na seção pública, escrevemos mais métodos, para definir e retornar as coordenadas relativas do objeto:

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:
//--- (1) Set and (2) return the X coordinate shift relative to the base object
   void              SetCoordXRelative(const int value)                                { this.m_shift_coord_x=value;                }
   int               CoordXRelative(void)                                        const { return this.m_shift_coord_x;               }
//--- (1) Set and (2) return the Y coordinate shift relative to the base object
   void              SetCoordYRelative(const int value)                                { this.m_shift_coord_y=value;                }
   int               CoordYRelative(void)                                        const { return this.m_shift_coord_y;               }
   
//--- Event handler


No construtor padrão, escrevemos a inicialização do ponteiro para o objeto base e o deslocamento das coordenadas relativas do objeto:

//--- Default constructor/Destructor
                     CGCnvElement() : m_shadow(false),m_chart_color_bg((color)::ChartGetInteger(::ChartID(),CHART_COLOR_BACKGROUND))
                        { this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_base=NULL; this.m_shift_coord_x=0; this.m_shift_coord_y=0; }
                    ~CGCnvElement()
                        { this.m_canvas.Destroy();             }

Se o ponteiro do objeto base for NULL, isso significará que esse objeto não está incorporado a nenhum outro elemento gráfico.

No bloco de métodos para acesso simplificado às propriedades do objeto, escreveremos métodos para retornar as coordenadas relativas das bordas direita e inferior do objeto:

//--- 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 relative coordinate of the (1) right and (2) bottom element edge
   int               RightEdgeRelative(void)             const { return this.CoordXRelative()+this.m_canvas.Width();                   }
   int               BottomEdgeRelative(void)            const { return this.CoordYRelative()+this.m_canvas.Height();                  }
//--- Return the (1) X, (2) Y coordinates, (3) element width and (4) height,

Tornamos o método BringToTop() virtual:

//--- Set the object above all
   virtual void      BringToTop(void)                          { CGBaseObj::SetVisible(false,false); CGBaseObj::SetVisible(true,false);}
//--- (1) Show and (2) hide the element
   virtual void      Show(void)                                { CGBaseObj::SetVisible(true,false);                                    }
   virtual void      Hide(void)                                { CGBaseObj::SetVisible(false,false);                                   }

A razão pela qual este método também será virtual é a mesma mencionada acima, porque a lista de todos os elementos gráficos incorporados ao objeto também deve ser processada ao definir o objeto base acima de todos. Se não for assim, todos os elementos incorporados ao objeto trazido para o primeiro plano simplesmente desaparecerão visualmente dele, uma vez que eles também precisam ser movidos acima do objeto base, mantendo ordem em que foram anexados a ele. E isso será tratado nos métodos virtuais BringToTop() dos objetos herdeiros, que possuem listas de objetos incorporados.

Nos construtores paramétricos e protegidos, inicializamos o ponteiro para o objeto base e o deslocamento relativo das coordenadas:

//+------------------------------------------------------------------+
//| 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_type=OBJECT_DE_TYPE_GELEMENT; 
   this.m_element_base=NULL;
   this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND);
   this.m_name=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
   this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id);
   this.m_subwindow=wnd_num;
   this.m_type_element=element_type;
   this.SetFont(DEF_FONT,DEF_FONT_SIZE);
   this.m_text_anchor=0;
   this.m_text_x=0;
   this.m_text_y=0;
   this.m_color_bg=colour;
   this.m_opacity=opacity;
   this.m_shift_coord_x=0;
   this.m_shift_coord_y=0;
   if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,colour,opacity,redraw))
     {

A inicialização do ponteiro e das variáveis é escrita da mesma forma no construtor protegido.


O objeto-sombra pode estar minimamente no objeto-forma e mais adiante na hierarquia de herança. Já o usamos antes, mas para objetos fixos. Se, por outro lado, atribuirmos uma sombra a um objeto móvel, notaremos imediatamente uma falha no movimento do objeto gráfico, pois a sombra permanecerá no lugar. Portanto, precisamos, em primeiro lugar, adicionar variáveis para armazenar o deslocamento da sombra em relação ao objeto que a projeta e, em segundo lugar, mover a sombra após o objeto movido. Para ser mais preciso, como o objeto sombra está abaixo do elemento gráfico, primeiro temos que mover o objeto sombra, depois o elemento gráfico, e depois atualizar o gráfico. Neste caso, veremos que o objeto está se movendo e a sombra que ele projeta também está se movendo atrás do objeto.

Não só isso, mas quando clicamos em um objeto, ele é trazido para o primeiro plano, portanto sua sombra também deve estar acima de todos os outros objetos no gráfico, de modo que a sombra do objeto que está sendo movido seja desenhada sobre outros elementos gráficos abaixo do objeto que está sendo movido.

No arquivo \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh, na seção privada, vamos declarar variáveis para armazenar o deslocamento da sombra em relação ao objeto gráfico que projeta essa sombra:

//+------------------------------------------------------------------+
//| Shadows object class                                             |
//+------------------------------------------------------------------+
class CShadowObj : public CGCnvElement
  {
private:
   color             m_color_shadow;                  // Shadow color
   uchar             m_opacity_shadow;                // Shadow opacity
   int               m_offset_x;                      // Shadow X axis shift
   int               m_offset_y;                      // Shadow Y axis shift

Na seção pública , vamos escrever métodos que retornam valores de deslocamento de sombra em relação ao objeto:

//--- Supported object 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 the shadow shift by (1) X and (2) Y
   int               OffsetX(void)                                      const { return this.m_offset_x;        }
   int               OffsetY(void)                                      const { return this.m_offset_y;        }

//--- Draw an object shadow

No construtor da classe, inicializamos os valores dessas variáveis:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CShadowObj::CShadowObj(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_SHADOW_OBJ,chart_id,subwindow,name,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GSHADOW; 
   CGCnvElement::SetColorBackground(clrNONE);
   CGCnvElement::SetOpacity(0);
   CGCnvElement::SetActive(false);
   this.m_opacity_shadow=127;
   color gray=CGCnvElement::ChangeColorSaturation(ChartColorBackground(),-100);
   this.m_color_shadow=CGCnvElement::ChangeColorLightness(gray,-50);
   this.m_shadow=false;
   this.m_visible=true;
   this.m_offset_x=0;
   this.m_offset_y=0;
   CGCnvElement::Erase();
  }
//+------------------------------------------------------------------+


No método que desenha o objeto sombra, escrevemos os valores de deslocamento passados nos argumentos do método para essas novas variáveis:

//+------------------------------------------------------------------+
//| Draw the object shadow                                           |
//+------------------------------------------------------------------+
void CShadowObj::DrawShadow(const int shift_x,const int shift_y,const uchar blur_value)
  {
//--- Set the shadow shift values to the variables by X and Y axes
   this.m_offset_x=shift_x;
   this.m_offset_y=shift_y;
//--- Calculate the height and width of the drawn rectangle
   int w=this.Width()-OUTER_AREA_SIZE*2;
   int h=this.Height()-OUTER_AREA_SIZE*2;
//--- Draw a filled rectangle with calculated dimensions
   this.DrawShadowFigureRect(w,h);
//--- Calculate the blur radius, which cannot exceed a quarter of the OUTER_AREA_SIZE constant
   int radius=(blur_value>OUTER_AREA_SIZE/4 ? OUTER_AREA_SIZE/4 : blur_value);
//--- If failed to blur the shape, exit the method (GaussianBlur() displays the error on the journal)
   if(!this.GaussianBlur(radius))
      return;
//--- Shift the shadow object by X/Y offsets specified in the method arguments and update the canvas
   CGCnvElement::Move(this.CoordX()+this.m_offset_x,this.CoordY()+this.m_offset_y);
   CGCnvElement::Update();
  }
//+------------------------------------------------------------------+

Depois que a sombra é desenhada, nós a deslocamos consoante valores especificados. Em princípio, o método praticamente não mudou, exceto por armazenar os valores de deslocamento ao longo dos eixos X e Y em novas variáveis. Anteriormente, o objeto sombra era compensado pelos valores passados nos argumentos do método, e agora pelos valores escritos nas novas variáveis, que é o mesmo. Mas agora temos os deslocamentos especificados ao criar e desenhar o objeto sombra, e vamos precisar deles ao movermos o objeto de sombra.


No arquivo de objeto-forma \MQL5\Include\DoEasy\Objects\Graph\Form.mqh, movemos os métodos CreateNameDependentObject() e CreateShadowObj()
da seção privada da classe para a seção protegida:

protected:
   int               m_frame_width_left;                       // Form frame width to the left
   int               m_frame_width_right;                      // Form frame width to the right
   int               m_frame_width_top;                        // Form frame width at the top
   int               m_frame_width_bottom;                     // Form frame width at the bottom
//--- Initialize the variables
   void              Initialize(void);
   void              Deinitialize(void);
//--- Create a shadow object
   void              CreateShadowObj(const color colour,const uchar opacity);
//--- Return the name of the dependent object
   string            CreateNameDependentObject(const string base_name)  const
                       { return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+1)+"_"+base_name;   }
   
public:

Como estes métodos devem estar disponíveis nos objetos das classes herdeiras, seu lugar é na seção protegida da classe.

Vamos tornar virtual o método privado que cria um novo objeto gráfico, pois algumas modificações precisarão ser feitas nos seus objetos descendentes. E vamos declarar um método virtual que retorna as coordenadas do objeto vinculado quando ele é criado:

//--- Create a new graphical object
   virtual 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);
//--- Return the initial coordinates of a bound object
   virtual void      GetCoords(int &x,int &y);

O método que cria o novo objeto vinculado está localizado dentro da classe do objeto-forma. O objeto-forma é herdado da classe do objeto-elemento gráfico. Assim, essas duas classes (elemento e forma) são conhecidas e visíveis no objeto de classe-forma. E elas podem ser criadas aqui. Mas precisamos conseguir criar outros controles, como uma janela, um painel e outros objetos de classes que faremos durante o desenvolvimento de objetos WinForms. Porém, eles não serão visíveis nesta classe. Assim, em cada uma delas haverá um método virtual CreateNewGObject() no qual será escrito o código para criar os controles que são visíveis dentro dessas classes.

Na seção pública da classe, vamos declarar os métodos virtuais que tornamos virtuais na classe de objeto-elemento gráfico:

public:
//--- Return (1) the mouse status relative to the form, as well as (2) X and (3) Y coordinate of the cursor
   ENUM_MOUSE_FORM_STATE MouseFormState(const int id,const long lparam,const double dparam,const string sparam);
   int               MouseCursorX(void)            const { return this.m_mouse.CoordX();     }
   int               MouseCursorY(void)            const { return this.m_mouse.CoordY();     }
//--- Set the flags of mouse scrolling, context menu and the crosshairs tool for the chart
   void              SetChartTools(const bool flag);
//--- (1) Set and (2) return the shift of X and Y coordinates relative to the cursor
   void              SetOffsetX(const int value)         { this.m_offset_x=value;            }
   void              SetOffsetY(const int value)         { this.m_offset_y=value;            }
   int               OffsetX(void)                 const { return this.m_offset_x;           }
   int               OffsetY(void)                 const { return this.m_offset_y;           }

//--- Update the coordinates (shift the canvas)
   virtual bool      Move(const int x,const int y,const bool redraw=false);
//--- Set the priority of a graphical object for receiving the event of clicking on a chart
   virtual bool      SetZorder(const long value,const bool only_prop);
//--- Set the object above all
   virtual void      BringToTop(void);
   
//--- Event handler

Abaixo escreveremos a implementação desses métodos que possuem os handlers necessários para a classe do objeto-forma, onde é necessário processar o deslocamento de outros controles vinculados a este objeto.

O método que retorna o objeto sombra anteriormente retornava erroneamente o tipo de objeto-elemento gráfico CGCnvElement. Não há erro crítico nisso, exceto que os métodos escritos na classe de objeto sombra não ficarão disponíveis para nós.
Bem, vamos corrigir esse erro para que o método retorne o tipo de objeto sombra e vamos adicionar dois métodos para retornar o número de controles vinculados e o controle por seu índice na lista de objetos vinculados:

//--- Return (1) the list of attached objects and (2) the shadow object
   CForm            *GetObject(void)                                          { return &this;                  }
   CArrayObj        *GetListElements(void)                                    { return &this.m_list_elements;  }
   CShadowObj       *GetShadowObj(void)                                       { return this.m_shadow_obj;      }
//--- Return the pointer to (1) the animation object, the list of (2) text and (3) rectangular animation frames
   CAnimations      *GetAnimationsObj(void)                                   { return this.m_animations;      }
   CArrayObj        *GetListFramesText(void)
                       { return(this.m_animations!=NULL ? this.m_animations.GetListFramesText() : NULL);       }
   CArrayObj        *GetListFramesQuad(void)
                       { return(this.m_animations!=NULL ? this.m_animations.GetListFramesQuad() : NULL);       }

//--- Return the (1) number of bound elements and (2) the bound element by the index in the list
   int               ElementsTotal(void)                       const { return this.m_list_elements.Total();    }
   CGCnvElement     *GetElement(const int index)                     { return this.m_list_elements.At(index);  }
   
//--- Set the form (1) color scheme and (2) style


No método que cria o novo elemento incorporado, vamos alterar os argumentos do método. Anteriormente, entre outras propriedades, o número do elemento a ser criado e o sinalizador de relocação eram passados para o método. Como o número será selecionado automaticamente e os elementos ancorados não precisam de um sinalizador de realocação, pois os objetos herdam todos os movimentos do objeto base ao qual estão incorporados, passaremos o tipo do objeto criado em vez do número, e simplesmente removemos o sinalizador de realocação dos argumentos do método. Agora a declaração do método fica assim:

//--- Create a new attached element
   bool              CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool activity);
//--- Add a new attached element


No método que desenha a sombra do objeto, inseriremos o valor padrão para o valor de desfoque de sombra em vez do valor 4 passado anteriormente:

//--- Add a new attached element
   bool              AddNewElement(CGCnvElement *obj,const int x,const int y);

//--- Draw an object shadow
   void              DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=DEF_SHADOW_BLUR);

//--- Draw the form frame


Anteriormente, o método que criava um novo objeto gráfico criava apenas um objeto do tipo elemento gráfico CGCnvElement.
Agora vamos modificar o método para que seja criado um objeto do tipo que é passado nos argumentos do método:

//+------------------------------------------------------------------+
//| 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)
  {
   string name=this.CreateNameDependentObject(obj_name);
   CGCnvElement *element=NULL;
   //--- Depending on the created object type,
   switch(type)
     {
      //--- create a graphical element object
      case GRAPH_ELEMENT_TYPE_ELEMENT :
         element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity);
        break;
      //--- create a form object
      case GRAPH_ELEMENT_TYPE_FORM :
         element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name);
   return element;
  }
//+------------------------------------------------------------------+


O método que cria um novo elemento incorporado sofreu alterações significativas:

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CForm::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                             const int x,
                             const int y,
                             const int w,
                             const int h,
                             const color colour,
                             const uchar opacity,
                             const bool activity)
  {
//--- If the type of a created graphical element is less than the "element", inform of that and return 'false'
   if(element_type<GRAPH_ELEMENT_TYPE_ELEMENT)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_NOT_INTENDED),::StringSubstr(::EnumToString(element_type),19));
      return false;
     }
//--- Specify the element index in the list
   int num=this.m_list_elements.Total()+1;
//--- Create a graphical element name
   string ns=(::StringLen((string)num)<2 ? ::IntegerToString(num,2,'0') : (string)num);
   string name="Elm"+ns;
//--- Get the initial coordinates of the object relative to the coordinate system of the base object
   int elm_x=x;
   int elm_y=y;
   this.GetCoords(elm_x,elm_y);
//--- Create a new graphical element
   CGCnvElement *obj=this.CreateNewGObject(element_type,num,name,elm_x,elm_y,w,h,colour,opacity,false,activity);
   if(obj==NULL)
      return false;
//--- and add it to the list of bound graphical elements
   if(!this.AddNewElement(obj,elm_x,elm_y))
     {
      delete obj;
      return false;
     }
//--- Set the minimum properties for a bound graphical element
   obj.SetBase(this.GetObject());
   obj.SetCoordXRelative(x);
   obj.SetCoordYRelative(y);
   obj.SetZorder(this.Zorder(),false);
//--- Draw an added object and return 'true'
   obj.Erase(colour,opacity,true);
   return true;
  }
//+------------------------------------------------------------------+

Toda a lógica é descrita nos comentários do código. Ao criar o nome de um objeto gráfico, usaremos o nome do objeto base + a string "_Elm" + o número do objeto na lista. Para o número do objeto, usaremos a seguinte lógica: se o número do objeto for de 1 a 9 inclusive, adicionamos um zero à esquerda ao dígito para que seja 01, 02, 03, .., .., 08, 09 . Para 10 e além, não adicionaremos nada. Nesse método, apenas o nome do objeto é criado e o nome base é atribuído a ele no método CreateNewGObject() em sua primeira linha quando o método CreateNameDependentObject() é chamado.

O método que adiciona um novo elemento anexado à lista também mudou um pouco. Por enquanto, o método usará um loop para encontrar um objeto na lista que tenha o mesmo nome daquele que está sendo adicionado:

//+------------------------------------------------------------------+
//| Add a new attached element                                       |
//+------------------------------------------------------------------+
bool CForm::AddNewElement(CGCnvElement *obj,const int x,const int y)
  {
   if(obj==NULL)
      return false;
   for(int i=0;i<this.m_list_elements.Total();i++)
     {
      CGCnvElement *elm=this.m_list_elements.At(i);
      if(elm==NULL)
         continue;
      if(elm.Name()==obj.Name())
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_OBJ_ALREADY_IN_LIST),": ",obj.NameObj());
         return false;
        }
     }
   if(!this.m_list_elements.Add(obj))
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST),": ",obj.NameObj());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

Antes a busca era assim

   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());
      return false;
     }

por motivos que ainda não estão claros para mim, não funciona, pois sempre retorna a presença de um objeto com o nome passado para o método na lista de objetos vinculados, o que não deveria acontecer. Trataremos disso mais tarde. E essa busca foi substituída por um loop.

Método que retorna as coordenadas iniciais do objeto vinculado:

//+------------------------------------------------------------------+
//| Return the initial coordinates of a bound object                 |
//+------------------------------------------------------------------+
void CForm::GetCoords(int &x,int &y)
  {
   x=this.CoordX()+this.m_frame_width_left+x;
   y=this.CoordY()+this.m_frame_width_top+y;
  }
//+------------------------------------------------------------------+

O método retorna por referência os valores de suas coordenadas relativas, passados por referência, aos valores ajustados à largura da moldura à esquerda para a coordenada X e em cima para a coordenada Y. Simplesmente porque se um objeto tem uma moldura, então os objetos adicionados não devem estar localizados nela. Logo, se uma coordenada for passada, por exemplo, 0, ela será deslocada com base na largura da moldura. Ou seja, se o quadro tiver uma largura de 3, todas as coordenadas serão deslocadas consoante esse valor.

Método virtual que atualiza as coordenadas de um elemento:

//+------------------------------------------------------------------+
//| Update the coordinate elements                                   |
//+------------------------------------------------------------------+
bool CForm::Move(const int x,const int y,const bool redraw=false)
  {
   CGCnvElement *base=this.GetBase();
   bool res=true;
//--- If the element is not movable and is a base object, leave
   if(!this.Movable() && base==NULL)
      return false;
//--- If the object has a shadow and we failed to set new coordinate values to the properties of the shadow object, return 'false'
   if(this.m_shadow)
     {
      if(this.m_shadow_obj==NULL || !this.m_shadow_obj.Move(x-OUTER_AREA_SIZE+this.m_shadow_obj.OffsetX(),y-OUTER_AREA_SIZE+this.m_shadow_obj.OffsetY(),false))
         return false;
     }
//--- If failed to set new values into graphical object properties, return 'false'
   if(!this.SetCoordX(x) || !this.SetCoordY(y))
      return false;
//--- In the loop by all bound objects,
   for(int i=0;i<this.m_list_elements.Total();i++)
     {
      //--- get the next object and shift it
      CGCnvElement *obj=m_list_elements.At(i);
      if(obj==NULL)
         continue;
      if(!obj.Move(x+this.m_frame_width_left+obj.CoordXRelative(),y+this.m_frame_width_top+obj.CoordYRelative(),false))
         return false;
     }
   //--- If the update flag is set and this is a base object, redraw the chart.
   if(redraw && base==NULL)
      ::ChartRedraw(this.ChartID());
   //--- Return 'true'
   return true;
  }
//+------------------------------------------------------------------+

O método é obtido a partir da classe do objeto pai CGCnvElement, ele recebe um loop que percorre a lista de objetos vinculados, e o mesmo método também é chamado para cada um deles. Assim, todos os objetos ao longo da cadeia serão deslocados após seu objeto base.
Para garantir que cada objeto no qual este método é chamado não atualize o gráfico, uma verificação é adicionada aqui para garantir que é o objeto base, e não um dos anexos (já que o objeto base não está vinculado a nenhum outro objeto base, seu ponteiro para o objeto base é NULL, é isso que é verificado aqui). Ou seja, o gráfico será atualizado uma vez, somente no final do loop.

Método que define a prioridade de um objeto gráfico para receber um evento de clique do mouse em um gráfico:

//+------------------------------------------------------------------+
//| Set the priority of a graphical object                           |
//| for receiving the event of clicking on a chart                   |
//+------------------------------------------------------------------+
bool CForm::SetZorder(const long value,const bool only_prop)
  {
   if(!CGCnvElement::SetZorder(value,only_prop))
      return false;
   if(this.m_shadow)
     {
      if(this.m_shadow_obj==NULL || !this.m_shadow_obj.SetZorder(value,only_prop))
         return false;
     }
   int total=this.m_list_elements.Total();
   for(int i=0;i<total;i++)
     {
      CGCnvElement *obj=this.m_list_elements.At(i);
      if(obj==NULL)
         continue;
      if(!obj.SetZorder(value,only_prop))
         return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

Aqui, assim como no método acima, um loop é adicionado para percorrer todos os objetos anexados para os quais o mesmo método é chamado.

Método que define o objeto acima de tudo:

//+------------------------------------------------------------------+
//| Set the object above all the rest                                |
//+------------------------------------------------------------------+
void CForm::BringToTop(void)
  {
//--- If the shadow usage flag is set
   if(this.m_shadow)
     {
      //--- If the shadow object is created, move it to the foreground
      if(this.m_shadow_obj!=NULL)
         this.m_shadow_obj.BringToTop();
     }
//--- Move the object to the foreground (the object is located above the shadow)
   CGCnvElement::BringToTop();
//--- In the loop by all bound objects,
   int total=this.m_list_elements.Total();
   for(int i=0;i<total;i++)
     {
      //--- get the next object from the list
      CGCnvElement *obj=this.m_list_elements.At(i);
      if(obj==NULL)
         continue;
      //--- and move it to the foreground
      obj.BringToTop();
     }
  }
//+------------------------------------------------------------------+

A lógica do método é descrita nos comentários ao código. O que eu gostaria de esclarecer é que se um objeto tem uma sombra, ele deve ser movido primeiro para o primeiro plano, ele estará mais acima de todos os outros objetos no gráfico. Em seguida, o próprio objeto é transferido para o primeiro plano e ficará mais acima que a sombra e, em um loop sobre todos os objetos anexados, os transferimos sobre o objeto ao qual estão vinculados.


Modificamos a classe de objeto do painel no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh.

Na seção privada da classe, vamos declarar dois métodos virtuais que já fizemos hoje na classe pai:

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

public:

No método de criação de novo objeto gráfico nesta classe, podemos adicionar a criação de objeto CPanel, pois é naturalmente visível nesta classe, que possui o mesmo tipo. O método GetCoords() ainda terá a mesma implementação que a classe pai. Posteriormente, ao finalizar esta classe, muito provavelmente este método será modificado aqui.

Vamos alterar os métodos que definem os valores de Padding para cada lado do painel:

//--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control
   void              PaddingLeft(const uint value)
                       {
                        this.m_padding[0]=((int)value<this.m_frame_width_left ? this.m_frame_width_left : (int)value);
                       }
   void              PaddingTop(const uint value)
                       {
                        this.m_padding[1]=((int)value<this.m_frame_width_top ? this.m_frame_width_top : (int)value);
                       }
   void              PaddingRight(const uint value)
                       {
                        this.m_padding[2]=((int)value<this.m_frame_width_right ? this.m_frame_width_right : (int)value);
                       }
   void              PaddingBottom(const uint value)
                       {
                        this.m_padding[3]=((int)value<this.m_frame_width_bottom ? this.m_frame_width_bottom : (int)value);
                       }
   void              PaddingAll(const uint value)
                       {
                        this.PaddingLeft(value); this.PaddingTop(value); this.PaddingRight(value); this.PaddingBottom(value);
                       }
//--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields inside the control

Agora, se o valor de Padding que está sendo definido for menor que a largura da moldura do painel, o valor será igual à largura do lado correspondente da moldura.


No construtor padrão, vamos adicionar a configuração de tipo de objeto gráfico como "painel" às propriedades do objeto:

                     CPanel(const string name) : CForm(::ChartID(),0,name,0,0,0,0)
                       {
                        CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL);
                        CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_PANEL);
                        this.m_type=OBJECT_DE_TYPE_GWF_PANEL; 
                        this.m_fore_color=CLR_DEF_FORE_COLOR;
                        this.m_bold_type=FW_TYPE_NORMAL;
                        this.MarginAll(3);
                        this.PaddingAll(0);
                        this.Initialize();
                       }
//--- Destructor

E escreveremos exatamente a mesma linha em todos os outros construtores de classe. Sem esta linha, o tipo do objeto registrado pelo construtor da classe pai é definido como "Form". Por isso, nesses construtores, adicionamos uma configuração forçada dessa propriedade como "Panel".
Sim, isso é uma falha na lógica dessas classes. Vamos ver o que podemos fazer com isso no futuro...

Os nomes das macro-substituições CLR_FORE_COLOR renomeadas anteriormente em todos os construtores de classe foram substituídos por novos: CLR_DEF_FORE_COLOR.

Método que cria um novo objeto gráfico:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CPanel::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)
  {
   string name=this.CreateNameDependentObject(obj_name);
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT :
         element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity);
        break;
      case GRAPH_ELEMENT_TYPE_FORM :
         element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_PANEL :
         element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name);
   return element;
  }
//+------------------------------------------------------------------+

Este método repete a lógica do método de mesmo nome do objeto pai discutido acima, mas aqui é adicionada a criação de outro controle de objeto-controle "Panel", já que esse tipo de objeto é conhecido nesta classe, afinal, ele é uma classe deste tipo.
Em outras classes que criaremos posteriormente, serão adicionadas a este método virtual strings que criam elementos gráficos correspondentes aos seus tipos.

Método que retorna as coordenadas iniciais do objeto vinculado:

//+------------------------------------------------------------------+
//| Return the initial coordinates of a bound object                 |
//+------------------------------------------------------------------+
void CPanel::GetCoords(int &x,int &y)
  {
   x=this.CoordX()+this.FrameWidthLeft()+x;
   y=this.CoordY()+this.FrameWidthTop()+y;
  }
//+------------------------------------------------------------------+

No método, a lógica é a mesma do método de mesmo nome do objeto pai. A diferença é que aqui usamos os métodos públicos da classe pai para acessar o valor da largura da borda, e não os valores das variáveis ocultas na seção privada e não disponíveis nas classes descendentes.


Na classe-coleção de elementos gráficos \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh, vamos adicionar métodos para acesso rápido aos controles gráficos:

//--- Return the list of graphical elements by element type
   CArrayObj        *GetListCanvElementByType(const ENUM_GRAPH_ELEMENT_TYPE type)
                       {
                        return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_TYPE,type,EQUAL);
                       }

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

//--- Constructor
                     CGraphElementsCollection();

A lógica de tais métodos já foi considerada muitas vezes antes, e é uma seleção-filtragem comum por propriedade especificada.

No método que cria o objeto gráfico WinForms Panel na tela do gráfico e subjanela especificados, escrevemos a criação e renderização do objeto-sombra
se a presença de uma sombra for especificada nos argumentos do método:

//--- Create graphical object WinForms Panel object on canvas on a specified chart and subwindow
   int               CreatePanel(const long chart_id,
                                 const int subwindow,
                                 const string name,
                                 const int x,
                                 const int y,
                                 const int w,
                                 const int h,
                                 const color clr,
                                 const uchar opacity,
                                 const bool movable,
                                 const bool activity,
                                 const int  frame_width=-1,
                                 ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL,
                                 const bool shadow=false,
                                 const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CPanel *obj=new CPanel(chart_id,subwindow,name,x,y,w,h);
                        ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id);
                        if(res==ADD_OBJ_RET_CODE_ERROR)
                           return WRONG_VALUE;
                        obj.SetID(id);
                        obj.SetActive(activity);
                        obj.SetMovable(movable);
                        obj.SetColorBackground(clr);
                        obj.SetColorFrame(clr);
                        obj.SetOpacity(opacity,false);
                        //--- Draw the shadow drawing flag
                        obj.SetShadow(shadow);
                        if(shadow)
                          {
                           //--- Calculate the shadow color as the chart background color converted to the monochrome one
                           //--- and darken the monochrome color by 20 units
                           color clrS=obj.ChangeColorLightness(obj.ChangeColorSaturation(obj.ColorBackground(),-100),-20);
                           //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes
                           //--- Set the shadow opacity to the default value, while the blur radius is equal to 4
                           obj.DrawShadow(3,3,clrS,CLR_DEF_SHADOW_OPACITY,DEF_SHADOW_BLUR);
                          }
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity());
                        obj.Erase(clr,opacity,redraw);
                        if(frame_width>0)
                           obj.FrameWidthAll(frame_width);
                        obj.SetActiveAreaShift(obj.FrameWidthLeft(),obj.FrameWidthBottom(),obj.FrameWidthRight(),obj.FrameWidthTop());
                        obj.DrawFormFrame(obj.FrameWidthTop(),obj.FrameWidthBottom(),obj.FrameWidthLeft(),obj.FrameWidthRight(),obj.ColorFrame(),obj.Opacity(),frame_style);
                        obj.Done();
                        return obj.ID();
                       }

E adicionamos métodos que criam objetos-painel com diferentes tipos de preenchimentos (gradiente vertical e horizontal e gradiente vertical e horizontal cíclico). Aqui consideraremos apenas um desses métodos, pois todos os outros diferem apenas na combinação de sinalizadores passados para o método Erase() da classe CGCnvElement:

//--- Create a WinForms Panel object graphical object on canvas on a specified chart and subwindow with the vertical gradient filling
   int               CreatePanelVGradient(const long chart_id,
                                          const int subwindow,
                                          const string name,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          color &clr[],
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity,
                                          const int  frame_width=-1,
                                          ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL,
                                          const bool shadow=false,
                                          const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CPanel *obj=new CPanel(chart_id,subwindow,name,x,y,w,h);
                        ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id);
                        if(res==ADD_OBJ_RET_CODE_ERROR)
                           return WRONG_VALUE;
                        obj.SetID(id);
                        obj.SetActive(activity);
                        obj.SetMovable(movable);
                        obj.SetColorBackground(clr[0]);
                        obj.SetColorFrame(clr[0]);
                        obj.SetOpacity(opacity,false);
                        //--- Draw the shadow drawing flag
                        obj.SetShadow(shadow);
                        if(shadow)
                          {
                           //--- Calculate the shadow color as the chart background color converted to the monochrome one
                           //--- and darken the monochrome color by 20 units
                           color clrS=obj.ChangeColorLightness(obj.ChangeColorSaturation(obj.ColorBackground(),-100),-20);
                           //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes
                           //--- Set the shadow opacity to the default value, while the blur radius is equal to 4
                           obj.DrawShadow(3,3,clrS,CLR_DEF_SHADOW_OPACITY,DEF_SHADOW_BLUR);
                          }
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity());
                        obj.Erase(clr,opacity,true,false,redraw);
                        if(frame_width>0)
                           obj.FrameWidthAll(frame_width);
                        obj.SetActiveAreaShift(obj.FrameWidthLeft(),obj.FrameWidthBottom(),obj.FrameWidthRight(),obj.FrameWidthTop());
                        obj.DrawFormFrame(obj.FrameWidthTop(),obj.FrameWidthBottom(),obj.FrameWidthLeft(),obj.FrameWidthRight(),obj.ColorFrame(),obj.Opacity(),frame_style);
                        obj.Done();
                        return obj.ID();
                       }
//--- ...

  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+

Os métodos são quase idênticos entre si, exceto pelos sinalizadores passados para os métodos Erase(). E, ao contrário do primeiro método, esses métodos não recebem a variável que indica a cor do preenchimento do painel, mas, sim, um array das cores usadas para o preenchimento de gradiente.

Os métodos Erase() da classe pai CGCnvElement, usados para pintar a área retangular do objeto, permitem pintar com uma única cor ou com diferentes métodos de preenchimento de gradiente. Nós os vimos anteriormente ao criar uma classe de objeto-elemento gráfico.

Você pode se familiarizar com todos esses métodos nos arquivos anexados ao artigo.

Para facilitar o acesso e o trabalho com controles por meio de nossos programas, na classe principal da biblioteca CEngine no arquivo \MQL5\Include\DoEasy\Engine.mqh na seção pública escrevemos métodos para acesso rápido a esses objetos e na seção privada da classe adicionamos uma variável para armazenar o prefixo dos nomes dos objetos:

   ENUM_PROGRAM_TYPE    m_program_type;                  // Program type
   string               m_name_program;                  // Program name
   string               m_name_prefix;                   // Object name prefix
//--- Return the counter index by id
   int                  CounterIndex(const int id) const;

...

//--- Return the list of graphical elements by chart ID and object name
   CArrayObj           *GetListCanvElementByName(const long chart_id,const string name)
                          {
                           return this.m_graph_objects.GetListCanvElementByName(chart_id,name);
                          }
//--- Return the list of graphical elements by object type
   CArrayObj           *GetListCanvElementByType(const ENUM_GRAPH_ELEMENT_TYPE type)
                          {
                           return this.m_graph_objects.GetListCanvElementByType(type);
                          }
   
//--- Return the graphical element by chart ID and object name
   CGCnvElement        *GetCanvElementByName(const long chart_id,const string name)
                          {
                           return this.m_graph_objects.GetCanvElement(chart_id,name);
                          }
//--- Return the graphical element by chart and object IDs
   CGCnvElement        *GetCanvElementByID(const long chart_id,const int element_id)
                          {
                           return this.m_graph_objects.GetCanvElement(chart_id,element_id);
                          }
   
//--- Return the WForm Element object by object name on the current chart
   CGCnvElement        *GetWFElement(const string name)
                          {
                           string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_ELEMENT);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Return the WForm Element object by chart ID and object name
   CGCnvElement        *GetWFElement(const long chart_id,const string name)
                          {
                           string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_ELEMENT);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Return the WForm Element object by object ID
   CGCnvElement        *GetWFElement(const int element_id)
                          {
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_ELEMENT);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
   
//--- Return the WForm Form object by object name on the current chart
   CForm               *GetWFForm(const string name)
                          {
                           string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_FORM);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Return the WForm Form object by chart ID and object name
   CForm               *GetWFForm(const long chart_id,const string name)
                          {
                           string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_FORM);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Return the WForm Element object by object ID
   CForm               *GetWFForm(const int element_id)
                          {
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_FORM);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
   
//--- Return the WForm Panel object by object name on the current chart
   CPanel              *GetWFPanel(const string name)
                          {
                           string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_PANEL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Return the WForm Panel object by chart ID and object name
   CPanel              *GetWFPanel(const long chart_id,const string name)
                          {
                           string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_PANEL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Return the WForm Panel object by object ID
   CPanel              *GetWFPanel(const int element_id)
                          {
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_PANEL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }

A lógica de todos os métodos consiste em uma simples filtragem de listas pelas propriedades especificadas dos objetos, que vimos repetidamente e não devem causar dificuldades. Em qualquer caso, você sempre pode colocar suas dúvidas na discussão do artigo.

Vamos escrever um método que cria um objeto WinForm Element:

//--- Create the WinForm Element object
   CGCnvElement        *CreateWFElement(const long chart_id,
                                        const int subwindow,
                                        const string name,
                                        const int x,
                                        const int y,
                                        const int w,
                                        const int h,
                                        color &clr[],
                                        const uchar opacity,
                                        const bool v_gradient=true,
                                        const bool c_gradient=false,
                                        const bool redraw=false)
                          {
                           //--- Get the created object ID
                           int obj_id=
                             (
                              //--- In case of a vertical gradient:
                              v_gradient ?
                                (
                                 //--- if not a cyclic gradient, create an object with the vertical gradient filling
                                 !c_gradient ? this.m_graph_objects.CreateElementVGradient(chart_id,subwindow,name,x,y,w,h,clr,opacity,false,true,redraw) :
                                 //--- otherwise, create an object with the cyclic vertical gradient filling
                                 this.m_graph_objects.CreateElementVGradientCicle(chart_id,subwindow,name,x,y,w,h,clr,opacity,false,true,redraw)
                                ) :
                              //--- If this is not a vertical gradient:
                              !v_gradient ?
                                (
                                 //--- if not a cyclic gradient, create an object with the horizontal gradient filling
                                 !c_gradient ? this.m_graph_objects.CreateElementHGradient(chart_id,subwindow,name,x,y,w,h,clr,opacity,false,true,redraw) :
                                 //--- otherwise, create an object with the cyclic horizontal gradient filling
                                 this.m_graph_objects.CreateElementHGradientCicle(chart_id,subwindow,name,x,y,w,h,clr,opacity,false,true,redraw)
                                ) :
                              WRONG_VALUE
                             );
                           //--- return the pointer to an object by its ID
                           return this.GetWFElement(obj_id);
                          }

Dependendo do tipo de preenchimento (gradiente vertical ou horizontal, cíclico ou não), chamamos os métodos apropriados para criar um objeto elemento gráfico, que escrevemos acima na classe coleção de elementos gráficos.

Com base neste método, escrevemos métodos para criar objetos-elementos gráficos no gráfico atual e na subjanela especificada e no gráfico atual na janela principal:

//--- Create the WinForm Element object in the specified subwindow on the current chart
   CGCnvElement        *CreateWFElement(const int subwindow,
                                        const string name,
                                        const int x,
                                        const int y,
                                        const int w,
                                        const int h,
                                        color &clr[],
                                        const uchar opacity,
                                        const bool v_gradient=true,
                                        const bool c_gradient=false,
                                        const bool redraw=false)
                          {
                           return this.CreateWFElement(::ChartID(),subwindow,name,x,y,w,h,clr,opacity,v_gradient,c_gradient,redraw);
                          }
//--- Create the WinForm Element object in the main window of the current chart
   CGCnvElement        *CreateWFElement(const string name,
                                        const int x,
                                        const int y,
                                        const int w,
                                        const int h,
                                        color &clr[],
                                        const uchar opacity,
                                        const bool v_gradient=true,
                                        const bool c_gradient=false,
                                        const bool redraw=false)
                          {
                           return this.CreateWFElement(::ChartID(),0,name,x,y,w,h,clr,opacity,v_gradient,c_gradient,redraw);
                          }

Os métodos retornam o resultado da chamada do método de criação de um elemento gráfico, indicando o gráfico e os identificadores de subjanela necessários.

Da mesma forma, escrevemos métodos para criar objetos WinForm Form:

//--- Create the WinForm Form object
   CForm               *CreateWFForm(const long chart_id,
                                     const int subwindow,
                                     const string name,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h,
                                     color &clr[],
                                     const uchar opacity,
                                     const bool movable,
                                     const bool v_gradient=true,
                                     const bool c_gradient=false,
                                     const bool shadow=false,
                                     const bool redraw=false)
                          {
                           int obj_id=
                             (
                              v_gradient ?
                                (
                                 !c_gradient ? this.m_graph_objects.CreateFormVGradient(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,shadow,redraw) :
                                 this.m_graph_objects.CreateFormVGradientCicle(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,shadow,redraw)
                                ) :
                              !v_gradient ?
                                (
                                 !c_gradient ? this.m_graph_objects.CreateFormVGradient(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,shadow,redraw) :
                                 this.m_graph_objects.CreateFormVGradientCicle(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,shadow,redraw)
                                ) :
                              WRONG_VALUE
                             );
                           return this.GetWFForm(obj_id);
                          }
//--- Create the WinForm Form object in the specified subwindow on the current chart
   CForm               *CreateWFForm(const int subwindow,
                                     const string name,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h,
                                     color &clr[],
                                     const uchar opacity,
                                     const bool movable,
                                     const bool v_gradient=true,
                                     const bool c_gradient=false,
                                     const bool shadow=false,
                                     const bool redraw=false)
                          {
                           return this.CreateWFForm(::ChartID(),subwindow,name,x,y,w,h,clr,opacity,movable,v_gradient,c_gradient,shadow,redraw);
                          }
//--- Create the WinForm Form object in the main window of the current chart
   CForm               *CreateWFForm(const string name,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h,
                                     color &clr[],
                                     const uchar opacity,
                                     const bool movable,
                                     const bool v_gradient=true,
                                     const bool c_gradient=false,
                                     const bool shadow=false,
                                     const bool redraw=false)
                          {
                           return this.CreateWFForm(::ChartID(),0,name,x,y,w,h,clr,opacity,movable,v_gradient,c_gradient,shadow,redraw);
                          }

Tudo é exatamente o mesmo aqui: um método cria um objeto-forma com o preenchimento especificado e os outros dois métodos retornam o resultado da chamada do primeiro, mas especificando o gráfico e o ID da subjanela desejados.

Exatamente da mesma forma, escrevemos métodos para criar um objeto Painel do WinForm:

//--- Create the WinForm Panel object
   CForm               *CreateWFPanel(const long chart_id,
                                      const int subwindow,
                                      const string name,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      color &clr[],
                                      const uchar opacity,
                                      const bool movable,
                                      const bool v_gradient=true,
                                      const bool c_gradient=false,
                                      const int frame_width=-1,
                                      const ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL,
                                      const bool shadow=false,
                                      const bool redraw=false)
                          {
                           int obj_id=
                             (
                              v_gradient ?
                                (
                                 !c_gradient ? this.m_graph_objects.CreatePanelVGradient(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,frame_width,frame_style,shadow,redraw) :
                                 this.m_graph_objects.CreatePanelVGradientCicle(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,frame_width,frame_style,shadow,redraw)
                                ) :
                              !v_gradient ?
                                (
                                 !c_gradient ? this.m_graph_objects.CreatePanelHGradient(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,frame_width,frame_style,shadow,redraw) :
                                 this.m_graph_objects.CreatePanelHGradientCicle(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,frame_width,frame_style,shadow,redraw)
                                ) :
                              WRONG_VALUE
                             );
                           return this.GetWFPanel(obj_id);
                          }
//--- Create the WinForm Panel object in the specified subwindow on the current chart
   CForm               *CreateWFPanel(const int subwindow,
                                      const string name,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      color &clr[],
                                      const uchar opacity,
                                      const bool movable,
                                      const bool v_gradient=true,
                                      const bool c_gradient=false,
                                      const int frame_width=-1,
                                      const ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL,
                                      const bool shadow=false,
                                      const bool redraw=false)
                          {
                           return this.CreateWFPanel(::ChartID(),subwindow,name,x,y,w,h,clr,opacity,movable,v_gradient,c_gradient,frame_width,frame_style,shadow,redraw);
                          }
//--- Create the WinForm Panel object in the main window of the current chart
   CForm               *CreateWFPanel(const string name,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      color &clr[],
                                      const uchar opacity,
                                      const bool movable,
                                      const bool v_gradient=true,
                                      const bool c_gradient=false,
                                      const int frame_width=-1,
                                      const ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL,
                                      const bool shadow=false,
                                      const bool redraw=false)
                          {
                           return this.CreateWFPanel(::ChartID(),0,name,x,y,w,h,clr,opacity,movable,v_gradient,c_gradient,frame_width,frame_style,shadow,redraw);
                          }

Vamos deixar esses métodos para uma análise independente, pois eles são todos idênticos aos discutidos acima.

No construtor da classe, criamos um prefixo para o nome dos objetos gráficos:

//+------------------------------------------------------------------+
//| CEngine constructor                                              |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true),
                     m_last_trade_event(TRADE_EVENT_NO_EVENT),
                     m_last_account_event(WRONG_VALUE),
                     m_last_symbol_event(WRONG_VALUE),
                     m_global_error(ERR_SUCCESS)
  {
   this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif;
   this.m_is_tester=::MQLInfoInteger(MQL_TESTER);
   this.m_program_type=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE);
   this.m_name_program=::MQLInfoString(MQL_PROGRAM_NAME);
   this.m_name_prefix=this.m_name_program+"_";
   
//--- ...

//--- ...

  }

Como todos devem ter notado, a variável m_name foi renomeada para m_name_program para indicar com mais precisão sua finalidade.


Teste

Para testar, vamos pegar o Expert Advisor do artigo anterior e salvá-lo na nova pasta \MQL5\Experts\TestDoEasy\Part103\ com o novo nome TestDoEasyPart103.mq5.

Agora a criação de controles será mais fácil em comparação com o Expert Advisor anterior, porque temos métodos na classe CEngine para criá-los e simultaneamente obter ponteiros para os objetos criados. Depois de criar o objeto-painel, vamos criar cinco objetos-formas vinculados a ele:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set EA global variables
   ArrayResize(array_clr,2);        // Array of gradient filling colors
   array_clr[0]=C'26,100,128';      // Original ≈Dark-azure color
   array_clr[1]=C'35,133,169';      // Lightened original color
//--- Create the array with the current symbol and set it to be used in the library
   string array[1]={Symbol()};
   engine.SetUsedSymbols(array);
   //--- Create the timeseries object for the current symbol and period, and show its description in the journal
   engine.SeriesCreate(Symbol(),Period());
   engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions
//--- Create form objects
   string name="";
   int obj_id=WRONG_VALUE;
   CArrayObj *list=NULL;
   CForm *form=NULL;
   for(int i=0;i<FORMS_TOTAL;i++)
     {
      form=engine.CreateWFForm("Form_0"+string(i+1),30,(form==NULL ? 100 : form.BottomEdge()+20),100,30,array_clr,245,true);
      if(form==NULL)
         continue;
      //--- Set ZOrder to zero, display the text describing the gradient type and update the form
      //--- Text parameters: the text coordinates and the anchor point in the form center
      //--- Create a new text animation frame with the ID of 0 and display the text on the form
      form.SetZorder(0,false);
      form.TextOnBG(0,form.TypeElementDescription()+": ID "+(string)form.ID()+", ZD "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,false);
     }
//--- Create four graphical elements
   CGCnvElement *elm=NULL;
   array_clr[0]=C'0x65,0xA4,0xA9';
   array_clr[1]=C'0x48,0x75,0xA2';
//--- Vertical gradient
   elm=engine.CreateWFElement("CElmVG",form.RightEdge()+20,20,200,50,array_clr,127,true);
   if(elm!=NULL)
     {
      elm.SetFontSize(10);
      elm.Text(elm.Width()/2,elm.Height()/2,elm.TypeElementDescription()+": ID "+(string)elm.ID()+", ZD "+(string)elm.Zorder(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER);
      elm.Update();
     }
//--- Vertical cyclic gradient
   elm=engine.CreateWFElement("CElmVGC",form.RightEdge()+20,80,200,50,array_clr,127,true,true);
   if(elm!=NULL)
     {
      elm.SetFontSize(10);
      elm.Text(elm.Width()/2,elm.Height()/2,elm.TypeElementDescription()+": ID "+(string)elm.ID()+", ZD "+(string)elm.Zorder(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER);
      elm.Update();
     }
//--- Horizontal gradient
   elm=engine.CreateWFElement("CElmHG",form.RightEdge()+20,140,200,50,array_clr,127,false,false);
   if(elm!=NULL)
     {
      elm.SetFontSize(10);
      elm.Text(elm.Width()/2,elm.Height()/2,elm.TypeElementDescription()+": ID "+(string)elm.ID()+", ZD "+(string)elm.Zorder(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER);
      elm.Update();
     }
//--- Horizontal cyclic gradient
   elm=engine.CreateWFElement("CElmHGC",form.RightEdge()+20,200,200,50,array_clr,127,false,true);
   if(elm!=NULL)
     {
      elm.SetFontSize(10);
      elm.Text(elm.Width()/2,elm.Height()/2,elm.TypeElementDescription()+": ID "+(string)elm.ID()+", ZD "+(string)elm.Zorder(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER);
      elm.Update();
     }
//--- Create WinForms Panel object
   CPanel *pnl=NULL;
   pnl=engine.CreateWFPanel("WFPanel",elm.RightEdge()+20,50,230,150,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true);
   if(pnl!=NULL)
     {
      pnl.FontDrawStyle(FONT_STYLE_NORMAL);
      pnl.Bold(true);
      pnl.SetFontSize(10);
      pnl.TextOnBG(0,pnl.TypeElementDescription()+": ID "+(string)pnl.ID()+", ZD "+(string)pnl.Zorder(),pnl.Width()/2,pnl.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',pnl.Opacity());
      //--- In the loop, create five bound form objects located horizontally on top of the panel indented by 4 pixels from each other
      CGCnvElement *obj=NULL;
      for(int i=0;i<5;i++)
        {
         //--- create a form object with calculated coordinates along the X axis 
         pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_FORM,(obj==NULL ? 3 : obj.RightEdgeRelative()+4),3,40,30,C'0xCD,0xDA,0xD7',200,true);
         //--- To control the creation of bound objects,
         //--- get the pointer to the bound object by the loop index
         obj=pnl.GetElement(i);
         //--- take the pointer to the base object from the obtained object
         CPanel *p=obj.GetBase();
         //--- and display the name of a created bound object and the name of its base object in the journal
         Print
           (
            TextByLanguage("Объект ","Object "),obj.TypeElementDescription()," ",obj.Name(),
            TextByLanguage(" привязан к объекту "," is attached to object "),p.TypeElementDescription()," ",p.Name()
           );
        }
      pnl.Update(true);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Compilamos o Expert Advisor e o iniciamos no gráfico:


Bem, vemos que todos os objetos incorporados ao painel foram criados com sucesso, a sombra do objeto-painel fica em cima de outros objetos presentes no gráfico e se move junto com o objeto para o qual foi criada. Ao construir uma linha vertical, assim como qualquer outro objeto gráfico padrão, todos os controles, exceto os elementos gráficos fixos, ainda ficam acima do objeto gráfico recém-criado.

Ao criar controles vinculados ao painel, os registros serão exibidos no log, onde podemos verificar se cada um dos objetos vinculados possui um ponteiro para o objeto base:

Object Form TestDoEasyPart103_WFPanel_Elm01 is attached to object Control element "Panel" TestDoEasyPart103_WFPanel
Object Form TestDoEasyPart103_WFPanel_Elm02 is attached to object Control element "Panel" TestDoEasyPart103_WFPanel
Object Form TestDoEasyPart103_WFPanel_Elm03 is attached to object Control element "Panel" TestDoEasyPart103_WFPanel
Object Form TestDoEasyPart103_WFPanel_Elm04 is attached to object Control element "Panel" TestDoEasyPart103_WFPanel
Object Form TestDoEasyPart103_WFPanel_Elm05 is attached to object Control element "Panel" TestDoEasyPart103_WFPanel


O que virá a seguir?

No próximo artigo, continuaremos trabalhando na funcionalidade do controle "Painel".

Todos os arquivos da versão atual da biblioteca, os arquivos do EA de teste e o indicador de controle de eventos do gráfico para MQL5 estão anexados abaixo. Você pode baixá-los e testar tudo sozinho. Se você tiver dúvidas, comentários e sugestões, pode colocá-los nos comentários do artigo.

Voltar ao conteúdo

*Artigos desta série:

DoEasy. Controles (Parte 1): Primeiros passos
DoEasy. Controles (Parte 2): Continuamos trabalhando na classe CPanel


Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/10733

Arquivos anexados |
MQL5.zip (4337.26 KB)
DoEasy. Controles (Parte 4): Controle "Painel", parâmetros Padding e Dock DoEasy. Controles (Parte 4): Controle "Painel", parâmetros Padding e Dock
Neste artigo, vamos gerar o funcionamento de alguns parâmetros de painel, nomeadamente Padding (margens internas/campos para todos os lados do elemento) e Dock (a forma como o objeto está localizado dentro do contêiner).
Desenvolvendo um EA de negociação do zero (Parte 26): Em direção ao futuro (I) Desenvolvendo um EA de negociação do zero (Parte 26): Em direção ao futuro (I)
Vamos levar nosso sistema de ordens para um outro patamar, mas antes temos algumas coisas a resolver. O problema é que existem questões que são dependentes de como você deseja operar e que tipo de coisa você estará fazendo no momento em que estiver operando.
Como desenvolver um sistema de negociação baseado no indicador ADX Como desenvolver um sistema de negociação baseado no indicador ADX
Neste artigo, nós continuaremos nossa série sobre como projetar um sistema de negociação usando os indicadores mais populares e falaremos sobre o indicador Índice Direcional Médio (ADX). Nós aprenderemos este indicador em detalhes para entendê-lo bem e aprenderemos como usá-lo através de uma estratégia simples. Ao aprender algo profundamente, nós podemos obter mais "insights" e podemos usá-lo melhor.
DoEasy. Controles (Parte 2): Continuamos trabalhando na classe CPanel DoEasy. Controles (Parte 2): Continuamos trabalhando na classe CPanel
Neste artigo, vamos nos livrar de alguns erros ao trabalhar com elementos gráficos e continuar desenvolvendo o controle CPanel. Isto último irá se tratar de métodos para definir os parâmetros da fonte, que é usada por padrão para todos os objetos de texto do painel, objetos esses que, por sua vez, podem ser localizados nele no futuro.