English Русский 中文 Español Deutsch 日本語
preview
DoEasy. Controles (Parte 11): Objetos WinForms - grupos, objeto WinForms CheckedListBox

DoEasy. Controles (Parte 11): Objetos WinForms - grupos, objeto WinForms CheckedListBox

MetaTrader 5Exemplos | 20 outubro 2022, 08:54
166 0
Artyom Trishkin
Artyom Trishkin

Conteúdo


Ideia

Os objetos WinForms anexados a um contêiner se tornam um conjunto de objetos em um grupo. Sem importar se estão anexados a um objeto GroupBox ou a qualquer painel, o contêiner se torna uma entidade para os objetos, unindo-os todos em um único grupo. Os objetos, por sua vez, começam a se comportar de acordo com certas regras do grupo. Por exemplo, o objeto RadioButton, que é praticamente inútil ao ser executado uma única vez. Se ele for criado desmarcado, depois de clicar nele, sua caixa de seleção será marcada e não será possível desmarcá-lo novamente. Para desmarcar esta caixa de seleção, deveremos selecionar outro objeto do mesmo tipo. E aqui acontece algo diferente e é que se você selecionar outro objeto RadioButton como parte do mesmo contêiner, o primeiro será desmarcado e o segundo, que foi clicado, será verificado. Mas se você tentar selecionar um RadioButton anexado a outro contêiner, o primeiro objeto selecionado no primeiro grupo não será desmarcado. E isso é natural, é por isso que são diferentes grupos de objetos em diferentes contêineres.

Mas e se quisermos ter dois conjuntos de objetos RadioButton no mesmo contêiner que funcionem independentemente um do outro? Afinal, eles se juntam em um conjunto de objetos graças ao seu contêiner (do qual devem herdar o número de grupo) e funcionam de acordo com o número de grupo comum recebido de seu contêiner. E para fazer vários conjuntos de trabalho independentes desses objetos em um contêiner, apresentaremos o conceito de grupo de objetos.

Então, se criarmos um conjunto de seis objetos RadioButton em um contêiner com o número de grupo 1, todos eles serão atribuídos ao grupo número 1, e todos eles serão vinculados pelo número do grupo de seu contêiner e funcionarão em conformidade - o clique em qualquer um dos seis objetos RadioButton desmarcará a seleção para os cinco restantes.

Mas se quatro objetos do grupo 1 forem atribuídos ao grupo 2 e os dois objetos restantes forem atribuídos ao grupo 3, dois subgrupos de objetos serão criados dentro do contêiner com o grupo 1 - um com o número do grupo 2 e outro com o número do grupo 3. E, portanto, cada um desses novos grupos funcionará apenas em conjunto com os objetos de seu grupo.

Isso nos permitirá criar vários subgrupos em um contêiner, unidos em um grupo próprio sob seu próprio número, e não haverá necessidade de criar novos contêineres para grupos dentro do principal.

Assim, combinando dois botões de alternância em um grupo, faremos esses dois botões como um interruptor de dois botões - ao pressionar o primeiro botão, o segundo é liberado e vice-versa. Assim, será possível fazer com que vários botões Toggle combinados em um grupo possam estar em um estado quer seja todos os botões não pressionados ou apenas um deles sempre no estado pressionado e os demais no estado liberado.

Bem, modificaremos um pouco a classe do objeto conta, pois em alguns servidores o nome padrão do terminal não é retornado senão outro. Normalmente, ao solicitar o nome do terminal, o servidor retorna a string "MetaTrader 5", mas acontece que a corretora adiciona algo próprio lá e o nome do terminal fica diferente. Portanto, para determinar o tipo de servidor, verificaremos não o nome do terminal, mas a substring "MetaTrader 5" em seu nome.


Modificando as classes da biblioteca

No arquivo \MQL5\Include\DoEasy\Defines.mqh, na enumeração de tipos de elementos gráficos, inserimos um novo tipo de objeto WinForms:

//+------------------------------------------------------------------+
//| The list of graphical element types                              |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_STANDARD,                       // Standard graphical object
   GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED,              // Extended standard graphical object
   GRAPH_ELEMENT_TYPE_SHADOW_OBJ,                     // Shadow object
   GRAPH_ELEMENT_TYPE_ELEMENT,                        // Element
   GRAPH_ELEMENT_TYPE_FORM,                           // Form
   GRAPH_ELEMENT_TYPE_WINDOW,                         // Window
   //--- WinForms
   GRAPH_ELEMENT_TYPE_WF_UNDERLAY,                    // Panel object underlay
   GRAPH_ELEMENT_TYPE_WF_BASE,                        // Windows Forms Base
   //--- 'Container' object types are to be set below
   GRAPH_ELEMENT_TYPE_WF_CONTAINER,                   // Windows Forms container base object
   GRAPH_ELEMENT_TYPE_WF_PANEL,                       // Windows Forms Panel
   GRAPH_ELEMENT_TYPE_WF_GROUPBOX,                    // Windows Forms GroupBox
   //--- 'Standard control' object types are to be set below
   GRAPH_ELEMENT_TYPE_WF_COMMON_BASE,                 // Windows Forms base standard control
   GRAPH_ELEMENT_TYPE_WF_LABEL,                       // Windows Forms Label
   GRAPH_ELEMENT_TYPE_WF_BUTTON,                      // Windows Forms Button
   GRAPH_ELEMENT_TYPE_WF_CHECKBOX,                    // Windows Forms CheckBox
   GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,                 // Windows Forms RadioButton
   GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,            // Windows Forms CheckedListBox
  };
//+------------------------------------------------------------------+


Adicionaremos uma nova propriedade à enumeração de propriedades inteiras do elemento gráfico e aumentaremos o número de propriedades inteiras do objeto de 81 para 83:

