DoEasy. Controles (Parte 5): Objeto base WinForms, controle Painel, parâmetro AutoSize

Artyom Trishkin | 10 agosto, 2022

Conteúdo


Ideia

Antes de começar a preparar as propriedades AutoSize e AutoSizeMode do objeto-painel, vamos criar uma classe que serve de base para todos os objetos WinForms da biblioteca. Como muitas das propriedades desses objetos são herdadas umas das outras, as propriedades inerentes ao painel, cujo objeto estamos trabalhando agora, podem ser usadas para outros objetos WinForms. É por isso que, para não escrevermos, de fato, as mesmas propriedades para cada um dos objetos, criaremos um objeto WinForms base, do qual herdaremos todos os outros objetos do tipo em questão. O próprio objeto base será herdado da classe do objeto-forma onde a interação com o mouse é gerada.

Se um objeto localizado dentro de um painel tiver a propriedade Dock ativada (discutida no artigo anterior), esse objeto "grudará" nas bordas de seu contêiner. A borda do contêiner é especificada na propriedade DockMode. Neste caso, se cada objeto que vem a seguir colocado dentro do painel tiver o mesmo encaixe na borda de seu contêiner (painel) que o objeto anterior colocado dentro do painel, então ele já será vinculado ao lado mais próximo do objeto anterior e não, ao lado especificado do contêiner. Por isso, todos os objetos colocados dentro do painel com encaixe, por exemplo, na borda esquerda do contêiner, serão ordenados em uma única fileira da esquerda para a direita. E se o painel tiver o AutoSize habilitado, o contêiner será automaticamente aumentado em largura para que todos os objetos alinhados dentro dele não excedam a largura do contêiner. O contêiner deve comportar-se da mesma maneira se um objeto saliente da borda do painel for colocado dentro dele: se o painel tiver o AutoSize habilitado, ele deve ajustar o tamanho de seus lados para que o objeto não ultrapasse seus limites.

Além disso, há uma diferença se o objeto estiver vinculado a um dos lados do contêiner e o próprio contêiner tiver a propriedade AutoSize habilitada ou não.
Todos os objetos colocados dentro de um contêiner com o autodimensionamento habilitado serão colocados em ordem de prioridade dada pela ordem do objeto na lista de objetos vinculados ao contêiner. Isso nos dará a oportunidade de predeterminar a localização dos objetos dentro do contêiner, contêiner esse onde o tamanho deles se ajusta automaticamente ao tamanho total de todos os elementos vinculados ao mesmo. Vamos ver isso em artigos ulteriores. Hoje vamos modificar as classes da biblioteca de acordo com a tarefa proposta, que é a de criar um novo objeto base WinForms e preparar a propriedade Autosize ao criar um elemento vinculado a ele diretamente desde o painel.

Além das tarefas apresentadas, levaremos a cabo uma pequena otimização da construção de elementos gráficos dentro do painel. Como precisamos primeiro gerar todos os elementos vinculados ao painel, respeitando seu números de ordem e seu valor de encaixe (Dock), e depois redimensionar o painel caso ele tenha a propriedade de autodimensionamento definida para caber em seu interior, e se realmente necessário, então primeiro precisamos colocar "virtualmente" todos os elementos dentro do painel, depois ver o quanto precisamos redimensionar o painel, alterá-lo se necessário, e só então redesenhar todos os elementos localizados dentro do mesmo. Se, em vez disso, nós desenharmos logo os elementos, o redimensionamento e o ponto de encaixe se tornará visível em tempo real, o que se reflete em itens corrompidos ao redor do painel, o que não é bonito, e está incorreto. Por isso, primeiro vamos ordenar os elementos de forma correta, redimensioná-los se necessário (isso depende do método de vinculação do elemento e seu número na lista de elementos vinculados), depois devemos calcular quanto o painel precisa ser redimensionado e, somente depois de fazer tudo isso, redesenhar o painel e os elementos novamente localizados dentro dele mantendo a nova ordem.


Modificando as classes da biblioteca

Como estamos criando um novo objeto de biblioteca hoje, precisamos adicionar seu tipo à lista de tipos de objetos de biblioteca. Também precisaremos saber o tipo de objeto ao acessar o objeto substrato do painel.
Adicionamos um novo tipo, isto é, um objeto base WinForms, à lista de tipos de objeto da biblioteca no arquivo \MQL5\Include\DoEasy\Defines.mqh:

//+------------------------------------------------------------------+
//| List of library object types                                     |
//+------------------------------------------------------------------+
enum ENUM_OBJECT_DE_TYPE
  {
//--- Graphics
   OBJECT_DE_TYPE_GBASE =  COLLECTION_ID_LIST_END+1,              // "Base object of all library graphical objects" object type
   OBJECT_DE_TYPE_GELEMENT,                                       // "Graphical element" object type
   OBJECT_DE_TYPE_GFORM,                                          // Form object type
   OBJECT_DE_TYPE_GFORM_CONTROL,                                  // "Form for managing pivot points of graphical object" object type
   OBJECT_DE_TYPE_GSHADOW,                                        // Shadow object type
   //--- WinForms
   OBJECT_DE_TYPE_GWF_BASE,                                       // inForms Base object type (base abstract WinForms object)
   OBJECT_DE_TYPE_GWF_PANEL,                                      // WinForms Panel object type
//--- Animation
   OBJECT_DE_TYPE_GFRAME,                                         // "Single animation frame" object type
//--- ...
//--- ...
  }


Adicionamos dois novos tipos à lista de tipos de elementos gráficos, mais especificamente um elemento gráfico substrato e um objeto base WinForms, e renomeamos a macro-substituição GRAPH_ELEMENT_TYPE_PANEL para GRAPH_ELEMENT_TYPE_WF_PANEL:

//+------------------------------------------------------------------+
//| 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_WF_UNDERLAY,                    // Panel object underlay
   GRAPH_ELEMENT_TYPE_WF_BASE,                        // Windows Forms container base object
   GRAPH_ELEMENT_TYPE_WF_PANEL,                       // Windows Forms Panel
  };
//+------------------------------------------------------------------+

Graças a esses tipos, agora sabemos qual objeto está selecionado, e faremos algo com ele se esse tipo for o que estamos procurando.

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

   MSG_GRAPH_ELEMENT_TYPE_WINDOW,                     // Window
   MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY,                // Underlay of the Panel WinForms control object
   MSG_GRAPH_ELEMENT_TYPE_WF_BASE,                    // WinForms base control
   MSG_GRAPH_ELEMENT_TYPE_WF_PANEL,                   // Panel control

e as mensagens de texto correspondentes aos índices recém-adicionados:

   {"Окно","Window"},
   {"Подложка объекта-элемента управления WinForms \"Панель\"","Underlay object-control WinForms \"Panel\""},
   {"Базовый элемент управления WinForms","Base WinForms control"},
   {"Элемент управления \"Panel\"","Control element \"Panel\""},


Todos os objetos gráficos da biblioteca são herdados da classe do objeto gráfico base da biblioteca CGBaseObj. É nesta classe que estão localizados todos os métodos básicos para trabalhar com qualquer objeto gráfico da biblioteca. No arquivo \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh , ao método que retorna a descrição do tipo de elemento gráfico, adicionamos a exibição da descrição de dois novos tipos de elementos gráficos da biblioteca:

//+------------------------------------------------------------------+
//| Return the description of the graphical element type             |
//+------------------------------------------------------------------+
string CGBaseObj::TypeElementDescription(void)
  {
   return
     (
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD)           :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)  :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_ELEMENT            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT)            :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_SHADOW_OBJ         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ)         :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_FORM               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM)               :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WINDOW             ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW)             :
      //---
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY)        :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_BASE            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE)            :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_PANEL           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL)           :
      "Unknown"
     );
  }
//+------------------------------------------------------------------+


Vamos tornar alguns métodos virtuais na classe de objeto do elemento gráfico na tela, da qual são herdados os demais objetos gráficos Canvas da biblioteca. A classe está localizada no arquivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh.
A questão é que nem todos os objetos descendentes desta classe podem ser afetados pela implementação de alguns métodos nela criados. Ao fazê-los virtuais aqui, no entanto, tornamos possível mudar estes métodos naquelas classes descendentes onde a implementação dos métodos deve ser diferente daquela criada nesta classe.

//+------------------------------------------------------------------+
//| Methods of simplified access to object properties                |
//+------------------------------------------------------------------+
//--- Set the (1) X, (2) Y coordinates, (3) element width, (4) height, (5) right (6) and bottom edge,
   virtual bool      SetCoordX(const int coord_x);
   virtual bool      SetCoordY(const int coord_y);
   virtual bool      SetWidth(const int width);
   virtual bool      SetHeight(const int height);
   void              SetRightEdge(void)                        { this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge());           }
   void              SetBottomEdge(void)                       { this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.BottomEdge());         }

...

//+------------------------------------------------------------------+
//| The methods of filling, clearing and updating raster data        |
//+------------------------------------------------------------------+
//--- Clear the element filling it with color and opacity
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Clear the element with a gradient fill
   virtual void      Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);
//--- Clear the element completely
   virtual void      Erase(const bool redraw=false);
//--- Update the element
   void              Update(const bool redraw=false)           { this.m_canvas.Update(redraw);                                         }

Agora, nessas classes descendentes onde é necessária uma implementação diferente desses métodos, simplesmente escreveremos exatamente os mesmos métodos virtuais, mas com sua própria implementação, que é diferente daquela escrita aqui, e são precisamente esses métodos virtuais das classes descendentes que serão chamadas ao acessar a classe pai do método virtual.

Em alguns métodos das classes que estão sendo modificadas hoje, precisamos adicionar um sinalizador que indica a necessidade de redesenhar o objeto gráfico. Isso é necessário para otimizar o processamento de listas de objetos, para não redesenhar constantemente cada objeto da lista, mas, sim, primeiro processar todos os objetos da lista (por exemplo, redimensionar cada um e movê-lo para um novo local), e após processar toda a lista, só com redesenhar cada objeto já em suas novas coordenadas ou usando seu novo tamanho. Isto é visualmente mais rápido do que redesenhar cada objeto da lista logo após alterar suas dimensões e coordenadas e ir para o próximo objeto da lista e redimensionar, ajustar as coordenadas e redesenhar exatamente da mesma forma.

Alteramos os nomes das variáveis e dos métodos no arquivo da classe do objeto-sombra \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh — removemos a palavra "shadow". Só acho que encontrar essa substring nos nomes das variáveis e nos nomes dos métodos do objeto sombra é desnecessário.

Como ao redimensionar o objeto que projeta a sombra, precisaremos redesenhar completamente a sombra com novas dimensões, mas com os mesmos parâmetros de sombra que estavam antes do redimensionamento, adicionaremos mais uma variável que armazena o raio do desfoque da sombra, acrescentamos dois métodos para obter acesso a uma nova variável, e no método de redesenho de sombra inserimos o sinalizador que indica a necessidade de redesenhar todo o objeto-sombra:

//+------------------------------------------------------------------+
//| Shadows object class                                             |
//+------------------------------------------------------------------+
class CShadowObj : public CGCnvElement
  {
private:
   color             m_color;                         // Shadow color
   uchar             m_opacity;                       // Shadow opacity
   uchar             m_blur;                          // Blur
//--- Gaussian blur
   bool              GaussianBlur(const uint radius);
//--- Return the array of weight ratios
   bool              GetQuadratureWeights(const double mu0,const int n,double &weights[]);
//--- Draw the object shadow form
   void              DrawShadowFigureRect(const int w,const int h);

public:
//--- Constructor
                     CShadowObj(const long chart_id,
                                const int subwindow,
                                const string name,
                                const int x,
                                const int y,
                                const int w,
                                const int h);

//--- 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; }
   
//--- Draw an object shadow
   void              Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw);
   
//+------------------------------------------------------------------+
//| Methods of simplified access to object properties                |
//+------------------------------------------------------------------+
//--- (1) Set and (2) return the shadow color
   void              SetColor(const color colour)                             { this.m_color=colour;     }
   color             Color(void)                                        const { return this.m_color;     }
//--- (1) Set and (2) return the shadow opacity
   void              SetOpacity(const uchar opacity)                          { this.m_opacity=opacity;  }
   uchar             Opacity(void)                                      const { return this.m_opacity;   }
//--- (1) Set and (2) return the shadow blur
   void              SetBlur(const uchar blur)                                { this.m_blur=blur;        }
   uchar             Blur(void)                                         const { return this.m_blur;      }
  };
//+------------------------------------------------------------------+


Estando no construtor de classe, inserimos o valor de opacidade de sombra padrão especificado através da macro-substituição CLR_DEF_SHADOW_OPACITY no arquivo Defines.mqh da biblioteca, e especificamos o valor de desfoque de sombra padrão também através da macro-substituição DEF_SHADOW_BLUR do mesmo arquivo:

//+------------------------------------------------------------------+
//| 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=CLR_DEF_SHADOW_OPACITY;
   this.m_blur=DEF_SHADOW_BLUR;
   color gray=CGCnvElement::ChangeColorSaturation(ChartColorBackground(),-100);
   this.m_color=CGCnvElement::ChangeColorLightness(gray,-50);
   this.m_shadow=false;
   this.m_visible=true;
   CGCnvElement::Erase();
  }
//+------------------------------------------------------------------+


Na implementação do método que desenha a sombra do objeto, agora indicaremos explicitamente o sinalizador para redesenho de sombra e, em vez da variável local radius, usaremos a nova variável m_blur. Isso nos lembra do valor de desfoque de sombra para o redesenho imediato do objeto-sombra com os parâmetros com os quais foi originalmente desenhado:

//+------------------------------------------------------------------+
//| Draw the object shadow                                           |
//+------------------------------------------------------------------+
void CShadowObj::Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw)
  {
//--- Set the shadow shift values to the variables by X and Y axes
   this.SetCoordXRelative(shift_x);
   this.SetCoordYRelative(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
   this.m_blur=(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(this.m_blur))
      return;
//--- Shift the shadow object by X/Y offsets specified in the method arguments and update the canvas
   CGCnvElement::Move(this.CoordX()+this.CoordXRelative(),this.CoordY()+this.CoordYRelative(),redraw);
   CGCnvElement::Update(redraw);
  }
//+------------------------------------------------------------------+


Uma vez que continuar a fazer o objeto base de todos os objetos da biblioteca WinForms, algumas das variáveis e métodos já existentes nas classes escritas de objetos WinForms e suas classes pai precisarão ser transferidas para a nova classe base ou para seu pai - a classe CForm. Isso é necessário para que todas essas variáveis e métodos estejam disponíveis nos níveis dessas classes da hierarquia de herança onde são necessários.

Por exemplo, as coordenadas e dimensões de um objeto durante sua criação são armazenadas em variáveis na classe CPanel. Porém, precisaremos dos mesmos dados em outros objetos WinForms. Por esse motivo, vamos transferi-los para a classe pai dos objetos WinForms no arquivo \MQL5\Include\DoEasy\Objects\Graph\Form.mqh.
Parecia lógico transferir essas variáveis e métodos para a classe base de objetos WinForms, mas como podemos precisar de tais dados para objetos-formas, cuja classe será o pai para a classe de objeto base de todos os objetos WinForms, vamos transferir essas variáveis para ele:

//+------------------------------------------------------------------+
//| Form object class                                                |
//+------------------------------------------------------------------+
class CForm : public CGCnvElement
  {
private:
   CArrayObj         m_list_elements;                          // List of attached elements
   CAnimations      *m_animations;                             // Pointer to the animation object
   CShadowObj       *m_shadow_obj;                             // Pointer to the shadow object
   CMouseState       m_mouse;                                  // "Mouse status" class object
   ENUM_MOUSE_FORM_STATE m_mouse_form_state;                   // Mouse status relative to the form
   ushort            m_mouse_state_flags;                      // Mouse status flags
   color             m_color_frame;                            // Form frame color
   int               m_offset_x;                               // Offset of the X coordinate relative to the cursor
   int               m_offset_y;                               // Offset of the Y coordinate relative to the cursor
   int               m_init_x;                                 // Newly created form X coordinate
   int               m_init_y;                                 // Newly created form Y coordinate
   int               m_init_w;                                 // Newly created form width
   int               m_init_h;                                 // Newly created form height

//--- Reset the array size of (1) text, (2) rectangular and (3) geometric animation frames
   void              ResetArrayFrameT(void);
   void              ResetArrayFrameQ(void);
   void              ResetArrayFrameG(void);
   
//--- Create a new graphical object

Para otimizar o código, o método CreateNewElement() será dividido em dois, uma vez que criaremos outro método CreateAndAddNewElement(), onde um novo elemento será criado e adicionado à lista. Vamos declarar este método na seção protegida da classe e transferir métodos da classe CPanel para a seção public para trabalhar com variáveis que armazenam as coordenadas iniciais e tamanhos do objeto:

protected:
   CArrayObj         m_list_tmp;                               // List for storing the pointers
   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;   }
//--- Update coordinates of bound objects
   virtual bool      MoveDependentObj(const int x,const int y,const bool redraw=false);
//--- Create a new bound element and add it to the list of bound objects
   CGCnvElement     *CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                            CGCnvElement *main,
                                            const int x,
                                            const int y,
                                            const int w,
                                            const int h,
                                            const color colour,
                                            const uchar opacity,
                                            const bool activity);
   
public:
//--- Return the initial (1) X and (2) Y coordinate, (3) form width and (4) height
   int               GetCoordXInit(void)           const { return this.m_init_x;             }
   int               GetCoordYInit(void)           const { return this.m_init_y;             }
   int               GetWidthInit(void)            const { return this.m_init_w;             }
   int               GetHeightInit(void)           const { return this.m_init_h;             }
//--- Set the initial (1) X and (2) Y coordinate, (3) form width and (4) height
   void              SetCoordXInit(const int value)      { this.m_init_x=value;              }
   void              SetCoordYInit(const int value)      { this.m_init_y=value;              }
   void              SetWidthInit(const int value)       { this.m_init_w=value;              }
   void              SetHeightInit(const int value)      { this.m_init_h=value;              }
//--- 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();     }


Vamos tornar o próprio método CreateNewElement() virtual e adicionar um sinalizador de redesenho de objeto recém-criado nele:

//--- Create a new attached element
   virtual bool      CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                      CGCnvElement *main,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool activity,
                                      const bool redraw);
//--- Add a new attached element
   bool              AddNewElement(CGCnvElement *obj,const int x,const int y);

Tornamos o método virtual para que em cada classe herdada consigamos fazer exatamente o mesmo método, mas com sua própria implementação. O sinalizador de redesenho do objeto é necessário para que, ao criar muitos objetos simultaneamente, não os exiba um por um logo após a criação, mas, sim, primeiro crie todos os objetos e depois ajuste as dimensões do painel onde eles são criados, e só então desenhe tudo ao mesmo tempo, uma vez que visualmente será mais rápido, e não haverá itens corrompidos com o painel redimensionável aparecendo constantemente à medida que cada objeto sucessivo é criado a partir de todos os objetos vinculados a ele, simultaneamente criados no loop.


No bloco de métodos para acesso simplificado às propriedades do objeto, vamos declarar dois métodos para trabalhar com a propriedade "Desfoque de sombra":

//+------------------------------------------------------------------+
//| Methods of simplified access to object properties                |
//+------------------------------------------------------------------+
//--- (1) Set and (2) get the form frame color
   void              SetColorFrame(const color colour)                        { this.m_color_frame=colour;     }
   color             ColorFrame(void)                                   const { return this.m_color_frame;     }
//--- (1) Set and (2) return the form shadow color
   void              SetColorShadow(const color colour);
   color             ColorShadow(void) const;
//--- (1) Set and (2) return the form shadow opacity
   void              SetOpacityShadow(const uchar opacity);
   uchar             OpacityShadow(void) const;
//--- (1) Set and (2) return the form shadow blur
   void              SetBlurShadow(const uchar blur);
   uchar             BlurShadow(void) const;

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


Em todos os construtores paramétricos, após chamar o método de inicialização da propriedade, escrevemos chamadas para os métodos de inicialização da propriedade que armazenam as dimensões e coordenadas iniciais do objeto:

//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CForm::CForm(const long chart_id,
             const int subwindow,
             const string name,
             const int x,
             const int y,
             const int w,
             const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,chart_id,subwindow,name,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GFORM; 
   this.Initialize();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+
//| Current chart constructor specifying the subwindow               |
//+------------------------------------------------------------------+
CForm::CForm(const int subwindow,
             const string name,
             const int x,
             const int y,
             const int w,
             const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,::ChartID(),subwindow,name,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GFORM; 
   this.Initialize();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+
//| Constructor on the current chart in the main chart window        |
//+------------------------------------------------------------------+
CForm::CForm(const string name,
             const int x,
             const int y,
             const int w,
             const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,::ChartID(),0,name,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GFORM; 
   this.Initialize();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+


No próprio método de inicialização, inseriremos zeros nesses valores:

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


Método que cria um novo elemento vinculado e o adiciona à lista de objetos vinculados:

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//| and add it to the list of bound objects                          |
//+------------------------------------------------------------------+
CGCnvElement *CForm::CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                            CGCnvElement *main,
                                            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 NULL;
     }
//--- Specify the element index in the list
   int num=this.m_list_elements.Total();
//--- Create a graphical element name
   string ns=(::StringLen((string)num)<2 ? ::IntegerToString(num,2,'0') : (string)num);
   string name="Elm"+ns;
//--- Get the screen 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 NULL;
//--- and add it to the list of bound graphical elements
   if(!this.AddNewElement(obj,elm_x,elm_y))
     {
      delete obj;
      return NULL;
     }
//--- Set the minimum properties for a bound graphical element
   obj.SetColorBackground(colour);
   obj.SetOpacity(opacity);
   obj.SetActive(activity);
   obj.SetMain(main);
   obj.SetBase(this.GetObject());
   obj.SetID(this.ID());
   obj.SetNumber(num);
   obj.SetCoordXRelative(x);
   obj.SetCoordYRelative(y);
   obj.SetZorder(this.Zorder(),false);
   obj.SetCoordXRelativeInit(x);
   obj.SetCoordYRelativeInit(y);
   return obj;
  }
