English Русский 中文 Español Deutsch 日本語
preview
DoEasy. Controles (Parte 24): Objeto WinForms dica

DoEasy. Controles (Parte 24): Objeto WinForms dica

MetaTrader 5Exemplos | 23 janeiro 2023, 14:05
367 0
Artyom Trishkin
Artyom Trishkin

Conteúdo


Ideia

Na biblioteca, cada controle tem um ponteiro tanto para o objeto base quanto para o objeto pai da hierarquia de objetos relacionados, em outras palavras, muitos elementos gráficos estão de alguma forma relacionados entre si. Na implementação atual, a especificação desses objetos para um elemento gráfico recém-criado ainda tem falhas que levam o elemento a "perder" seus objetos pais. Nós os especificamos depois de criar o objeto e descobrimos que essas ações nem sempre levam a um resultado. Achar o local do erro é bastante complicado e não leva a um resultado. Por isso, decidimos mudar a lógica de especificação dos objetos pais na hierarquia de relacionamentos. Adicionaremos ponteiros para objetos pais a todos os construtores de todos os elementos gráficos. Assim, nós os especificaremos diretamente no momento da criação do objeto gráfico, o que nos poupará de especificar objetos pais toda vez depois de criar o próximo controle vinculado. Para os controles que são independentes (não são criados como objetos anexados a nenhum elemento), passaremos NULL como ponteiros para objetos pais.

Essa mudança nos permitirá evitar a busca por um bug, devido ao qual alguns desses objetos não "sabem" sobre seus pais, e simplificará a escrita e a depuração de novos controles de biblioteca no futuro.

Além disso, além de finalizar os construtores de objetos-elementos gráficos, hoje vamos criar vários novos objetos WinForms. Ao trabalhar com controles em aplicativos do Windows, podemos notar como o cursor do mouse muda de aparência quando entra na área de um objeto. Isso indica que a interação com essa região pode resultar, por exemplo, na mudança da aparência do objeto, ou pode indicar a capacidade de realizar alguma ação naquela área, como usar a funcionalidade de arrastar e soltar.

Infelizmente, a linguagem MQL5 não permite alterar a aparência do cursor do mouse, mas precisamos de uma forma de informar o usuário da biblioteca sobre a possível interação com os controles. A solução é exibir objetos gráficos auxiliares nas áreas onde é possível interagir com o cursor do mouse. A aparência desses objetos indicará as possíveis ações que podem ser executadas nessa área do controle.

Vamos chamá-los de objetos dica. A estrutura desses elementos será construída da seguinte forma: para todos os objetos dica, criaremos um objeto básico com funcionalidade básica e, com base nele, criaremos objetos específicos, cada um com sua finalidade específica.

Hoje, vamos criar um objeto dica base e seus derivados, objetos que indicam a possibilidade de mover o separador para cima, para baixo, para a esquerda e para a direita. Quando o cursor do mouse estiver sobre a área do separador no controle SplitContainer, esses objetos aparecerão ao lado do cursor, indicando que o separador pode ser movido da esquerda para a direita ou de cima para baixo.

Esses objetos dica criados hoje serão úteis no futuro, tanto como parte de novos controles quanto para indicar novas funcionalidades adicionadas a controles existentes, como a possibilidade de alterar o tamanho de um objeto gráfico.


Modificando as classes da biblioteca

O objeto dica padrão deve ter suas cores e tamanho definidos. Vamos fazer isso no arquivo \MQL5\Include\DoEasy\Defines.mqh:

#define CLR_DEF_CONTROL_SPLIT_CONTAINER_BACK_COLOR    (C'0xF0,0xF0,0xF0')  // SplitContainer control background color
#define CLR_DEF_CONTROL_SPLIT_CONTAINER_MOUSE_DOWN    (C'0xF0,0xF0,0xF0')  // Color of SplitContainer control background when clicking on the control
#define CLR_DEF_CONTROL_SPLIT_CONTAINER_MOUSE_OVER    (C'0xF0,0xF0,0xF0')  // Color of SplitContainer control background when hovering the mouse over the control
#define CLR_DEF_CONTROL_SPLIT_CONTAINER_BORDER_COLOR  (C'0x65,0x65,0x65')  // SplitContainer control frame color

#define CLR_DEF_CONTROL_HINT_BACK_COLOR               (C'0xFF,0xFF,0xE1')  // Hint control background color
#define CLR_DEF_CONTROL_HINT_BORDER_COLOR             (C'0x76,0x76,0x76')  // Hint control frame color
#define CLR_DEF_CONTROL_HINT_FORE_COLOR               (C'0x5A,0x5A,0x5A')  // Hint control text color

#define DEF_CONTROL_LIST_MARGIN_X      (1)                        // Gap between columns in ListBox controls
#define DEF_CONTROL_LIST_MARGIN_Y      (0)                        // Gap between rows in ListBox controls

#define DEF_FONT                       ("Calibri")                // Default font
#define DEF_FONT_SIZE                  (8)                        // Default font size
#define DEF_CHECK_SIZE                 (12)                       // Verification flag default size
#define DEF_ARROW_BUTTON_SIZE          (15)                       // Default arrow button size
#define OUTER_AREA_SIZE                (16)                       // Size of one side of the outer area around the form workspace
#define DEF_FRAME_WIDTH_SIZE           (3)                        // Default form/panel/window frame width
#define DEF_HINT_ICON_SIZE             (11)                       // Hint object side size
//--- Graphical object parameters
#define PROGRAM_OBJ_MAX_ID             (10000)                    // Maximum value of an ID of a graphical object belonging to a program
#define CTRL_POINT_RADIUS              (5)                        // Radius of the control point on the form for managing graphical object pivot points
#define CTRL_POINT_COLOR               (clrDodgerBlue)            // Radius of the control point on the form for managing graphical object pivot points
#define CTRL_FORM_SIZE                 (40)                       // Size of the control point form for managing graphical object pivot points
//+------------------------------------------------------------------+


Na lista de tipos de elementos gráficos, inserimos os novos tipos de objetos que vamos criar hoje:

//+------------------------------------------------------------------+
//| 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_WF_CHECKED_LIST_BOX,            // Windows Forms CheckedListBox
   GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,             // Windows Forms ButtonListBox
   //--- Auxiliary elements of WinForms objects
   GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM,               // Windows Forms ListBoxItem
   GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,                  // Windows Forms TabHeader
   GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,                   // Windows Forms TabField
   GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,       // Windows Forms SplitContainerPanel
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON,                // Windows Forms ArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,             // Windows Forms UpArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,           // Windows Forms DownArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,           // Windows Forms LeftArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,          // Windows Forms RightArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,        // Windows Forms UpDownArrowButtonsBox
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,        // Windows Forms LeftRightArrowButtonsBox
   GRAPH_ELEMENT_TYPE_WF_SPLITTER,                    // Windows Forms Splitter
   GRAPH_ELEMENT_TYPE_WF_HINT_BASE,                   // Windows Forms HintBase
   GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,              // Windows Forms HintMoveLeft
   GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,             // Windows Forms HintMoveRight
   GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,                // Windows Forms HintMoveUp
   GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,              // Windows Forms HintMoveDown
  };
//+------------------------------------------------------------------+


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

   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,    // UpDownArrowBox control
   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,    // LeftRightArrowBox control
   MSG_GRAPH_ELEMENT_TYPE_WF_HINT_BASE,               // HintBase control
   MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,          // HintMoveLeft control
   MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,         // HintMoveLeft control
   MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,            // HintMoveLeft control
   MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,          // HintMoveLeft control
   MSG_GRAPH_OBJ_BELONG_PROGRAM,                      // Graphical object belongs to a program
   MSG_GRAPH_OBJ_BELONG_NO_PROGRAM,                   // Graphical object does not belong to a program

e os textos das mensagens correspondentes aos índices recém-adicionados:

   {"Элемент управления \"UpDownArrowBox\"","Control element \"UpDownArrowBox\""},
   {"Элемент управления \"LeftRightArrowBox\"","Control element \"LeftRightArrowBox\""},
   {"Элемент управления \"HintBase\"","Control element \"HintBase\""},
   {"Элемент управления \"HintMoveLeft\"","Control element \"HintMoveLeft\""},
   {"Элемент управления \"HintMoveRight\"","Control element \"HintMoveRight\""},
   {"Элемент управления \"HintMoveUp\"","Control element \"HintMoveUp\""},
   {"Элемент управления \"HintMoveDown\"","Control element \"HintMoveDown\""},
   {"Графический объект принадлежит программе","The graphic object belongs to the program"},
   {"Графический объект не принадлежит программе","The graphic object does not belong to the program"},


Agora podemos exibir descrições dos novos objetos que serão criados hoje. E para exibir a descrição dos objetos gráficos, temos um método no arquivo \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh. Neste método, escrevemos o retorno da string desejada por tipo de elemento:

//+------------------------------------------------------------------+
//| Return the description of the graphical element type             |
//+------------------------------------------------------------------+
string CGBaseObj::TypeElementDescription(const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   return
     (
      type==GRAPH_ELEMENT_TYPE_STANDARD                  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD)                 :
      type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)        :
      type==GRAPH_ELEMENT_TYPE_ELEMENT                   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT)                  :
      type==GRAPH_ELEMENT_TYPE_SHADOW_OBJ                ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ)               :
      type==GRAPH_ELEMENT_TYPE_FORM                      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM)                     :
      type==GRAPH_ELEMENT_TYPE_WINDOW                    ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW)                   :
      //--- WinForms
      type==GRAPH_ELEMENT_TYPE_WF_UNDERLAY               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY)              :
      type==GRAPH_ELEMENT_TYPE_WF_BASE                   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE)                  :
      //--- Containers
      type==GRAPH_ELEMENT_TYPE_WF_CONTAINER              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CONTAINER)             :
      type==GRAPH_ELEMENT_TYPE_WF_GROUPBOX               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GROUPBOX)              :
      type==GRAPH_ELEMENT_TYPE_WF_PANEL                  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL)                 :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL)           :
      type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER)       :
      //--- Standard controls
      type==GRAPH_ELEMENT_TYPE_WF_COMMON_BASE            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE)           :
      type==GRAPH_ELEMENT_TYPE_WF_LABEL                  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LABEL)                 :
      type==GRAPH_ELEMENT_TYPE_WF_CHECKBOX               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX)              :
      type==GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON)           :
      type==GRAPH_ELEMENT_TYPE_WF_BUTTON                 ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON)                :
      type==GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX)     :
      type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX)              :
      type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM          ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM)         :
      type==GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX       ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX)      :
      type==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX)       :
      //--- Auxiliary control objects
      type==GRAPH_ELEMENT_TYPE_WF_TAB_HEADER             ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER)            :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_FIELD              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD)             :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON)          :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP)       :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN)     :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT)     :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT     ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT)    :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX)  :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX)  :
      type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL) :
      type==GRAPH_ELEMENT_TYPE_WF_SPLITTER               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLITTER)              :
      type==GRAPH_ELEMENT_TYPE_WF_HINT_BASE              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_BASE)             :
      type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT)        :
      type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT)       :
      type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP)          :
      type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN)        :
      "Unknown"
     );
  }  
//+------------------------------------------------------------------+


Vamos aprimorar a forma como especificamos e recebemos os objetos pais na classe do objeto-elemento gráfico. Iremos incluir ponteiros para os objetos base e principais da hierarquia de relações no construtor desta classe para garantir o correto relacionamento entre os elementos gráficos criados.

No arquivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, removemos os métodos para definir os objetos pais (agora os passaremos diretamente para o construtor da classe), deixando apenas os métodos para retornar ponteiros. Renomeamos o método IsBase() para IsDependent():

//--- (1) Set and (2) return the initial shift of the (1) X and (2) Y coordinate relative to the base object
   void              SetCoordXRelativeInit(const int value)                            { this.m_init_relative_x=value;              }
   void              SetCoordYRelativeInit(const int value)                            { this.m_init_relative_y=value;              }
   int               CoordXRelativeInit(void)                                    const { return this.m_init_relative_x;             }
   int               CoordYRelativeInit(void)                                    const { return this.m_init_relative_y;             }
   
//--- Return the pointer to the parent element within related objects of the current group
   CGCnvElement     *GetBase(void)                                                     { return this.m_element_base;                }
//--- Return the pointer to the parent element within all groups of related objects
   CGCnvElement     *GetMain(void)     { return(this.m_element_main==NULL ? this.GetObject() : this.m_element_main);                }

//--- Return the flag indicating that the object is (1) main, (2) base
   bool              IsMain(void)                                                      { return this.m_element_main==NULL;          }
   bool              IsDependent(void)                                                 { return this.m_element_base!=NULL;          }
//--- Get the (1) main and (2) base object ID
   int               GetMainID(void)
                       {
                        if(this.IsMain())
                           return this.ID();
                        CGCnvElement *main=this.GetMain();
                        return(main!=NULL ? main.ID() : WRONG_VALUE);
                       }
   int               GetBaseID(void)
                       {
                        if(!this.IsDependent())
                           return this.ID();
                        CGCnvElement *base=this.GetBase();
                        return(base!=NULL ? base.ID() : WRONG_VALUE);
                       }