//+------------------------------------------------------------------+
//| Integer properties of the graphical element on the canvas        |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_PROP_INTEGER
  {
   CANV_ELEMENT_PROP_ID = 0,                          // Element ID
   CANV_ELEMENT_PROP_TYPE,                            // Graphical element type
   //---...
   //---...
   CANV_ELEMENT_PROP_ACT_BOTTOM,                      // Bottom border of the element active area
   CANV_ELEMENT_PROP_GROUP,                           // Group the graphical element belongs to
   CANV_ELEMENT_PROP_ZORDER,                          // Priority of a graphical object for receiving the event of clicking on a chart
   //---...
   //---...
   CANV_ELEMENT_PROP_BUTTON_TOGGLE,                   // Toggle flag of the control featuring a button
   CANV_ELEMENT_PROP_BUTTON_GROUP,                    // Button group flag
   CANV_ELEMENT_PROP_BUTTON_STATE,                    // Status of the Toggle control featuring a button
   //---...
   //---...
   CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_DOWN,     // Color of control checkbox when clicking on the control
   CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER,     // Color of control checkbox when hovering the mouse over the control
   
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (83)          // Total number of integer properties
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Number of integer properties not used in sorting
//+------------------------------------------------------------------+

O grupo ao qual o elemento gráfico pertence é o número do grupo e o sinalizador do grupo de botões é um sinalizador que indica que o botão funciona como parte de vários botões de alternância. Se três botões compõem um objeto-botão de alternância, cada um deles deve ter um sinalizador de botão de grupo definido e devem ter o mesmo grupo.


Adicionaremos novas propriedades à lista de critérios para ordenar os elementos gráficos na tela:

//+------------------------------------------------------------------+
//| Possible sorting criteria of graphical elements on the canvas    |
//+------------------------------------------------------------------+
#define FIRST_CANV_ELEMENT_DBL_PROP  (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP)
#define FIRST_CANV_ELEMENT_STR_PROP  (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP)
enum ENUM_SORT_CANV_ELEMENT_MODE
  {
//--- Sort by integer properties
   SORT_BY_CANV_ELEMENT_ID = 0,                       // Sort by element ID
   SORT_BY_CANV_ELEMENT_TYPE,                         // Sort by graphical element type   
   //---...
   //---...
   SORT_BY_CANV_ELEMENT_ACT_BOTTOM,                   // Sort by the bottom border of the element active area
   SORT_BY_CANV_ELEMENT_GROUP,                        // Sort by a group the graphical element belongs to
   SORT_BY_CANV_ELEMENT_ZORDER,                       // Sort by the priority of a graphical object for receiving the event of clicking on a chart
   //---...
   //---...
   SORT_BY_CANV_ELEMENT_BUTTON_TOGGLE,                // Sort by the Toggle flag of the control featuring a button
   SORT_BY_CANV_ELEMENT_BUTTON_GROUP,                 // Sort by button group flag
   SORT_BY_CANV_ELEMENT_BUTTON_STATE,                 // Sort by the status of the Toggle control featuring a button
   SORT_BY_CANV_ELEMENT_CHECK_BACKGROUND_COLOR,       // Sort by color of control checkbox background
   SORT_BY_CANV_ELEMENT_CHECK_BACKGROUND_COLOR_OPACITY,   // Sort by opacity of control checkbox background color
   SORT_BY_CANV_ELEMENT_CHECK_BACKGROUND_COLOR_MOUSE_DOWN,// Sort by color of control checkbox background when clicking on the control
   SORT_BY_CANV_ELEMENT_CHECK_BACKGROUND_COLOR_MOUSE_OVER,// Sort by color of control checkbox background when hovering the mouse over the control
   SORT_BY_CANV_ELEMENT_CHECK_FORE_COLOR,             // Sort by color of control checkbox frame
   SORT_BY_CANV_ELEMENT_CHECK_FORE_COLOR_OPACITY,     // Sort by opacity of control checkbox frame color
   SORT_BY_CANV_ELEMENT_CHECK_FORE_COLOR_MOUSE_DOWN,  // Sort by color of control checkbox frame when clicking on the control
   SORT_BY_CANV_ELEMENT_CHECK_FORE_COLOR_MOUSE_OVER,  // Sort by color of control checkbox frame when hovering the mouse over the control
   SORT_BY_CANV_ELEMENT_CHECK_FLAG_COLOR,             // Sort by color of control checkbox
   SORT_BY_CANV_ELEMENT_CHECK_FLAG_COLOR_OPACITY,     // Sort by opacity of control checkbox color
   SORT_BY_CANV_ELEMENT_CHECK_FLAG_COLOR_MOUSE_DOWN,  // Sort by color of control checkbox when clicking on the control
   SORT_BY_CANV_ELEMENT_CHECK_FLAG_COLOR_MOUSE_OVER,  // Sort by color of control checkbox when hovering the mouse over the control
//--- Sort by real properties

//--- Sort by string properties
   SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Sort by an element object name
   SORT_BY_CANV_ELEMENT_NAME_RES,                     // Sort by the graphical resource name
   SORT_BY_CANV_ELEMENT_TEXT,                         // Sort by graphical element text
  };
//+------------------------------------------------------------------+

Agora poderemos ordenar, selecionar e filtrar todos os elementos gráficos por novas propriedades.


No arquivo \MQL5\Include\DoEasy\Data.mqh, escrevemos os índices das novas mensagens:

//--- CPanel
   MSG_PANEL_OBJECT_ERR_FAILED_CREATE_UNDERLAY_OBJ,   // Failed to create the underlay object
   MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE,           // Error. The created object should be of WinForms Base type or be derived from it

//--- CCheckedListBox
   MSG_CHECKED_LIST_ERR_FAILED_CREATE_CHECK_BOX_OBJ,  // Failed to create the CheckBox object
   MSG_CHECKED_LIST_ERR_FAILED_GET_CHECK_BOX_OBJ,     // Failed to get the CheckBox object from the object list

//--- Integer properties of graphical elements

...

   MSG_CANV_ELEMENT_PROP_AUTOCHECK,                   // Auto change flag status when it is selected
   MSG_CANV_ELEMENT_PROP_BUTTON_TOGGLE,               // Toggle flag of the control featuring a button
   MSG_CANV_ELEMENT_PROP_BUTTON_GROUP,                // Button group flag
   MSG_CANV_ELEMENT_PROP_BUTTON_STATE,                // Status of the Toggle control featuring a button
   MSG_CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR,      // Color of control checkbox background

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

//--- CPanel
   {"Не удалось создать объект-подложку","Failed to create underlay object"},
   {"Ошибка. Создаваемый объект должен иметь тип WinForms Base или быть его наследником","Error. The object being created must be of type WinForms Base or be derived from it"},

//--- CCheckedListBox
   {"Не удалось создать объект CheckBox","Failed to create CheckBox object"},
   {"Не удалось получить объект CheckBox из списка объектов","Failed to get CheckBox object from list of objects"},

//--- Integer properties of graphical elements

...

   {"Автоматическое изменение состояния флажка при его выборе","Automatically change the state of the checkbox when it is selected"},
   {"Флаг \"Переключатель\" элемента управления, имеющего кнопку","\"Button-toggle\" flag of a control"},
   {"Флаг группы кнопки","Button group flag"},
   {"Состояние элемента управления \"Переключатель\", имеющего кнопку","The \"Toggle-button\" control state"},
   {"Цвет фона флажка проверки элемента управления","The background color of the control's validation checkbox"},


No arquivo de classe do objeto-conta \MQL5\Include\DoEasy\Objects\Accounts\Account.mqh, vamos alterar ligeiramente a definição do tipo de servidor no construtor de classe. Como mencionado acima, não receberemos a string do nome do terminal, mas procuraremos a substring "MetaTrader 5"na linha com o nome do terminal :

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CAccount::CAccount(void)
  {
   this.m_type=OBJECT_DE_TYPE_ACCOUNT;
//--- Initialize control data
   this.SetControlDataArraySizeLong(ACCOUNT_PROP_INTEGER_TOTAL);
   this.SetControlDataArraySizeDouble(ACCOUNT_PROP_DOUBLE_TOTAL);
   this.ResetChangesParams();
   this.ResetControlsParams();
  
//--- Save integer properties
   this.m_long_prop[ACCOUNT_PROP_LOGIN]                              = ::AccountInfoInteger(ACCOUNT_LOGIN);
   this.m_long_prop[ACCOUNT_PROP_TRADE_MODE]                         = ::AccountInfoInteger(ACCOUNT_TRADE_MODE);
   this.m_long_prop[ACCOUNT_PROP_LEVERAGE]                           = ::AccountInfoInteger(ACCOUNT_LEVERAGE);
   this.m_long_prop[ACCOUNT_PROP_LIMIT_ORDERS]                       = ::AccountInfoInteger(ACCOUNT_LIMIT_ORDERS);
   this.m_long_prop[ACCOUNT_PROP_MARGIN_SO_MODE]                     = ::AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE);
   this.m_long_prop[ACCOUNT_PROP_TRADE_ALLOWED]                      = ::AccountInfoInteger(ACCOUNT_TRADE_ALLOWED);
   this.m_long_prop[ACCOUNT_PROP_TRADE_EXPERT]                       = ::AccountInfoInteger(ACCOUNT_TRADE_EXPERT);
   this.m_long_prop[ACCOUNT_PROP_MARGIN_MODE]                        = #ifdef __MQL5__::AccountInfoInteger(ACCOUNT_MARGIN_MODE) #else ACCOUNT_MARGIN_MODE_RETAIL_HEDGING #endif ;
   this.m_long_prop[ACCOUNT_PROP_CURRENCY_DIGITS]                    = #ifdef __MQL5__::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS) #else 2 #endif ;
   this.m_long_prop[ACCOUNT_PROP_SERVER_TYPE]                        = (::StringFind(::TerminalInfoString(TERMINAL_NAME),"MetaTrader 5")>WRONG_VALUE ? 5 : 4);
   this.m_long_prop[ACCOUNT_PROP_FIFO_CLOSE]                         = (#ifdef __MQL5__::TerminalInfoInteger(TERMINAL_BUILD)<2155 ? false : ::AccountInfoInteger(ACCOUNT_FIFO_CLOSE) #else false #endif );
   this.m_long_prop[ACCOUNT_PROP_HEDGE_ALLOWED]                      = (#ifdef __MQL5__::TerminalInfoInteger(TERMINAL_BUILD)<3245 ? false : ::AccountInfoInteger(ACCOUNT_HEDGE_ALLOWED) #else false #endif );
   
//--- Save real properties
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_BALANCE)]          = ::AccountInfoDouble(ACCOUNT_BALANCE);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_CREDIT)]           = ::AccountInfoDouble(ACCOUNT_CREDIT);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_PROFIT)]           = ::AccountInfoDouble(ACCOUNT_PROFIT);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_EQUITY)]           = ::AccountInfoDouble(ACCOUNT_EQUITY);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN)]           = ::AccountInfoDouble(ACCOUNT_MARGIN);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_FREE)]      = ::AccountInfoDouble(ACCOUNT_MARGIN_FREE);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_LEVEL)]     = ::AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_SO_CALL)]   = ::AccountInfoDouble(ACCOUNT_MARGIN_SO_CALL);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_SO_SO)]     = ::AccountInfoDouble(ACCOUNT_MARGIN_SO_SO);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_INITIAL)]   = ::AccountInfoDouble(ACCOUNT_MARGIN_INITIAL);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_MAINTENANCE)]=::AccountInfoDouble(ACCOUNT_MARGIN_MAINTENANCE);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_ASSETS)]           = ::AccountInfoDouble(ACCOUNT_ASSETS);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_LIABILITIES)]      = ::AccountInfoDouble(ACCOUNT_LIABILITIES);
   this.m_double_prop[this.IndexProp(ACCOUNT_PROP_COMMISSION_BLOCKED)]=::AccountInfoDouble(ACCOUNT_COMMISSION_BLOCKED);
   
//--- Save string properties
   this.m_string_prop[this.IndexProp(ACCOUNT_PROP_NAME)]             = ::AccountInfoString(ACCOUNT_NAME);
   this.m_string_prop[this.IndexProp(ACCOUNT_PROP_SERVER)]           = ::AccountInfoString(ACCOUNT_SERVER);
   this.m_string_prop[this.IndexProp(ACCOUNT_PROP_CURRENCY)]         = ::AccountInfoString(ACCOUNT_CURRENCY);
   this.m_string_prop[this.IndexProp(ACCOUNT_PROP_COMPANY)]          = ::AccountInfoString(ACCOUNT_COMPANY);

//--- Account object name, object and account type (MetaTrader 5 or 4)
   this.m_name=CMessage::Text(MSG_LIB_PROP_ACCOUNT)+" "+(string)this.Login()+": "+this.Name()+" ("+this.Company()+")";
   this.m_type=COLLECTION_ACCOUNT_ID;
   this.m_type_server=(uchar)this.m_long_prop[ACCOUNT_PROP_SERVER_TYPE];

//--- Filling in the current account data
   for(int i=0;i<ACCOUNT_PROP_INTEGER_TOTAL;i++)
      this.m_long_prop_event[i][3]=this.m_long_prop[i];
   for(int i=0;i<ACCOUNT_PROP_DOUBLE_TOTAL;i++)
      this.m_double_prop_event[i][3]=this.m_double_prop[i];

//--- Update the base object data and search for changes
   CBaseObjExt::Refresh();
  }
//+-------------------------------------------------------------------+

Inserimos o valor acima (5 ou 4) na variável m_type_server. Anteriormente, escrevemos nele o resultado da verificação quanto ao nome do terminal como valor "MetaTrader 5", o que às vezes acontece erroneamente. Agora escrevemos nele o valor que já escrevemos na propriedade da conta.


No arquivo \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh da classe do objeto gráfico base da biblioteca, vamos tornar virtuais os métodos de configuração e obtenção do grupo do objeto :

//--- Set the values of the class variables
   void              SetObjectID(const long value)             { this.m_object_id=value;              }
   void              SetBelong(const ENUM_GRAPH_OBJ_BELONG belong){ this.m_belong=belong;             }
   void              SetTypeGraphObject(const ENUM_OBJECT obj) { this.m_type_graph_obj=obj;           }
   void              SetTypeElement(const ENUM_GRAPH_ELEMENT_TYPE type) { this.m_type_element=type;   }
   void              SetSpecies(const ENUM_GRAPH_OBJ_SPECIES species){ this.m_species=species;        }
   virtual void      SetGroup(const int value)                 { this.m_group=value;                  }
   void              SetName(const string name)                { this.m_name=name;                    }
   void              SetDigits(const int value)                { this.m_digits=value;                 }

...

   virtual long      Zorder(void)                        const { return this.m_zorder;             }
   int               SubWindow(void)                     const { return this.m_subwindow;          }
   int               ShiftY(void)                        const { return this.m_shift_y;            }
   int               VisibleOnTimeframes(void)           const { return this.m_timeframes_visible; }
   int               Digits(void)                        const { return this.m_digits;             }
   virtual int       Group(void)                         const { return this.m_group;              }
   bool              IsBack(void)                        const { return this.m_back;               }
   bool              IsSelected(void)                    const { return this.m_selected;           }
   bool              IsSelectable(void)                  const { return this.m_selectable;         }
   bool              IsHidden(void)                      const { return this.m_hidden;             }
   bool              IsVisible(void)                     const { return this.m_visible;            }

Precisaremos redefini-los em classes herdadas.