//+------------------------------------------------------------------+

A lógica do método repete essencialmente a lógica do método CreateNewElement(), só que não há renderização incondicional do elemento criado. O método simplesmente retorna um ponteiro para um elemento gráfico criado com sucesso, ou NULL em caso de erro ao criá-lo ou adicioná-lo à lista. Além disso, um pequeno bug foi corrigido aqui - o objeto agora é definido como seu número na lista de objetos vinculados, e isso não acontecia anteriormente, e o número do objeto na lista, respectivamente, era sempre zero.

O método que cria um novo elemento vinculado agora parece diferente - todo o código para criar um novo objeto e adicioná-lo à lista foi movido para o método acima, que é chamado aqui. Caso o objeto não seja criado ou adicionado à lista, o método retorna false, caso contrário, o objeto é desenhado com o sinalizador de redesenho e true é retornado.

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CForm::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                             CGCnvElement *main,
                             const int x,
                             const int y,
                             const int w,
                             const int h,
                             const color colour,
                             const uchar opacity,
                             const bool activity,
                             const bool redraw)
  {
//--- Create a new graphical element
   CGCnvElement *obj=this.CreateAndAddNewElement(element_type,main,x,y,w,h,colour,opacity,activity);
//--- If the object has been created, draw the added object and return 'true'
   if(obj==NULL)
      return false;
   obj.Erase(colour,opacity,redraw);
   return true;
  }
//+------------------------------------------------------------------+


No método que desenha a sombra, agora você precisa passar o sinalizador de desenho de sombra para o método Draw() da classe do objeto sombra:

//+------------------------------------------------------------------+
//| Draw the shadow                                                  |
//+------------------------------------------------------------------+
void CForm::DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=DEF_SHADOW_BLUR)
  {
//--- If the shadow flag is disabled, exit
   if(!this.m_shadow)
      return;
//--- If there is no shadow object, create it
   if(this.m_shadow_obj==NULL)
      this.CreateShadowObj(colour,opacity);
//--- If the shadow object exists, draw the shadow on it,
//--- set the shadow object visibility flag and
//--- move the form object to the foreground
   if(this.m_shadow_obj!=NULL)
     {
      this.m_shadow_obj.Draw(shift_x,shift_y,blur,true);
      this.m_shadow_obj.SetVisible(true,false);
      this.BringToTop();
     }
  }
//+------------------------------------------------------------------+

Aqui, para desenhar a sombra, sempre passaremos true. Nos métodos de chamada por operador condicional é verificado se a sombra não precisar ser desenhada. Isso é mais fácil do que controlar o desenho de um retângulo de sombra não desfocado, acompanhar se ele é desenhado ou não antes de usar o desfoque desse retângulo e outras armadilhas relacionadas. Basta verificar se a sombra precisa ser desenhada e chamar esse método.


Método que define o desfoque da sombra da forma:

//+------------------------------------------------------------------+
//| Set the form shadow blur                                         |
//+------------------------------------------------------------------+
void CForm::SetBlurShadow(const uchar blur)
  {
   if(this.m_shadow_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT));
      return;
     }
   this.m_shadow_obj.SetBlur(blur);
  }
//+------------------------------------------------------------------+

Se o objeto-sombra não existir, uma mensagem será exibida informando que o objeto-sombra precisa ser criado primeiro, caso contrário, o método para definir a quantidade de desfoque para o objeto-sombra é chamado.

O método que retorna o desfoque da sombra da forma:

//+------------------------------------------------------------------+
//| Return the form shadow blur                                      |
//+------------------------------------------------------------------+
uchar CForm::BlurShadow(void) const
  {
   if(this.m_shadow_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT));
      return 0;
     }
   return this.m_shadow_obj.Blur();
  }
//+------------------------------------------------------------------+

Se o objeto-sombra não existir, será exibida uma mensagem informando que o objeto-sombra deve ser criado primeiro, caso contrário, ele retornará o valor de desfoque do objeto-sombra.

Além das alterações descritas acima, foram feitas pequenas melhorias na classe que não afetam sua lógica, portanto não as descreveremos aqui. Todas as alterações podem ser encontradas nos arquivos anexados ao artigo.


Classe do objeto base WinForms

Na pasta biblioteca \MQL5\Include\DoEasy\Objects\Graph\WForms\, criamos um novo arquivo WinFormBase.mqh da classe CWinFormBase. A classe deve ser herdada da classe de objeto-forma CForm. Para que a classe base fique visível neste arquivo, incluímos o arquivo de classe de objeto-forma nele:

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

Vamos transferir para esta classe as variáveis e métodos para trabalhar com eles a partir do arquivo de classe do objeto WinForms "Painel" do arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh. Em princípio, todas as variáveis e métodos que escrevemos para o objeto-painel estarão localizados aqui. E, claro, vamos adicionar novos.

Colocamos as seguintes variáveis na seção protegida da classe:

//+------------------------------------------------------------------+
//| Form object class                                                |
//+------------------------------------------------------------------+
class CWinFormBase : public CForm
  {
protected:
   color             m_fore_color;                                   // Default text color for all control objects
   ENUM_FW_TYPE      m_bold_type;                                    // Font width type
   ENUM_FRAME_STYLE  m_border_style;                                 // Control frame style
   bool              m_autosize;                                     // Flag of the element auto resizing depending on the content
   ENUM_CANV_ELEMENT_DOCK_MODE m_dock_mode;                          // Mode of binding control 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

private:

Na seção privada, colocamos o método que retorna os sinalizadores de fonte:

private:
//--- Return the font flags
   uint              GetFontFlags(void);

public:

e na seção pública declaramos métodos virtuais para limpar o elemento, seu redesenho e redimensionamento, construtores de classe e transferimos os seguintes métodos a partir do arquivo de classe CPanel:

public:
//--- Clear the element filling it with color and opacity
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Clear the element with a gradient fill
   virtual void      Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);
//--- Clear the element completely
   virtual void      Erase(const bool redraw=false);
//--- Redraw the object
   virtual void      Redraw(bool redraw);
//--- Set the new size for the (1) current object and (2) the object specified by index
   virtual bool      Resize(const int w,const int h,const bool redraw);
   virtual bool      Resize(const int index,const int w,const int h,const bool redraw);

//--- Constructors
                     CWinFormBase(const long chart_id,
                                  const int subwindow,
                                  const string name,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h);
                     CWinFormBase(const string name) : CForm(::ChartID(),0,name,0,0,0,0)
                       {
                        this.m_type=OBJECT_DE_TYPE_GWF_BASE; 
                       }
                       
//--- (1) Set and (2) return the default text color of all panel objects
   void              SetForeColor(const color clr)                   { this.m_fore_color=clr;               }
   color             ForeColor(void)                           const { return this.m_fore_color;            }

//--- (1) Set and (2) return the Bold font flag
   void              SetBold(const bool flag);
   bool              Bold(void);
//--- (1) Set and (2) return the Italic font flag
   void              SetItalic(const bool flag);
   bool              Italic(void);
//--- (1) Set and (2) return the Strikeout font flag
   void              SetStrikeout(const bool flag);
   bool              Strikeout(void);
//--- (1) Set and (2) return the Underline font flag
   void              SetUnderline(const bool flag);
   bool              Underline(void);
//--- (1) Set and (2) return the font style
   void              SetFontDrawStyle(ENUM_FONT_STYLE style);
   ENUM_FONT_STYLE   FontDrawStyle(void);
//--- (1) Set and (2) return the font width type
   void              SetFontBoldType(ENUM_FW_TYPE type);
   ENUM_FW_TYPE      FontBoldType(void)                        const { return this.m_bold_type;             }
//--- (1) Set and (2) return the frame style
   void              SetBorderStyle(const ENUM_FRAME_STYLE style)    { this.m_border_style=style;           }
   ENUM_FRAME_STYLE  BorderStyle(void)                         const { return this.m_border_style;          }

//--- (1) Set and (2) return the flag of the element auto resizing depending on the content
   virtual void      SetAutoSize(const bool flag,const bool redraw)  { this.m_autosize=flag;                }
   bool              AutoSize(void)                                  { return this.m_autosize;              }
   
//--- (1) Set and (2) return the mode of binding element borders to the container
   virtual void      SetDockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode,const bool redraw)
                       {
                        this.m_dock_mode=mode;
                       }
   ENUM_CANV_ELEMENT_DOCK_MODE DockMode(void)                  const { return this.m_dock_mode;             }
   
//--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides between the fields of this and another control
   void              SetMarginLeft(const int value)                  { this.m_margin[0]=value;              }
   void              SetMarginTop(const int value)                   { this.m_margin[1]=value;              }
   void              SetMarginRight(const int value)                 { this.m_margin[2]=value;              }
   void              SetMarginBottom(const int value)                { this.m_margin[3]=value;              }
   void              SetMarginAll(const int value)
                       {
                        this.SetMarginLeft(value); this.SetMarginTop(value); this.SetMarginRight(value); this.SetMarginBottom(value);
                       }
//--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields of this and another control
   int               MarginLeft(void)                          const { return this.m_margin[0];             }
   int               MarginTop(void)                           const { return this.m_margin[1];             }
   int               MarginRight(void)                         const { return this.m_margin[2];             }
   int               MarginBottom(void)                        const { return this.m_margin[3];             }

//--- 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
   virtual void      SetPaddingLeft(const uint value)
                       {
                        this.m_padding[0]=((int)value<this.m_frame_width_left ? this.m_frame_width_left : (int)value);
                       }
   virtual void      SetPaddingTop(const uint value)
                       {
                        this.m_padding[1]=((int)value<this.m_frame_width_top ? this.m_frame_width_top : (int)value);
                       }
   virtual void      SetPaddingRight(const uint value)
                       {
                        this.m_padding[2]=((int)value<this.m_frame_width_right ? this.m_frame_width_right : (int)value);
                       }
   virtual void      SetPaddingBottom(const uint value)
                       {
                        this.m_padding[3]=((int)value<this.m_frame_width_bottom ? this.m_frame_width_bottom : (int)value);
                       }
   virtual void      SetPaddingAll(const uint value)
                       {
                        this.SetPaddingLeft(value); this.SetPaddingTop(value); this.SetPaddingRight(value); this.SetPaddingBottom(value);
                       }
   
//--- Set the width of the element frame (1) to the left, (2) at the top, (3) to the right and (4) at the bottom
   virtual void      SetFrameWidthLeft(const uint value)             { this.m_frame_width_left=(int)value;  }
   virtual void      SetFrameWidthTop(const uint value)              { this.m_frame_width_top=(int)value;   }
   virtual void      SetFrameWidthRight(const uint value)            { this.m_frame_width_right=(int)value; }
   virtual void      SetFrameWidthBottom(const uint value)           { this.m_frame_width_bottom=(int)value;}
   virtual void      SetFrameWidthAll(const uint value)
                       {
                        this.SetFrameWidthLeft(value); this.SetFrameWidthTop(value); this.SetFrameWidthRight(value); this.SetFrameWidthBottom(value);
                       }
   