O fato do nome do método "IsBase" indicar que ele está verificando "se este é o objeto base" para algum outro objeto não é propriamente correto. A questão é que, se o valor da variável m_element_base é NULL, significa que este objeto não é base, ou seja, ele não está ligado a nenhum outro objeto. Mas pode ser um objeto base para outros (se forem criados dentro dele como objetos anexados) ou pode não ser base se não tiver elementos gráficos anexados a ele.

Assim, podemos concluir que a afirmação "Is Base" é incorreta. Em vez disso, podemos determinar se um objeto é dependente (anexado ao objeto pai) ao verificar se m_element_base é igual a NULL. Se sim, o objeto não está anexado a nenhum outro, ou seja, é independente. Portanto, propomos renomear o método IsBase para IsDependent para refletir essa distinção.
Além disso, as verificações do objeto base também serão alteradas para ver se se o objeto é independente ou dependente:

   int               GetBaseID(void)
                       {
                        if(!this.IsDependent())
                           return this.ID();
                        CGCnvElement *base=this.GetBase();
                        return(base!=NULL ? base.ID() : WRONG_VALUE);
                       }

Se o objeto não estiver vinculado a nenhum outro objeto, seu identificador será devolvido. Caso contrário, o identificador do objeto base será devolvido.


No construtor protegido e público paramétrico, incluiremos novas variáveis formais, através das quais passaremos os ponteiros para os objetos principal e base aos quais o objeto criado estará vinculado:

protected:
//--- Protected constructor
                     CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                  CGCnvElement *main_obj,CGCnvElement *base_obj,
                                  const long    chart_id,
                                  const int     wnd_num,
                                  const string  descript,
                                  const int     x,
                                  const int     y,
                                  const int     w,
                                  const int     h);
public:
//--- (1) Set and (2) return the X coordinate shift relative to the base object
   void              SetCoordXRelative(const int value)                                { this.m_shift_coord_x=value;                }
   int               CoordXRelative(void)                                        const { return this.m_shift_coord_x;               }
//--- (1) Set and (2) return the Y coordinate shift relative to the base object
   void              SetCoordYRelative(const int value)                                { this.m_shift_coord_y=value;                }
   int               CoordYRelative(void)                                        const { return this.m_shift_coord_y;               }
   
//--- Event handler
   virtual void      OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Parametric constructor
                     CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                  CGCnvElement *main_obj,CGCnvElement *base_obj,
                                  const int     element_id,
                                  const int     element_num,
                                  const long    chart_id,
                                  const int     wnd_num,
                                  const string  descript,
                                  const int     x,
                                  const int     y,
                                  const int     w,
                                  const int     h,
                                  const color   colour,
                                  const uchar   opacity,
                                  const bool    movable=true,
                                  const bool    activity=true,
                                  const bool    redraw=false);
//--- Default constructor
                     CGCnvElement() : m_shadow(false),m_chart_color_bg((color)::ChartGetInteger(::ChartID(),CHART_COLOR_BACKGROUND))
                        {
                         this.m_type=OBJECT_DE_TYPE_GELEMENT;
                         this.m_element_main=NULL;
                         this.m_element_base=NULL;
                         this.m_shift_coord_x=0;
                         this.m_shift_coord_y=0;
                        }
//--- Destructor
                    ~CGCnvElement()
                        { this.m_canvas.Destroy();             }
     
//+------------------------------------------------------------------+

Agora, ao criar um objeto, será necessário passar os ponteiros para dois objetos pais para o construtor. Se o objeto for criado como independente, em vez de ponteiros, usaremos o valor NULL. Isso nos livrará completamente de ações adicionais para especificar esses objetos após a criação do novo elemento gráfico.

Anteriormente nos construtores, especificamos valores padrão para objetos pais:

   this.m_element_main=NULL;
   this.m_element_base=NULL;

Agora vamos inserir os valores passados nos parâmetros formais:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           CGCnvElement *main_obj,CGCnvElement *base_obj,
                           const int      element_id,
                           const int      element_num,
                           const long     chart_id,
                           const int      wnd_num,
                           const string   descript,
                           const int      x,
                           const int      y,
                           const int      w,
                           const int      h,
                           const color    colour,
                           const uchar    opacity,
                           const bool     movable=true,
                           const bool     activity=true,
                           const bool     redraw=false) : m_shadow(false)
  {
   this.SetTypeElement(element_type);
   this.m_type=OBJECT_DE_TYPE_GELEMENT; 
   this.m_element_main=main_obj;
   this.m_element_base=base_obj;
   this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND);
   this.m_name=this.CreateNameGraphElement(element_type);
   //---...
   //---...

A partir de agora, ao criar um objeto, ele imediatamente terá conhecimento de seus objetos pais. Caso o elemento gráfico não esteja vinculado a nenhum outro, as duas variáveis ​​terão valor NULL, o que indica que o objeto é o principal e independente.


O método que recorta a imagem contornada pelo escopo retangular calculado agora verificará se o objeto é independente do pai:

//+------------------------------------------------------------------+
//| Crop the image outlined by the calculated                        |
//| rectangular visibility scope                                     |
//+------------------------------------------------------------------+
void CGCnvElement::Crop(void)
  {
//--- Get the pointer to the base object
   CGCnvElement *base=this.GetBase();
//--- If the object does not have a base object it is attached to, then there is no need to crop the hidden areas - leave
   if(!this.IsDependent())
      return;
//--- Set the initial coordinates and size of the visibility scope to the entire object
//---...
//---...


No arquivo \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh da classe de objeto sombra, da mesma forma, no construtor da classe, escreveremos a transferência de ponteiros para os objetos principal e base:

//--- Draw the object shadow form
   void              DrawShadowFigureRect(const int w,const int h);

public:
//--- Constructor indicating the main and base objects, chart ID and subwindow
                     CShadowObj(CGCnvElement *main_obj,CGCnvElement *base_obj,
                                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


Na implementação do construtor, na linha de inicialização, passaremos para o objeto pai os ponteiros para os objetos principal e base passados para o construtor :

//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CShadowObj::CShadowObj(CGCnvElement *main_obj,CGCnvElement *base_obj,
                       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,main_obj,base_obj,chart_id,subwindow,name,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GSHADOW; 
   CGCnvElement::SetBackgroundColor(clrNONE,true);
   CGCnvElement::SetOpacity(0);
   CGCnvElement::SetActive(false);
   this.m_opacity=CLR_DEF_SHADOW_OPACITY;
   this.m_blur=DEF_SHADOW_BLUR;
   color gray=CGCnvElement::ChangeColorSaturation(this.ChartBackgroundColor(),-100);
   this.m_color=CGCnvElement::ChangeColorLightness(gray,-50);
   this.m_shadow=false;
   this.SetVisibleFlag(false,false);
   CGCnvElement::Erase();
  }
//+------------------------------------------------------------------+

Ao criar um objeto sombra para qualquer objeto, os ponteiros para o objeto a partir do qual (para o qual) a sombra é criada são passados ​​para o construtor do objeto sombra. Consequentemente, neste construtor, os ponteiros para o objeto principal e base serão passados ​​para o construtor do objeto base de todos os elementos gráficos CGCnvElement e serão gravados nas variáveis ​​correspondentes. Dessa forma, passamos os ponteiros para o objeto pai em toda a cadeia de classes filhas através dos construtores. Assim, sempre garantimos a indicação precisa dos objetos principais e base para qualquer controle criado, imediatamente ao criá-lo.


No arquivo \MQL5\Include\DoEasy\Objects\Graph\Form.mqh da classe do objeto forma, também adicionamos a transferência de ponteiros para os objetos principal e base em todos os construtores:

protected:
//--- Protected constructor with object type, chart ID and subwindow
                     CForm(const ENUM_GRAPH_ELEMENT_TYPE type,
                           CGCnvElement *main_obj,CGCnvElement *base_obj,
                           const long chart_id,
                           const int subwindow,
                           const string descript,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
public:                     
//--- Constructors
                     CForm(CGCnvElement *main_obj,CGCnvElement *base_obj,
                           const long chart_id,
                           const int subwindow,
                           const string descript,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CForm() { this.m_type=OBJECT_DE_TYPE_GFORM; this.Initialize(); }
//--- Destructor
                    ~CForm();

...

//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CForm::CForm(const ENUM_GRAPH_ELEMENT_TYPE type,
             CGCnvElement *main_obj,CGCnvElement *base_obj,
             const long chart_id,
             const int subwindow,
             const string descript,
             const int x,
             const int y,
             const int w,
             const int h) : CGCnvElement(type,main_obj,base_obj,chart_id,subwindow,descript,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 indicating the main and base objects,                |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CForm::CForm(CGCnvElement *main_obj,CGCnvElement *base_obj,
             const long chart_id,
             const int subwindow,
             const string descript,
             const int x,
             const int y,
             const int w,
             const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,main_obj,base_obj,chart_id,subwindow,descript,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 método que cria um novo objeto gráfico, escreveremos a transferência dos ponteiros para os objetos principal e base:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CForm::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                      const int obj_num,
                                      const string descript,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool movable,
                                      const bool activity)
  {
   CGCnvElement *element=NULL;
   //--- Depending on the created object type,
   switch(type)
     {
      //--- create a graphical element object
      case GRAPH_ELEMENT_TYPE_ELEMENT :
         element=new CGCnvElement(type,this.GetMain(),this.GetObject(),this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity);
        break;
      //--- create a form object
      case GRAPH_ELEMENT_TYPE_FORM :
         element=new CForm(type,this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   element.SetMovable(movable);
   element.SetCoordXRelative(element.CoordX()-this.CoordX());
   element.SetCoordYRelative(element.CoordY()-this.CoordY());
   return element;
  }
//+------------------------------------------------------------------+

Como estas linhas criam um elemento ligado ao objeto base, o valor retornado pelo método GetMain() é passado como um ponteiro para o objeto principal, e o valor retornado pelo método GetObject() é passado como um ponteiro para o objeto base. O ponteiro para o objeto principal é indicado durante a criação do objeto. E é este ponteiro que estamos enviando para o construtor do recém-criado objeto vinculado. Portanto, sempre haverá apenas um objeto principal em toda a hierarquia, o primeiro da cadeia. Mas nós passamos um ponteiro para o objeto base como um ponteiro, porque lá o objeto anexado é criado. Portanto, o ponteiro para o objeto base para o elemento vinculado recém-criado deve apontar para este objeto.


Removemos as referências aos objetos principal e base dos métodos CreateAndAddNewElement() e CreateNewElement(), pois esses métodos já foram removidos da classe do elemento gráfico, e agora os ponteiros para os objetos principal e base são passados nos construtores das classes:

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//| and add it to the list of bound objects                          |
//+------------------------------------------------------------------+
CGCnvElement *CForm::CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                            const int x,
                                            const int y,
                                            const int w,
                                            const int h,
                                            const color colour,
                                            const uchar opacity,
                                            const bool activity)
  {
//--- If the type of a created graphical element is less than the "element", inform of that and return 'false'
   if(element_type<GRAPH_ELEMENT_TYPE_ELEMENT)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_NOT_INTENDED),::StringSubstr(::EnumToString(element_type),19));
      .return NULL;
     }
//--- Specify the element index in the list
   int num=this.m_list_elements.Total();
//--- Create a description of the default graphical element
   string descript=TypeGraphElementAsString(element_type);
//--- 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,descript,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.SetBackgroundColor(colour,true);
   obj.SetOpacity(opacity);
   obj.SetActive(activity);
   obj.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain());
   obj.SetBase(this.GetObject());
   obj.SetID(this.GetMaxIDAll()+1);
   obj.SetNumber(num);
   obj.SetCoordXRelative(obj.CoordX()-this.CoordX());
   obj.SetCoordYRelative(obj.CoordY()-this.CoordY());
   obj.SetZorder(this.Zorder(),false);
   obj.SetCoordXRelativeInit(obj.CoordXRelative());
   obj.SetCoordYRelativeInit(obj.CoordYRelative());
   obj.SetVisibleFlag(this.IsVisible(),false);
   obj.SetActive(this.Active());
   obj.SetEnabled(this.Enabled());
   return obj;
  }
//+------------------------------------------------------------------+

...

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CForm::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                             const int x,
                             const int y,
                             const int w,
                             const int h,
                             const color colour,
                             const uchar opacity,
                             const bool activity,
                             const bool redraw)
  {
//--- Create a new graphical element
   CGCnvElement *obj=this.CreateAndAddNewElement(element_type,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.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain());
   obj.SetBase(this.GetBase());
   obj.Erase(colour,opacity,redraw);
   return true;
  }
//+------------------------------------------------------------------+


No método que cria o objeto sombra, passamos ponteiros para os objetos principal e base ao construtor da classe de objeto sombra:

//+------------------------------------------------------------------+
//| Create the shadow object                                         |
//+------------------------------------------------------------------+
void CForm::CreateShadowObj(const color colour,const uchar opacity)
  {
//--- If the shadow flag is disabled or the shadow object already exists, exit
   if(!this.m_shadow || this.m_shadow_obj!=NULL)
      return;
//--- Calculate the shadow object coordinates according to the offset from the top and left
   int x=this.CoordX()-OUTER_AREA_SIZE;
   int y=this.CoordY()-OUTER_AREA_SIZE;
//--- Calculate the width and height in accordance with the top, bottom, left and right offsets
   int w=this.Width()+OUTER_AREA_SIZE*2;
   int h=this.Height()+OUTER_AREA_SIZE*2;
//--- Create a new shadow object and set the pointer to it in the variable
   this.m_shadow_obj=new CShadowObj(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),this.NameObj()+"Shadow",x,y,w,h);
   if(this.m_shadow_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ));
      return;
     }
   this.m_list_tmp.Add(this.m_shadow_obj);
//--- Set the properties for the created shadow object
   this.m_shadow_obj.SetID(this.ID());
   this.m_shadow_obj.SetNumber(-1);
   this.m_shadow_obj.SetOpacity(opacity);
   this.m_shadow_obj.SetColor(colour);
   this.m_shadow_obj.SetMovable(this.Movable());
   this.m_shadow_obj.SetActive(false);
   this.m_shadow_obj.SetVisibleFlag(false,false);
//--- Move the form object to the foreground
   this.BringToTop();
  }
//+------------------------------------------------------------------+

Neste caso, recebemos e enviamos o objeto principal que está registrado neste objeto e especificamos este objeto como o objeto base, isso significa que a lógica para passar ponteiros é como descrita acima.

No método que atualiza as coordenadas do elemento, verificamos o sinalizador de independência do objeto e o sinalizador de que este é o objeto principal:

//+------------------------------------------------------------------+
//| Update the coordinate elements                                   |
//+------------------------------------------------------------------+
bool CForm::Move(const int x,const int y,const bool redraw=false)
  {
//--- Get the pointers to the base and main objects in the bound objects hierarchy, as well as the shadow object
   CGCnvElement *base=this.GetBase();
   CGCnvElement *main=this.GetMain();
   bool res=true;
//--- If the element is not movable and it is an independent object, leave
   if(!this.IsDependent() && !this.Movable())
      return false;
//--- If the object has a shadow and we failed to set new coordinate values to the properties of the shadow object, return 'false'
   if(this.m_shadow && this.m_shadow_obj!=NULL)
     {
      if(!this.m_shadow_obj.Move(x-OUTER_AREA_SIZE+this.m_shadow_obj.CoordXRelative(),y-OUTER_AREA_SIZE+this.m_shadow_obj.CoordYRelative(),false))
         return false;
     }
//--- If failed to set new values into graphical object properties, return 'false'
   if(!this.SetCoordX(x) || !this.SetCoordY(y))
      return false;
//--- Shift all bound objects
   if(!this.MoveDependentObj(x,y,false))
      return false;
   //--- If the update flag is set, and this is the main object, redraw the chart.
   if(this.IsMain() && redraw)
      ::ChartRedraw(this.ChartID());
   //--- Return 'true'
   return true;
  }
//+------------------------------------------------------------------+


No arquivo \MQL5\Include\DoEasy\Objects\Graph\ Graph\GraphElmControl.mqh da classe de controle do elemento gráfico, no método que cria um objeto forma no gráfico especificado na subjanela especificada, inseriremos uma indicação da ausência dos objetos principal e base:

//+------------------------------------------------------------------------+
//| Create the form object on a specified chart in a specified subwindow   |
//+------------------------------------------------------------------------+
CForm *CGraphElmControl::CreateForm(const int form_id,const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h)
  {
   CForm *form=new CForm(NULL,NULL,chart_id,wnd,name,x,y,w,h);
   if(form==NULL)
      .return NULL;
   form.SetID(form_id);
   form.SetNumber(0);
   return form;
  }
//+------------------------------------------------------------------+

Aqui ao construtor do objeto forma passamos valores NULL como ponteiros para os objetos principal e base, o que indica que o objeto que está sendo criado não está vinculado a nenhum outro e é ele próprio o principal.


Da mesma forma, especificamos os valores NULL na linha de inicialização do construtor da classe da forma para gerenciar pontos de referência de um objeto gráfico no arquivo \MQL5\Include\DoEasy\Objects\Graph\Extend\CGStdGraphObjExtToolkit.mqh:

//+------------------------------------------------------------------+
//| Class of the form for managing pivot points of a graphical object|
//+------------------------------------------------------------------+
class CFormControl : public CForm
  {
private:
   bool              m_drawn;                   // Flag indicating that the pivot point is drawn on the form
   int               m_pivot_point;             // Pivot point managed by the form
public:
//--- (1) Return and (2) set the drawn point flag
   bool              IsControlAlreadyDrawn(void)               const { return this.m_drawn;                       }
   void              SetControlPointDrawnFlag(const bool flag)       { this.m_drawn=flag;                         }
//--- (1) Return and (2) set the pivot point managed by the form
   int               GraphObjPivotPoint(void)                  const { return this.m_pivot_point;                 }
   void              SetGraphObjPivotPoint(const int index)          { this.m_pivot_point=index;                  }
//--- Constructor
                     CFormControl(void)                              { this.m_type=OBJECT_DE_TYPE_GFORM_CONTROL;  }
                     CFormControl(const long chart_id,const int subwindow,const string name,const int pivot_point,const int x,const int y,const int w,const int h) :
                        CForm(GRAPH_ELEMENT_TYPE_FORM,NULL,NULL,chart_id,subwindow,name,x,y,w,h)
                          {
                           this.m_type=OBJECT_DE_TYPE_GFORM_CONTROL;
                           this.m_pivot_point=pivot_point;
                          }
  };
//+------------------------------------------------------------------+


No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh da classe do objeto base de todos os objetos WinForms da biblioteca, aos construtores de classe adicionamos a transferência de ponteiros para os objetos principal e base:

protected:
//--- Protected constructor with object type, chart ID and subwindow
                     CWinFormBase(const ENUM_GRAPH_ELEMENT_TYPE type,
                                  CGCnvElement *main_obj,CGCnvElement *base_obj,
                                  const long chart_id,
                                  const int subwindow,
                                  const string descript,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h);
public:
//--- Constructors
                     CWinFormBase(CGCnvElement *main_obj,CGCnvElement *base_obj,
                                  const long chart_id,
                                  const int subwindow,
                                  const string descript,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h);
                       
//--- (1) Set and (2) return the default text color of all panel objects

...

//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CWinFormBase::CWinFormBase(const ENUM_GRAPH_ELEMENT_TYPE type,
                           CGCnvElement *main_obj,CGCnvElement *base_obj,
                           const long chart_id,
                           const int subwindow,
                           const string descript,
                           const int x,
                           const int y,
                           const int w,
                           const int h) : CForm(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Set the specified graphical element type for the object and assign the library object type to the current object
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_BASE; 
//--- Initialize all variables
   this.SetText("");
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetForeStateOnColor(this.ForeColor(),true);
   this.SetForeStateOnColorMouseDown(this.ForeColor());
   this.SetForeStateOnColorMouseOver(this.ForeColor());
   this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY);
   this.SetFontBoldType(FW_TYPE_NORMAL);
   this.SetMarginAll(0);
   this.SetPaddingAll(0);
   this.SetBorderSizeAll(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_gradient_v=true;
   this.m_gradient_c=false;
  }
//+------------------------------------------------------------------+
//| Constructor indicating the main and base objects,                |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CWinFormBase::CWinFormBase(CGCnvElement *main_obj,CGCnvElement *base_obj,
                           const long chart_id,
                           const int subwindow,
                           const string descript,
                           const int x,
                           const int y,
                           const int w,
                           const int h) : CForm(GRAPH_ELEMENT_TYPE_WF_BASE,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Set the graphical element and library object types as a base WinForms object
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BASE);
   this.m_type=OBJECT_DE_TYPE_GWF_BASE; 
//--- Initialize all variables
   this.SetText("");
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetForeStateOnColor(this.ForeColor(),true);
   this.SetForeStateOnColorMouseDown(this.ForeColor());
   this.SetForeStateOnColorMouseOver(this.ForeColor());
   this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY);
   this.SetFontBoldType(FW_TYPE_NORMAL);
   this.SetMarginAll(0);
   this.SetPaddingAll(0);
   this.SetBorderSizeAll(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_gradient_v=true;
   this.m_gradient_c=false;
  }
//+------------------------------------------------------------------+


No método que redesenha o objeto, alteramos a verificação de que este é o objeto principal:

//+------------------------------------------------------------------+
//| Redraw the object                                                |
//+------------------------------------------------------------------+
void CWinFormBase::Redraw(bool redraw)
  {
//--- If the object type is less than the "Base WinForms object" or the object is not to be displayed, exit
   if(this.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE || !this.Displayed())
      return;
//--- Get the "Shadow" object

//---...
//---...

//--- 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(this.IsMain() && redraw)
      ::ChartRedraw(this.ChartID());
  }
//+------------------------------------------------------------------+

Anteriormente, aqui a verificação era feita assim:

if(redraw && this.GetMain()==NULL)

Em princípio, é a mesma coisa, mas como existe um método, por que não usá-lo?

As melhorias para construtores de classes e verificações de objetos base foram implementadas em todas as classes da biblioteca WinForms. Essas melhorias serão aplicadas automaticamente em novos objetos. Para economizar espaço, não entraremos em detalhes sobre essas melhorias aqui.

Cabeçalhos dos arquivos das classe, nos quais foram aprimorados os construtores para passar ponteiros para os objetos principal e base:

CommonBase.mqh, Button.mqh, ElementsListBox.mqh, RadioButton.mqh, ArrowButton.mqh, ArrowLeftButton.mqh, ArrowRightButton.mqh, ArrowUpButton.mqh, ArrowDownButton.mqh, ListBoxItem.mqh, TabHeader.mqh.

Mas existem arquivos de classe onde, além de construtores aprimorados, foram feitas alterações nos métodos para criar um novo objeto gráfico. A transferência de ponteiros para os objetos principais e base foi incluída no construtor de objetos criados.

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CButtonListBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                               const int obj_num,
                                               const string descript,
                                               const int x,
                                               const int y,
                                               const int w,
                                               const int h,
                                               const color colour,
                                               const uchar opacity,
                                               const bool movable,
                                               const bool activity)
  {
//--- create the CButton object
   CGCnvElement *element=new CButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
//--- set the object relocation flag and relative coordinates
   element.SetMovable(movable);
   element.SetCoordXRelative(element.CoordX()-this.CoordX());
   element.SetCoordYRelative(element.CoordY()-this.CoordY());
   return element;
  }
//+------------------------------------------------------------------+


As melhorias semelhantes à criação de novos objetos gráficos (além do refinamento dos construtores) foram implementadas nos arquivos correspondentes:

ButtonListBox.mqh, CheckedListBox.mqh, ListBox.mqh, ArrowLeftRightBox.mqh, ArrowUpDownBox.mqh.

Vamos começar a criar os novos objetos auxiliares, as dicas.


Objeto auxiliar dica e seus derivados

O conceito de construção do objeto dica não será diferente do de construção da maioria dos objetos da biblioteca. Haverá um objeto base e seus herdeiros irão aprimorar e implementar as funcionalidades necessárias de um objeto de dica específico.


Na pasta \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\, criamos um novo arquivo Splitter.mqh da classe CSplitter. A classe deve ser herdada da classe base de todos os objetos WinForms CWinFormBase, e seu arquivo deve estar anexado ao arquivo da classe gerada:

//+------------------------------------------------------------------+
//|                                                     HintBase.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\WinFormBase.mqh"
//+------------------------------------------------------------------+
//| Class of the base Hint object of the WForms controls             |
//+------------------------------------------------------------------+
class CHintBase : public CWinFormBase
  {
  }


Na seção protegida, escreveremos um método virtual para desenhar uma dica, e declaramos um construtor protegido. Na seção pública da classe, escreveremos métodos para definir e retornar a cor da dica, declararemos um construtor paramétrico e métodos virtuais para mostrar, redesenhar e limpar o objeto, bem como um método para desenhar uma moldura:

//+------------------------------------------------------------------+
//| Class of the base Hint object of the WForms controls             |
//+------------------------------------------------------------------+
class CHintBase : public CWinFormBase
  {
private:

protected:
   //--- Draw a hint
   virtual void      DrawHint(const int shift) {return;}
//--- Protected constructor with object type, chart ID and subwindow
                     CHintBase(const ENUM_GRAPH_ELEMENT_TYPE type,
                               CGCnvElement *main_obj,CGCnvElement *base_obj,
                               const long chart_id,
                               const int subwindow,
                               const string descript,
                               const int x,
                               const int y,
                               const int w,
                               const int h);
public:
//--- (1) Set and (2) return the hint color
   void              SetHintColor(const color clr)       { this.SetForeColor(clr,false);  }
   color             HintColor(void)               const { return this.ForeColor();       }
//--- Constructor
                     CHintBase(CGCnvElement *main_obj,CGCnvElement *base_obj,
                               const long chart_id,
                               const int subwindow,
                               const string descript,
                               const int x,
                               const int y,
                               const int w,
                               const int h);
//--- Display the element
   virtual void      Show(void);
//--- Redraw the object
   virtual void      Redraw(bool redraw);
//--- 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);
//--- Draw the hint frame
   virtual void      DrawFrame(void);
  };
//+------------------------------------------------------------------+