Os elementos gráficos na GUI são interconectados por uma hierarquia comum de localização relativa entre si. O objeto base de uma cadeia de objetos relacionados é o objeto ao qual os subordinados estão ligados. Por sua vez, os objetos subordinados podem ter suas próprias cadeias de objetos associadas, e a base, por sua vez, pode ser um elo na cadeia de objetos vinculados a outro. Nesse caso, o objeto principal é considerado aquele que possui objetos subordinados, mas não está vinculado a nenhum, ou seja, é o ancestral de toda a hierarquia de conexões de todos os objetos subordinados. Normalmente, tal objeto é uma janela no Windows, uma forma em C#, e aqui também será "Janela", pois a definição de "forma" já é tomada por um elemento gráfico que implementa a funcionalidade para trabalhar com o mouse, e esse objeto é o pai do objeto WinForms base.

Para poder obter os identificadores dos objetos base e principal, no arquivo MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh da classe do elemento gráfico, escrevemos métodos que os retornem:

//--- Return the flag indicating that the object is (1) main, (2) base
   bool              IsMain(void)                                                      { return this.GetMain()==NULL;               }
   bool              IsBase(void)                                                      { return this.GetBase()==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.IsBase())
                           return this.ID();
                        CGCnvElement *base=this.GetBase();
                        return(base!=NULL ? base.ID() : WRONG_VALUE);
                       }
//--- Return the pointer to a canvas object

Usando o exemplo de obtenção do identificador de um objeto base: se o objeto já for um objeto base (possui objetos subordinados), retornamos seu identificador. Caso contrário, obtemos um ponteiro para o objeto base (esses ponteiros são escritos em cada objeto subordinado) e, se o ponteiro for recebido, retornamos o identificador do objeto base, caso contrário, retornamos -1 (erro, o objeto não pôde ser obtido).
Para obter o ID do objeto principal, a lógica do método é idêntica.


Vamos escrever métodos virtuais para obter e definir o grupo de um elemento gráfico:

//--- Priority of a graphical object for receiving the event of clicking on a chart
   virtual long      Zorder(void)                        const { return this.GetProperty(CANV_ELEMENT_PROP_ZORDER);                    }
   virtual bool      SetZorder(const long value,const bool only_prop)
                       {
                        if(!CGBaseObj::SetZorder(value,only_prop))
                           return false;
                        this.SetProperty(CANV_ELEMENT_PROP_ZORDER,value);
                        return true;
                       }
//--- Graphical object group
   virtual int       Group(void)                         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_GROUP);                }
   virtual void      SetGroup(const int value)
                       {
                        CGBaseObj::SetGroup(value);
                        this.SetProperty(CANV_ELEMENT_PROP_GROUP,value);
                       }
//+------------------------------------------------------------------+

O método Group() retorna o valor escrito na propriedade "grupo" do objeto.

No método para definir a propriedade "grupo", o valor passado para o método é definido primeiro no objeto pai e depois gravado na propriedade do elemento gráfico.


Cada elemento gráfico da coleção deve ter seu próprio identificador exclusivo. Isso nos permitirá fazer referência diretamente ao objeto por seu identificador (se estiver armazenado no programa), e não procurá-lo em ciclos por todos os objetos. Atualmente, os identificadores exclusivos são atribuídos apenas aos elementos gráficos criados diretamente do programa. Se criarmos (também programaticamente) novos objetos vinculados a partir dos já criados (que estão vinculados ao objeto a partir do qual o novo está sendo criado), o objeto subordinado recém-criado receberá o identificador do objeto a partir do qual foi criado. Nesta situação, podemos obter este objeto pelo identificador do base, indicando o número do objeto na lista de subordinados.

É claro que essa abordagem também funciona, mas para simplificar o trabalho com programas criados com base na biblioteca, procuraremos um identificador exclusivo e o atribuiremos ao elemento gráfico subordinado recém-criado. Assim, atribuiremos um identificador único a cada elemento gráfico, pelo qual podemos acessá-lo e, em segundo lugar, teremos a maneira que está implementada no momento, isto é, consultar o objeto base e obter o elemento necessário da lista por número do elemento. Ter duas maneiras de obter ponteiros para objetos é definitivamente melhor do que uma, especialmente porque agora haverá uma maneira mais rápida de acessar um elemento por seu identificador exclusivo.

No arquivo de classe do objeto-forma \MQL5\Include\DoEasy\Objects\Graph\Form.mqh, na seção public, vamos declarar quatro métodos para encontrar o valor máximo da propriedade de objeto e identificador:

//--- Return (1) itself, the list of (2) attached objects, (3) the list of interaction objects and (4) shadow object
   CForm            *GetObject(void)                                          { return &this;                  }
   CArrayObj        *GetListElements(void)                                    { return &this.m_list_elements;  }
   CArrayObj        *GetListInteractObj(void)                                 { return &this.m_list_interact;  }
   CShadowObj       *GetShadowObj(void)                                       { return this.m_shadow_obj;      }
//--- Return the maximum value (1) of the specified integer property and (2) ID from all objects bound to the base one
   long              GetMaxLongPropForm(const ENUM_CANV_ELEMENT_PROP_INTEGER prop);
   int               GetMaxIDForm(void);
//--- Return the maximum value (1) of the specified integer property and (2) ID from the entire hierarchy of related objects
   long              GetMaxLongPropAll(const ENUM_CANV_ELEMENT_PROP_INTEGER prop);
   int               GetMaxIDAll(void);
//--- Return the pointer to (1) the animation object, the list of (2) text and (3) rectangular animation frames

Métodos com parâmetros retornarão o valor máximo encontrado da propriedade especificada, enquanto métodos sem parâmetros retornarão o valor máximo encontrado do ID do elemento gráfico.


No método para criar um novo elemento anexado e adicioná-lo à lista de objetos anexados CreateAndAddNewElement(), alteraremos a configuração do identificador do objeto recém-criado.

Anteriormente, o identificador do objetoa partir do qual o novo era criado era definido aqui:

   obj.SetID(this.ID());

agora vamos escrever o valor do identificador como o valor máximo encontrado do identificador de toda a hierarquia de objetos subordinados, começando pelo principal, e adicionamos 1 ao valor resultante. Ou seja, o novo objeto terá o maior valor de todos os IDs de todos os objetos da coleção:

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

//--- ...
//--- 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());
   return obj;
  }
//+------------------------------------------------------------------+


Após o cursor ser retirado fora da área do elemento gráfico, temos o manipulador desse evento ativado para restaurar as cores do plano de fundo, texto e moldura do objeto aos seus valores padrão, pois essas cores mudam quando o cursor passa sobre a área do elemento para exibir a atividade do elemento gráfico. Durante vários testes, notei que as cores nem sempre são restauradas corretamente. Às vezes você precisa reposicionar o cursor na forma onde esses objetos estão localizados para que sua cor seja redefinida para a original. Gradualmente, com o desenvolvimento do componente visual dos objetos da biblioteca, nos livraremos de tais omissões. E agora vamos adicionar mais uma condição ao manipulador do último evento do mouse, que processará a retirada do cursor para fora do elemento gráfico:

//+------------------------------------------------------------------+
//| Last mouse event handler                                         |
//+------------------------------------------------------------------+
void CForm::OnMouseEventPostProcessing(void)
  {
   ENUM_MOUSE_FORM_STATE state=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              :
        if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED)
          {
           this.SetBackgroundColor(this.BackgroundColorInit(),false);
           this.SetBorderColor(this.BorderColorInit(),false);
           this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT);
           this.Redraw(true);
          }
        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
      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        :
        break;
      //--- MOUSE_EVENT_NO_EVENT
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

Agora vamos processar não apenas a situação em que o cursor estava na área ativa do objeto, mas também a situação em que o cursor estava na área do objeto, pois nem sempre a área ativa possui dimensões de toda a área do elemento, e o cursor, antes de ir além da forma da zona ativa, cai na inativa, mas dentro do elemento gráfico. Agora, essa situação também é levada em consideração.


Método que retorna o valor máximo da propriedade inteira especificada de todos os objetos subordinados ao base:

//+------------------------------------------------------------------+
//| Return the maximum value of the specified integer                |
//| property from all objects subordinate to the base one            |
//+------------------------------------------------------------------+
long CForm::GetMaxLongPropForm(const ENUM_CANV_ELEMENT_PROP_INTEGER prop)
  {
//--- Get the pointer to the base object
   CForm *base=this.GetBase();
//--- If the base object is received, then set the property value of the specified property, otherwise the value is equal to -1
   long property=(base!=NULL ? base.GetProperty(prop) : WRONG_VALUE);
//--- If the received value is greater than -1
   if(property>WRONG_VALUE)
     {
      //--- In the loop through the list of bound objects
      for(int i=0;i<this.ElementsTotal();i++)
        {
         //--- get the next object
         CForm *elm=this.GetElement(i);
         if(elm==NULL)
            continue;
         //--- If the property value of the received object is greater than the value set in the property,
         //--- set the property value of the current object to 'property'
         if(elm.GetProperty(prop)>property)
            property=elm.GetProperty(prop);
         //--- Get the maximum property value from objects bound to the current one
         long prop_form=elm.GetMaxLongPropForm(prop);
         //--- If the received value is greater than the 'property' value
         //--- set the received value to 'property'
         if(prop_form>property)
            property=prop_form;
        }
     }
//--- Return the found maximum property value
   return property;
  }
//+------------------------------------------------------------------+

A lógica do método é descrita nos comentários ao código. Este método busca o valor máximo da propriedade inteira especificada que possui valores de zero ou maiores (não negativos) de todos os objetos vinculados à forma. Nesse caso, o valor da propriedade do objeto base é levado em consideração e a pesquisa começa a partir dele. Como todos os objetos subordinados são criados do base um após o outro, não pode haver uma situação em que perdemos o valor máximo da propriedade daqueles objetos que estão diretamente ligados à base e iniciamos a busca a partir de um objeto localizado na hierarquia distante da base.
No entanto, se precisarmos pesquisar exatamente em toda a hierarquia do objeto base, podemos criar facilmente esse método.


Método que retorna o valor máximo do identificador do elemento gráfico de todos os objetos anexados ao base:

//+------------------------------------------------------------------+
//| Returns the maximum value of an ID                               |
//| from all bound objects                                           |
//+------------------------------------------------------------------+
int CForm::GetMaxIDForm(void)
  {
   return (int)this.GetMaxLongPropForm(CANV_ELEMENT_PROP_ID);
  }
//+------------------------------------------------------------------+

O método simplesmente retorna o resultado do trabalho do método acima, para o qual a propriedade "identificador de objeto" é passada para pesquisa.


Método que retorna o valor máximo da propriedade inteira especificada de toda a hierarquia de objetos relacionados:

//+------------------------------------------------------------------+
//| Return the maximum value of the specified integer                |
//| property from the entire hierarchy of related objects            |
//+------------------------------------------------------------------+
long CForm::GetMaxLongPropAll(const ENUM_CANV_ELEMENT_PROP_INTEGER prop)
  {
//--- Get the pointer to the main object
   CForm *main=(this.IsMain() ? this.GetObject() : this.GetMain());
//--- If the main object is obtained, then set the value of the specified property to 'property', otherwise the value is equal to -1
   long property=(main!=NULL ? main.GetProperty(prop) : WRONG_VALUE);
//--- If the received value is greater than -1
   if(property>WRONG_VALUE)
     {
      //--- In the loop through the list of objects bound to the main object
      for(int i=0;i<main.ElementsTotal();i++)
        {
         //--- get the next object
         CForm *elm=main.GetElement(i);
         if(elm==NULL)
            continue;
         //--- Get the maximum value of the property from the entire hierarchy of objects subordinate to the current one in the loop
         long prop_form=elm.GetMaxLongPropForm(prop);
         //--- If the received value is greater than the 'property' value
         //--- set the received value to 'property'
         if(prop_form>property)
            property=prop_form;
        }
     }
//--- Return the found maximum property value
   return property;
  }
//+------------------------------------------------------------------+

A lógica do método é descrita em detalhes nos comentários ao código e é semelhante ao método de encontrar a propriedade máxima de objetos anexados ao base. Ao contrário do primeiro, neste método iniciamos a busca a partir do objeto principal — o ancestral da hierarquia de objetos relacionados e percorremos as listas de todos os objetos de toda a cadeia hierárquica de objetos relacionados. Como resultado, temos o maior valor de propriedade de toda a hierarquia de objetos subordinados.


Método que retorna o valor máximo de identificador a partir de toda a hierarquia de objetos relacionados:

//+------------------------------------------------------------------+
//| Returns the maximum value of an ID                               |
//| from the entire hierarchy of related objects                     |
//+------------------------------------------------------------------+
int CForm::GetMaxIDAll(void)
  {
   return (int)this.GetMaxLongPropAll(CANV_ELEMENT_PROP_ID);
  }
//+------------------------------------------------------------------+

O método retorna o resultado da chamada do método acima, nos parâmetros dos quais a propriedade "identificador" é passada para buscar seu valor máximo.


As descrições das propriedades dos elementos gráficos são implementadas na classe do objeto base WinForms no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh.

No método GetPropertyDescription(), escrevemos os blocos de código para retornar a descrição de duas novas propriedades do elemento gráfico:

      property==CANV_ELEMENT_PROP_ACT_BOTTOM                   ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_ACT_BOTTOM)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_GROUP                        ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_GROUP)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_ZORDER                       ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_ZORDER)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :

...

      property==CANV_ELEMENT_PROP_BUTTON_TOGGLE                ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_BUTTON_TOGGLE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)(bool)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_BUTTON_GROUP                 ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_BUTTON_GROUP)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)(bool)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_BUTTON_STATE                 ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_BUTTON_STATE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)(bool)this.GetProperty(property)
         )  :

Agora o método retornará corretamente as descrições de todas as propriedades do elemento gráfico.


O objeto WinForms RadioButton só pode funcionar corretamente em conjunto com outros objetos desse tipo. Além disso, esses objetos devem estar vinculados ao mesmo contêiner ou ter o mesmo valor de grupo, ou seja, fazer parte do mesmo grupo de objetos. Se um desses objetos for clicado com o mouse, sua caixa de seleção será marcada (caso não tenha sido selecionada antes), e todos os outros objetos deste grupo terão suas caixas de seleção desmarcadas. Quando clicarmos novamente em um objeto já selecionado, sua caixa de seleção não será desmarcada.

Vamos fazer melhorias na classe de objeto RadioButton no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\RadioButton.mqh.

Na seção privada da classe, vamos declarar um método que define o estado "não selecionado" para todos os objetos do mesmo grupo, e na seção pública vamos escrever um método que define o estado especificado do objeto e sua caixa de seleção:

//+------------------------------------------------------------------+
//| CheckBox object class of the WForms controls                     |
//+------------------------------------------------------------------+
class CRadioButton : public CCheckBox
  {
private:
//--- Set the state of the checkbox as "not selected" for all RadioButtons of the same group in the container
   void              UncheckOtherAll(void);
protected:
//--- Displays the checkbox for the specified state
   virtual void      ShowControlFlag(const ENUM_CANV_ELEMENT_CHEK_STATE state);

//--- '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);
   
public:
//--- Set the checkbox status
   virtual void      SetChecked(const bool flag)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_CHECKED,flag);
                        this.SetCheckState((ENUM_CANV_ELEMENT_CHEK_STATE)flag);
                        if(this.Checked())
                           this.UncheckOtherAll();
                       }
//--- Constructor

No método de configuração do estado da caixa de seleção, escrevemos o valor passado para o método em sua propriedade e, em seguida, definimos o estado de seleção (selecionado ou não). Além disso, se o estado do objeto for "selecionado", chamamos um método no qual todos os outros objetos semelhantes desse grupo são definidos com o estado "não selecionado" e a caixa de seleção é desmarcada.


No manipulador de eventos "Cursor dentro da área ativa, o botão do mouse liberado (esquerdo)" adicionamos um bloco de código no qual o estado do objeto é verificado, se estiver no estado não selecionado, então chamamos o método para definir o objeto para o estado "selecionado":

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| left mouse button released                                       |
//+------------------------------------------------------------------+
void CRadioButton::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- The mouse button released outside the element means refusal to interact with the element
   if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge())
     {
      this.SetCheckBackgroundColor(this.BackgroundColorInit(),false);
      this.SetCheckBorderColor(this.CheckBorderColorInit(),false);
      this.SetCheckFlagColor(this.CheckFlagColorInit(),false);
      Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel"));
     }
//--- The mouse button released within the element means a  click on the control
   else
     {
      this.SetCheckBackgroundColor(this.CheckBackgroundColorMouseOver(),false);
      this.SetCheckBorderColor(this.CheckBorderColorMouseOver(),false);
      this.SetCheckFlagColor(this.CheckFlagColorInit(),false);
      if(!this.Checked())
         this.SetChecked(true);
      Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.Checked()=",this.Checked(),", ID=",this.ID(),", Group=",this.Group());
     }
   this.Redraw(false);
  }
//+------------------------------------------------------------------+

Aqui, uma entrada de depuração é enviada para o log indicando o evento, o estado do elemento (selecionado/não selecionado), seu ID e número do grupo. Em seguida, removeremos essa entrada e a substituiremos pelo envio de uma mensagem à biblioteca e ao programa de controle.


Método que define o estado da caixa de seleção como "não selecionado" para todos os objetos RadioButton do mesmo grupo no contêiner:

//+------------------------------------------------------------------+
//| Sets the state of the checkbox to "not selected"                 |
//| for all RadioButton objects of the same group in the container   |
//+------------------------------------------------------------------+
void CRadioButton::UncheckOtherAll(void)
  {
//--- Get the pointer to the base object
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return;
//--- From the base object, get a list of all objects of the RadioButton type
   CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON);
//--- Select all objects from the received list, except for the given one (the names of the selected objects are not equal to the name of this one)
   list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,this.Name(),NO_EQUAL);
//--- From the received list, select only those objects whose group index matches the group of the current one
   list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_GROUP,this.Group(),EQUAL);
//--- If the list of objects is received,
   if(list!=NULL)
     {
      //--- in the loop through all objects in the list
      for(int i=0;i<list.Total();i++)
        {
         //--- get the next object,
         CRadioButton *obj=list.At(i);
         if(obj==NULL)
            continue;
         //--- set its state to "not selected"
         obj.SetChecked(false);
         //--- Redraw the object to display an unselected checkbox
         obj.Redraw(false);
        }
     }
  }
//+------------------------------------------------------------------+

Cada linha de código é comentada e, espero, a lógica do método não causará dúvidas. Resumindo, precisamos obter uma lista de todos os objetos RadioButton anexados ao contêiner. Todos eles devem ter o mesmo grupo, e a lista não deve conter o objeto desde o qual esse método foi chamado (afinal, este é o objeto no qual o mouse foi clicado e ficou selecionado, o que significa que você não precisa remover o sinalizador de seleção dele). Percorremos a lista resultante e definimos cada um dos objetos para um estado não selecionado e desmarcamos a caixa. Os objetos são redesenhados para refletir as alterações.


Na classe de objeto de botão no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh, faremos modificações semelhantes, que nos permitirão fazer botões não apenas "clicáveis", mas que possam ter um estado ligado/desligado. Assim, poderemos defini-los em grupos nos quais os botões conectados por um grupo funcionarão juntos.

Na seção privada da classe, vamos declarar um método que define o estado de todos os botões de um grupo como "liberado":

//+------------------------------------------------------------------+
//| Label object class of WForms controls                            |
//+------------------------------------------------------------------+
class CButton : public CLabel
  {
private:
   int               m_text_x;                                 // Text X coordinate
   int               m_text_y;                                 // Text Y coordinate
   color             m_array_colors_bg_tgl[];                  // Array of element background colors for the 'enabled' state
   color             m_array_colors_bg_tgl_dwn[];              // Array of control background colors for the 'enabled' state when clicking on the control
   color             m_array_colors_bg_tgl_ovr[];              // Array of control background colors for the 'enabled' state when hovering the mouse over the control
   color             m_array_colors_bg_tgl_init[];             // Array of initial element background colors for the 'enabled' state
//--- Set the button state as "released" for all Buttons of the same group in the container
   void              UnpressOtherAll(void);
protected:


Na seção public da classe, vamos alterar o método que define o estado do botão e escrever métodos para definir e retornar o sinalizador do botão que funciona em grupo com outros objetos de botão:

//--- (1) Set and (2) return the Toggle control status
   void              SetState(const bool flag)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_BUTTON_STATE,flag);
                        if(this.State())
                          {
                           this.UnpressOtherAll();
                          }
                       }
   bool              State(void)                         const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_STATE);                                }
   
//--- (1) Set and (2) return the group flag
   void              SetGroupButtonFlag(const bool flag)       { this.SetProperty(CANV_ELEMENT_PROP_BUTTON_GROUP,flag);                                        }
   bool              GroupButton(void)                   const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_GROUP);                                }

No método que define o estado do botão, o estado é escrito primeiro na propriedade do objeto e, em seguida, se o estado for "pressionado", chamamos o método que define o estado dos demais botões de um grupo como "liberado".


No manipulador do último evento do mouse, adicionaremos uma verificação de mais uma condição semelhante ao método da classe de objeto-forma de mesmo nome que consideramos acima:

//+------------------------------------------------------------------+
//| Last mouse event handler                                         |
//+------------------------------------------------------------------+
void CButton::OnMouseEventPostProcessing(void)
  {
   ENUM_MOUSE_FORM_STATE state=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       :
        if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED)
          {
           this.SetBackgroundColor(this.State() ? this.BackgroundColorToggleON() : this.BackgroundColorInit(),false);
           this.SetBorderColor(this.BorderColorInit(),false);
           this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT);
           this.Redraw(false);
          }
        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
      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 :
        break;
      //--- MOUSE_EVENT_NO_EVENT
      default:
        break;
     }
  }