//--- Return the width of the element frame (1) to the left, (2) at the top, (3) to the right and (4) at the bottom
   int               FrameWidthLeft(void)                      const { return this.m_frame_width_left;      }
   int               FrameWidthTop(void)                       const { return this.m_frame_width_top;       }
   int               FrameWidthRight(void)                     const { return this.m_frame_width_right;     }
   int               FrameWidthBottom(void)                    const { return this.m_frame_width_bottom;    }
   
//--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields inside the control
   int               PaddingLeft(void)                         const { return this.m_padding[0];            }
   int               PaddingTop(void)                          const { return this.m_padding[1];            }
   int               PaddingRight(void)                        const { return this.m_padding[2];            }
   int               PaddingBottom(void)                       const { return this.m_padding[3];            }
   
  };
//+------------------------------------------------------------------+

Vamos tornar a maioria dos métodos transferidos virtuais, para poder substituí-los em classes herdadas, se necessário. Todos os métodos para definir propriedades agora têm o prefixo "Set". Isso indica a propriedade do método de maneira precisa.

No construtor paramétrico, definimos o tipo de elemento gráfico e o objeto da biblioteca e os valores da propriedade padrão, ou os valores que são passados nos parâmetros:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CWinFormBase::CWinFormBase(const long chart_id,
                           const int subwindow,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h) : CForm(chart_id,subwindow,name,x,y,w,h)
  {
//--- Set the graphical element and library object types as a base WinForms object
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BASE);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BASE);
   this.m_type=OBJECT_DE_TYPE_GWF_BASE; 
//--- Initialize all variables
   this.m_fore_color=CLR_DEF_FORE_COLOR;
   this.m_bold_type=FW_TYPE_NORMAL;
   this.SetMarginAll(0);
   this.SetPaddingAll(0);
   this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetAutoSize(false,false);
   CForm::SetCoordXInit(x);
   CForm::SetCoordYInit(y);
   CForm::SetWidthInit(w);
   CForm::SetHeightInit(h);
   this.m_shadow=false;
   this.m_frame_width_right=0;
   this.m_frame_width_left=0;
   this.m_frame_width_top=0;
   this.m_frame_width_bottom=0;
   this.m_gradient_v=true;
   this.m_gradient_c=false;
  }
//+------------------------------------------------------------------+

Métodos transferidos a partir da classe CPanel:

//+------------------------------------------------------------------+
//| Return the font flags                                            |
//+------------------------------------------------------------------+
uint CWinFormBase::GetFontFlags(void)
  {
   string name;
   int size;
   uint flags;
   uint angle;
   CGCnvElement::GetFont(name,size,flags,angle);
   return flags;
  }
//+------------------------------------------------------------------+
//| Set the Bold font flag                                           |
//+------------------------------------------------------------------+
void CWinFormBase::SetBold(const bool flag)
  {
   uint flags=this.GetFontFlags();
   if(flag)
     {
      this.m_bold_type=FW_TYPE_BOLD;
      CGCnvElement::SetFontFlags(flags | FW_BOLD);
     }
   else
      this.m_bold_type=FW_TYPE_NORMAL;
  }
//+------------------------------------------------------------------+
//| Return the Bold font flag                                        |
//+------------------------------------------------------------------+
bool CWinFormBase::Bold(void)
  {
   uint flags=this.GetFontFlags();
   return(flags &FW_BOLD)==FW_BOLD;
  }
//+------------------------------------------------------------------+
//| Set the Italic font flag                                         |
//+------------------------------------------------------------------+
void CWinFormBase::SetItalic(const bool flag)
  {
   uint flags=this.GetFontFlags();
   if(flag)
      CGCnvElement::SetFontFlags(flags | FONT_ITALIC);
  }
//+------------------------------------------------------------------+
//| Return the Italic font flag                                      |
//+------------------------------------------------------------------+
bool CWinFormBase::Italic(void)
  {
   uint flags=this.GetFontFlags();
   return(flags &FONT_ITALIC)==FONT_ITALIC;
  }
//+------------------------------------------------------------------+
//| Set the Strikeout font flag                                      |
//+------------------------------------------------------------------+
void CWinFormBase::SetStrikeout(const bool flag)
  {
   uint flags=this.GetFontFlags();
   if(flag)
      CGCnvElement::SetFontFlags(flags | FONT_STRIKEOUT);
  }
//+------------------------------------------------------------------+
//| Return the Strikeout font flag                                   |
//+------------------------------------------------------------------+
bool CWinFormBase::Strikeout(void)
  {
   uint flags=this.GetFontFlags();
   return(flags &FONT_STRIKEOUT)==FONT_STRIKEOUT;
  }
//+------------------------------------------------------------------+
//| Set the Underline font flag                                      |
//+------------------------------------------------------------------+
void CWinFormBase::SetUnderline(const bool flag)
  {
   uint flags=this.GetFontFlags();
   if(flag)
      CGCnvElement::SetFontFlags(flags | FONT_UNDERLINE);
  }
//+------------------------------------------------------------------+
//| Return the Underline font flag                                   |
//+------------------------------------------------------------------+
bool CWinFormBase::Underline(void)
  {
   uint flags=this.GetFontFlags();
   return(flags &FONT_UNDERLINE)==FONT_UNDERLINE;
  }
//+------------------------------------------------------------------+
//| Set the font style                                               |
//+------------------------------------------------------------------+
void CWinFormBase::SetFontDrawStyle(ENUM_FONT_STYLE style)
  {
   switch(style)
     {
      case FONT_STYLE_ITALIC     :  this.SetItalic(true);      break;
      case FONT_STYLE_UNDERLINE  :  this.SetUnderline(true);   break;
      case FONT_STYLE_STRIKEOUT  :  this.SetStrikeout(true);   break;
      default: break;
     }
  }
//+------------------------------------------------------------------+
//| Return the font style                                            |
//+------------------------------------------------------------------+
ENUM_FONT_STYLE CWinFormBase::FontDrawStyle(void)
  {
   return
     (
      this.Italic()     ?  FONT_STYLE_ITALIC    :
      this.Underline()  ?  FONT_STYLE_UNDERLINE :
      this.Strikeout()  ?  FONT_STYLE_UNDERLINE :
      FONT_STYLE_NORMAL
     );
  }
//+------------------------------------------------------------------+
//| Set the font width type                                          |
//+------------------------------------------------------------------+
void CWinFormBase::SetFontBoldType(ENUM_FW_TYPE type)
  {
   this.m_bold_type=type;
   uint flags=this.GetFontFlags();
   switch(type)
     {
      case FW_TYPE_DONTCARE   : CGCnvElement::SetFontFlags(flags | FW_DONTCARE);    break;
      case FW_TYPE_THIN       : CGCnvElement::SetFontFlags(flags | FW_THIN);        break;
      case FW_TYPE_EXTRALIGHT : CGCnvElement::SetFontFlags(flags | FW_EXTRALIGHT);  break;
      case FW_TYPE_ULTRALIGHT : CGCnvElement::SetFontFlags(flags | FW_ULTRALIGHT);  break;
      case FW_TYPE_LIGHT      : CGCnvElement::SetFontFlags(flags | FW_LIGHT);       break;
      case FW_TYPE_REGULAR    : CGCnvElement::SetFontFlags(flags | FW_REGULAR);     break;
      case FW_TYPE_MEDIUM     : CGCnvElement::SetFontFlags(flags | FW_MEDIUM);      break;
      case FW_TYPE_SEMIBOLD   : CGCnvElement::SetFontFlags(flags | FW_SEMIBOLD);    break;
      case FW_TYPE_DEMIBOLD   : CGCnvElement::SetFontFlags(flags | FW_DEMIBOLD);    break;
      case FW_TYPE_BOLD       : CGCnvElement::SetFontFlags(flags | FW_BOLD);        break;
      case FW_TYPE_EXTRABOLD  : CGCnvElement::SetFontFlags(flags | FW_EXTRABOLD);   break;
      case FW_TYPE_ULTRABOLD  : CGCnvElement::SetFontFlags(flags | FW_ULTRABOLD);   break;
      case FW_TYPE_HEAVY      : CGCnvElement::SetFontFlags(flags | FW_HEAVY);       break;
      case FW_TYPE_BLACK      : CGCnvElement::SetFontFlags(flags | FW_BLACK);       break;
      default                 : CGCnvElement::SetFontFlags(flags | FW_NORMAL);      break;
     }
  }
//+------------------------------------------------------------------+

Discutimos todos esses métodos anteriormente outros artigos sobre como criar um objeto-painel.


Métodos virtuais para limpar (colorir) um elemento:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//+------------------------------------------------------------------+
void CWinFormBase::Erase(const color colour,const uchar opacity,const bool redraw=false)
  {
//--- Fill the element having the specified color and the redrawing flag
   CGCnvElement::Erase(colour,opacity,redraw);
//--- If the object has a frame, draw it
   if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw)
      this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle());
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element with a gradient fill                           |
//+------------------------------------------------------------------+
void CWinFormBase::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false)
  {
//--- Fill the element having the specified color array and the redrawing flag
   CGCnvElement::Erase(colors,opacity,vgradient,cycle,redraw);
//--- If the object has a frame, draw it
   if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw)
      this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle());
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element completely                                     |
//+------------------------------------------------------------------+
void CWinFormBase::Erase(const bool redraw=false)
  {
//--- Fully clear the element with the redrawing flag
   CGCnvElement::Erase(redraw);
  }
//+------------------------------------------------------------------+

Esses métodos virtuais substituem os mesmos métodos da classe pai. Aqui, primeiro, o método da classe pai é chamado para preencher o elemento com color, o sinalizador de moldura no objeto é verificado, assim, se a moldura deve estar presente e o sinalizador de redesenho de objeto é definido, a moldura é desenhada. Em seguida, o objeto é atualizado.


Método que define as novas dimensões do objeto atual:

//+------------------------------------------------------------------+
//| Set the new size for the current object                          |
//+------------------------------------------------------------------+
bool CWinFormBase::Resize(const int w,const int h,const bool redraw)
  {
//--- If the object width and height are equal to the passed ones, return 'true'
   if(this.Width()==w && this.Height()==h)
      return true;
//--- Declare the variable with the property change result
   bool res=true;
//--- Save the panel initial size
   int prev_w=this.Width();
   int prev_h=this.Height();
//--- Set the property change result to the 'res' variable
//--- (if the property value is not equal to the passed value)
   if(this.Width()!=w)
      res &=this.SetWidth(w);
   if(this.Height()!=h)
      res &=this.SetHeight(h);
   if(!res)
      return false;
//--- Calculate the value, by which the size should be changed
   int excess_w=this.Width()-prev_w;
   int excess_h=this.Height()-prev_h;
//--- Get the "Shadow" object
   CShadowObj *shadow=this.GetShadowObj();
//--- If the object has a shadow and the "Shadow" object has been received,
   if(this.IsShadow() && shadow!=NULL)
     {
      //---  save shadow shifts by X and Y,
      int x=shadow.CoordXRelative();
      int y=shadow.CoordYRelative();
      //--- set the shadow new width and height
      res &=shadow.SetWidth(shadow.Width()+excess_w);
      res &=shadow.SetHeight(shadow.Height()+excess_h);
      if(!res)
         return false;
      //--- If there is no need to redraw, remove the shadow
      if(!redraw)
         shadow.Erase();
      //--- Save the previously set shadow shift values relative to the panel
      shadow.SetCoordXRelative(x);
      shadow.SetCoordYRelative(y);
     }
//--- Redraw the element with new size
   if(redraw)
      this.Redraw(true);
//--- All is successful - return 'true'
   return true;
  }