Os métodos que definem e retornam a cor da dica na verdade definem e retornam a cor do texto do objeto - ForeColor().

Vamos dar uma olhada mais de perto nos métodos declarados.

Construtor protegido:

//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CHintBase::CHintBase(const ENUM_GRAPH_ELEMENT_TYPE type,
                     CGCnvElement *main_obj,CGCnvElement *base_obj,
                     const long chart_id,
                     const int subwindow,
                     const string descript,
                     const int x,
                     const int y,
                     const int w,
                     const int h) : CWinFormBase(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Set the specified graphical element type for the object and assign the library object type to the current object
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetPaddingAll(0);
   this.SetMarginAll(0);
   this.SetBorderSizeAll(0);
  }
//+------------------------------------------------------------------+

O construtor recebe o tipo de objeto criado, os ponteiros para o objeto principal e base, e outros parâmetros padrão para todos esses construtores. Na linha de inicialização do construtor da classe base, é passado o tipo de objeto passado, os ponteiros para o objeto principal e base, e outras propriedades passadas nos parâmetros formais do construtor. No corpo da classe, definimos o tipo de elemento passado no método, definimos o tipo de objeto gráfico da biblioteca como objeto auxiliar e definimos valores zero para Padding, Margin e BorderSize.

Construtor paramétrico:

//+------------------------------------------------------------------+
//| Constructor indicating the main and base objects,                |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CHintBase::CHintBase(CGCnvElement *main_obj,CGCnvElement *base_obj,
                     const long chart_id,
                     const int subwindow,
                     const string descript,
                     const int x,
                     const int y,
                     const int w,
                     const int h) : CWinFormBase(GRAPH_ELEMENT_TYPE_WF_HINT_BASE,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_HINT_BASE);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetPaddingAll(0);
   this.SetMarginAll(0);
   this.SetBorderSizeAll(0);
  }
//+------------------------------------------------------------------+

Aqui, assim como para todos os objetos WinForms da biblioteca, passamos os ponteiros para o objeto principal e base para o construtor, bem como outros parâmetros padrão para a criação do objeto. Na linha de inicialização, passamos o tipo de objeto criado como objeto dica base para o objeto pai. Passamos os ponteiros para o objeto principal e base e outros parâmetros passados no construtor. No corpo do construtor, especificamos o tipo de objeto criado como objeto dica base e o tipo de objeto gráfico da biblioteca como objeto auxiliar.


Método que redesenha o objeto:

//+------------------------------------------------------------------+
//| Redraw the object                                                |
//+------------------------------------------------------------------+
void CHintBase::Redraw(bool redraw)
  {
//--- Fill the object with background color having transparency
   this.Erase(this.BackgroundColor(),this.Opacity(),true);
  }
//+------------------------------------------------------------------+

Aqui simplesmente chamamos o método de limpeza do objeto com a cor de fundo e a opacidade definidas para o objeto.


Método que limpa o elemento, preenchendo-o com cor e opacidade:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//+------------------------------------------------------------------+
void CHintBase::Erase(const color colour,const uchar opacity,const bool redraw=false)
  {
//--- Fill the element having the specified color and the redrawing flag
   CGCnvElement::EraseNoCrop(colour,opacity,false);
//--- If the object has a frame, draw it
   if(this.BorderStyle()!=FRAME_STYLE_NONE)
      this.DrawFrame();
//--- Draw a hint
   this.DrawHint(1);
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

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


Método que limpa o elemento com preenchimento de gradiente:

//+------------------------------------------------------------------+
//| Clear the element with a gradient fill                           |
//+------------------------------------------------------------------+
void CHintBase::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::EraseNoCrop(colors,opacity,vgradient,cycle,false);
//--- If the object has a frame, draw it
   if(this.BorderStyle()!=FRAME_STYLE_NONE)
      this.DrawFrame();
//--- Draw a hint
   this.DrawHint(1);
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Este método, assim como o anterior, preenche o fundo com uma cor, mas ao invés de usar uma única cor, ele usa uma matriz de cores. Ele também desenha uma borda e, se ela estiver presente, uma dica.


Método que desenha a borda do elemento:

//+------------------------------------------------------------------+
//| Draw the element border                                          |
//+------------------------------------------------------------------+
void CHintBase::DrawFrame(void)
  {
   this.DrawRectangle(0,0,this.Width()-1,this.Height()-1,this.BorderColor(),this.Opacity());
  }
//+------------------------------------------------------------------+

Aqui, usando o método DrawRectangle, um quadro é desenhado ao redor das bordas do objeto com a cor e a opacidade do quadro definidas para ele.


Método que mostra o elemento:

//+------------------------------------------------------------------+
//| Show the element                                                 |
//+------------------------------------------------------------------+
void CHintBase::Show(void)
  {
//--- If the element should not be displayed (hidden inside another control), leave
   if(!this.Displayed())
      return;
//--- If the object has a shadow, display it
   if(this.m_shadow_obj!=NULL)
      this.m_shadow_obj.Show();
//--- Display the main form
   CGCnvElement::Show();
   this.Redraw(false);
//--- In the loop by all bound graphical objects,
   for(int i=0;i<this.m_list_elements.Total();i++)
     {
      //--- get the next graphical element
      CGCnvElement *element=this.m_list_elements.At(i);
      if(element==NULL || !element.Displayed())
         continue;
      //--- and display it
      element.Show();
     }
  }
//+------------------------------------------------------------------+

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


Esse objeto será a base para todos os outros objetos dicas. Hoje criaremos objetos que dão dicas sobre a possibilidade de mover o separador para a esquerda, direita, cima e baixo. Eles mostrarão setas a partir da base, apontando na direção correspondente de movimento possível.

Vamos criar um objeto dica que indique a possibilidade de deslocamento para a esquerda.

Na pasta \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\, criamos um novo arquivo HintMoveLeft.mqh da classe CHintMoveLeft. A classe deve ser herdada da classe do objeto dica base e seu arquivo deve ser incluído no arquivo da classe criada:

//+------------------------------------------------------------------+
//|                                                 HintMoveLeft.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "HintBase.mqh"
//+------------------------------------------------------------------+
//| HintMoveLeft base object class of the WForms controls            |
//+------------------------------------------------------------------+
class CHintMoveLeft : public CHintBase
  {
  }


Na seção protegida da classe, para desenhar a dica declaramos um método virtual, que substitui o método da classe pai, e declaramos um construtor protegido da classe. Na seção pública, declaramos o manipulador do evento "Cursor está dentro da área ativa, botões do mouse não pressionados" e o construtor paramétrico:

//+------------------------------------------------------------------+
//| HintMoveLeft object class of the WForms controls                 |
//+------------------------------------------------------------------+
class CHintMoveLeft : public CHintBase
  {

protected:
   //--- Draw a hint
   virtual void      DrawHint(const int shift);
//--- Protected constructor with object type, chart ID and subwindow
                     CHintMoveLeft(const ENUM_GRAPH_ELEMENT_TYPE type,
                                   CGCnvElement *main_obj,CGCnvElement *base_obj,
                                   const long chart_id,
                                   const int subwindow,
                                   const string descript,
                                   const int x,
                                   const int y,
                                   const int w,
                                   const int h);
public:
//--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler
   virtual void      MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Constructor
                     CHintMoveLeft(CGCnvElement *main_obj,CGCnvElement *base_obj,
                                   const long chart_id,
                                   const int subwindow,
                                   const string descript,
                                   const int x,
                                   const int y,
                                   const int w,
                                   const int h);
  };
//+------------------------------------------------------------------+


Vamos dar uma olhada mais de perto nos métodos declarados.

Construtor protegido:

//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CHintMoveLeft::CHintMoveLeft(const ENUM_GRAPH_ELEMENT_TYPE type,
                             CGCnvElement *main_obj,CGCnvElement *base_obj,
                             const long chart_id,
                             const int subwindow,
                             const string descript,
                             const int x,
                             const int y,
                             const int w,
                             const int h) : CHintBase(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Set the specified graphical element type for the object and assign the library object type to the current object
   this.SetTypeElement(type);
  }
//+------------------------------------------------------------------+

No construtor, é passado o tipo do objeto a ser criado, os ponteiros para os objetos principais e base e outros parâmetros necessários para a criação do novo elemento gráfico. Na linha de inicialização do construtor da classe pai, é passado o tipo do objeto criado e todas as outras propriedades especificadas nas variáveis do construtor. No corpo da classe, especificamos o tipo do objeto criado, passado como parâmetros formais no construtor.


Construtor paramétrico:

//+------------------------------------------------------------------+
//| Constructor indicating the main and base objects,                |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CHintMoveLeft::CHintMoveLeft(CGCnvElement *main_obj,CGCnvElement *base_obj,
                             const long chart_id,
                             const int subwindow,
                             const string descript,
                             const int x,
                             const int y,
                             const int w,
                             const int h) : CHintBase(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT);
  }
//+------------------------------------------------------------------+

Aqui tudo é exatamente igual ao construtor protegido, exceto que o tipo do objeto que está sendo criado não é passado nos parâmetros formais, mas é codificado no código.


Método que desenha a dica:

//+------------------------------------------------------------------+
//| Draw a hint                                                      |
//+------------------------------------------------------------------+
void CHintMoveLeft::DrawHint(const int shift)
  {
   int w=this.Width();
   int h=this.Height();
   int middle=int(h*0.5);
   this.DrawRectangleFill(w-2,0,w-1,h-1,this.HintColor(),255);
   this.DrawTriangleFill(shift,middle,shift+3,middle-3,shift+3,middle+3,this.HintColor(),255);
   this.DrawLine(shift+3,middle,w-3,middle,this.HintColor(),255);
  }
//+------------------------------------------------------------------+

Aqui, obtemos as medidas de largura e altura do objeto inteiro e calculamos a linha central a partir da qual os valores para a seta serão contados. Desenhamos um retângulo vertical preenchido com cores, com dois pixels de largura no lado direito do objeto. Desenhamos um triângulo cujos ângulos são calculados a partir do valor de deslocamento passado para o método e a linha central. Finalmente, traçamos uma linha horizontal central, iniciando na borda esquerda e recuando pelo valor de deslocamento passado. Essa linha termina no retângulo vertical desenhado no lado direito do objeto. Dessa forma, obtemos uma seta voltada para esquerda, baseada no lado direito do objeto. O tamanho da seta é determinado pelo valor de deslocamento passado para o método, sendo que quanto maior o valor, menor será a seta.

Quando o cursor do mouse é movido para a área de separação no elemento de controle SplitContainer, uma linha pontilhada aparece. Também deve aparecer duas setas para a esquerda-direita ou para cima-baixo, dependendo da orientação do separador - se é vertical ou horizontal. Assim que o cursor do mouse é retirado da área de separação, a linha pontilhada e as setas devem desaparecer. Isso acontece no manipulador de eventos "Cursor dentro da área ativa" dos painéis do objeto SplitContainer. No entanto, se o cursor for movido para a área de objetos auxiliares, o manipulador dos painéis não será chamado. Isso significa que precisamos criar um manipulador semelhante nos objetos auxiliares. Todos esses manipuladores virtuais são declarados na classe do objeto forma e devem ser substituídos em classes herdadas. Vamos redefini-lo aqui.

Manipulador do evento "Cursor dentro da área ativa, nenhum botão do mouse pressionado":

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CHintMoveLeft::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Set the flag for not displaying the object and hide it
   this.SetDisplayed(false);
   this.Hide();
//--- Get the pointer to the base object
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return;
//--- From the base object, get the pointers to the "right, up and down shift" hint objects,
//--- set the object non-display flag for them and hide the controls
   CWinFormBase *hint_mr=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,0);
   if(hint_mr!=NULL)
     {
      hint_mr.SetDisplayed(false);
      hint_mr.Hide();
     }
   CWinFormBase *hint_mu=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,0);
   if(hint_mu!=NULL)
     {
      hint_mu.SetDisplayed(false);
      hint_mu.Hide();
     }
   CWinFormBase *hint_md=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,0);
   if(hint_md!=NULL)
     {
      hint_md.SetDisplayed(false);
      hint_md.Hide();
     }
  }
//+------------------------------------------------------------------+

Toda a lógica do método está descrita nos comentários ao código. Resumindo: quando o cursor entra na área ativa de um objeto, o gatilho desse manipulador é acionado. Nele, nós primeiro ocultamos esse objeto. Em seguida, obtemos um ponteiro para o objeto base ao qual os objetos auxiliares estão anexados. Neste método, obtemos auxílios com setas para a direita, cima e baixo. A seta para a esquerda é esse objeto e já está escondida. Se os ponteiros para os objetos solicitados são válidos, escondemos esses objetos também. Dessa forma, quando o cursor entra na área desse objeto, ele se esconde e esconde outros objetos auxiliares.


Vamos criar um objeto dica que indique a possibilidade de deslocamento para a direita.

Na pasta \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ , criamos um novo arquivo HintMoveRight.mqh da classe CHintMoveRight. A classe deve ser herdada da classe do objeto dica base e seu arquivo deve ser incluído no arquivo da classe criada:

//+------------------------------------------------------------------+
//|                                                HintMoveRight.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "HintBase.mqh"
//+------------------------------------------------------------------+
//| Class of the base HintMoveRight object of the WForms controls    |
//+------------------------------------------------------------------+
class CHintMoveRight : public CHintBase
  {
  }

A classe é idêntica à anterior, exceto pelo método que desenha a dica e o manipulador de evento do mouse:

//+------------------------------------------------------------------+
//| Class of the HintMoveRight object of the WForms controls         |
//+------------------------------------------------------------------+
class CHintMoveRight : public CHintBase
  {

protected:
   //--- Draw a hint
   virtual void      DrawHint(const int shift);
//--- Protected constructor with object type, chart ID and subwindow
                     CHintMoveRight(const ENUM_GRAPH_ELEMENT_TYPE type,
                                    CGCnvElement *main_obj,CGCnvElement *base_obj,
                                    const long chart_id,
                                    const int subwindow,
                                    const string descript,
                                    const int x,
                                    const int y,
                                    const int w,
                                    const int h);
public:
//--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler
   virtual void      MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Constructor
                     CHintMoveRight(CGCnvElement *main_obj,CGCnvElement *base_obj,
                                    const long chart_id,
                                    const int subwindow,
                                    const string descript,
                                    const int x,
                                    const int y,
                                    const int w,
                                    const int h);
  };
//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CHintMoveRight::CHintMoveRight(const ENUM_GRAPH_ELEMENT_TYPE type,
                               CGCnvElement *main_obj,CGCnvElement *base_obj,
                               const long chart_id,
                               const int subwindow,
                               const string descript,
                               const int x,
                               const int y,
                               const int w,
                               const int h) : CHintBase(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Set the specified graphical element type for the object and assign the library object type to the current object
   this.SetTypeElement(type);
  }
//+------------------------------------------------------------------+
//| Constructor indicating the main and base objects,                |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CHintMoveRight::CHintMoveRight(CGCnvElement *main_obj,CGCnvElement *base_obj,
                              const long chart_id,
                              const int subwindow,
                              const string descript,
                              const int x,
                              const int y,
                              const int w,
                              const int h) : CHintBase(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT);
  }
//+------------------------------------------------------------------+


Método que desenha a dica:

//+------------------------------------------------------------------+
//| Draw a hint                                                      |
//+------------------------------------------------------------------+
void CHintMoveRight::DrawHint(const int shift)
  {
   int w=this.Width();
   int h=this.Height();
   int middle=int(h*0.5);
   this.DrawRectangleFill(0,0,1,h-1,this.HintColor(),255);
   this.DrawTriangleFill(shift+8,middle,shift+8-3,middle+3,shift+8-3,middle-3,this.HintColor(),255);
   this.DrawLine(2,middle,shift+4,middle,this.HintColor(),255);
  }
//+------------------------------------------------------------------+

Aqui desenhamos um retângulo vertical no lado esquerdo do objeto, desenhamos o triângulo da seta a partir do valor de deslocamento mais 8 (comprimento da linha), começando do retângulo desenhado como base e terminando no triângulo desenhado. À medida que o valor de deslocamento aumenta, o comprimento da seta aumenta.

Manipulador do evento "Cursor dentro da área ativa, nenhum botão do mouse pressionado":

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CHintMoveRight::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Set the flag for not displaying the object and hide it
   this.SetDisplayed(false);
   this.Hide();
//--- Get the pointer to the base object
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return;
//--- From the base object, get the pointers to the "left, up and down shift" hint objects,
//--- set the object non-display flag for them and hide the controls
   CWinFormBase *hint_ml=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,0);
   if(hint_ml!=NULL)
     {
      hint_ml.SetDisplayed(false);
      hint_ml.Hide();
     }
   CWinFormBase *hint_mu=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,0);
   if(hint_mu!=NULL)
     {
      hint_mu.SetDisplayed(false);
      hint_mu.Hide();
     }
   CWinFormBase *hint_md=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,0);
   if(hint_md!=NULL)
     {
      hint_md.SetDisplayed(false);
      hint_md.Hide();
     }
  }
//+------------------------------------------------------------------+

A lógica do método é idêntica à deste método na classe anterior, só que aqui o objeto é uma seta para a direita, portanto, após ocultá-lo, obtemos ponteiros para indicar objetos com setas para a esquerda, para cima e para baixo.


As duas outras classes de objetos dicas com setas para cima e para baixo serão analisadas como estão, pois sua lógica é idêntica às duas classes mencionadas anteriormente e não há necessidade de se repetir.

Classe do objeto auxiliar que indica a possibilidade de movimento para cima:

//+------------------------------------------------------------------+
//|                                                   HintMoveUp.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "HintBase.mqh"
//+------------------------------------------------------------------+
//| HintMoveUp object class of WForms controls                       |
//+------------------------------------------------------------------+
class CHintMoveUp : public CHintBase
  {

protected:
   //--- Draw a hint
   virtual void      DrawHint(const int shift);
//--- Protected constructor with object type, chart ID and subwindow
                     CHintMoveUp(const ENUM_GRAPH_ELEMENT_TYPE type,
                                 CGCnvElement *main_obj,CGCnvElement *base_obj,
                                 const long chart_id,
                                 const int subwindow,
                                 const string descript,
                                 const int x,
                                 const int y,
                                 const int w,
                                 const int h);
public:
//--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler
   virtual void      MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Constructor
                     CHintMoveUp(CGCnvElement *main_obj,CGCnvElement *base_obj,
                                 const long chart_id,
                                 const int subwindow,
                                 const string descript,
                                 const int x,
                                 const int y,
                                 const int w,
                                 const int h);
  };
//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CHintMoveUp::CHintMoveUp(const ENUM_GRAPH_ELEMENT_TYPE type,
                         CGCnvElement *main_obj,CGCnvElement *base_obj,
                         const long chart_id,
                         const int subwindow,
                         const string descript,
                         const int x,
                         const int y,
                         const int w,
                         const int h) : CHintBase(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Set the specified graphical element type for the object and assign the library object type to the current object
   this.SetTypeElement(type);
  }
//+------------------------------------------------------------------+
//| Constructor indicating the main and base objects,                |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CHintMoveUp::CHintMoveUp(CGCnvElement *main_obj,CGCnvElement *base_obj,
                         const long chart_id,
                         const int subwindow,
                         const string descript,
                         const int x,
                         const int y,
                         const int w,
                         const int h) : CHintBase(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP);
  }
//+------------------------------------------------------------------+
//| Draw a hint                                                      |
//+------------------------------------------------------------------+
void CHintMoveUp::DrawHint(const int shift)
  {
   int w=this.Width();
   int h=this.Height();
   int middle=int(w*0.5);
   this.DrawRectangleFill(0,h-2,w-1,h-1,this.HintColor(),255);
   this.DrawTriangleFill(middle,shift,middle+3,shift+3,middle-3,shift+3,this.HintColor(),255);
   this.DrawLine(middle,shift+4,middle,h-3,this.HintColor(),255);
  }
//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CHintMoveUp::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Set the flag for not displaying the object and hide it
   this.SetDisplayed(false);
   this.Hide();
//--- Get the pointer to the base object
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return;
//--- From the base object, get the pointers to the "down, left and right shift" hint objects,
//--- set the object non-display flag for them and hide the controls
   CWinFormBase *hint_md=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,0);
   if(hint_md!=NULL)
     {
      hint_md.SetDisplayed(false);
      hint_md.Hide();
     }
   CWinFormBase *hint_ml=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,0);
   if(hint_ml!=NULL)
     {
      hint_ml.SetDisplayed(false);
      hint_ml.Hide();
     }
   CWinFormBase *hint_mr=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,0);
   if(hint_mr!=NULL)
     {
      hint_mr.SetDisplayed(false);
      hint_mr.Hide();
     }
  }
//+------------------------------------------------------------------+


Classe do objeto auxiliar que indica a possibilidade de movimento para baixo:

//+------------------------------------------------------------------+
//|                                                 HintMoveDown.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "HintBase.mqh"
//+------------------------------------------------------------------+
//| HintMoveDown object class of WForms controls                     |
//+------------------------------------------------------------------+
class CHintMoveDown : public CHintBase
  {

protected:
   //--- Draw a hint
   virtual void      DrawHint(const int shift);
//--- Protected constructor with object type, chart ID and subwindow
                     CHintMoveDown(const ENUM_GRAPH_ELEMENT_TYPE type,
                                   CGCnvElement *main_obj,CGCnvElement *base_obj,
                                   const long chart_id,
                                   const int subwindow,
                                   const string descript,
                                   const int x,
                                   const int y,
                                   const int w,
                                   const int h);
public:
//--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler
   virtual void      MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Constructor
                     CHintMoveDown(CGCnvElement *main_obj,CGCnvElement *base_obj,
                                   const long chart_id,
                                   const int subwindow,
                                   const string descript,
                                   const int x,
                                   const int y,
                                   const int w,
                                   const int h);
  };
//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CHintMoveDown::CHintMoveDown(const ENUM_GRAPH_ELEMENT_TYPE type,
                             CGCnvElement *main_obj,CGCnvElement *base_obj,
                             const long chart_id,
                             const int subwindow,
                             const string descript,
                             const int x,
                             const int y,
                             const int w,
                             const int h) : CHintBase(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Set the specified graphical element type for the object and assign the library object type to the current object
   this.SetTypeElement(type);
  }
//+------------------------------------------------------------------+
//| Constructor indicating the main and base objects,                |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CHintMoveDown::CHintMoveDown(CGCnvElement *main_obj,CGCnvElement *base_obj,
                             const long chart_id,
                             const int subwindow,
                             const string descript,
                             const int x,
                             const int y,
                             const int w,
                             const int h) : CHintBase(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN);
  }
//+------------------------------------------------------------------+
//| Draw a hint                                                      |
//+------------------------------------------------------------------+
void CHintMoveDown::DrawHint(const int shift)
  {
   int w=this.Width();
   int h=this.Height();
   int middle=int(w*0.5);
   this.DrawRectangleFill(0,0,w-1,1,this.HintColor(),255);
   this.DrawTriangleFill(middle,shift+8,middle-3,shift+8-3,middle+3,shift+8-3,this.HintColor(),255);
   this.DrawLine(middle,2,middle,shift+8-4,this.HintColor(),255);
  }
//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CHintMoveDown::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Set the flag for not displaying the object and hide it
   this.SetDisplayed(false);
   this.Hide();
//--- Get the pointer to the base object
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return;
//--- From the base object, get the pointers to the "up, left and right shift" hint objects,
//--- set the object non-display flag for them and hide the controls
   CWinFormBase *hint_mu=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,0);
   if(hint_mu!=NULL)
     {
      hint_mu.SetDisplayed(false);
      hint_mu.Hide();
     }
   CWinFormBase *hint_ml=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,0);
   if(hint_ml!=NULL)
     {
      hint_ml.SetDisplayed(false);
      hint_ml.Hide();
     }
   CWinFormBase *hint_mr=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,0);
   if(hint_mr!=NULL)
     {
      hint_mr.SetDisplayed(false);
      hint_mr.Hide();
     }
  }
//+------------------------------------------------------------------+

Objetos de seta geralmente devem ser utilizados em conjunto - a seta esquerda é complementada pela seta direita, e a seta de cima é complementada pela seta de baixo. Por isso, quando aplicamos um deslocamento nos métodos de desenho da dica, a seta diminui em um objeto e aumenta no objeto oposto. Dessa forma, podemos criar uma animação na dica com o deslocamento mútuo das setas de dois objetos trabalhando em conjunto. Se a seta esquerda diminui, a seta direita aumenta. Aplicando o mesmo deslocamento em um laço, podemos obter um deslocamento constante das setas para a esquerda e direita, animando assim a dica.


Em todas as classes de objeto contêiner, precisamos adicionar criação desses novos objetos.

No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh da classe do objeto contêiner base, além do refinamento dos construtores de classes de outros objetos descritos acima, no método que define o parâmetros do objeto anexado, removemos as linhas de definição dos objetos principal e base:

//+------------------------------------------------------------------+
//| Set parameters for the attached object                           |
//+------------------------------------------------------------------+
void CContainer::SetObjParams(CWinFormBase *obj,const color colour)
  {
   obj.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
   obj.SetBase(this.GetObject());
//--- Set the text color of the object to be the same as that of the base container
   obj.SetForeColor(this.ForeColor(),true);

e no final adicionamos a configuração dos parâmetros mínimos para os objetos recém-criados:

//---...
//---...
      //--- For the "ArrowButton" WinForms object
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         :
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      :
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    :
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    :
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   :
        obj.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
        obj.SetBorderStyle(FRAME_STYLE_SIMPLE);
        break;
      //--- For "Hint" WinForms object
      case GRAPH_ELEMENT_TYPE_WF_HINT_BASE            :
        obj.SetBackgroundColor(CLR_CANV_NULL,true);
        obj.SetBorderColor(CLR_CANV_NULL,true);
        obj.SetForeColor(CLR_CANV_NULL,true);
        obj.SetOpacity(0,false);
        obj.SetBorderStyle(FRAME_STYLE_NONE);
        break;
      //--- For "HintMoveLeft", "HintMoveRight", "HintMoveUp" and "HintMoveDown" WinForms object 
      case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT       :
      case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT      :
      case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP         :
      case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN       :
        obj.SetBackgroundColor(CLR_CANV_NULL,true);
        obj.SetBorderColor(CLR_CANV_NULL,true);
        obj.SetForeColor(CLR_DEF_CONTROL_HINT_FORE_COLOR,true);
        obj.SetOpacity(0,false);
        obj.SetBorderStyle(FRAME_STYLE_NONE);
        break;
      default:
        break;
     }
   obj.Crop();
  }