//+------------------------------------------------------------------+


Método que define o estado do botão como "desativado" para todos os objetos Button do mesmo grupo no contêiner:

//+------------------------------------------------------------------+
//| Sets the state of the button to "released"                       |
//| for all Buttons of the same group in the container               |
//+------------------------------------------------------------------+
void CButton::UnpressOtherAll(void)
  {
//--- Get the pointer to the base object
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return;
//--- Get the list of all objects of the Button type from the base object
   CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_BUTTON);
//--- Select all objects from the received list, except for the given one (the names of the selected objects are not equal to the name of this one)
   list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,this.Name(),NO_EQUAL);
//--- From the received list, select only those objects whose group index matches the group of the current one
   list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_GROUP,this.Group(),EQUAL);
//--- If the list of objects is received,
   if(list!=NULL)
     {
      //--- in the loop through all objects in the list
      for(int i=0;i<list.Total();i++)
        {
         //--- get the next object,
         CButton *obj=list.At(i);
         if(obj==NULL)
            continue;
         //--- set the button status to "released",
         obj.SetState(false);
         //--- set the background color to the original one (the cursor is on another button outside this one)
         obj.SetBackgroundColor(obj.BackgroundColorInit(),false);
         //--- Redraw the object to display the changes
         obj.Redraw(false);
        }
     }
  }
//+------------------------------------------------------------------+

A lógica do método é idêntica ao método da classe de objeto RadioButton, está totalmente descrita nos comentários ao código e, espero, dispensa explicações.


O objeto WinForms CheckBox em seu estado normal, ao passar o mouse sobre ele com o cursor do mouse, altera a cor do plano de fundo, da caixa de seleção e da moldura da área da caixa de seleção. O fundo do próprio objeto permanece inalterado (transparente). Mas se esses objetos forem combinados em um grupo (como será no objeto que faremos a seguir), quando passarmos o mouse sobre o objeto com o cursor do mouse, sua cor de fundo também mudará. Para usar o objeto CheckBox para criar um objeto-lista de objetos CheckBox, faremos alterações neste objeto no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh.

Vamos tornar o método para definir o estado da caixa de seleção virtual, como em seu objeto filho RadioButton:

//--- (1) Set and (2) return the checkbox status
   virtual void      SetChecked(const bool flag)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_CHECKED,flag);
                        if((bool)this.CheckState()!=flag)
                           this.SetCheckState((ENUM_CANV_ELEMENT_CHEK_STATE)flag);
                       }
   bool              Checked(void)                             const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_CHECKED);                            }


No construtor da classe, definimos explicitamente a cor de fundo do objeto como opacidade total e definimos o deslocamento da zona ativa em um pixel de cada lado:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CCheckBox::CCheckBox(const long chart_id,
                     const int subwindow,
                     const string name,
                     const int x,
                     const int y,
                     const int w,
                     const int h) : CLabel(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CHECKBOX);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CHECKBOX);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.SetCoordX(x);
   this.SetCoordY(y);
   this.SetCheckWidth(DEF_CHECK_SIZE);
   this.SetCheckHeight(DEF_CHECK_SIZE);
   this.SetWidth(w);
   this.SetHeight(h);
   this.Initialize();
   this.SetOpacity(0);
   this.SetForeColorMouseDown(CLR_DEF_FORE_COLOR_MOUSE_DOWN);
   this.SetForeColorMouseOver(CLR_DEF_FORE_COLOR_MOUSE_OVER);
   this.SetCheckBackgroundColor(CLR_DEF_CHECK_BACK_COLOR,true);
   this.SetCheckBackgroundColorMouseDown(CLR_DEF_CHECK_BACK_MOUSE_DOWN);
   this.SetCheckBackgroundColorMouseOver(CLR_DEF_CHECK_BACK_MOUSE_OVER);
   this.SetCheckBorderColor(CLR_DEF_CHECK_BORDER_COLOR,true);
   this.SetCheckBorderColorMouseDown(CLR_DEF_CHECK_BORDER_MOUSE_DOWN);
   this.SetCheckBorderColorMouseOver(CLR_DEF_CHECK_BORDER_MOUSE_OVER);
   this.SetCheckFlagColor(CLR_DEF_CHECK_FLAG_COLOR,true);
   this.SetCheckFlagColorMouseDown(CLR_DEF_CHECK_FLAG_MOUSE_DOWN);
   this.SetCheckFlagColorMouseOver(CLR_DEF_CHECK_FLAG_MOUSE_OVER);
   this.SetWidthInit(this.Width());
   this.SetHeightInit(this.Height());
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetTextAlign(ANCHOR_LEFT);
   this.SetActiveAreaShift(1,1,1,1);
   this.m_text_x=0;
   this.m_text_y=0;
   this.m_check_x=0;
   this.m_check_y=0;
   this.Redraw(false);
  }
//+------------------------------------------------------------------+

Aqui definimos o plano de fundo com opacidade total, pois precisaremos criar objetos com a opacidade do objeto base e, para não definir constantemente a opacidade desse objeto em seu estado normal quando ele for criado, iremos defini-lo explicitamente aqui no construtor. E o deslocamento da área ativa em um pixel de cada lado é uma tentativa de aumentar o espaço entre os objetos CheckBox adjacentes para que, quando o cursor se afastar de um objeto, ele tenha tempo de "visitar as vizinhanças" do objeto, sem passar imediatamente sobre o próximo - para que o cursor passe pelo objeto base e não atinja imediatamente o próximo. Tudo isso é o resultado de buscar uma solução para o problema em que os objetos adjacentes nem sempre recuperam sua cor de fundo padrão quando o cursor é retirado para fora deles. No entanto, essa solução nem sempre ajuda, e ainda precisamos encontrar tempo para entender completamente esse problema e resolvê-lo.

No método que redesenha o objeto, ao invés de especificar a opacidade total (valor 0), vamos agora especificar o valor de opacidade registrado nas propriedades do objeto:

//+------------------------------------------------------------------+
//| Redraw the object                                                |
//+------------------------------------------------------------------+
void CCheckBox::Redraw(bool redraw)
  {
//--- Fill the object with the background color having full transparency
   this.Erase(this.BackgroundColor(),this.Opacity(),true);
//--- Set corrected text coordinates relative to the checkbox
   this.SetCorrectTextCoords();
//--- Draw the text and checkbox within the set coordinates of the object and the binding point, and update the object 
   this.Text(this.m_text_x,this.m_text_y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor());
   this.ShowControlFlag(this.CheckState());
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Isso é necessário para que, para diferentes realizações desses objetos, possamos usar a cor de fundo para alterá-la ao passar o mouse sobre o objeto. Com um fundo opaco, como era o caso antes, nenhuma mudança de cor de fundo pode ser exibida.


Em todos os manipuladores de eventos do mouse que exigem a alteração da cor de fundo do objeto, inseriremos as modificações apropriadas:

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CCheckBox::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   this.SetCheckBackgroundColor(this.CheckBackgroundColorMouseOver(),false);
   this.SetCheckBorderColor(this.CheckBorderColorMouseOver(),false);
   this.SetCheckFlagColor(this.CheckFlagColorMouseOver(),false);
   this.SetBackgroundColor(this.BackgroundColorMouseOver(),false);
   this.Redraw(false);
  }
//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| a mouse button is clicked (any)                                  |
//+------------------------------------------------------------------+
void CCheckBox::MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   this.SetCheckBackgroundColor(this.CheckBackgroundColorMouseDown(),false);
   this.SetCheckBorderColor(this.CheckBorderColorMouseDown(),false);
   this.SetCheckFlagColor(this.CheckFlagColorMouseDown(),false);
   this.SetBackgroundColor(this.BackgroundColorMouseDown(),false);
   this.Redraw(false);
  }
//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| left mouse button released                                       |
//+------------------------------------------------------------------+
void CCheckBox::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- The mouse button released outside the element means refusal to interact with the element
   if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge())
     {
      this.SetCheckBackgroundColor(this.CheckBackgroundColorInit(),false);
      this.SetCheckBorderColor(this.CheckBorderColorInit(),false);
      this.SetCheckFlagColor(this.CheckFlagColorInit(),false);
      this.SetBackgroundColor(this.BackgroundColorInit(),false);
      //--- Send a test entry to the journal
      Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel"));
     }
//--- The mouse button released within the element means a  click on the control
   else
     {
      this.SetCheckBackgroundColor(this.CheckBackgroundColorMouseOver(),false);
      this.SetCheckBorderColor(this.CheckBorderColorMouseOver(),false);
      this.SetCheckFlagColor(this.CheckFlagColorInit(),false);
      this.SetBackgroundColor(this.BackgroundColorMouseOver(),false);
      this.SetChecked(!this.Checked());
      //--- Send a test entry to the journal
      Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.Checked()=",this.Checked(),", ID=",this.ID());
     }
   this.Redraw(false);
  }
//+------------------------------------------------------------------+


No manipulador do último evento do mouse, adicionaremos uma verificação do estado que já conhecemos:

//+------------------------------------------------------------------+
//| Last mouse event handler                                         |
//+------------------------------------------------------------------+
void CCheckBox::OnMouseEventPostProcessing(void)
  {
   ENUM_MOUSE_FORM_STATE state=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       :
        if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED)
          {
           this.SetBackgroundColor(this.BackgroundColorInit(),false);
           this.SetBorderColor(this.BorderColorInit(),false);
           this.SetCheckBackgroundColor(this.CheckBackgroundColorInit(),false);
           this.SetCheckBorderColor(this.CheckBorderColorInit(),false);
           this.SetCheckFlagColor(this.CheckFlagColorInit(),false);
           this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT);
           this.Redraw(false);
          }
        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
      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 :
        break;
      //--- MOUSE_EVENT_NO_EVENT
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

Agora podemos começar a criar um novo objeto.


Objeto WinForms CheckedListBox

Este objeto WinForms é um painel que contém uma lista de objetos CheckBox. Quando movemos o cursor do mouse sobre objetos de lista, sua cor de fundo muda junto com a cor da caixa de seleção, do fundo e da moldura. Os objetos da lista podem ser localizados verticalmente um acima do outro e em colunas. Hoje faremos apenas a disposição vertical dos objetos.

No diretório de biblioteca \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\, criamos um novo arquivo Label.mqh da classe CLabel.

A classe deve ser herdada do objeto contêiner base, e para que ela "veja" tanto a classe CContainer quanto a classe CCheckBox, anexamos a ela o arquivo de classe do objeto-painel, no qual estão todos os arquivos necessários das devidas classes já estão visíveis:

//+------------------------------------------------------------------+
//|                                               CheckedListBox.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 "..\Containers\Panel.mqh"
//+------------------------------------------------------------------+
//| CCheckedListBox object Class of the WForms controls              |
//+------------------------------------------------------------------+
class CCheckedListBox : public CContainer
  {
  }