//+------------------------------------------------------------------+

A lógica do método é descrita nos comentários ao código. Resumindo: se as dimensões presentes do objeto forem passadas para o método, nada precisará ser alterado, portanto retornamos imediatamente true. Se os tamanhos passados para o método não corresponderem aos que o objeto possui atualmente, nós os alteramos. Se houver uma sombra, também alteramos seu tamanho e, em seguida, redesenhamos todo o objeto se o sinalizador de redesenho estiver definido. Ou seja, se o sinalizador de redesenho não estiver definido, o tamanho do objeto será alterado, mas ele não será redesenhado. Isso é necessário para acelerar o redesenho de muitos objetos vinculados a outro objeto. É só após redimensionar todos os objetos relacionados que todos eles precisam ser redesenhados.

Método que define novas dimensões para o objeto especificado consoante o índice:

//+------------------------------------------------------------------+
//| Set the new size for the object specified by object index        |
//+------------------------------------------------------------------+
bool CWinFormBase::Resize(const int index,const int w,const int h,const bool redraw)
  {
   CWinFormBase *obj=this.GetElement(index);
   return(obj!=NULL ? obj.Resize(w,h,redraw) : false);
  }
//+------------------------------------------------------------------+

O método recebe o índice do objeto na lista de objetos vinculados que precisam ser redimensionados, que também são passados para o método.
Obtemos o objeto por índice na lista e retornamos o resultado de chamar seu método Resize(), discutido acima.


Método que redesenha o objeto:

//+------------------------------------------------------------------+
//| Redraw the object                                                |
//+------------------------------------------------------------------+
void CWinFormBase::Redraw(bool redraw)
  {
//--- If the object type is less than the "Base WinForms object", exit
   if(this.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE)
      return;
//--- Get the "Shadow" object
   CShadowObj *shadow=this.GetShadowObj();
//--- If the object has a shadow and the "Shadow" object exists, redraw it
   if(this.IsShadow() && shadow!=NULL)
     {
      //--- remove the previously drawn shadow,
      shadow.Erase();
      //--- save the relative shadow coordinates,
      int x=shadow.CoordXRelative();
      int y=shadow.CoordYRelative();
      //--- redraw the shadow,
      if(redraw)
         shadow.Draw(0,0,shadow.Blur(),redraw);
      //--- restore relative shadow coordinates
      shadow.SetCoordXRelative(x);
      shadow.SetCoordYRelative(y);
     }
//--- If the redraw flag is set,
   if(redraw)
     {
      //--- completely redraw the object and save its new initial look
      this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c,redraw);
      this.Done();
     }
//--- otherwise, remove the object
   else
      this.Erase();
//--- Redraw all bound objects with the redraw flag
   for(int i=0;i<this.ElementsTotal();i++)
     {
      CWinFormBase *element=this.GetElement(i);
      if(element==NULL)
         continue;
      element.Redraw(redraw);
     }
//--- If the redraw flag is set and if this is the main object the rest are bound to,
//--- redraw the chart to display changes immediately
   if(redraw && this.GetMain()==NULL)
      ::ChartRedraw(this.ChartID());
  }
//+------------------------------------------------------------------+

A lógica deste método é semelhante à do que redimensiona o objeto. Primeiro, redesenhamos a sombra do objeto, pois ela deve ser menor que o resto, então, com o sinalizador de redesenho definido, redesenhamos completamente o objeto e definimos sua aparência atual como "reference". Se o sinalizador de redesenho não estiver definido, apagamos o objeto-sombra desenhado e o próprio objeto. Em seguida, percorremos a lista de objetos vinculados e redesenhamos cada um deles de acordo com o sinalizador de redesenho, isto é, quer seja apagamos tudo ou desenhamos tudo.
No final, atualizamos o gráfico para exibir imediatamente as alterações feitas se o sinalizador de redesenho estiver definido e se esse objeto for o objeto principal em toda a hierarquia de objetos relacionados (ele não possui um objeto principal e seu método GetMain() retornará NULL).

A classe base de todos os objetos WinForms da biblioteca está pronta.

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

Ao definir a propriedade Dock para objetos vinculados a um painel, esses objetos devem ser ordenados automaticamente consoante seus números na lista de objetos vinculados e segundo as regras definidas para o tipo de encaixe. Ou seja, se o Dock estiver à esquerda, então o objeto será esticado em altura até a altura total do painel ao qual ele está vinculado, e sua coordenada esquerda deverá ser o lado esquerdo ou o subtrato do painel (se o objeto for o primeiro na lista), ou o lado direito do objeto anterior da lista de objetos vinculados. E assim para cada objeto da lista. Ao mesmo tempo, o sinalizador de redimensionamento automático do próprio painel e o modo de redimensionamento - apenas aumentar, ou aumentar e diminuir - também são importantes.

Hoje trataremos da configuração da propriedade Dock para objetos vinculados apenas no modo de autodimensionamento do painel desabilitado. A fim de determinar a que face o objeto atual está acoplado a partir da lista de objetos vinculados, criamos quatro objetos - pelo número de faces - superior, inferior, esquerda e direita. Cada objeto para o qual a propriedade Dock está definida se inserirá em um desses objetos - para que os outros objetos da lista possam saber quais as coordenadas do objeto a ser acoplado (porque eles serão acoplados ao objeto anterior da lista se também tiver a propriedade Dock definida para ele).

Como agora todos os objetos WimForms serão herdados do objeto base WinForms, em vez do arquivo de objeto-forma incluído:

//+------------------------------------------------------------------+
//|                                                        Panel.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\..\Form.mqh"
//+------------------------------------------------------------------+
//| Panel object class of WForms controls                            |
//+------------------------------------------------------------------+
class CPanel : public CForm

inserimos o arquivo de objeto base WinForms:

//+------------------------------------------------------------------+
//|                                                        Panel.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\..\WForms\WinFormBase.mqh"
//+------------------------------------------------------------------+
//| Panel object class of WForms controls                            |
//+------------------------------------------------------------------+


Na seção privada da classe, declararemos ponteiros para os quatro objetos onde armazenaremos ponteiros para objetos, cujas coordenadas usaremos para acoplar os objetos Dock:

class CPanel : public CWinFormBase
  {
private:
   CGCnvElement     *m_obj_top;                                      // Pointer to the object whose coordinates the current upper object is bound to
   CGCnvElement     *m_obj_bottom;                                   // Pointer to the object whose coordinates the current bottom object is bound to
   CGCnvElement     *m_obj_left;                                     // Pointer to the object whose coordinates the current left object is bound to
   CGCnvElement     *m_obj_right;                                    // Pointer to the object whose coordinates the current right object is bound to
   CGCnvElement     *m_underlay;                                     // Underlay for placing elements
   bool              m_autoscroll;                                   // Auto scrollbar flag
   int               m_autoscroll_margin[2];                         // Array of fields around the control during an auto scroll
   ENUM_CANV_ELEMENT_AUTO_SIZE_MODE m_autosize_mode;                 // Mode of the element auto resizing depending on the content
//--- 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);
//--- Create the underlay object
   bool              CreateUnderlayObj(void);
//--- Set the underlay as a coordinate system zero
   void              SetUnderlayAsBase(void);

protected:

Em vez do método SetDockingToContainer(), vamos declarar o método SetUnderlayAsBase(), que atribuirá o objeto substrato a todos os quatro ponteiros declarados acima. Quando criamos um objeto pela primeira vez, a origem das coordenadas de encaixe do primeiro objeto vinculado é o substrato do objeto criado. À medida que os objetos Dock forem localizados, as variáveis serão apontadas para os objetos correspondentes já vinculados às faces respectivas. Mas no início, o objeto de encaixe é o substrato.

Na seção protegida da classe, declaramos dois métodos que retornam o tamanho máximo pelo qual todos os objetos Dock se expandem além do objeto ao qual estão ancorados, declaramos um método que redimensiona automaticamente o contêiner para os caberem objetos nele contidos e escrevemos quatro métodos que retornam ponteiros para os objetos aos quais o próximo objeto Dock da lista de objetos vinculados ao contêiner deve ser ancorado:

protected:
//--- Return the maximum value of Dock object borders going beyond the container by width and (2) height
   int               GetExcessMaxX(void);
   int               GetExcessMaxY(void);
//--- Set (1) X, (2) Y coordinate, (3) width, (4) height and (5) all underlay parameters
   bool              SetCoordXUnderlay(const int value)              { return(this.m_underlay!=NULL ? this.m_underlay.SetCoordX(value) : false);   }
   bool              SetCoordYUnderlay(const int value)              { return(this.m_underlay!=NULL ? this.m_underlay.SetCoordY(value) : false);   }
   bool              SetWidthUnderlay(const int value)               { return(this.m_underlay!=NULL ? this.m_underlay.SetWidth(value)  : false);   }
   bool              SetHeightUnderlay(const int value)              { return(this.m_underlay!=NULL ? this.m_underlay.SetHeight(value) : false);   }
   bool              SetUnderlayParams(void);
//--- Return the underlay (1) X, (2) Y coordinate, (3) width and (4) height
   int               GetCoordXUnderlay(void)                   const { return(this.m_underlay!=NULL ?  this.m_underlay.CoordX() : 0);              }
   int               GetCoordYUnderlay(void)                   const { return(this.m_underlay!=NULL ?  this.m_underlay.CoordY() : 0);              }
   int               GetWidthUnderlay(void)                    const { return(this.m_underlay!=NULL ?  this.m_underlay.Width()  : 0);              }
   int               GetHeightUnderlay(void)                   const { return(this.m_underlay!=NULL ?  this.m_underlay.Height() : 0);              }
//--- Return the underlay (1) X and (2) Y coordinate relative to the panel
   int               GetCoordXUnderlayRelative(void)           const { return(this.m_underlay!=NULL ?  this.m_underlay.CoordXRelative() : 0);      }
   int               GetCoordYUnderlayRelative(void)           const { return(this.m_underlay!=NULL ?  this.m_underlay.CoordYRelative() : 0);      }
   
//--- Return the object whose coordinates the current (1) top, (2) bottom, (3) left or (4) right object is bound to
   CGCnvElement     *GetTopObj(void)                                 { return this.m_obj_top;               }
   CGCnvElement     *GetBottomObj(void)                              { return this.m_obj_bottom;            }
   CGCnvElement     *GetLeftObj(void)                                { return this.m_obj_left;              }
   CGCnvElement     *GetRightObj(void)                               { return this.m_obj_right;             }

//--- Adjust the element size to fit its content
   bool              AutoSizeProcess(const bool redraw);
   
public:

Todas as outras variáveis e métodos daqui já foram transferidos para a classe base do objeto WinForms, e mais detalhes sobre tais alterações podem ser encontrados nos arquivos anexados ao artigo.

A seção pública da classe, com todos os métodos transferidos, correções feitas e novos métodos adicionados, agora fica assim:

public:
//--- Return the (1) underlay and (2) the object the current object is bound to
   CGCnvElement     *GetUnderlay(void)                               { return this.m_underlay;              }
//--- Update the coordinates (shift the canvas)
   virtual bool      Move(const int x,const int y,const bool redraw=false);
//--- Set the (1) X, (2) Y coordinates, (3) element width and (4) height
   virtual bool      SetCoordX(const int coord_x);
   virtual bool      SetCoordY(const int coord_y);
   virtual bool      SetWidth(const int width);
   virtual bool      SetHeight(const int height);
//--- Create a new attached element
   virtual bool      CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                      CGCnvElement *main,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool activity,
                                      const bool redraw);