//+------------------------------------------------------------------+


No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh da classe do objeto painel, adicionamos os arquivos das classes recém-criadas à lista de arquivos incluídos:

//+------------------------------------------------------------------+
//|                                                        Panel.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Container.mqh"
#include "..\Helpers\TabField.mqh"
#include "..\Helpers\ArrowUpButton.mqh"
#include "..\Helpers\ArrowDownButton.mqh"
#include "..\Helpers\ArrowLeftButton.mqh"
#include "..\Helpers\ArrowRightButton.mqh"
#include "..\Helpers\ArrowUpDownBox.mqh"
#include "..\Helpers\ArrowLeftRightBox.mqh"
#include "..\Helpers\HintMoveLeft.mqh"
#include "..\Helpers\HintMoveRight.mqh"
#include "..\Helpers\HintMoveUp.mqh"
#include "..\Helpers\HintMoveDown.mqh"
#include "GroupBox.mqh"
#include "TabControl.mqh"
#include "SplitContainer.mqh"
#include "..\..\WForms\Common Controls\ListBox.mqh"
#include "..\..\WForms\Common Controls\CheckedListBox.mqh"
#include "..\..\WForms\Common Controls\ButtonListBox.mqh"
//+------------------------------------------------------------------+

Agora esses objetos estarão disponíveis para serem criados em todos os objetos contêiner da biblioteca.

Lembre-se de que os construtores de todas as classes de objetos WinForms já foram aprimorados para especificar os objetos principal e base, e essas alterações não serão discutidas aqui.

No método que cria um novo objeto gráfico, adicionaremos linhas para criar novos objetos da biblioteca criados por nós acima.

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                       const int obj_num,
                                       const string descript,
                                       const int x,
                                       const int y,
                                       const int w,
                                       const int h,
                                       const color colour,
                                       const uchar opacity,
                                       const bool movable,
                                       const bool activity)
  {
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT                 : element=new CGCnvElement(type,this.GetMain(),this.GetObject(),this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break;
      case GRAPH_ELEMENT_TYPE_FORM                    : element=new CForm(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);               break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER            : element=new CContainer(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX             : element=new CGroupBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL                : element=new CPanel(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);              break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL                : element=new CLabel(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);              break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX             : element=new CCheckBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON          : element=new CRadioButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);        break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON               : element=new CButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);             break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX             : element=new CListBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);            break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM        : element=new CListBoxItem(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX     : element=new CCheckedListBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX      : element=new CButtonListBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);      break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER           : element=new CTabHeader(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD            : element=new CTabField(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL          : element=new CTabControl(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         : element=new CArrowButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      : element=new CArrowUpButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);      break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    : element=new CArrowDownButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    : element=new CArrowLeftButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   : element=new CArrowRightButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);   break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);  break;
      case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER      : element=new CSplitContainer(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_SPLITTER             : element=new CSplitter(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_HINT_BASE            : element=new CHintBase(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT       : element=new CHintMoveLeft(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT      : element=new CHintMoveRight(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);      break;
      case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP         : element=new CHintMoveUp(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN       : element=new CHintMoveDown(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      default  : break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+

De acordo com o tipo de objeto passado para o método, é criado um novo objeto da classe correspondente, cujo construtor agora recebe os ponteiros para os objetos principal e base.

No método que cria o objeto de suporte, especificaremos os objetos principal e base para o objeto criado.

//+------------------------------------------------------------------+
//| Create the underlay object                                       |
//+------------------------------------------------------------------+
bool CPanel::CreateUnderlayObj(void)
  {
   this.m_underlay=new CGCnvElement(GRAPH_ELEMENT_TYPE_WF_UNDERLAY,this.GetMain(),this.GetObject(),this.ID(),this.Number(),
                                    this.ChartID(),this.SubWindow(),this.NameObj()+"Underlay",
                                    this.CoordXWorkspace(),this.CoordYWorkspace(),this.WidthWorkspace(),this.HeightWorkspace(),
                                    CLR_CANV_NULL,0,false,false);
   if(m_underlay==NULL)
     {
      CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_FAILED_CREATE_UNDERLAY_OBJ);
      return false;
     }
   if(!this.m_list_tmp.Add(this.m_underlay))
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
      delete this.m_underlay;
      return false;
     }
   this.SetUnderlayParams();
   return true;
  }
//+------------------------------------------------------------------+


No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh da classe de controle GroupBox, no método que cria um novo objeto gráfico, apuraremos como a classe do objeto painel acima:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CGroupBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int obj_num,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity)
  {
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT                 : element=new CGCnvElement(type,this.GetMain(),this.GetObject(),this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break;
      case GRAPH_ELEMENT_TYPE_FORM                    : element=new CForm(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);               break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER            : element=new CContainer(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX             : element=new CGroupBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL                : element=new CPanel(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);              break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL                : element=new CLabel(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);              break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX             : element=new CCheckBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON          : element=new CRadioButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);        break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON               : element=new CButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);             break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX             : element=new CListBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);            break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM        : element=new CListBoxItem(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX     : element=new CCheckedListBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX      : element=new CButtonListBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);      break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER           : element=new CTabHeader(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD            : element=new CTabField(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL          : element=new CTabControl(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         : element=new CArrowButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      : element=new CArrowUpButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);      break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    : element=new CArrowDownButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    : element=new CArrowLeftButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   : element=new CArrowRightButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);   break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);  break;
      case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER      : element=new CSplitContainer(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_SPLITTER             : element=new CSplitter(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_HINT_BASE            : element=new CHintBase(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT       : element=new CHintMoveLeft(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT      : element=new CHintMoveRight(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);      break;
      case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP         : element=new CHintMoveUp(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN       : element=new CHintMoveDown(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      default  : break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+

Modificações idênticas deste método foram feitas em todas as outras classes de objetos contêineres nos arquivos TabControl.mqh, TabField.mqh, SplitContainerPanel.mqh, e outras não serão consideradas aqui.


No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqh da classe do controle TabControl, no método CreateTabPages() que cria o número especificado de guias, excluímos todas as linhas para definir os objetos principal e base para os objetos cabeçalhos das guias

      header.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
      header.SetBase(this.GetObject());

para objetos campos das guias

      field.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
      field.SetBase(this.GetObject());

para objetos botões direita-esquerda e cima-baixo

//--- Create the left-right button object
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,this.Width()-32,0,15,15,clrNONE,255,this.Active(),false);
//--- Get the pointer to a newly created object
   CArrowLeftRightBox *box_lr=this.GetArrLeftRightBox();
   if(box_lr!=NULL)
     {
      this.SetVisibleLeftRightBox(false);
      this.SetSizeLeftRightBox(box_lr.Width());
      box_lr.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
      box_lr.SetBase(this.GetObject());
      box_lr.SetID(this.GetMaxIDAll());
      box_lr.SetBorderStyle(FRAME_STYLE_NONE);
      box_lr.SetBackgroundColor(CLR_CANV_NULL,true);
      box_lr.SetOpacity(0);
      box_lr.Hide();
      CArrowLeftButton *lb=box_lr.GetArrowLeftButton();
      if(lb!=NULL)
        {
         lb.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         lb.SetBase(box_lr);
         lb.SetID(this.GetMaxIDAll());
        }
      CArrowRightButton *rb=box_lr.GetArrowRightButton();
      if(rb!=NULL)
        {
         rb.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         rb.SetBase(box_lr);
         rb.SetID(this.GetMaxIDAll());
        }
     }
//--- Create the up-down button object
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,0,this.Height()-32,15,15,clrNONE,255,this.Active(),false);
//--- Get the pointer to a newly created object
   CArrowUpDownBox *box_ud=this.GetArrUpDownBox();
   if(box_ud!=NULL)
     {
      this.SetVisibleUpDownBox(false);
      this.SetSizeUpDownBox(box_ud.Height());
      box_ud.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
      box_ud.SetBase(this.GetObject());
      box_ud.SetID(this.GetMaxIDAll());
      box_ud.SetBorderStyle(FRAME_STYLE_NONE);
      box_ud.SetBackgroundColor(CLR_CANV_NULL,true);
      box_ud.SetOpacity(0);
      box_ud.Hide();
      CArrowDownButton *db=box_ud.GetArrowDownButton();
      if(db!=NULL)
        {
         db.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         db.SetBase(box_ud);
         db.SetID(this.GetMaxIDAll());
        }
      CArrowUpButton *ub=box_ud.GetArrowUpButton();
      if(ub!=NULL)
        {
         ub.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         ub.SetBase(box_ud);
         ub.SetID(this.GetMaxIDAll());
        }
     }
//--- Arrange all titles in accordance with the specified display modes and select the specified tab
   this.ArrangeTabHeaders();
   this.Select(selected_page,true);
   return true;
  }
//+------------------------------------------------------------------+


Modificamos a classe do controle SplitContainer no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\SplitContainer.mqh.

Na seção pública da classe, escrevemos métodos que retornam ponteiros para objetos dica:

//--- Return the pointer to the separator
   CSplitter        *GetSplitter(void)                         { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_SPLITTER,0);                      }
//--- Return a pointer to the (1) "Left shift", (1) "Right shift", (1) "Up shift" and (1) "Down shift" hint objects
   CHintMoveLeft    *GetHintMoveLeft(void)                     { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,0);                }
   CHintMoveRight   *GetHintMoveRight(void)                    { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,0);               }
   CHintMoveUp      *GetHintMoveUp(void)                       { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,0);                  }
   CHintMoveDown    *GetHintMoveDown(void)                     { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,0);                }

//--- (1) set and (2) return the minimum possible size of the panel 1 and 2


No método que cria os painéis, removemos o laço que define os objetos principal e base para os painéis criados, bem como a configuração deles para o objeto separador:

         return;
      for(int i=0;i<2;i++)
        {
         CSplitContainerPanel *panel=this.GetPanel(i);
         if(panel==NULL)
            continue;
         panel.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         panel.SetBase(this.GetObject());
        }
      //---
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLITTER,this.m_splitter_x,this.m_splitter_y,this.m_splitter_w,this.m_splitter_h,clrNONE,255,true,false))
         return;
      CSplitter *splitter=this.GetSplitter();
      if(splitter!=NULL)
        {
         splitter.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         splitter.SetBase(this.GetObject());
         splitter.SetMovable(true);
         splitter.SetDisplayed(false);
         splitter.Hide();
        }


Além disso, adicionamos ao método quatro objetos dica e definimos seus parâmetros:

//+------------------------------------------------------------------+
//| Create the panels                                                |
//+------------------------------------------------------------------+
void CSplitContainer::CreatePanels(void)
  {
   this.m_list_elements.Clear();
   if(this.SetsPanelParams())
     {
      //--- Create two panels
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,this.m_panel1_x,this.m_panel1_y,this.m_panel1_w,this.m_panel1_h,clrNONE,255,true,false))
         return;
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,this.m_panel2_x,this.m_panel2_y,this.m_panel2_w,this.m_panel2_h,clrNONE,255,true,false))
         return;
      //--- Create a separator object
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLITTER,this.m_splitter_x,this.m_splitter_y,this.m_splitter_w,this.m_splitter_h,clrNONE,255,true,false))
         return;
      CSplitter *splitter=this.GetSplitter();
      if(splitter!=NULL)
        {
         splitter.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION,this.SplitterOrientation());
         splitter.SetMovable(true);
         splitter.SetDisplayed(false);
         splitter.Hide();
        }
      //--- Create the HintMoveLeft object
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,this.m_splitter_x-DEF_HINT_ICON_SIZE,this.m_splitter_y,DEF_HINT_ICON_SIZE,DEF_HINT_ICON_SIZE,clrNONE,255,true,false))
         return;
      CHintMoveLeft *hint_ml=this.GetHintMoveLeft();
      if(hint_ml!=NULL)
        {
         hint_ml.SetMovable(false);
         hint_ml.SetDisplayed(false);
         hint_ml.Hide();
        }
      //--- Create the HintMoveRight object
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,this.m_splitter_x+this.m_splitter_w,this.m_splitter_y,DEF_HINT_ICON_SIZE,DEF_HINT_ICON_SIZE,clrNONE,255,true,false))
         return;
      CHintMoveRight *hint_mr=this.GetHintMoveRight();
      if(hint_mr!=NULL)
        {
         hint_mr.SetMovable(false);
         hint_mr.SetDisplayed(false);
         hint_mr.Hide();
        }
      //--- Create the HintMoveUp object
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,this.m_splitter_x,this.m_splitter_y-DEF_HINT_ICON_SIZE,DEF_HINT_ICON_SIZE,DEF_HINT_ICON_SIZE,clrNONE,255,true,false))
         return;
      CHintMoveUp *hint_mu=this.GetHintMoveUp();
      if(hint_mu!=NULL)
        {
         hint_mu.SetMovable(false);
         hint_mu.SetDisplayed(false);
         hint_mu.Hide();
        }
      //--- Create the HintMoveDown object
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,this.m_splitter_x,this.m_splitter_y+this.m_splitter_h,DEF_HINT_ICON_SIZE,DEF_HINT_ICON_SIZE,clrNONE,255,true,false))
         return;
      CHintMoveDown *hint_md=this.GetHintMoveDown();
      if(hint_md!=NULL)
        {
         hint_md.SetMovable(false);
         hint_md.SetDisplayed(false);
         hint_md.Hide();
        }
     }
  }