Na seção privada, declararemos um método virtual para criar um novo objeto gráfico e, na seção pública, declararemos um método para criar uma lista do número especificado de objetos CheckBox e um construtor paramétrico:

//+------------------------------------------------------------------+
//| CCheckedListBox object Class of the WForms controls              |
//+------------------------------------------------------------------+
class CCheckedListBox : public CContainer
  {
private:
//--- Create a new graphical object
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string name,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);
public:
//--- Create the specified number of CheckBox objects
   void              CreateCheckBox(const int count);
//--- Constructor
                     CCheckedListBox(const long chart_id,
                                     const int subwindow,
                                     const string name,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h);
  };
//+------------------------------------------------------------------+


Construtor paramétrico:

//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CCheckedListBox::CCheckedListBox(const long chart_id,
                                 const int subwindow,
                                 const string name,
                                 const int x,
                                 const int y,
                                 const int w,
                                 const int h) : CContainer(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
  }
//+------------------------------------------------------------------+

Definimos o objeto como tipo de objeto WinForms e tipo de objeto gráfico da biblioteca. Em seguida, definimos o tamanho da moldura do objeto como sendo um pixel, o tipo de moldura é simples e definimos a moldura padrão e a cor do texto para objetos gráficos na biblioteca.


Método que cria o número especificado de objetos CheckBox no painel principal:

//+------------------------------------------------------------------+
//| Create the specified number of CheckBox objects                  |
//+------------------------------------------------------------------+
void CCheckedListBox::CreateCheckBox(const int count)
  {
//--- Create a pointer to the CheckBox object
   CCheckBox *cbox=NULL;
//--- In the loop through the specified number of objects
   for(int i=0;i<count;i++)
     {
      //--- Set the coordinates and dimensions of the created object
      int x=this.BorderSizeLeft()+1;
      int y=(cbox==NULL ? this.BorderSizeTop()+1 : cbox.BottomEdgeRelative()+4);
      int w=this.Width()-this.BorderSizeLeft()-this.BorderSizeRight();
      int h=DEF_CHECK_SIZE+1;
      //--- If the object could not be created, display a message in the log
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,x,y,w,h,clrNONE,255,true,false))
         CMessage::ToLog(DFUN,MSG_CHECKED_LIST_ERR_FAILED_CREATE_CHECK_BOX_OBJ);
      //--- Get the created object from the list by the loop index
      cbox=this.GetElement(i);
      //--- If the object could not be obtained, display a message in the log
      if(cbox==NULL)
         CMessage::ToLog(DFUN,MSG_CHECKED_LIST_ERR_FAILED_GET_CHECK_BOX_OBJ);
      //--- Set the frame size to zero
      cbox.SetBorderSizeAll(0);
      //--- Set the left center alignment of the checkbox and the text
      cbox.SetCheckAlign(ANCHOR_LEFT);
      cbox.SetTextAlign(ANCHOR_LEFT);
      //--- Set the object text
      cbox.SetText("CheckBox"+string(i+1));
      //--- Set the opacity of the base object and the default background color
      cbox.SetOpacity(this.Opacity());
      cbox.SetBackgroundColor(this.BackgroundColor(),true);
      cbox.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_STD_MOUSE_OVER);
      cbox.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_STD_MOUSE_DOWN);
     }
//--- For the base object, set the auto resizing mode as "increase and decrease"
//--- and set the auto resize flag
   this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,false);
   this.SetAutoSize(true,false);
  }
//+------------------------------------------------------------------+

A lógica do método está totalmente comentada no código. Resumindo: o número necessário de objetos CheckBox que precisam ser criados no painel é passado para o método. No loop para o número especificado de objetos, nós os criamos e definimos as propriedades necessárias para eles. Após a conclusão do loop de criação de objetos CheckBox, configuramos o modo de redimensionamento automático do painel para que ele possa se ajustar ao tamanho total de todos os objetos criados nele e configuramos o sinalizador de redimensionamento automático do painel, que por sua vez ajustará o painel consoante o tamanho dos objetos criados nele.

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

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CCheckedListBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                                const int obj_num,
                                                const string obj_name,
                                                const int x,
                                                const int y,
                                                const int w,
                                                const int h,
                                                const color colour,
                                                const uchar opacity,
                                                const bool movable,
                                                const bool activity)
  {
   string name=this.CreateNameDependentObject(obj_name);
//--- create the CheckBox object
   CGCnvElement *element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name);
//--- 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;
  }
//+------------------------------------------------------------------+

Como um dos pais desse objeto é a classe do objeto-forma, onde é criado o funcional para trabalhar com o mouse, e a classe desse objeto não é visível nele, precisamos substituir o método virtual da classe CForm para criar um novo objeto gráfico aqui. Nesse método, não precisamos verificar o tipo do objeto passado para o método, pois aqui sabemos exatamente que tipo de objeto precisamos criar, e esse tipo é o CheckBox, que criamos aqui, e definimos os valores mínimos ​para ele - o sinalizador de movimentação e as coordenadas relativas.

Todos os outros métodos para a classe funcionar já estão em suas classes pai.

Naturalmente, modificamos ainda mais a classe desse objeto para gerar sua funcionalidade adicional. Mas hoje, para verificação, vamos ficar por aqui.

Agora precisamos adicionar o processamento desse tipo de objeto em todas as classes-contêineres para que possamos criar esses objetos nelas.

No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh da classe de objeto do painel, inclua o arquivo da classe recém-criada:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Container.mqh"
#include "GroupBox.mqh"
#include "..\..\WForms\Common Controls\CheckedListBox.mqh"
//+------------------------------------------------------------------+
//| Panel object class of WForms controls                            |
//+------------------------------------------------------------------+

Agora esta nova classe estará visível em todas as classes contêiner da biblioteca.


No método que cria um novo objeto gráfico, vamos adicionar manipulação do tipo do novo objeto de biblioteca:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                       const int obj_num,
                                       const string obj_name,
                                       const int x,
                                       const int y,
                                       const int w,
                                       const int h,
                                       const color colour,
                                       const uchar opacity,
                                       const bool movable,
                                       const bool activity)
  {
   string name=this.CreateNameDependentObject(obj_name);
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT :
         element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity);
        break;
      case GRAPH_ELEMENT_TYPE_FORM :
         element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER         :
         element=new CContainer(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX          :
         element=new CGroupBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL             :
         element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL             :
         element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX          :
         element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON       :
         element=new CRadioButton(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON            :
         element=new CButton(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX  :
         element=new CCheckedListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name);
   return element;
  }
//+------------------------------------------------------------------+

Aqui nós simplesmente criamos um novo objeto da classe CCheckedListBox.

No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh da classe do objeto contêiner base, no método que cria um novo elemento anexado, definimos o grupo para o objeto criado como para o objeto base, mas somente se o objeto criado não for um objeto-contêiner. Para objetos WinForms "Rótulo de texto", "CheckBox", "RadioButton" adicionamos uma configuração de cor de fundo transparente e sua transparência total, e adicionamos o processamento do objeto WinForms "CheckedListBox":

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CContainer::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)
  {
//--- If the object type is less than the base WinForms object
   if(element_type<GRAPH_ELEMENT_TYPE_WF_BASE)
     {
      //--- report the error and return 'false'
      CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE);
      return false;
     }
//--- If failed to create a new graphical element, return 'false'
   CWinFormBase *obj=CForm::CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity);
   if(obj==NULL)
      return false;
//--- Set the text color of the created object to be the same as that of the base container
   obj.SetForeColor(this.ForeColor(),true);
//--- If the created object is not a container, set the same group for it as the one for its base object
   if(obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_CONTAINER || obj.TypeGraphElement()>GRAPH_ELEMENT_TYPE_WF_GROUPBOX)
      obj.SetGroup(this.Group());
//--- Depending on the created object type,
   switch(obj.TypeGraphElement())
     {
      //--- For the Container, Panel and GroupBox WinForms objects
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER         :
      case GRAPH_ELEMENT_TYPE_WF_PANEL             :
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX          :
        //--- set the frame color equal to the background color 
        obj.SetBorderColor(obj.BackgroundColor(),true);
        break;
      //--- For the Text Label, CheckBox and RadioButton WinForms objects
      case GRAPH_ELEMENT_TYPE_WF_LABEL             :
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX          :
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON       :
        //--- set the object text color depending on the one passed to the method:
        //--- either the container text color, or the one passed to the method.
        //--- The frame color is set equal to the text color
        //--- Set the background color to transparent
        obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour,true);
        obj.SetBorderColor(obj.ForeColor(),true);
        obj.SetBackgroundColor(CLR_CANV_NULL,true);
        obj.SetOpacity(0,false);
        break;
      //--- For the Button WinForms object
      case GRAPH_ELEMENT_TYPE_WF_BUTTON            :
        //--- set the object text color as a container text color depending on the one passed to the method:
        //--- set the background color depending on the one passed to the method:
        //--- either the default standard control background color, or the one passed to the method.
        //--- The frame color is set equal to the text color
        obj.SetForeColor(this.ForeColor(),true);
        obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true);
        obj.SetBorderColor(obj.ForeColor(),true);
        obj.SetBorderStyle(FRAME_STYLE_SIMPLE);
        break;
      //--- For the CheckedListBox WinForms object
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX  :
        //--- set the object text color as a container text color depending on the one passed to the method:
        //--- set the background color depending on the one passed to the method:
        //--- either the default standard control background color, or the one passed to the method.
        //--- The frame color is set equal to the text color
        obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true);
        obj.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        break;
      default:
        break;
     }
//--- If the panel has auto resize enabled and features bound objects, call the resize method
   if(this.AutoSize() && this.ElementsTotal()>0)
      this.AutoSizeProcess(redraw);
//--- Redraw the panel and all added objects, and return 'true'
   this.Redraw(redraw);
   return true;
  }
//+------------------------------------------------------------------+


No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh da classe de objeto GroupBox, no método para criação de um novo objeto gráfico, adicionamos o processamento do novo tipo de objeto CheckedListBox:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CGroupBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int obj_num,
                                          const string obj_name,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity)
  {
   string name=this.CreateNameDependentObject(obj_name);
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT              :
         element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity);
        break;
      case GRAPH_ELEMENT_TYPE_FORM                 :
         element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER         :
         element=new CContainer(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX          :
         element=new CGroupBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL             :
         element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL             :
         element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX          :
         element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON       :
         element=new CRadioButton(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON            :
         element=new CButton(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX  :
         element=new CCheckedListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name);
   return element;
  }
//+------------------------------------------------------------------+


No método de inicialização da variável Initialize(), no final, adicionamos a configuração de valor de grupo padrão:

   this.SetEnabled(true);
   this.SetVisible(true,false);
//--- Set the default group value
   this.SetGroup((int)this.GetMaxLongPropAll(CANV_ELEMENT_PROP_GROUP)+1);
  }