//--- Redraw the object
   virtual void      Redraw(bool redraw);
//--- Update the element
   void              Update(const bool redraw=false)                 { this.m_canvas.Update(redraw);        }
   
//--- Reset the size of all bound objects to the initial ones
   bool              ResetSizeAllToInit(void);
//--- Place bound objects in the order of their Dock binding
   bool              ArrangeObjects(const bool redraw);

//--- (1) Set and (2) return the auto scrollbar flag
   void              SetAutoScroll(const bool flag)                  { this.m_autoscroll=flag;              }
   bool              AutoScroll(void)                                { return this.m_autoscroll;            }
//--- Set the (1) field width, (2) height, (3) the height of all fields around the control during auto scrolling
   void              SetAutoScrollMarginWidth(const int value)       { this.m_autoscroll_margin[0]=value;   }
   void              SetAutoScrollMarginHeight(const int value)      { this.m_autoscroll_margin[1]=value;   }
   void              SetAutoScrollMarginAll(const int value)
                       {
                        this.SetAutoScrollMarginWidth(value); this.SetAutoScrollMarginHeight(value);
                       }
//--- Return the (1) field width and (2) height around the control during auto scrolling
   int               AutoScrollMarginWidth(void)               const { return this.m_autoscroll_margin[0];  }
   int               AutoScrollMarginHeight(void)              const { return this.m_autoscroll_margin[1];  }
  
//--- (1) Set the flag of the element auto resizing depending on the content
   virtual void      SetAutoSize(const bool flag,const bool redraw)
                       {
                        bool prev=this.AutoSize();
                        if(prev==flag)
                           return;
                        CWinFormBase::SetAutoSize(flag,redraw);
                        if(prev!=this.AutoSize() && this.ElementsTotal()>0)
                           this.AutoSizeProcess(redraw);
                       }
//--- (1) Set and (2) return the mode of the element auto resizing depending on the content
   void              SetAutoSizeMode(const ENUM_CANV_ELEMENT_AUTO_SIZE_MODE mode,const bool redraw)
                       {
                        ENUM_CANV_ELEMENT_AUTO_SIZE_MODE prev=this.AutoSizeMode();
                        if(prev==mode)
                           return;
                        this.m_autosize_mode=mode;
                        if(prev!=this.AutoSizeMode() && this.ElementsTotal()>0)
                           this.AutoSizeProcess(redraw);
                       }
   ENUM_CANV_ELEMENT_AUTO_SIZE_MODE AutoSizeMode(void)         const { return this.m_autosize_mode;         }
   
//--- (1) Set and (2) return the mode of binding element borders to the container
   virtual void      SetDockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode,const bool redraw)
                       {
                        if(this.DockMode()==mode)
                           return;
                        CWinFormBase::SetDockMode(mode,redraw);
                        CPanel *base=this.GetBase();
                        if(base!=NULL)
                           base.ArrangeObjects(redraw);
                       }

//--- 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
   virtual void      SetPaddingLeft(const uint value)
                       {
                        CWinFormBase::SetPaddingLeft(value);
                        if(this.m_underlay!=NULL)
                          {
                           //--- Set the underlay shift along the X axis
                           this.m_underlay.SetCoordXRelative(this.PaddingLeft());
                           //--- Set the X coordinate and the underlay width
                           this.SetCoordXUnderlay(this.CoordX()+this.PaddingLeft());
                           this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight());
                          }
                       }
   virtual void      SetPaddingTop(const uint value)
                       {
                        CWinFormBase::SetPaddingTop(value);
                        if(this.m_underlay!=NULL)
                          {
                           //--- Set the underlay shift along the Y axis
                           this.m_underlay.SetCoordYRelative(this.PaddingTop());
                           //--- Set the Y coordinate and underlay height
                           this.SetCoordYUnderlay(this.CoordY()+this.PaddingTop());
                           this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom());
                          }
                       }
   virtual void      SetPaddingRight(const uint value)
                       {
                        CWinFormBase::SetPaddingRight(value);
                        if(this.m_underlay!=NULL)
                          {
                           //--- Set the underlay width
                           this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight());
                          }
                       }
   virtual void      SetPaddingBottom(const uint value)
                       {
                        CWinFormBase::SetPaddingBottom(value);
                        if(this.m_underlay!=NULL)
                          {
                           //--- Set the underlay height
                           this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom());
                          }
                       }
   virtual void      SetPaddingAll(const uint value)
                       {
                        this.SetPaddingLeft(value); this.SetPaddingTop(value); this.SetPaddingRight(value); this.SetPaddingBottom(value);
                       }
   
//--- Set the width of the form frame (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides of the control
   virtual void      SetFrameWidthLeft(const uint value)
                       {
                        this.m_frame_width_left=(int)value;
                        if(this.PaddingLeft()<this.FrameWidthLeft())
                           this.SetPaddingLeft(this.FrameWidthLeft());
                       }
   virtual void      SetFrameWidthTop(const uint value)
                       {
                        this.m_frame_width_top=(int)value;
                        if(this.PaddingTop()<this.FrameWidthTop())
                           this.SetPaddingTop(this.FrameWidthTop());
                       }
   virtual void      SetFrameWidthRight(const uint value)
                       {
                        this.m_frame_width_right=(int)value;
                        if(this.PaddingRight()<this.FrameWidthRight())
                           this.SetPaddingRight(this.FrameWidthRight());
                       }
   virtual void      SetFrameWidthBottom(const uint value)
                       {
                        this.m_frame_width_bottom=(int)value;
                        if(this.PaddingBottom()<this.FrameWidthBottom())
                           this.SetPaddingBottom(this.FrameWidthBottom());
                       }
   virtual void      SetFrameWidthAll(const uint value)
                       {
                        this.SetFrameWidthLeft(value); this.SetFrameWidthTop(value); this.SetFrameWidthRight(value); this.SetFrameWidthBottom(value);
                       }

//--- Constructors
                     CPanel(const long chart_id,
                           const int subwindow,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CPanel(const string name) : CWinFormBase(::ChartID(),0,name,0,0,0,0)
                       {
                        CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_PANEL);
                        CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_PANEL);
                        this.m_type=OBJECT_DE_TYPE_GWF_PANEL; 
                        this.SetForeColor(CLR_DEF_FORE_COLOR);
                        this.SetFontBoldType(FW_TYPE_NORMAL);
                        this.SetMarginAll(3);
                        this.SetPaddingAll(0);
                        this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
                        this.SetBorderStyle(FRAME_STYLE_BEVEL);
                        this.SetAutoScroll(false);
                        this.SetAutoScrollMarginAll(0);
                        this.SetAutoSize(false,false);
                        this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false);
                        this.Initialize();
                        if(this.CreateUnderlayObj())
                           this.SetUnderlayAsBase();
                       }
//--- Destructor
                    ~CPanel();
  };
//+------------------------------------------------------------------+

Todos os métodos Set agora têm o prefixo "Set".

Os métodos de redimensionamento automático, modo de redimensionamento automático e de encaixe de objetos Dock no contêiner primeiro chamam o método de classe base para definir a propriedade e depois chamam o método de redimensionamento de contêiner:

   virtual void      SetAutoSize(const bool flag,const bool redraw)
                       {
                        bool prev=this.AutoSize();
                        if(prev==flag)
                           return;
                        CWinFormBase::SetAutoSize(flag,redraw);
                        if(prev!=this.AutoSize() && this.ElementsTotal()>0)
                           this.AutoSizeProcess(redraw);
                       }

Enquanto isso, o redimensionamento do contêiner deve ser realizado somente se pelo menos um objeto estiver vinculado ao contêiner.

Outros métodos similares são feitos com lógica semelhante e podem ser estudados por conta própria.
Qualquer dúvida que você possa ter pode ser colocada na descrição do artigo.

No método que define todos os parâmetros do plano de fundo, especificamos o tipo de objeto "substrato":

//+------------------------------------------------------------------+
//| Set all underlay parameters                                      |
//+------------------------------------------------------------------+
bool CPanel::SetUnderlayParams(void)
  {
//--- Set the object type
   this.m_underlay.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_UNDERLAY);
//--- Set the underlay shift values to the variables by X and Y axes
   this.m_underlay.SetCoordXRelative(this.PaddingLeft());
   this.m_underlay.SetCoordYRelative(this.PaddingTop());
//--- Set the underlay coordinates and size
   bool res=true;
   res &=this.SetCoordXUnderlay(this.CoordX()+this.PaddingLeft());
   res &=this.SetCoordYUnderlay(this.CoordY()+this.PaddingTop());
   res &=this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight());
   res &=this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom());
   return res;
  }
//+------------------------------------------------------------------+

Saber que o objeto selecionado é do tipo "substrato" é necessário para nós ao colocarmos objetos Dock dentro do contêiner. Se o objeto ao qual se deseja encaixar o objeto atual na lista de objetos vinculados for um substrato, então a borda para encaixar o lado esquerdo deve ser a coordenada X do substrato, por exemplo, enquanto que se não for um substrato mas outro objeto Dock, então o lado direito desse outro objeto Dock será a borda para encaixar.

Nos métodos que definem as coordenadas e dimensões do painel, primeiro chamamos o método de definição de propriedades do objeto base e, em seguida, alteramos as coordenadas e dimensões objeto substrato:

//+------------------------------------------------------------------+
//| Set the panel X coordinate                                       |
//+------------------------------------------------------------------+
bool CPanel::SetCoordX(const int coord_x)
  {
   if(!CGCnvElement::SetCoordX(coord_x))
      return false;
   return(this.m_underlay!=NULL ? this.SetCoordXUnderlay(coord_x+this.PaddingLeft()) : true);
  }