//+------------------------------------------------------------------+

Se o objeto dica for criado, restringimos seu movimento com o mouse, definimos um sinalizador de invisibilidade e ocultamos o elemento criado.


Para saber sua orientação diretamente desde o objeto separador (vertical/horizontal), no método que define a localização do separador, após sua criação, inserimos o valor passado para o método na propriedade do objeto separador:

//+------------------------------------------------------------------+
//| set the separator location                                       |
//+------------------------------------------------------------------+
void CSplitContainer::SetSplitterOrientation(const ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION value,const bool only_prop)
  {
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION,value);
//--- If there are no panels or separator, leave
   CSplitContainerPanel *p1=this.GetPanel1();
   CSplitContainerPanel *p2=this.GetPanel2();
   CSplitter *sp=this.GetSplitter();
   if(p1==NULL || p2==NULL || sp==NULL)
      return;
//--- Set the orientation property for the separator object
   sp.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION,value);
//--- If only setting the property, leave
   if(only_prop)
      return;
//--- Set the parameters of the panels and the separator
   this.SetsPanelParams();
//--- If panel 1 is resized successfully
   if(p1.Resize(this.m_panel1_w,this.m_panel1_h,true))
     {
      //--- If panel 2 coordinates are changed to new ones
      if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y,true))
        {
         //--- if panel 2 has been successfully resized,
         if(p2.Resize(this.m_panel2_w,this.m_panel2_h,true))
           {
            //--- set new relative coordinates of panel 2
            p2.SetCoordXRelative(p2.CoordX()-this.CoordX());
            p2.SetCoordYRelative(p2.CoordY()-this.CoordY());
           }
        }
      //--- If the size of the separator object has been successfully changed, 
      //--- set new values of separator coordinates
      if(sp.Resize(this.m_splitter_w,this.m_splitter_h,false))
         this.SetSplitterDistance(this.SplitterDistance(),true);
     }
  }
//+------------------------------------------------------------------+

Agora, ao receber de fora desta classe o ponteiro para o separador, podemos saber sua localização, o que nos será útil no futuro.


No manipulador de eventos que lida com o movimento do separador, ocultamos todos os objetos dicas visíveis disponíveis:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CSplitContainer::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Adjust subwindow Y shift
   CGCnvElement::OnChartEvent(id,lparam,dparam,sparam);
//--- If the event ID is moving the separator
   if(id==WF_CONTROL_EVENT_MOVING)
     {
      //--- Get the pointer to the separator object
      CSplitter *splitter=this.GetSplitter();
      if(splitter==NULL || this.SplitterFixed())
         return;
      //--- Get the pointers to hint objects
      CHintMoveLeft *hint_ml=this.GetHintMoveLeft();
      CHintMoveRight *hint_mr=this.GetHintMoveRight();
      CHintMoveUp *hint_mu=this.GetHintMoveUp();
      CHintMoveDown *hint_md=this.GetHintMoveDown();
      if(hint_ml==NULL || hint_mr==NULL || hint_mu==NULL || hint_md==NULL)
         return;
      
      //--- Disable the display of hints and hide them
      hint_ml.SetDisplayed(false);
      hint_ml.Hide();
      hint_mr.SetDisplayed(false);
      hint_mr.Hide();
      hint_mu.SetDisplayed(false);
      hint_mu.Hide();
      hint_md.SetDisplayed(false);
      hint_md.Hide();
         
      //--- Declare the variables for separator coordinates
      int x=(int)lparam;
      int y=(int)dparam;
      //--- Depending on the separator direction,
      switch(this.SplitterOrientation())
        {
         //--- vertical position
         case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL :
           //--- Set the Y coordinate equal to the Y coordinate of the control element
           y=this.CoordY();
           //--- Adjust the X coordinate so that the separator does not go beyond the control element
           //--- taking into account the resulting minimum width of the panels
           if(x<this.CoordX()+this.Panel1MinSize())
              x=this.CoordX()+this.Panel1MinSize();
           if(x>this.CoordX()+this.Width()-this.Panel2MinSize()-this.SplitterWidth())
              x=this.CoordX()+this.Width()-this.Panel2MinSize()-this.SplitterWidth();
           break;
         //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL
         //--- horizontal position of the separator
         default:
           //--- Set the X coordinate equal to the X coordinate of the control element
           x=this.CoordX();
           //--- Adjust the Y coordinate so that the separator does not go beyond the control element
           //--- taking into account the resulting minimum height of the panels
           if(y<this.CoordY()+this.Panel1MinSize())
              y=this.CoordY()+this.Panel1MinSize();
           if(y>this.CoordY()+this.Height()-this.Panel2MinSize()-this.SplitterWidth())
              y=this.CoordY()+this.Height()-this.Panel2MinSize()-this.SplitterWidth();
           break;
        }
      //--- Draw an empty rectangle
      this.DrawRectangleEmpty();
      //--- If the separator is shifted by the calculated coordinates,
      if(splitter.Move(x,y,true))
        {
         //--- set the separator relative coordinates
         splitter.SetCoordXRelative(splitter.CoordX()-this.CoordX());
         splitter.SetCoordYRelative(splitter.CoordY()-this.CoordY());
         //--- Depending on the direction of the separator, set its new coordinates
         this.SetSplitterDistance(!this.SplitterOrientation() ? splitter.CoordX()-this.CoordX() : splitter.CoordY()-this.CoordY(),false);
        }
     }
  }
//+------------------------------------------------------------------+

O método manipula o evento de movimento do objeto separador. Antes de ajustar o tamanho e a posição dos painéis, todos os objetos auxiliares são ocultados. Assim, quando você passa o mouse sobre a área do separador, aparecem dicas sobre a possível direção do deslocamento do separador. Depois de capturar o separador com o mouse e começar a movê-lo, as dicas ficam ocultas.

Da mesma forma, precisamos ocultar as dicas se o cursor for movido para fora da área do separadora.

No manipulador de eventos "Cursor dentro da área ativa, botões do mouse não pressionados", adicionamos apagar o retângulo pontilhado e ocultar objetos dica:

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CSplitContainer::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- If the separator is non-movable, leave
   if(this.SplitterFixed())
      return;
//--- Draw an empty rectangle in the control area
   this.DrawRectangleEmpty();
//--- Get the pointer to the separator
   CSplitter *splitter=this.GetSplitter();
   if(splitter==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER));
      return;
     }
//--- If the separator is not displayed
   if(!splitter.Displayed())
     {
      //--- Enable the display of the separator, show and redraw it
      splitter.SetDisplayed(true);
      splitter.Show();
      splitter.Redraw(true);
     }
//--- Get the pointer to hint objects
   CHintMoveLeft *hint_ml=this.GetHintMoveLeft();
   CHintMoveRight *hint_mr=this.GetHintMoveRight();
   CHintMoveUp *hint_mu=this.GetHintMoveUp();
   CHintMoveDown *hint_md=this.GetHintMoveDown();
   if(hint_ml==NULL || hint_mr==NULL || hint_mu==NULL || hint_md==NULL)
      return;
   hint_ml.SetDisplayed(false);
   hint_ml.Hide();
   hint_mr.SetDisplayed(false);
   hint_mr.Hide();
   hint_mu.SetDisplayed(false);
   hint_mu.Hide();
   hint_md.SetDisplayed(false);
   hint_md.Hide();
  }
//+------------------------------------------------------------------+

Assim que o cursor for afastado da área do separador (e estiver na área de controle do objeto), o cursor entrará na área ativa do objeto. O manipulador desse evento primeiro apaga o retângulo pontilhado que contorna a área do separador e, em seguida, oculta todas as dicas visíveis.


Assim que o cursor entrar na área de controle onde o separador está localizado, devem ser exibidas dicas sobre o possível deslocamento do separador.

No manipulador de eventos "Cursor dentro da área de controle, botões do mouse não pressionados", adicionamos a funcionalidade descrita:

//+------------------------------------------------------------------+
//| The cursor is inside the control area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CSplitContainer::MouseControlAreaNotPressedHandler(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- If the separator is non-movable, leave
   if(this.SplitterFixed())
      return;
//--- Draw an empty rectangle in the control area
   this.DrawRectangleEmpty();
//--- Draw a dotted rectangle in the control area
   this.DrawRectangleDotted();
//--- Get the pointer to the separator
   CSplitter *splitter=this.GetSplitter();
   if(splitter==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER));
      return;
     }
//--- If the separator is not displayed
   if(!splitter.Displayed())
     {
      //--- Enable the display of the separator and show it
      splitter.SetDisplayed(true);
      splitter.Erase(true);
      splitter.Show();
     }
//--- Get the pointers to hint objects
   CHintMoveLeft *hint_ml=this.GetHintMoveLeft();
   CHintMoveRight *hint_mr=this.GetHintMoveRight();
   CHintMoveUp *hint_mu=this.GetHintMoveUp();
   CHintMoveDown *hint_md=this.GetHintMoveDown();
   if(hint_ml==NULL || hint_mr==NULL || hint_mu==NULL || hint_md==NULL)
      return;
//--- Get cursor coordinates
   int x=this.m_mouse.CoordX()-this.CoordX();
   int y=this.m_mouse.CoordY()-this.CoordY();
//--- Depending on the separator direction,
   switch(this.SplitterOrientation())
     {
      //--- vertical position
      case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL :
        //--- Set and adjust the coordinates of the "left shift" hint object
        x=this.CoordX()+this.m_splitter_x-hint_ml.Width();//-1;
        y+=this.CoordY()-hint_ml.Height()-1;
        if(y<this.CoordY()+this.m_splitter_y)
           y=this.CoordY()+this.m_splitter_y;
        //--- Shift the hint object to the calculated coordinates, set its visibility flag and display the object
        if(hint_ml.Move(x,y))
          {
           hint_ml.SetCoordXRelative(x-this.CoordX());
           hint_ml.SetCoordYRelative(y-this.CoordY());
           hint_ml.SetDisplayed(true);
           hint_ml.Show();
          }
        //--- Set and adjust the coordinates of the "right shift" hint object
        x=this.CoordX()+this.m_splitter_x+this.m_splitter_w;//+1;
        //--- Shift the hint object to the calculated coordinates, set its visibility flag and display the object
        if(hint_mr.Move(x,y))
          {
           hint_mr.SetCoordXRelative(x-this.CoordX());
           hint_mr.SetCoordYRelative(y-this.CoordY());
           hint_mr.SetDisplayed(true);
           hint_mr.Show();
          }
        break;
      //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL
      //--- horizontal position of the separator
      default:
        //--- Set and adjust the coordinates of the "up shift" hint object
        y=this.CoordY()+this.m_splitter_y-hint_mu.Height();//-1;
        x+=this.CoordX()-hint_mu.Width()-1;
        if(x<this.CoordX()+this.m_splitter_x)
           x=this.CoordX()+this.m_splitter_x;
        //--- Shift the hint object to the calculated coordinates, set its visibility flag and display the object
        if(hint_mu.Move(x,y))
          {
           hint_mu.SetCoordXRelative(x-this.CoordX());
           hint_mu.SetCoordYRelative(y-this.CoordY());
           hint_mu.SetDisplayed(true);
           hint_mu.Show();
          }
        //--- Set and adjust the coordinates of the "down shift" hint object
        y=this.CoordY()+this.m_splitter_y+this.m_splitter_h;//+1;
        //--- Shift the hint object to the calculated coordinates, set its visibility flag and display the object
        if(hint_md.Move(x,y))
          {
           hint_md.SetCoordXRelative(x-this.CoordX());
           hint_md.SetCoordYRelative(y-this.CoordY());
           hint_md.SetDisplayed(true);
           hint_md.Show();
          }
        break;
     }
  }
//+------------------------------------------------------------------+

A lógica do bloco de código adicionado é comentada na listagem de métodos. Obtemos ponteiros para objetos dica, vamos movê-los para o cursor para que fiquem localizados acima do mesmo e exibimos os objetos dica.

No manipulador do último evento do mouse, ocultamos os objetos dica se o último evento estiver fora da forma ou dentro da zona ativa:

//+------------------------------------------------------------------+
//| Last mouse event handler                                         |
//+------------------------------------------------------------------+
void CSplitContainer::OnMouseEventPostProcessing(void)
  {
   if(!this.IsVisible() || !this.Enabled() || !this.Displayed())
      return;
   ENUM_MOUSE_FORM_STATE state=this.GetMouseState();
   switch(state)
     {
      //--- The cursor is outside the form, the mouse buttons are not clicked
      //--- The cursor is outside the form, any mouse button is clicked
      //--- The cursor is outside the form, the mouse wheel is being scrolled
      case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED        :
      case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED            :
      case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL              :
      case MOUSE_FORM_STATE_NONE                            :
        if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED  ||
           this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED         ||
           this.MouseEventLast()==MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED        ||
           this.MouseEventLast()==MOUSE_EVENT_INSIDE_CONTROL_AREA_NOT_PRESSED ||
           this.MouseEventLast()==MOUSE_EVENT_INSIDE_CONTROL_AREA_PRESSED     ||
           this.MouseEventLast()==MOUSE_EVENT_INSIDE_CONTROL_AREA_WHEEL       ||
           this.MouseEventLast()==MOUSE_EVENT_NO_EVENT)
          {
            //--- Draw an empty rectangle in the control area
            this.DrawRectangleEmpty();
            //--- Get the pointer to the separator
            CSplitter *splitter=this.GetSplitter();
            if(splitter==NULL)
              {
               ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER));
               return;
              }
            splitter.SetDisplayed(false);
            splitter.Hide();
            //--- Get the pointers to hint objects
            CHintMoveLeft *hint_ml=this.GetHintMoveLeft();
            CHintMoveRight *hint_mr=this.GetHintMoveRight();
            CHintMoveUp *hint_mu=this.GetHintMoveUp();
            CHintMoveDown *hint_md=this.GetHintMoveDown();
            if(hint_ml==NULL || hint_mr==NULL || hint_mu==NULL || hint_md==NULL)
               return;
            //--- If the hide object is displayed, disable its display and hide it
            if(hint_ml.Displayed())
              {
               hint_ml.SetDisplayed(false);
               hint_ml.Hide();
              }
            if(hint_mr.Displayed())
              {
               hint_mr.SetDisplayed(false);
               hint_mr.Hide();
              }
            if(hint_mu.Displayed())
              {
               hint_mu.SetDisplayed(false);
               hint_mu.Hide();
              }
            if(hint_md.Displayed())
              {
               hint_md.SetDisplayed(false);
               hint_md.Hide();
              }
            //--- Set the current mouse state as the last one
            this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT);
          }
        break;
      //--- The cursor is inside the form, the mouse buttons are not clicked
      //--- The cursor is inside the form, any mouse button is clicked
      //--- The cursor is inside the form, the mouse wheel is being scrolled
      //--- The cursor is inside the active area, the mouse buttons are not clicked
      //--- The cursor is inside the active area, any mouse button is clicked
      //--- The cursor is inside the active area, the mouse wheel is being scrolled
      //--- The cursor is inside the active area, left mouse button is released
      //--- The cursor is within the window scrolling area, the mouse buttons are not clicked
      //--- The cursor is within the window scrolling area, any mouse button is clicked
      //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled
      //--- The cursor is within the window resizing area, the mouse buttons are not clicked
      //--- The cursor is within the window resizing area, the mouse button (any) is clicked
      //--- The cursor is within the window resizing area, the mouse wheel is being scrolled
      //--- The cursor is within the window resizing area, the mouse buttons are not clicked
      //--- The cursor is within the window resizing area, the mouse button (any) is clicked
      //--- The cursor is within the window separator area, the mouse wheel is being scrolled
      case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED         :
      case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED             :
      case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL               :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED  :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED      :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL        :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED     :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED  :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED      :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL        :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_NOT_PRESSED  :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_PRESSED      :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_WHEEL        :
      case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_NOT_PRESSED:
      case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_PRESSED    :
      case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_WHEEL      :
        break;
      //--- MOUSE_EVENT_NO_EVENT
      default:
        break;
     }
  }
//+------------------------------------------------------------------+


Assim que o cursor entrar no painel do controle SplitContainer, isto indica que ultrapassou a área de controle onde o separador está localizado. Se, por algum motivo, o manipulador de tal evento da classe CSplitContainer não funcionar, essa situação deve ser tratada na classe do objeto painel. No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\SplitContainerPanel.mqh, no manipulador de eventos "Cursor dentro da área ativa, botões do mouse não pressionados", adicionamos o código para ocultar objetos dica:

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CSplitContainerPanel::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Get the pointer to the base object
   CSplitContainer *base=this.GetBase();
//--- If the base object is not received, or the separator is non-movable, leave
   if(base==NULL || base.SplitterFixed())
      return;
//--- Draw an empty rectangle in the base object control area
   base.DrawRectangleEmpty();
//--- Get the pointer to the separator object from the base object
   CSplitter *splitter=base.GetSplitter();
   if(splitter==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER));
      return;
     }
//--- If the separator is displayed
   if(splitter.Displayed())
     {
      //--- Disable the display of the separator and hide it
      splitter.SetDisplayed(false);
      splitter.Hide();
     }
//--- Get the pointer to the hint objects from the base object
   CHintMoveLeft *hint_ml=base.GetHintMoveLeft();
   CHintMoveRight *hint_mr=base.GetHintMoveRight();
   CHintMoveUp *hint_mu=base.GetHintMoveUp();
   CHintMoveDown *hint_md=base.GetHintMoveDown();
   if(hint_ml==NULL || hint_mr==NULL || hint_mu==NULL || hint_md==NULL)
      return;
//--- If the hide object is displayed, disable its display and hide it
   if(hint_ml.Displayed())
     {
      hint_ml.SetDisplayed(false);
      hint_ml.Hide();
     }
   if(hint_mr.Displayed())
     {
      hint_mr.SetDisplayed(false);
      hint_mr.Hide();
     }
   if(hint_mu.Displayed())
     {
      hint_mu.SetDisplayed(false);
      hint_mu.Hide();
     }
   if(hint_md.Displayed())
     {
      hint_md.SetDisplayed(false);
      hint_md.Hide();
     }
  }
//+------------------------------------------------------------------+

A lógica do método é idêntica à lógica dos manipuladores acima.


Quando o cursor estiver localizado no objeto separador, precisamos fazer com que os objetos dica sigam atrás do cursor. O movimento do cursor sobre o separador pode ser rastreado na classe do objeto separador. Para fazer isso, é preciso adicionar um manipulador de evento à classe para detectar quando o cursor está sobre a área ativa do separador, e que dentro desse manipulador de evento deve-se obter os ponteiros para os objetos dicas e fazer com que eles sigam o cursor, mas sem permitir que eles saiam da área do separador

Declaramos um manipulador de evento virtual no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\Splitter.mqh :

//--- Clear the element completely
   virtual void      Erase(const bool redraw=false) { CWinFormBase::Erase(redraw);  }
//--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler
   virtual void      MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the active area, a mouse button is clicked (any)' event handler
   virtual void      MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the active area, the left mouse button is clicked' event handler
   virtual void      MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
  };
//+------------------------------------------------------------------+


Vamos criá-lo fora do corpo da classe.

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CSplitter::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Get the pointer to the base object
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return;
//--- Declare the pointers to hint objects
   CWinFormBase *hint_ml=NULL;
   CWinFormBase *hint_mr=NULL;
   CWinFormBase *hint_mu=NULL;
   CWinFormBase *hint_md=NULL;
//--- Get cursor coordinates
   int x=this.m_mouse.CoordX();
   int y=this.m_mouse.CoordY();
//--- Depending on the separator direction,
   switch((int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION))
     {
      //--- vertical position
      case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL :
         //--- From the base object, get the pointer to the "left shift" and "right shift" hint objects
         hint_ml=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,0);
         hint_mr=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,0);
         if(hint_ml==NULL || hint_mr==NULL)
            return;
         //--- Shift hints following the cursor
         hint_ml.Move(hint_ml.CoordX(),(y-hint_ml.Height()<this.CoordY() ? this.CoordY() : y-hint_ml.Height()));
         hint_mr.Move(hint_mr.CoordX(),(y-hint_mr.Height()<this.CoordY() ? this.CoordY() : y-hint_mr.Height()),true);
        break;
      //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL
      //--- horizontal position of the separator
      default:
         //--- From the base object, get the pointer to the "shift up" and "shift down" hint objects
         hint_mu=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,0);
         hint_md=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,0);
         if(hint_mu==NULL || hint_md==NULL)
            return;
         //--- Shift hints following the cursor
         hint_mu.Move((x-hint_mu.Width()<this.CoordX() ? this.CoordX() : x-hint_mu.Width()),hint_mu.CoordY());
         hint_md.Move((x-hint_md.Width()<this.CoordX() ? this.CoordX() : x-hint_md.Width()),hint_md.CoordY(),true);
        break;
     }
  }
//+------------------------------------------------------------------+

A lógica do método é descrita nos comentários ao código. É importante notar que limitamos o movimento dos objetos dica diretamente ao passar as coordenadas calculadas para o método de deslocamento do objeto.


No arquivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh da classe coleção de elementos gráficos, em todos os métodos de criação de objetos WinForms, passamos ponteiros para os objetos principal e base:

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

Aqui, NULL é especificado como objeto principal e base, pois criamos objetos independentes desta classe e não vinculados a nenhuma outra. Portanto, o objeto inicialmente será o principal e independente.

Temos muitos desses métodos nesta classe e todas as alterações são idênticas. Portanto, não é necessário descrevê-los todos aqui. Vamos considerar apenas um dos vários métodos para criar um objeto forma, pois nele e nos objetos WinForms subsequentes, a passagem de ponteiros é um pouco diferente da do método acima:

//--- Create a graphical form object on canvas on a specified chart and subwindow
   int               CreateForm(const long chart_id,
                                const int subwindow,
                                const string descript,
                                const int x,
                                const int y,
                                const int w,
                                const int h,
                                const color clr,
                                const uchar opacity,
                                const bool movable,
                                const bool activity,
                                const bool shadow=false,
                                const bool redraw=false)
                       {
                        int id=this.GetMaxID()+1;
                        CForm *obj=new CForm(NULL,NULL,chart_id,subwindow,descript,x,y,w,h);
                        ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id);
                        if(res==ADD_OBJ_RET_CODE_ERROR)
                           return WRONG_VALUE;
                        obj.SetID(id);
                        obj.SetActive(activity);
                        obj.SetMovable(movable);
                        obj.SetBackgroundColor(clr,true);
                        obj.SetBorderColor(clr,true);
                        obj.SetOpacity(opacity,false);
                        obj.SetShadow(shadow);
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.BorderColor(),obj.Opacity());
                        obj.Done();
                        obj.Erase(clr,opacity,redraw);
                        return obj.ID();
                       }

Aqui, antes de especificar os objetos principal e base, não passamos o tipo do objeto, porque ele já está registrado no construtor da classe que está sendo criada.

Todas as outras alterações são idênticas e já foram feitas neste arquivo da classe. Vamos testar o que temos.


Teste

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

Não precisamos fazer nenhuma alteração no EA. Vamos compilá-lo e executá-lo no gráfico:


A funcionalidade planejada funciona corretamente.


O que virá a seguir?

No próximo artigo, continuaremos trabalhando com objetos dica.

Todos os arquivos da versão atual da biblioteca, os arquivos do EA de teste e o indicador do controle de eventos dos gráficos para MQL5 estão anexados abaixo.

Voltar ao conteúdo

*Artigos desta série:

 
DoEasy. Controles (Parte 20): Objeto WinForms SplitContainer
DoEasy. Controles (Parte 21): O controle SplitContainer. Separador de painéis
DoEasy. Controles (Parte 22): SplitContainer. Alterando as propriedades do objeto criado
DoEasy. Controles (Parte 23): Apurando os objetos WinForms TabControl e SplitContainer

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

Arquivos anexados |
MQL5.zip (4496.43 KB)
Aprendendo a construindo um Expert Advisor que opera de forma automática (Parte 12): Automação (IV) Aprendendo a construindo um Expert Advisor que opera de forma automática (Parte 12): Automação (IV)
Se você acha que sistemas automáticos são simples, significa que você ainda não entendeu de fato o que se deve criar. Vamos aqui ver um problema que mata muito Expert Advisor. O disparo indiscriminado de ordens e uma possível solução para este problema.
Gestão de risco e capital utilizando Expert Advisors Gestão de risco e capital utilizando Expert Advisors
Este artigo é sobre o que você não pode ver em um relatório de backtest, o que você deve esperar usando um software de negociação automatizado, como gerenciar seu dinheiro se estiver usando expert advisors e como cobrir uma perda significativa para permanecer na atividade de negociação quando você está usando procedimentos automatizados.
Ciência de Dados e Aprendizado de Máquina (Parte 07): Regressão Polinomial Ciência de Dados e Aprendizado de Máquina (Parte 07): Regressão Polinomial
Ao contrário da regressão linear, a regressão polinomial é um modelo flexível destinado a performar melhor em tarefas que o modelo de regressão linear não poderia lidar. Vamos descobrir como fazer modelos polinomiais em MQL5 e tirar algo positivo disso.
Aprendendo a construindo um Expert Advisor que opera de forma automática (Parte 11): Automação (III) Aprendendo a construindo um Expert Advisor que opera de forma automática (Parte 11): Automação (III)
Um sistema automático sem segurança não irá dar certo. Mas segurança não nasce sem que entendamos adequadamente algumas coisas. Neste artigo vamos entender é tão difícil alcançar a segurança máxima em sistemas automáticos.