//+------------------------------------------------------------------+

Aqui chamamos o método discutido anteriormente que retorna o valor máximo da propriedade especificada de todos os objetos na coleção de elementos gráficos da biblioteca. Especificamos a propriedade "grupo" como o parâmetro desejado e adicionamos 1 ao valor resultante, o que tornará o valor do novo grupo máximo. É importante que os objetos-contêineres tenham um grupo exclusivo que o diferencie de outros objetos-contêineres.


No arquivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh da classe-coleção de elementos gráficos, adicionamos o arquivo de classe do objeto CheckedListBox à lista de arquivos incluídos:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Graph\WForms\Containers\GroupBox.mqh"
#include "..\Objects\Graph\WForms\Containers\Panel.mqh"
#include "..\Objects\Graph\WForms\Common Controls\CheckedListBox.mqh"
#include "..\Objects\Graph\Standard\GStdVLineObj.mqh"
#include "..\Objects\Graph\Standard\GStdHLineObj.mqh"


Na seção privada da classe, vamos declarar um método que retorna o identificador máximo de todos os elementos gráficos da coleção:

//--- Return the screen coordinates of each pivot point of the graphical object
   bool              GetPivotPointCoordsAll(CGStdGraphObj *obj,SDataPivotPoint &array_pivots[]);
//--- Return the maximum ID from all graphical elements of the collection
   int               GetMaxID(void);
   
public:


Em todos os métodos de criação de elementos gráficos, substituímos a linha onde o identificador está definido como número total de elementos gráficos da coleção,

int id=this.m_list_all_canv_elm_obj.Total();

pela linha onde o id é atribuído o id máximo de todos os elementos gráficos na coleção mais 1:

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

Tais alterações foram feitas em todos os métodos que criam elementos gráficos, e não as apresentaremos aqui - todas essas alterações podem ser encontradas nos arquivos da biblioteca anexados ao artigo.

No método para processar a forma ativa anterior sob o cursor, adicionamos uma chamada ao manipulador de eventos do mouse para o objeto de loop atual, para não pular seu processamento se o objeto, quando o cursor for movido para longe dele, ainda não tiver sido incluído na lista de objetos inativos em que é necessário alterar a cor para a original, mas já a tem:

//+------------------------------------------------------------------+
//| Post-processing of the former active form under the cursor       |
//+------------------------------------------------------------------+
void CGraphElementsCollection::FormPostProcessing(void)
  {
 //--- Get all the elements of the CForm type and above
   CArrayObj *list=GetList(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_FORM,EQUAL_OR_MORE);
   if(list==NULL)
      return;
   //--- In the loop by the list of received elements
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      //--- get the pointer to the object
      CForm *obj=list.At(i);
      //--- if failed to get the pointer, move on to the next one in the list
      if(obj==NULL)
         continue;
      obj.OnMouseEventPostProcessing();
      //--- Create the list of interaction objects and get their number
      int count=obj.CreateListInteractObj();
      //--- In the loop by the obtained list
      for(int j=0;j<count;j++)
        {
         //--- get the next object
         CForm *elm=obj.GetInteractForm(j);
         if(elm==NULL)
            continue;
         //--- and call its method of handling mouse events
         elm.OnMouseEventPostProcessing();
        }
     }
  }
//+------------------------------------------------------------------+


Para criar no futuro o processamento da interação do botão direito do mouse com os objetos da interface gráfica do programa, no manipulador de eventos OnChartEvent(), adicionamos uma verificação relativamente a pressionar e segurar o botão direito do mouse:

//--- Handling mouse events of graphical objects on canvas
//--- If the event is not a chart change
   else
     {
      //--- Check whether the mouse button is clicked
      ENUM_MOUSE_BUTT_KEY_STATE butt_state=this.m_mouse.ButtonKeyState(id,lparam,dparam,sparam);
      bool pressed=(butt_state==MOUSE_BUTT_KEY_STATE_LEFT || butt_state==MOUSE_BUTT_KEY_STATE_RIGHT ? true : false);
      ENUM_MOUSE_FORM_STATE mouse_state=MOUSE_FORM_STATE_NONE;
      //--- Declare static variables for the active form and status flags

ao mesmo tempo, escreveremos o estado dos botões do mouse em uma variável - para que posteriormente, se precisarmos desse valor, não chamemos novamente o método que lê o estado dos botões.


Método que retorna o ID máximo de todos os elementos gráficos da coleção:

//+------------------------------------------------------------------+
//| Return the maximum ID                                            |
//| of all graphical elements of the collection                      |
//+------------------------------------------------------------------+
int CGraphElementsCollection::GetMaxID(void)
  {
   int index=CSelect::FindGraphCanvElementMax(this.GetListCanvElm(),CANV_ELEMENT_PROP_ID);
   CGCnvElement *obj=this.m_list_all_canv_elm_obj.At(index);
   return(obj!=NULL ? obj.ID() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+

Aqui: obtemos o índice do objeto na lista-coleção que possui o maior valor de identificador. Obtemos um ponteiro para um objeto com base no índice recebido e retornamos o identificador do objeto se um ponteiro para um objeto foi recebido. Caso contrário — retornamos -1 — ou a coleção de elementos gráficos está vazia ou houve um erro ao obter o ponteiro.


Teste

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

Vamos aumentar o tamanho do painel principal e colocar em seu primeiro objeto contêiner GroupBox o objeto CheckBox, quatro objetos RadioButton com valor de grupo 2, três objetos-botões, dois dos quais terão um grupo de 2, e o terceiro terá um grupo padrão herdado do grupo de seu objeto-contêiner - grupo 1. Abaixo dos botões, colocamos mais dois objetos RadioButton com um valor de grupo de 3. Assim, neste contêiner teremos quatro objetos RadioButton com grupo 2, dois objetos RadioButton com grupo 3, e três botões - dois com grupo 2 e um com grupo 1.

À direita do primeiro contêiner GroupBox, colocaremos outro do mesmo tipo e criaremos um novo objeto CheckedListBox dentro dele, no qual criaremos quatro objetos CheckBox. Todos os objetos colocados em diferentes grupos do mesmo contêiner devem funcionar como conjuntos separados de objetos. Todo o componente visual, durante a interação dos objetos com o mouse, deve funcionar satisfatoriamente.

Colocamos o seguinte bloco de código no manipulador OnInit() do Expert Advisor para criar todos os elementos da GUI:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set EA global variables
   ArrayResize(array_clr,2);        // Array of gradient filling colors
   array_clr[0]=C'26,100,128';      // Original ≈Dark-azure color
   array_clr[1]=C'35,133,169';      // Lightened original color
//--- Create the array with the current symbol and set it to be used in the library
   string array[1]={Symbol()};
   engine.SetUsedSymbols(array);
   //--- Create the timeseries object for the current symbol and period, and show its description in the journal
   engine.SeriesCreate(Symbol(),Period());
   engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions

//--- Create WinForms Panel object
   CPanel *pnl=NULL;
   pnl=engine.CreateWFPanel("WFPanel",50,50,400,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false);
   if(pnl!=NULL)
     {
      //--- Set Padding to 4
      pnl.SetPaddingAll(4);
      //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs
      pnl.SetMovable(InpMovable);
      pnl.SetAutoSize(InpAutoSize,false);
      pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false);
      //--- In the loop, create 2 bound panel objects
      CPanel *obj=NULL;
      for(int i=0;i<2;i++)
        {
         //--- create the panel object with calculated coordinates, width of 90 and height of 40
         CPanel *prev=pnl.GetElement(i-1);
         int xb=0, yb=0;
         int x=(prev==NULL ? xb : xb+prev.Width()+20);
         int y=0;
         if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_PANEL,x,y,90,40,C'0xCD,0xDA,0xD7',200,true,false))
           {
            obj=pnl.GetElement(i);
            if(obj==NULL)
               continue;
            obj.SetBorderSizeAll(3);
            obj.SetBorderStyle(FRAME_STYLE_BEVEL);
            obj.SetBackgroundColor(obj.ChangeColorLightness(obj.BackgroundColor(),4*i),true);
            obj.SetForeColor(clrRed,true);
            //--- Calculate the width and height of the future text label object
            int w=obj.Width()-obj.BorderSizeLeft()-obj.BorderSizeRight();
            int h=obj.Height()-obj.BorderSizeTop()-obj.BorderSizeBottom();
            //--- Create a text label object
            obj.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LABEL,0,0,w,h,clrNONE,255,false,false);
            //--- Get the pointer to a newly created object
            CLabel *lbl=obj.GetElement(0);
            if(lbl!=NULL)
              {
               //--- If the object has an even or zero index in the list, set the default text color for it
               if(i % 2==0)
                  lbl.SetForeColor(CLR_DEF_FORE_COLOR,true);
               //--- If the object index in the list is odd, set the object opacity to 127
               else
                  lbl.SetForeColorOpacity(127);
               //--- Set the font Black width type and
               //--- specify the text alignment from the EA settings
               lbl.SetFontBoldType(FW_TYPE_BLACK);
               lbl.SetTextAlign(InpTextAlign);
               lbl.SetAutoSize((bool)InpTextAutoSize,false);
               //--- For an object with an even or zero index, specify the Bid price for the text, otherwise - the Ask price of the symbol 
               lbl.SetText(GetPrice(i % 2==0 ? SYMBOL_BID : SYMBOL_ASK));
               //--- Set the frame width, type and color for a text label and update the modified object
               lbl.SetBorderSizeAll(1);
               lbl.SetBorderStyle((ENUM_FRAME_STYLE)InpFrameStyle);
               lbl.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
               lbl.Update(true);
              }
           }
        }
      //--- Create the WinForms GroupBox1 object
      CGroupBox *gbox1=NULL;
      //--- The indent from the attached panels by 6 pixels will be the Y coordinate of GrotupBox1
      int w=pnl.GetUnderlay().Width();
      int y=obj.BottomEdgeRelative()+6;
      //--- If the attached GroupBox object is created
      if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,0,y,200,150,C'0x91,0xAA,0xAE',0,true,false))
        {
         //--- get the pointer to the GroupBox object by its index in the list of bound GroupBox type objects
         gbox1=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,0);
         if(gbox1!=NULL)
           {
            //--- set the "indented frame" type, the frame color matches the main panel background color,
            //--- while the text color is the background color of the last attached panel darkened by 1
            gbox1.SetBorderStyle(FRAME_STYLE_STAMP);
            gbox1.SetBorderColor(pnl.BackgroundColor(),true);
            gbox1.SetForeColor(gbox1.ChangeColorLightness(obj.BackgroundColor(),-1),true);
            gbox1.SetText("GroupBox1");
            //--- Create the CheckBox object
            gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,2,10,50,20,clrNONE,255,true,false);
            //--- get the pointer to the CheckBox object by its index in the list of bound CheckBox type objects
            CCheckBox *cbox=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,0);
            //--- If CheckBox is created and the pointer to it is received
            if(cbox!=NULL)
              {
               //--- Set the CheckBox parameters from the EA inputs
               cbox.SetAutoSize((bool)InpCheckAutoSize,false);
               cbox.SetCheckAlign(InpCheckAlign);
               cbox.SetTextAlign(InpCheckTextAlign);
               //--- Set the displayed text, frame style and color, as well as checkbox status
               cbox.SetText("CheckBox");
               cbox.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle);
               cbox.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
               cbox.SetChecked(true);
               cbox.SetCheckState((ENUM_CANV_ELEMENT_CHEK_STATE)InpCheckState);
              }
            //--- Create 4 RadioButton WinForms objects
            CRadioButton *rbtn=NULL;
            for(int i=0;i<4;i++)
              {
               //--- Create the RadioButton object
               int yrb=(rbtn==NULL ? cbox.BottomEdgeRelative() : rbtn.BottomEdgeRelative());
               gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,2,yrb+4,50,20,clrNONE,255,true,false);
               //--- get the pointer to the RadioButton object by its index in the list of bound RadioButton type objects
               rbtn=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,i);
               //--- If RadioButton1 is created and the pointer to it is received
               if(rbtn!=NULL)
                 {
                  //--- Set the RadioButton parameters from the EA inputs
                  rbtn.SetAutoSize((bool)InpCheckAutoSize,false);
                  rbtn.SetCheckAlign(InpCheckAlign);
                  rbtn.SetTextAlign(InpCheckTextAlign);
                  //--- Set the displayed text, frame style and color, as well as checkbox status
                  rbtn.SetText("RadioButton"+string(i+1));
                  rbtn.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle);
                  rbtn.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
                  rbtn.SetChecked(!i);
                  rbtn.SetGroup(2);
                 }
              }
            //--- Create 3 Button WinForms objects
            CButton *butt=NULL;
            for(int i=0;i<3;i++)
              {
               //--- Create the Button object
               int ybtn=(butt==NULL ? 12 : butt.BottomEdgeRelative()+4);
               gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON,(int)fmax(rbtn.RightEdgeRelative(),cbox.RightEdgeRelative())+20,ybtn,78,18,clrNONE,255,true,false);
               //--- get the pointer to the Button object by its index in the list of bound Button type objects
               butt=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON,i);
               //--- If Button is created and the pointer to it is received
               if(butt!=NULL)
                 {
                  //--- Set the Button parameters from the EA inputs
                  butt.SetAutoSize((bool)InpButtonAutoSize,false);
                  butt.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpButtonAutoSizeMode,false);
                  butt.SetTextAlign(InpButtonTextAlign);
                  //--- Set the text color, as well as frame style and color
                  butt.SetForeColor(butt.ChangeColorLightness(CLR_DEF_FORE_COLOR,2),true);
                  butt.SetBorderStyle((ENUM_FRAME_STYLE)InpButtonFrameStyle);
                  butt.SetBorderColor(butt.ChangeColorLightness(butt.BackgroundColor(),-10),true);
                  //--- Set the 'toggle' mode depending on the settings
                  butt.SetToggleFlag(InpButtonToggle);
                  //--- Set the displayed text on the button depending on the 'toggle' flag
                  string txt="Button"+string(i+1);
                  if(butt.Toggle())
                     butt.SetText("Toggle-"+txt);
                  else
                     butt.SetText(txt);
                  if(i<2)
                    {
                     butt.SetGroup(2);
                     if(butt.Toggle())
                       {
                        butt.SetBackgroundColorMouseOver(butt.ChangeColorLightness(butt.BackgroundColor(),-5));
                        butt.SetBackgroundColorMouseDown(butt.ChangeColorLightness(butt.BackgroundColor(),-10));
                        butt.SetBackgroundColorToggleON(C'0xE2,0xC5,0xB1',true);
                        butt.SetBackgroundColorToggleONMouseOver(butt.ChangeColorLightness(butt.BackgroundColorToggleON(),-5));
                        butt.SetBackgroundColorToggleONMouseDown(butt.ChangeColorLightness(butt.BackgroundColorToggleON(),-10));
                       }
                    }
                 }
              }
            //--- Create 2 RadioButton WinForms objects
            rbtn=NULL;
            for(int i=0;i<2;i++)
              {
               //--- Create the RadioButton object
               int yrb=(rbtn==NULL ? butt.BottomEdgeRelative() : rbtn.BottomEdgeRelative());
               gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,butt.CoordXRelative()-4,yrb+3,50,20,clrNONE,255,true,false);
               //--- get the pointer to the RadioButton object by its index in the list of bound RadioButton type objects
               rbtn=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,i+4);
               //--- If RadioButton1 is created and the pointer to it is received
               if(rbtn!=NULL)
                 {
                  //--- Set the RadioButton parameters from the EA inputs
                  rbtn.SetAutoSize((bool)InpCheckAutoSize,false);
                  rbtn.SetCheckAlign(InpCheckAlign);
                  rbtn.SetTextAlign(InpCheckTextAlign);
                  //--- Set the displayed text, frame style and color, as well as checkbox status
                  rbtn.SetText("RadioButton"+string(i+5));
                  rbtn.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle);
                  rbtn.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
                  rbtn.SetChecked(!i);
                  rbtn.SetGroup(3);
                 }
              }
           }
        }
      //--- Create the GroupBox2 WinForms object
      CGroupBox *gbox2=NULL;
      //--- The indent from the attached panels by 6 pixels will be the Y coordinate of GrotupBox2
      w=gbox1.Width()-1;
      int x=gbox1.RightEdgeRelative()+1;
      int h=gbox1.BottomEdgeRelative()-6;
      //--- If the attached GroupBox object is created
      if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,x,2,w,h,C'0x91,0xAA,0xAE',0,true,false))
        {
         //--- get the pointer to the GroupBox object by its index in the list of bound GroupBox type objects
         gbox2=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,1);
         if(gbox2!=NULL)
           {
            //--- set the "indented frame" type, the frame color matches the main panel background color,
            //--- while the text color is the background color of the last attached panel darkened by 1
            gbox2.SetBorderStyle(FRAME_STYLE_STAMP);
            gbox2.SetBorderColor(pnl.BackgroundColor(),true);
            gbox2.SetForeColor(gbox2.ChangeColorLightness(obj.BackgroundColor(),-1),true);
            gbox2.SetText("GroupBox2");
            //--- Create the CheckedListBox object
            gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,4,12,80,80,clrNONE,255,true,false);
            //--- get the pointer to the CheckedListBox object by its index in the list of bound objects of the CheckBox type
            CCheckedListBox *clbox=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,0);
            //--- If CheckedListBox is created and the pointer to it is received
            if(clbox!=NULL)
              {
               clbox.CreateCheckBox(4);
              }
           }
        }
      //--- Redraw all objects according to their hierarchy
      pnl.Redraw(true);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Nos comentários sobre o código, a lógica é descrita em grande detalhe. Deixamos para ser estudada independente. Qualquer dúvida pode sempre ser colocada na discussão do artigo.

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


Bem, vemos que a funcionalidade declarada funciona corretamente - os mesmos objetos do mesmo contêiner, mas colocados em grupos diferentes, funcionam corretamente. Os objetos de cada grupo são uma unidade independente e não afetam a operação de objetos do mesmo tipo de outro grupo. Os dois botões de alternância combinados em um grupo funcionam corretamente e não afetam o terceiro botão, que funciona sozinho. O objeto CheckedListBox em seu estado atual também funciona corretamente. Todo o componente visual se comporta corretamente (o que não exclui a ocorrência de falhas no futuro ao alterar as cores durante o processamento do estado anterior do mouse e seus botões). Mas vamos encontrar e corrigir todos os erros possíveis durante o futuro desenvolvimento de elementos gráficos.


O que virá a seguir?

No próximo artigo, continuaremos trabalhando nos elementos gráficos da GUI de programas criados com base na biblioteca.

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

Voltar ao conteúdo

*Artigos desta série:

DoEasy. Controles (Parte 1): Primeiros passos
DoEasy. Controles (Parte 2): Continuamos trabalhando na classe CPanel
DoEasy. Controles (Parte 3): Criando controles vinculados
DoEasy. Controles (Parte 4): Controle "Painel", parâmetros Padding e Dock
DoEasy. Controles (Parte 5): Objeto base WinForms, controle Painel, parâmetro AutoSize
DoEasy. Controles (Parte 6): Controle "Painel", redimensionamento automático do contêiner para adequá-lo ao seu conteúdo
DoEasy. Controles (Parte 7): Controle "Rótulo de texto"
DoEasy. Controles (Parte 8): Objetos básicos do WinForms por categoria, controles GroupBox e CheckBox
DoEasy. Controles (Parte 9): Reorganizando métodos de objetos WinForms, controles "RadioButton" e "Button"
DoEasy. Controles (Parte 10): Objetos WinForms, dando vida à interface



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

Arquivos anexados |
MQL5.zip (4447.61 KB)
Aprendendo a construindo um EA que opera de forma automática (Parte 02): Iniciando a programação Aprendendo a construindo um EA que opera de forma automática (Parte 02): Iniciando a programação
Aprenda como criar um EA que opera de forma automática, isto de forma simples e o mais seguro possível. No artigo anterior apresentei as primeiras etapas das quais você precisa compreender, antes mesmo de iniciar a construção de um EA, que opere de forma automática, ali mostrei.
DoEasy. Controles (Parte 10): Objetos WinForms, dando vida à interface DoEasy. Controles (Parte 10): Objetos WinForms, dando vida à interface
Chegou a hora de dar vida à interface gráfica e criar funcionalidades para a interação de objetos com o usuário e outros objetos. E para que objetos mais complexos funcionem corretamente, já precisamos que os objetos interajam entre si e interajam com o usuário.
Redes neurais de maneira fácil (Parte 19): Regras de associação usando MQL5 Redes neurais de maneira fácil (Parte 19): Regras de associação usando MQL5
Continuamos o tópico de busca de regras de associação. No artigo anterior, consideramos os aspectos teóricos desse tipo de problema. No artigo de hoje, ensinarei a implementação do método FP-Growth usando MQL5. Também vamos testá-la com dados reais.
Como desenvolver um sistema de negociação baseado no indicador Oscilador de Chaikin Como desenvolver um sistema de negociação baseado no indicador Oscilador de Chaikin
Bem-vindo ao nosso novo artigo da série sobre como desenvolver um sistema de negociação com base nos indicadores técnico mais populares. Através deste novo artigo, nós aprenderemos como desenvolver um sistema de negociação pelo indicador Oscilador de Chaikin.