//+------------------------------------------------------------------+
//| Set the panel Y coordinate                                       |
//+------------------------------------------------------------------+
bool CPanel::SetCoordY(const int coord_y)
  {
   if(!CGCnvElement::SetCoordY(coord_y))
      return false;
   return(this.m_underlay!=NULL ? this.SetCoordYUnderlay(coord_y+this.PaddingTop()) : true);
  }
//+------------------------------------------------------------------+
//| Set the panel width                                              |
//+------------------------------------------------------------------+
bool CPanel::SetWidth(const int width)
  {
   if(!CGCnvElement::SetWidth(width))
      return false;
   return(this.m_underlay!=NULL ? this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight()) : true);
  }
//+------------------------------------------------------------------+
//| Set the panel height                                             |
//+------------------------------------------------------------------+
bool CPanel::SetHeight(const int height)
  {
   if(!CGCnvElement::SetHeight(height))
      return false;
   return(this.m_underlay!=NULL ? this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom()) : true);
  }
//+------------------------------------------------------------------+


Método que redefine o tamanho de todos os objetos vinculados para seus tamanhos originais:

//+------------------------------------------------------------------+
//| Reset the size of all bound objects to the initial ones          |
//+------------------------------------------------------------------+
bool CPanel::ResetSizeAllToInit(void)
  {
   bool res=true;
   for(int i=0;i<this.ElementsTotal();i++)
     {
      CPanel *obj=this.GetElement(i);
      if(obj==NULL)
        {
         res &=false;
         continue;
        }
      res &=obj.Resize(i,obj.GetWidthInit(),obj.GetHeightInit());
     }
   return res;
  }
//+------------------------------------------------------------------+

Em um loop sobre todos os objetos vinculados ao contêiner, obtemos o próximo objeto e escrevemos na variável res o resultado da alteração do tamanho do objeto para seu tamanho original. No final do loop, a variável res armazenará o resultado geral do redimensionamento. Se pelo menos um objeto foi redimensionado com erro, a variável res será definida como false, caso contrário true.


Método que cria um novo elemento vinculado:

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CPanel::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                              CGCnvElement *main,
                              const int x,
                              const int y,
                              const int w,
                              const int h,
                              const color colour,
                              const uchar opacity,
                              const bool activity,
                              const bool redraw)
  {
//--- If failed to create a new graphical element, return 'false'
   CGCnvElement *obj=CForm::CreateAndAddNewElement(element_type,main,x,y,w,h,colour,opacity,activity);
   if(obj==NULL)
      return false;
//--- If the object type is a base WinForms object and higher,
   if(obj.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE)
     {
      //--- declare the pointer with the base WinForms object type and assign the pointer to the newly created object to it,
      //--- set the frame color equal to the background color
      CWinFormBase *wf=obj;
      wf.SetColorFrame(wf.ColorBackground());
     }
//--- If the panel has auto resize enabled and features bound objects, call the resize method
   if(this.AutoSize() && this.ElementsTotal()>0)
      this.AutoSizeProcess(redraw);
//--- Redraw the panel and all added objects, and return 'true'
   this.Redraw(redraw);
   return true;
  }
//+------------------------------------------------------------------+

A lógica do método está descrita nos comentários ao código e é bastante simples.

Método que retorna o valor máximo da largura das bordas dos objetos Dock que ultrapassam a borda do contêiner:

//+------------------------------------------------------------------+
//| Return the maximum value of Dock object borders                  |
//| going beyond the container by width                              |
//+------------------------------------------------------------------+
int CPanel::GetExcessMaxX(void)
  {
   int value=0;
   for(int i=0;i<this.ElementsTotal();i++)
     {
      CWinFormBase *element=this.GetElement(i);
      if(element==NULL)
         continue;
      if(element.RightEdge()>value)
         value=element.RightEdge();
     }
   return value-this.m_underlay.RightEdge();
  }
//+------------------------------------------------------------------+

Método que retorna o valor máximo dos limites do objeto Dock além da altura do contêiner:

//+------------------------------------------------------------------+
//| Return the maximum value of Dock object borders                  |
//| going beyond the container by height                             |
//+------------------------------------------------------------------+
int CPanel::GetExcessMaxY(void)
  {
   int value=0;
   for(int i=0;i<this.ElementsTotal();i++)
     {
      CWinFormBase *element=this.GetElement(i);
      if(element==NULL)
         continue;
      if(element.BottomEdge()>value)
         value=element.BottomEdge();
     }
   return value-this.m_underlay.BottomEdge();
  }
//+------------------------------------------------------------------+

Ambos os métodos têm lógica idêntica: em um loop sobre todos os objetos vinculados ao contêiner, obtemos o próximo objeto e, se sua borda direita (inferior) for maior que o valor escrito na variável value, essa variável recebe o valor da borda. No final do loop, o valor máximo da borda direita (inferior) de todos os objetos será gravado na variável value, e a diferença entre o valor encontrado e a borda direita (inferior) do objeto substrato será retornada. Assim, obteremos um valor positivo ou negativo em pixels, com o qual precisamos redimensionar o contêiner.


Método que ordena os objetos vinculados segundo sua encaixe Dock:

//+------------------------------------------------------------------+
//| Place bound objects in the order of their Dock binding           |
//+------------------------------------------------------------------+
bool CPanel::ArrangeObjects(const bool redraw)
  {
   CWinFormBase *prev=NULL, *obj=NULL;
   //--- If auto resizing mode is enabled
   if(this.AutoSize())
     {
      //--- In the loop by all bound objects,
      for(int i=0;i<this.ElementsTotal();i++)
        {
         //--- Get the previous element from the list
         prev=this.GetElement(i-1);
         //--- If there is no previous element, set the underlay as a previous element
         if(prev==NULL)
            this.SetUnderlayAsBase();
         //--- Get the current element
         obj=GetElement(i);
         //--- If the object has not been received or its type is less than the base WinForms object or it has no underlay, move on
         if(obj==NULL || obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE || this.m_underlay==NULL)
            continue;
         //--- Depending on the current object binding mode...
         //--- Top
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_TOP)
           {
            
           }
         //--- Bottom
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_BOTTOM)
           {
            
           }
         //--- Left
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_LEFT)
           {
            
           }
         //--- Right
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_RIGHT)
           {
            
           }
         //--- Binding with filling
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_FILL)
           {
            
           }
         //--- No binding
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_NONE)
           {
            
           }
        }
      this.Resize(this.GetWidthInit(),this.GetHeightInit(),false);
     }
   //--- If auto resizing mode disabled
   else
     {
      //--- In the loop by all bound objects,
      for(int i=0;i<this.ElementsTotal();i++)
        {
         //--- Get the current and previous elements from the list
         obj=this.GetElement(i);
         prev=this.GetElement(i-1);
         //--- If there is no previous element, set the underlay as a previous element
         if(prev==NULL)
            this.SetUnderlayAsBase();
         //--- If the object has not been received or its type is less than the base WinForms object or it has no underlay, move on
         if(obj==NULL || obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE || this.m_underlay==NULL)
            continue;
         int x=0, y=0; // Object binding coordinates
         //--- Depending on the current object binding mode...
         //--- Top
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_TOP)
           {
            //--- If failed to change the object size (for the entire underlay width and by the initial object height), move on to the next one
            if(!obj.Resize(this.GetWidthUnderlay(),obj.GetHeightInit(),false))
               continue;
            //--- Get the pointer to the object at the top whose edges are used to bind the current one
            CGCnvElement *coord_base=this.GetTopObj();
            //--- Get the object binding coordinates
            x=this.GetCoordXUnderlay();
            y=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.CoordY() : coord_base.BottomEdge()+1);
            //--- If failed to move the object to the obtained coordinates, move on to the next one
            if(!obj.Move(x,y,false))
               continue;
            //--- Set the current object as the top one whose edges will be used to bind the next one
            this.m_obj_top=obj;
           }
         //--- Bottom
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_BOTTOM)
           {
            //--- If failed to change the object size (for the entire underlay width and by the initial object height), move on to the next one
            if(!obj.Resize(this.GetWidthUnderlay(),obj.GetHeightInit(),false))
               continue;
            //--- Get the pointer to the object at the bottom whose edges are used to bind the current one
            CGCnvElement *coord_base=this.GetBottomObj();
            //--- Get the object binding coordinates
            x=this.GetCoordXUnderlay();
            y=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.BottomEdge()-obj.Height()-1 : coord_base.CoordY()-obj.Height()-1);
            //--- If failed to move the object to the obtained coordinates, move on to the next one
            if(!obj.Move(x,y,false))
               continue;
            //--- Set the current object as the bottom one whose edges will be used to bind the next one
            this.m_obj_bottom=obj;
           }
         //--- Left
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_LEFT)
           {
            //--- If failed to change the object size (for the initial object width and the entire underlay height), move on to the next one
            if(!obj.Resize(obj.GetWidthInit(),this.GetHeightUnderlay(),false))
               continue;
            //--- Get the pointer to the object at the left whose edges are used to bind the current one
            CGCnvElement *coord_base=this.GetLeftObj();
            //--- Get the object binding coordinates
            x=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.CoordX() : coord_base.RightEdge()+1);
            y=this.GetCoordYUnderlay();
            //--- If failed to move the object to the obtained coordinates, move on to the next one
            if(!obj.Move(x,y,false))
               continue;
            //--- Set the current object as the left one whose edges will be used to bind the next one
            this.m_obj_left=obj;
           }
         //--- Right
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_RIGHT)
           {
            //--- If failed to change the object size (for the initial object width and the entire underlay height), move on to the next one
            if(!obj.Resize(obj.GetWidthInit(),this.GetHeightUnderlay(),false))
               continue;
            //--- Get the pointer to the object at the right whose edges are used to bind the current one
            CGCnvElement *coord_base=this.GetRightObj();
            //--- Get the object binding coordinates
            x=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? m_underlay.RightEdge()-obj.Width() : coord_base.CoordX()-obj.Width()-1);
            y=this.GetCoordYUnderlay();
            //--- If failed to move the object to the obtained coordinates, move on to the next one
            if(!obj.Move(x,y,false))
               continue;
            //--- Set the current object as the right one whose edges will be used to bind the next one
            this.m_obj_right=obj;
           }
         //--- Binding with filling
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_FILL)
           {
            //--- If failed to change the object size (for the entire underlay width and height), move on to the next one
            if(!obj.Resize(this.GetWidthUnderlay(),this.GetHeightUnderlay(),false))
               continue;
            //--- Set the underlay as a binding object
            this.SetUnderlayAsBase();
            //--- Get the object binding coordinates
            x=this.GetLeftObj().CoordX();
            y=this.GetTopObj().CoordY();
            //--- If failed to move the object to the obtained coordinates, move on to the next one
            if(!obj.Move(x,y,false))
               continue;
           }
         //--- No binding
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_NONE)
           {
            //--- Reset the object size
            obj.Resize(obj.GetWidthInit(),obj.GetHeightInit(),false);
            //--- Get the initial object location coordinates
            x=this.GetCoordXUnderlay()+obj.CoordXRelativeInit();
            y=this.GetCoordYUnderlay()+obj.CoordYRelativeInit();
            //--- If failed to move the object to the obtained coordinates, move on to the next one
            if(!obj.Move(x,y,false))
               continue;
           }
         //--- Calculate and set the relative object coordinates
         obj.SetCoordXRelative(x-this.m_underlay.CoordX());
         obj.SetCoordYRelative(y-this.m_underlay.CoordY());
        }
     }
//--- Redraw the object with the redraw flag and return 'true'
   this.Redraw(redraw); 
   return true;
  }
//+------------------------------------------------------------------+

A lógica do método é descrita nos comentários ao código. Para um painel com sinalizador de redimensionamento automático, ainda não ordenamos os objeto dentro do contêiner (existem apenas stubs onde posteriormente escrevemos manipuladores), porque haverá uma lógica diferente daquela implementada para um painel sem redimensionamento automático.

Método que define o substrato como a origem das coordenadas dos objetos Dock vinculados:

//+------------------------------------------------------------------+
//| Set the underlay as a coordinate system zero                     |
//+------------------------------------------------------------------+
void CPanel::SetUnderlayAsBase(void)
  {
   this.m_obj_left=this.m_underlay;
   this.m_obj_right=this.m_underlay;
   this.m_obj_top=this.m_underlay;
   this.m_obj_bottom=this.m_underlay;
  }
//+------------------------------------------------------------------+

Aqui nós simplesmente atribuímos um ponteiro ao substrato para todos os quatro objetos de encaixe.

Método que redimensiona um elemento para caber em seu interior:

//+------------------------------------------------------------------+
//| Adjust the element size to fit its content                       |
//+------------------------------------------------------------------+
bool CPanel::AutoSizeProcess(const bool redraw)
  {
//--- Get values along X and Y axes, by which the panel size is to be changed
   int excess_w=this.GetExcessMaxX();
   int excess_h=this.GetExcessMaxY();
//--- If failed to change the size, return 'true'
   if(excess_w==0 && excess_h==0)
      return true;
   //--- If failed to change the panel size, return the result of its adjustment
   return
     (
      //--- if only a size increase
      this.AutoSizeMode()==CANV_ELEMENT_AUTO_SIZE_MODE_GROW ? 
      this.Resize(this.Width()+(excess_w>0  ? excess_w : 0),this.Height()+(excess_h>0  ? excess_h : 0),redraw) :
      //--- if both increase and decrease
      this.Resize(this.Width()+(excess_w!=0 ? excess_w : 0),this.Height()+(excess_h!=0 ? excess_h : 0),redraw)
     );
  }
//+------------------------------------------------------------------+

A lógica do método é descrita nos comentários ao código. Resumindo: obtemos os valores máximos em X e Y, pelos quais os objetos vinculados ultrapassam os limites do substrato. Um valor positivo indica que os objetos se estendem além dos limites do substrato, um valor negativo indica que o substrato é muito grande e pode ser reduzido. Bem, retornamos o resultado do redimensionamento do painel, levando em consideração o modo de dimensionamento automático de seus tamanhos (só aumentar, ou aumentar e diminuir)


No arquivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh da classe-coleção de elementos gráficos, devemos corrigir os nomes dos métodos de definição das propriedades BorderStyle() e FrameWidthAll() nos métodos de criação de painéis, assim: SetBorderStyle() e SetFrameWidthAll(). Nos arquivos vinculados ao artigo, os métodos já foram renomeados para uns novos.

No arquivo \MQL5\Include\DoEasy\Engine.mqh, em métodos que retornam um objeto WForm Panel, devemos alterar o nome antigo da macro-substituição GRAPH_ELEMENT_TYPE_PANEL para o novo: GRAPH_ELEMENT_TYPE_WF_PANEL:

//--- 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_WF_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_WF_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_WF_PANEL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }

//--- Create the WinForm Element object

Hoje, essas são todas as mudanças e melhorias necessárias.


Teste

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

O que vamos testar? Vamos adicionar um sinalizador para redimensionar automaticamente o painel consoante seu interior e um modo de redimensionamento automático nas configurações do Expert Advisor.

Vamos adicionar uma nova tecla, à qual o Expert Advisor responderá para posicionar objetos dentro do contêiner. Ao pressionar a tecla Q, todos os objetos criados dentro do painel serão posicionados de acordo com seu modo de encaixe. Teremos 6 objetos, de acordo com o número de modos de encaixe:

//+------------------------------------------------------------------+
//| Control borders bound to the container                           |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_DOCK_MODE
  {
   CANV_ELEMENT_DOCK_MODE_NONE,                       // Attached to the specified coordinates, size does not change
   CANV_ELEMENT_DOCK_MODE_TOP,                        // Attaching to the top and stretching along the container width
   CANV_ELEMENT_DOCK_MODE_BOTTOM,                     // Attaching to the bottom and stretching along the container width
   CANV_ELEMENT_DOCK_MODE_LEFT,                       // Attaching to the left and stretching along the container height
   CANV_ELEMENT_DOCK_MODE_RIGHT,                      // Attaching to the right and stretching along the container height
   CANV_ELEMENT_DOCK_MODE_FILL,                       // Stretching along the entire container width and height
  };
//+------------------------------------------------------------------+

Assim, cada um desses objetos receberá um modo de encaixe correspondente ao seu número na lista de objetos do painel. O primeiro objeto da lista não terá encaixe (CANV_ELEMENT_DOCK_MODE_NONE), o segundo terá CANV_ELEMENT_DOCK_MODE_TOP, o terceiro terá MODE_BOTTOM e assim por diante.

Na área global, adicionamos uma macro-substituição para a tecla Q e enumerações para parâmetros de entrada em inglês e no idioma do país do usuário e inserimos novos parâmetros de entrada:

//+------------------------------------------------------------------+
//|                                            TestDoEasyPart105.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
//--- defines
#define  FORMS_TOTAL (3)   // Number of created forms
#define  START_X     (4)   // Initial X coordinate of the shape
#define  START_Y     (4)   // Initial Y coordinate of the shape
#define  KEY_LEFT    (65)  // (A) Left
#define  KEY_RIGHT   (68)  // (D) Right
#define  KEY_UP      (87)  // (W) Up
#define  KEY_DOWN    (88)  // (X) Down
#define  KEY_FILL    (83)  // (S) Filling
#define  KEY_ORIGIN  (90)  // (Z) Default
#define  KEY_INDEX   (81)  // (Q) By index

//--- enumerations by compilation language
#ifdef COMPILE_EN
enum ENUM_AUTO_SIZE_MODE
  {
   AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW,                // Grow
   AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK   // Grow and Shrink
  };
#else 
enum ENUM_AUTO_SIZE_MODE
  {
   AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW,                // Increase only
   AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK   // Increase and decrease
  };
#endif 
//--- input parameters
sinput   bool                 InpMovable        =  true;                // Movable forms flag
sinput   ENUM_INPUT_YES_NO    InpAutoSize       =  INPUT_YES;           // Autosize
sinput   ENUM_AUTO_SIZE_MODE  InpAutoSizeMode   =  AUTO_SIZE_MODE_GROW; // Autosize Mode
//--- global variables
CEngine        engine;
color          array_clr[];
//+------------------------------------------------------------------+

Removemos do manipulador OnInit() o código para criar formas e elementos. Vamos deixar apenas a criação de painel e no loop no próprio painel vamos criar seis objetos-paineis vinculados com o sinalizador de redesenho false. Nesse caso, a altura do painel será um pouco menor que a altura total de todos os objetos construídos dentro dele, e a largura, ao contrário, será maior. Assim, podemos ver como o painel se ajusta aos tamanhos dos objetos construídos dentro dele:

//+------------------------------------------------------------------+
//| 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 WinForms Panel object
   CPanel *pnl=NULL;
   pnl=engine.CreateWFPanel("WFPanel",50,50,230,150,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true);
   if(pnl!=NULL)
     {
      //--- Set Padding to 4
      pnl.SetPaddingAll(4);
      //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs
      pnl.SetMovable(InpMovable);
      pnl.SetAutoSize(InpAutoSize,false);
      pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false);
      //--- In the loop, create 6 bound panel objects
      for(int i=0;i<6;i++)
        {
         //--- create the panel object with coordinates along the X axis in the center and 10 along the Y axis, the width of 80 and the height of 30
         CPanel *prev=pnl.GetElement(i-1);
         int xb=0, yb=0;
         int x=(i<3 ? (prev==NULL ? xb : prev.CoordXRelative()) : xb+prev.Width()+20);
         int y=(i<3 ? (prev==NULL ? yb : prev.BottomEdgeRelative()+16) : (i==3 ? yb : prev.BottomEdgeRelative()+16));
         pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_PANEL,pnl,x,y,80,40,C'0xCD,0xDA,0xD7',200,true,false);
        }
      pnl.Redraw(true);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Depois de criar todos os elementos dentro do painel, redesenhamos completamente o painel inteiro junto com os elementos criados nele (sinalizador de redesenho true)

No manipulador de eventos OnChartEvent() , no bloco de processamento de pressionamento de tecla, escrevemos um manipulador de pressionamento de tecla Q:

   //--- If a key is pressed
   if(id==CHARTEVENT_KEYDOWN)
     {
      CPanel *panel=engine.GetWFPanel(0);
      if(panel!=NULL && (lparam==KEY_UP || lparam==KEY_DOWN || lparam==KEY_LEFT || lparam==KEY_RIGHT || lparam==KEY_FILL || lparam==KEY_ORIGIN || lparam==KEY_INDEX))
        {
         for(int i=0;i<panel.ElementsTotal();i++)
           {
            CPanel *obj=panel.GetElement(i);
            if(obj!=NULL)
              {
               if(lparam==KEY_UP)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_TOP,false);
               else if(lparam==KEY_DOWN)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_BOTTOM,false);
               else if(lparam==KEY_LEFT)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_LEFT,false);
               else if(lparam==KEY_RIGHT)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_RIGHT,false);
               else if(lparam==KEY_FILL)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_FILL,false);
               else if(lparam==KEY_ORIGIN)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
               else if(lparam==KEY_INDEX)
                 {
                  obj.SetDockMode((ENUM_CANV_ELEMENT_DOCK_MODE)i,true);
                  Sleep(i>0 ? 500 : 0);
                 }
              }
           }
         panel.Redraw(true);
        }
     }

Ao mesmo tempo, para ver claramente como os objetos se movem para seus lugares de acordo com o modo de encaixe, faremos um atraso de meio segundo após cada próximo movimento do objeto para novas coordenadas.

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


Como podemos ver, o encaixe de objetos em cada lado do painel funciona corretamente, quando pressionamos a tecla Q, cada objeto é vinculado ao lado desejado do painel e, ao alterar os modos de redimensionamento automático do painel, ele se adapta ao seu interior de acordo com o modo de dimensionamento automático.

O que virá a seguir?

No próximo artigo, continuaremos trabalhando em objetos WinForms.

Todos os arquivos da versão atual da biblioteca, os arquivos do EA de teste e o indicador de controle de eventos de gráficos 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
DoEasy. Controles (Parte 3): Criando controles vinculados
DoEasy. Controles (Parte 4): Controle "Painel", parâmetros Padding e Dock