English Русский 中文 Español Deutsch 日本語
preview
DoEasy. Controles (Parte 15): Objeto WinForms TabControl - múltiplas fileiras de cabeçalhos de guias, métodos de manuseio de guias

DoEasy. Controles (Parte 15): Objeto WinForms TabControl - múltiplas fileiras de cabeçalhos de guias, métodos de manuseio de guias

MetaTrader 5Exemplos | 18 novembro 2022, 16:13
140 0
Artyom Trishkin
Artyom Trishkin

Conteúdo


Ideia

Continuamos o tópico do objeto WinForms TabControl. Se um controle tem mais guias do que pode caber na largura do objeto (falamos do posicionamentos das guias em cima), então os cabeçalhos que não cabem dentro do controle podem ser recortados ao longo da borda com botões de rolagem ou, se o objeto tiver o sinalizador Multiline definido, os cabeçalhos são colocados vários de cada vez (tantos quantos couberem no tamanho do elemento) em várias linhas. Para o modo de várias linhas, existem três maneiras de definir o tamanho das guias (SizeMode):

  • Normal - a largura das guias é definida conforme a largura do texto do cabeçalho, o espaço especificado nos valores PaddingWidth e PaddingHeight do cabeçalho é adicionado ao longo das bordas do cabeçalho;
  • Fixed - tamanho fixo, especificado nas configurações do controle. O texto do cabeçalho é truncado se não couber em suas dimensões;
  • FillToRight - as guias que se encaixam na largura do controle são esticadas para preencher toda a largura.

Quando uma guia é selecionada no modo Multiline ativo, seu cabeçalho, que não «faz fronteira» com o campo de guia, junto com toda a linha em que está localizada, aproxima-se do campo de guia, e os cabeçalhos que estavam adjacentes ao campo assumem o lugar da linha da guia selecionada.

Hoje estamos implementando tal regime. Mas vamos fazer isso apenas para o posicionamento da guia na parte superior do controle e para os modos de tamanho de guia Normal e Fixed. O modo FillToRight e a posicionamento das guias na parte inferior, esquerda e direita em todos os três modos de tamanho de guia serão elaborados em artigos posteriores, assim como o modo de rolagem para guias localizadas na mesma linha com o modo Multiline desativado.

Para interagir com o campo de guia, implementado anteriormente como um objeto-contêiner da classe CContainer, vamos criar um novo objeto TabField, que é o sucessor do objeto-contêiner com suas próprias propriedades e métodos para um trabalho completo com o campo de guia.


Modificando as classes da biblioteca

No arquivo de biblioteca \MQL5\Include\DoEasy\Defines.mqh, adicionamos um novo tipo de objeto WinForms auxiliar à lista de tipos de elementos gráficos:

//+------------------------------------------------------------------+
//| The list of graphical element types                              |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_STANDARD,                       // Standard graphical object
   GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED,              // Extended standard graphical object
   GRAPH_ELEMENT_TYPE_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
   GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,                 // Windows Forms TabControl
   //--- '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_ELEMENTS_LIST_BOX,           // Base list object of Windows Forms elements
   GRAPH_ELEMENT_TYPE_WF_LIST_BOX,                    // Windows Forms ListBox
   GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,            // Windows Forms CheckedListBox
   GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,             // Windows Forms ButtonListBox
   //--- Auxiliary elements of WinForms objects
   GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM,               // Windows Forms ListBoxItem
   GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,                  // Windows Forms TabHeader
   GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,                   // Windows Forms TabField
  };
//+------------------------------------------------------------------+

Como este objeto não funcionará como uma unidade independente, ele é auxiliar e funciona como parte do controle TabControl, juntamente com o mesmo objeto auxiliar TabHeader que criamos no último artigo.

Vamos adicionar uma nova enumeração de modos de configuração de tamanho de guia:

//+------------------------------------------------------------------+
//| Location of an object inside a control                           |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_ALIGNMENT
  {
   CANV_ELEMENT_ALIGNMENT_TOP,                        // Top
   CANV_ELEMENT_ALIGNMENT_BOTTOM,                     // Bottom
   CANV_ELEMENT_ALIGNMENT_LEFT,                       // Left
   CANV_ELEMENT_ALIGNMENT_RIGHT,                      // Right
  };
//+------------------------------------------------------------------+
//| Tab size setting mode                                            |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_TAB_SIZE_MODE
  {
   CANV_ELEMENT_TAB_SIZE_MODE_NORMAL,                 // By tab title width
   CANV_ELEMENT_TAB_SIZE_MODE_FIXED,                  // Fixed size
   CANV_ELEMENT_TAB_SIZE_MODE_FILL,                   // By TabControl width
  };
//+------------------------------------------------------------------+
//| Integer properties of the graphical element on the canvas        |
//+------------------------------------------------------------------+


No final da lista de propriedades inteiras do item gráfico na tela, adicionamos duas novas propriedades e aumentamos o número de propriedades inteiras de 88 para 90:

//+------------------------------------------------------------------+
//| 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_TAB_MULTILINE,                   // Several lines of tabs in TabControl
   CANV_ELEMENT_PROP_TAB_ALIGNMENT,                   // Location of tabs inside the control
   CANV_ELEMENT_PROP_TAB_SIZE_MODE,                   // Tab size setting mode
   CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,                 // Tab index number
   CANV_ELEMENT_PROP_ALIGNMENT,                       // Location of an object inside the control
   
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (90)          // Total number of integer properties
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Number of integer properties not used in sorting
//+------------------------------------------------------------------+


E adicionamos estas novas propriedades à lista de possíveis critérios para ordenar 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_TAB_MULTILINE,                // Sort by the flag of several rows of tabs in TabControl
   SORT_BY_CANV_ELEMENT_TAB_ALIGNMENT,                // Sort by the location of tabs inside the control
   SORT_BY_CANV_ELEMENT_TAB_SIZE_MODE,                // Sort by the mode of setting the tab size
   SORT_BY_CANV_ELEMENT_TAB_PAGE_NUMBER,              // Sort by the tab index number
   SORT_BY_CANV_ELEMENT_ALIGNMENT,                    // Sort by the location of the object inside 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
   SORT_BY_CANV_ELEMENT_DESCRIPTION,                  // Sort by graphical element description
  };
//+------------------------------------------------------------------+

Agora poderemos ordenar, filtrar, selecionar e pesquisar objetos por essas novas propriedades.

Como continuaremos a adicionar mais e mais novas propriedades (elas não se destinam a todos os objetos gráficos), quero esclarecer que até agora todas as propriedades recém-adicionadas estão disponíveis em todos os objetos. E, além disso, limitaremos sua disponibilidade para objetos onde tais propriedades não deveriam estar, simplesmente adicionando métodos às classes desses objetos que retornam um sinalizador para manter uma ou outra propriedade. Tais métodos são virtuais; há muito tempo temos bibliotecas para cada objeto nas classes base. Aqui não os adicionamos para cada novo objeto por uma simples razão: quando supomos que todos os objetos foram criados e vale a pena colocar as coisas em ordem no que diz respeito à disponibilidade de propriedades, faremos tudo de uma vez. E faremos isso de forma que todas as propriedades sejam visíveis para cada um dos objetos de controle - no respectivo painel de propriedades criado no gráfico.


Novas propriedades e enumerações adicionadas. Agora vamos adicionar textos para exibir suas descrições.

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

   MSG_LIB_TEXT_TOP,                                  // Top
   MSG_LIB_TEXT_BOTTOM,                               // Bottom
   MSG_LIB_TEXT_LEFT,                                 // Left
   MSG_LIB_TEXT_RIGHT,                                // Right
   
   MSG_LIB_TEXT_TAB_SIZE_MODE_NORMAL,                 // By tab title width
   MSG_LIB_TEXT_TAB_SIZE_MODE_FILL,                   // By TabControl width
   MSG_LIB_TEXT_TAB_SIZE_MODE_FIXED,                  // Fixed size
   
   MSG_LIB_TEXT_CORNER_LEFT_UPPER,                    // Center of coordinates at the upper left corner of the chart
   MSG_LIB_TEXT_CORNER_LEFT_LOWER,                    // Center of coordinates at the lower left corner of the chart
   MSG_LIB_TEXT_CORNER_RIGHT_LOWER,                   // Center of coordinates at the lower right corner of the chart
   MSG_LIB_TEXT_CORNER_RIGHT_UPPER,                   // Center of coordinates at the upper right corner of the chart

...

   MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,         // ButtonListBox control
   MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,              // Tab header
   MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,               // Tab field
   MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,             // TabControl
   MSG_GRAPH_OBJ_BELONG_PROGRAM,                      // Graphical object belongs to a program

...

//--- CButtonListBox
   MSG_BUTT_LIST_ERR_FAILED_SET_GROUP_BUTTON,         // Failed to set the group for the button with the index 
   MSG_BUTT_LIST_ERR_FAILED_SET_TOGGLE_BUTTON,        // Failed to set the Toggle flag to the button with the index 

//--- CTabControl
   MSG_ELM_LIST_ERR_FAILED_GET_TAB_OBJ,               // Failed to get TabControl tab

//--- Integer properties of graphical elements

...

   MSG_CANV_ELEMENT_PROP_TAB_MULTILINE,               // Several lines of tabs in the control
   MSG_CANV_ELEMENT_PROP_TAB_ALIGNMENT,               // Location of tabs inside the control
   MSG_CANV_ELEMENT_PROP_TAB_SIZE_MODE,               // Tab size setting mode
   MSG_CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,             // Tab index number
   MSG_CANV_ELEMENT_PROP_ALIGNMENT,                   // Location of an object inside the control
//--- Real properties of graphical elements

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

   {"Сверху","Top"},
   {"Снизу","Bottom"},
   {"Слева","Left"},
   {"Справа","Right"},
   
   {"По ширине текста заголовка вкладки","Fit to tab title text width"},
   {"По ширине элемента управления TabControl","Fit TabControl Width"},
   {"Фиксированный размер","Fixed size"},
   
   {"Центр координат в левом верхнем углу графика","Center of coordinates is in the upper left corner of the chart"},
   {"Центр координат в левом нижнем углу графика","Center of coordinates is in the lower left corner of the chart"},
   {"Центр координат в правом нижнем углу графика","Center of coordinates is in the lower right corner of the chart"},
   {"Центр координат в правом верхнем углу графика","Center of coordinates is in the upper right corner of the chart"},

...

   {"Элемент управления \"ButtonListBox\"","Control element \"ButtonListBox\""},
   {"Заголовок вкладки","Tab header"},
   {"Поле вкладки","Tab field"},
   {"Элемент управления \"TabControl\"","Control element \"TabControl\""},
   {"Графический объект принадлежит программе","The graphic object belongs to the program"},

...

//--- CButtonListBox
   {"Не удалось установить группу кнопке с индексом ","Failed to set group for button with index "},
   {"Не удалось установить флаг \"Переключатель\" кнопке с индексом ","Failed to set the \"Toggle\" flag on the button with index "},
   
//--- CTabControl
   {"Не удалось получить вкладку элемента управления TabControl","Failed to get tab of TabControl"},
   
//--- Integer properties of graphical elements

...

   {"Несколько рядов вкладок в элементе управления","Multiple rows of tabs in a control"},
   {"Местоположение вкладок внутри элемента управления","Location of tabs inside the control"},
   {"Режим установки размера вкладок","Tab Size Mode"},
   {"Порядковый номер вкладки","Tab ordinal number"},
   {"Местоположение объекта внутри элемента управления","Location of the object inside the control"},
   
//--- String properties of graphical elements


Como temos um novo modo para desenhar cabeçalhos de guias, precisamos retornar a descrição do modo selecionado. No arquivo de funções de serviço da biblioteca \MQL5\Include\DoEasy\Services\DELib.mqh, vamos escrever uma função que retorna uma descrição do modo de configuração do tamanho da guia:

//+------------------------------------------------------------------+
//| Return the description of the tab size setting mode              |
//+------------------------------------------------------------------+
string TabSizeModeDescription(ENUM_CANV_ELEMENT_TAB_SIZE_MODE mode)
  {
   switch(mode)
     {
      case CANV_ELEMENT_TAB_SIZE_MODE_NORMAL :  return CMessage::Text(MSG_LIB_TEXT_TAB_SIZE_MODE_NORMAL);   break;
      case CANV_ELEMENT_TAB_SIZE_MODE_FIXED  :  return CMessage::Text(MSG_LIB_TEXT_TAB_SIZE_MODE_FIXED);    break;
      case CANV_ELEMENT_TAB_SIZE_MODE_FILL   :  return CMessage::Text(MSG_LIB_TEXT_TAB_SIZE_MODE_FILL);     break;
      default                                :  return "Unknown"; break;
     }
  }
//+------------------------------------------------------------------+

O modo de configuração do tamanho das guias é passado para a função e, dependendo dela, a mensagem de texto correspondente é retornada.


No arquivo de classe do objeto gráfico base da biblioteca \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh, em seu método retornando a descrição do tipo do elemento gráfico, criamos uma seção para objetos auxiliares, movemos o retorno da descrição do cabeçalho da guia lá, e adicionamos o retorno da descrição do novo tipo - campo de guia:

//+------------------------------------------------------------------+
//| Return the description of the graphical element type             |
//+------------------------------------------------------------------+
string CGBaseObj::TypeElementDescription(const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   return
     (
      type==GRAPH_ELEMENT_TYPE_STANDARD               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD)              :
      type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)     :
      type==GRAPH_ELEMENT_TYPE_ELEMENT                ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT)               :
      type==GRAPH_ELEMENT_TYPE_SHADOW_OBJ             ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ)            :
      type==GRAPH_ELEMENT_TYPE_FORM                   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM)                  :
      type==GRAPH_ELEMENT_TYPE_WINDOW                 ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW)                :
      //--- WinForms
      type==GRAPH_ELEMENT_TYPE_WF_UNDERLAY            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY)           :
      type==GRAPH_ELEMENT_TYPE_WF_BASE                ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE)               :
      //--- Containers
      type==GRAPH_ELEMENT_TYPE_WF_CONTAINER           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CONTAINER)          :
      type==GRAPH_ELEMENT_TYPE_WF_GROUPBOX            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GROUPBOX)           :
      type==GRAPH_ELEMENT_TYPE_WF_PANEL               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL)              :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL)        :
      //--- Standard controls
      type==GRAPH_ELEMENT_TYPE_WF_COMMON_BASE         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE)        :
      type==GRAPH_ELEMENT_TYPE_WF_LABEL               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LABEL)              :
      type==GRAPH_ELEMENT_TYPE_WF_CHECKBOX            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX)           :
      type==GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON)        :
      type==GRAPH_ELEMENT_TYPE_WF_BUTTON              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON)             :
      type==GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX)  :
      type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX)           :
      type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM       ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM)      :
      type==GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX    ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX)   :
      type==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX     ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX)    :
      //--- Auxiliary control objects
      type==GRAPH_ELEMENT_TYPE_WF_TAB_HEADER          ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER)         :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_FIELD           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD)          :
      "Unknown"
     );
  }  
//+------------------------------------------------------------------+


No último artigo, bem no final, discutimos a possibilidade de otimizar um pouco um método que procura o número de objetos de um determinado tipo para criar seu nome. Lá, notamos que os dois métodos trabalhando juntos chamam o método duas vezes, o que cria o nome do objeto a partir da representação string da constante de enumeração que especifica o tipo do objeto.

Para evitar uma dupla chamada de método, no arquivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, onde estão localizados os métodos para criar os nomes dos elementos gráficos, na seção protegida da classe, adicione mais um método sobrecarregado, para o qual passaremos o nome anteriormente criado e agora conhecido do objeto para sua busca entre outros objetos gráficos no gráfico:

//--- Return the number of graphical elements (1) by type, (2) by name and type
   int               GetNumGraphElements(const ENUM_GRAPH_ELEMENT_TYPE type) const;
   int               GetNumGraphElements(const string name,const ENUM_GRAPH_ELEMENT_TYPE type) const;
//--- Create and return the graphical element name by its type
   string            CreateNameGraphElement(const ENUM_GRAPH_ELEMENT_TYPE type);
   
private:


Vamos criá-lo fora do corpo da classe.

//+------------------------------------------------------------------+
//| Return the number of graphical elements by type                  |
//+------------------------------------------------------------------+
int CGCnvElement::GetNumGraphElements(const ENUM_GRAPH_ELEMENT_TYPE type) const
  {
//--- Declare a variable with the number of graphical objects and
//--- get the total number of graphical objects on the chart and the subwindow where the graphical element is created
   int n=0, total=::ObjectsTotal(this.ChartID(),this.SubWindow());
//--- Create the name of a graphical object by its type
   string name=TypeGraphElementAsString(type);
//--- In the loop by all chart and subwindow objects,
   for(int i=0;i<total;i++)
     {
      //--- get the name of the next object
      string name_obj=::ObjectName(this.ChartID(),i,this.SubWindow());
      //--- if the object name does not contain the set prefix of the names of the library graphical objects, move on - this is not the object we are looking for
      if(::StringFind(name_obj,this.NamePrefix())==WRONG_VALUE)
         continue;
      //--- If the name of a graphical object selected in the loop has a substring with the created object name by its type,
      //--- then there is a graphical object of this type - increase the counter of objects of this type
      if(::StringFind(name_obj,name)>0)
         n++;
     }
//--- Return the number of found objects by their type
   return n;
  }
//+------------------------------------------------------------------+
//| Return the number of graphical elements by name and type         |
//+------------------------------------------------------------------+
int CGCnvElement::GetNumGraphElements(const string name,const ENUM_GRAPH_ELEMENT_TYPE type) const
  {
//--- Declare a variable with the number of graphical objects and
//--- get the total number of graphical objects on the chart and the subwindow where the graphical element is created
   int n=0, total=::ObjectsTotal(this.ChartID(),this.SubWindow());
//--- In the loop by all chart and subwindow objects,
   for(int i=0;i<total;i++)
     {
      //--- get the name of the next object
      string name_obj=::ObjectName(this.ChartID(),i,this.SubWindow());
      //--- if the object name does not contain the set prefix of the names of the library graphical objects, move on - this is not the object we are looking for
      if(::StringFind(name_obj,this.NamePrefix())==WRONG_VALUE)
         continue;
      //--- If the name of a graphical object selected in the loop has a substring with the created object name by its type,
      //--- then there is a graphical object of this type - increase the counter of objects of this type
      if(::StringFind(name_obj,name)>0)
         n++;
     }
//--- Return the number of found objects by their type
   return n;
  }
//+------------------------------------------------------------------+

Comparado com seu método "par", no qual o nome do objeto é criado a partir de seu tipo e depois pesquisado no nome do objeto gráfico, aqui obtemos esse nome nos parâmetros de entrada do método e procuramos uma substring no nome do objeto que contém o nome passado para o método.

Em geral, nada complicado, mas removemos uma chamada ao método de criação de nomes dessa maneira. Onde o nome ainda não é conhecido, usamos a chamada do primeiro método e, onde é conhecido, o segundo.

Anteriormente, o método que criava e retornava o nome do elemento gráfico por seu tipo faria uma dupla chamada a um método de criação de nome de objeto a partir de seu tipo, a primeira vez dentro do método e a segunda vez dentro do método a ser chamado, que também chamaria um método para criar um nome de objeto a partir de seu tipo:

//+------------------------------------------------------------------+
//| Create and return the graphical element name by its type         |
//+------------------------------------------------------------------+
string CGCnvElement::CreateNameGraphElement(const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   return this.NamePrefix()+TypeGraphElementAsString(type)+(string)this.GetNumGraphElements(type);

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

Agora vamos fazer alterações neste método: vamos criar um nome de objeto, usá-lo para construir uma string de retorno e enviá-la para o novo método sobrecarregado para encontrar o número de tais objetos:

//+------------------------------------------------------------------+
//| Create and return the graphical element name by its type         |
//+------------------------------------------------------------------+
string CGCnvElement::CreateNameGraphElement(const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   string name=TypeGraphElementAsString(type);
   return this.NamePrefix()+name+(string)this.GetNumGraphElements(name,type);
  }
//+------------------------------------------------------------------+


Houve uma falha um pouco irritante no método que cria um elemento-objeto gráfico, quando ocorria um erro na criação de um objeto gráfico, o método sempre retornava nenhum erro (código 0), mas o objeto não era criado. Poderíamos saber as causas do erro apenas através de pistas circunstanciais. Isso vale mais para o desenvolvimento de classes de elementos gráficos do que para sua utilização pelo usuário da biblioteca, pois todos os erros na criação de objetos já são eliminados na fase de desenvolvimento da classe. Porém, faremos alterações que nos permitam entender com mais precisão a causa do erro:

//+------------------------------------------------------------------+
//| Create the graphical element object                              |
//+------------------------------------------------------------------+
bool CGCnvElement::Create(const long chart_id,     // Chart ID
                          const int wnd_num,       // Chart subwindow
                          const int x,             // X coordinate
                          const int y,             // Y coordinate
                          const int w,             // Width
                          const int h,             // Height
                          const bool redraw=false) // Flag indicating the need to redraw
                         
  {
   ::ResetLastError();
   if(this.m_canvas.CreateBitmapLabel((chart_id==NULL ? ::ChartID() : chart_id),wnd_num,this.m_name,x,y,w,h,COLOR_FORMAT_ARGB_NORMALIZE))
     {
      this.Erase(CLR_CANV_NULL);
      this.m_canvas.Update(redraw);
      this.m_shift_y=(int)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_WINDOW_YDISTANCE,wnd_num);
      return true;
     }
   int err=::GetLastError();
   int code=(err==0 ? (w<1 ? MSG_CANV_ELEMENT_ERR_FAILED_SET_WIDTH : h<1 ? MSG_CANV_ELEMENT_ERR_FAILED_SET_HEIGHT : ERR_OBJECT_ERROR) : err);
   string subj=(w<1 ? "Width="+(string)w+". " : h<1 ? "Height="+(string)h+". " : "");
   CMessage::ToLog(DFUN_ERR_LINE+subj,code,true);
   
   return false;
  }
//+------------------------------------------------------------------+

Consideramos o código do último erro na variável (e sempre foi zero em caso de erro na criação de um recurso gráfico na classe CCanvas da Biblioteca Padrão), depois verificamos o código do erro, e se é igual a zero, então verificamos a largura e a altura do objeto criado. Se algum desses valores for menor que um, escrevemos no código de erro o código da mensagem correspondente ou um erro geral na criação de um objeto gráfico. Se o código de erro não for zero, escrevemos o código de erro na variável. Em seguida, criamos uma string com uma descrição adicional do código de erro - novamente, dependendo dos valores de largura e altura passados para o método, e exibimos uma mensagem indicando o nome do método com o número da linha, uma mensagem adicional e uma descrição do código de erro.


Todos os objetos de elementos gráficos são descendentes da classe de objeto-forma, que por sua vez também é descendente de outras classes. Mas nessa classe é criada a funcionalidade para trabalhar com o mouse, então todos os objetos da interface gráfica do programa são de alguma forma baseados nela. E todo objeto que está anexado ao seu objeto base, ou seja, que é criado a partir do objeto base, herda as propriedades de seu criador. Essas propriedades também incluem propriedades como atividade, visibilidade e acessibilidade. Se o objeto do qual outro objeto anexado foi criado não estiver ativo, ou seja, não responde ao cursor do mouse, seu objeto subordinado também deve herdar esse comportamento (que pode ser alterado posteriormente). Se o objeto não estiver disponível (não ativo e estiver pouco nítido para indicar que está inativo), o filho também deverá estar igual. E se o objeto é invisível, então o subordinado também deve ser escondido, o que também é natural.

No arquivo \MQL5\Include\DoEasy\Objects\Graph\Form.mqh da classe do objeto-painel, em seu método que serve para criar um novo elemento anexado e adicioná-lo à lista de objetos anexados, escreveremos a herança dessas propriedades para todos os objetos subordinados (objetos criados a partir de seus pais):

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

Agora, cada objeto anexado recém-criado herdará imediatamente essas propriedades de seu objeto base, e não acontecerá que, em um objeto inativo, alguns de seus subordinados criados de repente comecem a mostrar atividade, ou seu objeto subordinado apareça de repente no gráfico a partir de um objeto oculto, etc.

Da mesma forma, os manipuladores de eventos do mouse devem ignorar objetos inativos ou ocultos.

No mesmo arquivo, no manipulador do último evento do mouse, inseriremos linhas que proíbem o processamento de um objeto oculto ou inacessível:

//+------------------------------------------------------------------+
//| Last mouse event handler                                         |
//+------------------------------------------------------------------+
void CForm::OnMouseEventPostProcessing(void)
  {
   if(!this.IsVisible() || !this.Enabled())
      return;
   ENUM_MOUSE_FORM_STATE state=this.GetMouseState();
   switch(state)
     {

Se o objeto for tal, simplesmente saímos do método.


Fizemos isso para que, se um objeto tiver uma moldura, ele seja desenhado apenas se seu sinalizador de redesenho estiver definido para o objeto no método que serve para limpá-lo e preenchê-lo com a cor de fundo. Esse comportamento não é correto. Nem sempre é necessário desenhar objetos com o redesenho obrigatório de todo o gráfico. Mas, neste caso, se o sinalizador de redesenho for apagado, a moldura do objeto desaparecerá quando esse método for chamado. Além disso, já existe uma condição de desenhar de moldura - seu tipo é definido como não faltando. Portanto, em todas as classes onde existem métodos Erase(), removeremos a verificação do sinalizador de redesenho para exibir a moldura do objeto:

if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw)

No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh, escrevemos um método virtual vazio para desenhar a moldura do objeto:

//+------------------------------------------------------------------+
//| Form object class                                                |
//+------------------------------------------------------------------+
class CWinFormBase : public CForm
  {
protected:
   color             m_fore_color_init;                        // Initial color of the control text
   color             m_fore_state_on_color_init;               // Initial color of the control text when the control is "ON"
private:
//--- Return the font flags
   uint              GetFontFlags(void);
public:
//--- Draw a frame
   virtual void      DrawFrame(void){}
//--- Return by type the (1) list, (2) the number of bound controls, (3) the bound control by index in the list
   CArrayObj        *GetListElementsByType(const ENUM_GRAPH_ELEMENT_TYPE type);
   int               ElementsTotalByType(const ENUM_GRAPH_ELEMENT_TYPE type);
   CGCnvElement     *GetElementByType(const ENUM_GRAPH_ELEMENT_TYPE type,const int index);

Este método é necessário para que, em classes herdeiras onde uma moldura deve ser desenhada, em vez dos métodos DrawFrameBevel(), DrawFrameFlat(), DrawFrameSimple() e DrawFrameStamp() da classe de objeto-forma - uma vez que estes métodos são destinados a outros propósitos, a saber, desenhar uma moldura específica do objeto-forma. Se para qualquer elemento gráfico precisarmos desenhar uma moldura exclusiva, precisaremos substituir o método declarado aqui nessa classe e desenhar a moldura desejada com sua ajuda.


Os métodos Erase() agora removeram a verificação de sinalizadores de atualização para desenhar a borda:

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

Agora, a moldura é sempre desenhada se seu tipo estiver definido. Isso é feito em todos os arquivos de todas as classes que possuem métodos Erase.


Ao final do método que retorna a descrição da propriedade inteira do elemento, adicionamos blocos de código para retornar novas propriedades dos elementos gráficos:

      property==CANV_ELEMENT_PROP_TAB_ALIGNMENT                ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_ALIGNMENT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+AlignmentDescription((ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(property))
         )  :
      property==CANV_ELEMENT_PROP_TAB_SIZE_MODE                ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_SIZE_MODE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+TabSizeModeDescription((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)this.GetProperty(property))
         )  :
      property==CANV_ELEMENT_PROP_TAB_PAGE_NUMBER              ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_PAGE_NUMBER)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_ALIGNMENT                    ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_ALIGNMENT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+AlignmentDescription((ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(property))
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Return the description of the control real property              |
//+------------------------------------------------------------------+

Agora qualquer elemento gráfico poderá retornar uma string descrevendo a nova propriedade especificada e seu valor.


Em alguns construtores protegidos de elementos gráficos, bem no final do código do construtor, agora temos uma linha que faz com que o objeto criado seja redesenhado:

this.Redraw(false);

Esse comportamento não é correto. O objeto deve ser redesenhado somente após sua criação final, e não em cada próximo construtor de toda a hierarquia de herança do objeto criado.

Agora, se imaginarmos a cadeia da hierarquia de objetos: Obj0 --> Obj1 --> Obj2 --> Obj3 --> Obj4 --> ... ... --> ObjN onde Obj0 é o primeiro objeto na hierarquia e ObjN é o último, então, quando este for criado, todos os construtores de toda a cadeia de herança serão chamados um por um. E se houver uma linha de atualização especificada em cada uma delas, o objeto será redesenhado a cada vez.

Vamos remover essas linhas de todos os construtores protegidos de todas as classes.

Como exemplo, no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CommonBase.mqh:

//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CCommonBase::CCommonBase(const ENUM_GRAPH_ELEMENT_TYPE type,
                         const long chart_id,
                         const int subwindow,
                         const string descript,
                         const int x,
                         const int y,
                         const int w,
                         const int h) : CWinFormBase(type,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Set the specified graphical element type for the object and assign the library object type to the current object
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.SetCoordX(x);
   this.SetCoordY(y);
   this.SetWidth(w);
   this.SetHeight(h);
   this.Initialize();
   if(this.AutoSize())
      this.AutoSetWH();
   this.SetWidthInit(this.Width());
   this.SetHeightInit(this.Height());
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.Redraw(false);
  }
//+------------------------------------------------------------------+

As mesmas alterações já foram feitas nas classes:
C-Label
no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Label.mqh e CButton no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh.

Aqui, no mesmo arquivo de classe CCommonBase, faremos as alterações mencionadas acima nos métodos Erase() para remover a verificação do sinalizador de redesenho:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//+------------------------------------------------------------------+
void CCommonBase::Erase(const color colour,const uchar opacity,const bool redraw=false)
  {
//--- Fill the element having the specified color and the redrawing flag
   CGCnvElement::Erase(colour,opacity,redraw);
//--- If the object has a frame, draw it
   if(this.BorderStyle()!=FRAME_STYLE_NONE)
      this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),255,this.BorderStyle());
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element with a gradient fill                           |
//+------------------------------------------------------------------+
void CCommonBase::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false)
  {
//--- Fill the element having the specified color array and the redrawing flag
   CGCnvElement::Erase(colors,opacity,vgradient,cycle,redraw);
//--- If the object has a frame, draw it
   if(this.BorderStyle()!=FRAME_STYLE_NONE)
      this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),255,this.BorderStyle());
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Depois, não descreveremos essas alterações em outros arquivos de outras classes.


Como não é mais necessário enviar à força um sinalizador de redesenho definido para o método de desenho de moldura do objeto, no arquivo de classe objeto-botões \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh, no método de redesenho do objeto, especificamos «não forçosamente» como true, como era antes e que fazia com que todo o gráfico fosse redesenhado, e passamos o sinalizador redraw, que por sua vez é passado para o método desde fora e do qual a necessidade de redesenho dependerá:

//+------------------------------------------------------------------+
//| Redraw the object                                                |
//+------------------------------------------------------------------+
void CButton::Redraw(bool redraw)
  {
//--- Fill the object with the background color
   this.Erase(this.BackgroundColor(),this.Opacity(),redraw);
//--- Declare the variables for X and Y coordinates and set their values depending on the text alignment
   int x=0,y=0;
   CLabel::SetTextParamsByAlign(x,y);
//--- Draw the text within the set coordinates of the object and the binding point of the text, and update the object 
   this.Text(x,y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor());
   this.Update(redraw);
  }
//+------------------------------------------------------------------+


No método que define o estado do botão como "desativado" para todos os botões de um grupo no contêiner, no final adicionamos um redesenho do gráfico no qual o objeto-botão foi criado para exibir imediatamente as alterações após todos os botões serem processados :

//+------------------------------------------------------------------+
//| 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 a certain type from the base object (Button or its descendant)
   CArrayObj *list=base.GetListElementsByType(this.TypeGraphElement());
//--- 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);
         obj.SetForeColor(obj.ForeColorInit(),false);
         obj.SetBorderColor(obj.BorderColorInit(),false);
         //--- Redraw the object to display the changes
         obj.Redraw(false);
        }
     }
   ::ChartRedraw(this.ChartID());
  }
//+------------------------------------------------------------------+


No manipulador do último evento do mouse, logo no início, adicionamos uma verificação da invisibilidade ou inacessibilidade do elemento, como fizemos acima para o objeto-forma:

//+------------------------------------------------------------------+
//| Last mouse event handler                                         |
//+------------------------------------------------------------------+
void CButton::OnMouseEventPostProcessing(void)
  {
   if(!this.IsVisible() || !this.Enabled())
      return;
   ENUM_MOUSE_FORM_STATE state=GetMouseState();
   switch(state)
     {

As mesmas melhorias já foram feitas nas classes:
CCheckBox no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh e CTabHeader no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\TabHeader.mqh.

Não consideraremos mais essas mudanças.


Objeto TabField, campo da guia do controle TabControl

Para o controle TabControl, no último artigo criamos uma classe do objeto-cabeçalho de guia auxiliar TabHeader. A classe é herdada do objeto-botão, pois repete quase todas as suas funcionalidades. Tal cabeçalho está diretamente relacionado ao campo das guias, que juntas formam uma guia. E o próprio controle consiste em pelo menos duas dessas guias.

No último artigo, usamos um objeto-contêiner para elaborar o campo de guia. Este é o objeto base para todos os objetos-contêineres na biblioteca. O campo de guia deve conter objetos subordinados criados neste campo e, consequentemente, subordinados a ele. Certamente, a funcionalidade do objeto-contêiner base para fazer o trabalho com o campo não é suficiente. Por isso, com base no objeto-contêiner base, criaremos uma nova classe do objeto-campo de guia.

Na pasta biblioteca \MQL5\Include\DoEasy\Objects\Graph\WForms\, criamos um novo arquivo WinFormBase.mqh da classe CWinFormBase. A classe deve ser derivada da classe base do objeto-contêiner. O arquivo do objeto painel deve estar incluído no arquivo da classe criada, que dará acesso a ele a todos os arquivos de objetos gráficos da biblioteca:

//+------------------------------------------------------------------+
//|                                                     TabField.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"
//+------------------------------------------------------------------+
//| TabHeader object class of WForms TabControl                      |
//+------------------------------------------------------------------+
class CTabField : public CContainer
  {
  }


Na seção privada, vamos declarar um método que retorna um ponteiro para o objeto-cabeçalho correspondente a este campo, e um método virtual para criar elementos gráficos anexados à guia (a este objeto-campo):

//+------------------------------------------------------------------+
//| TabHeader object class of WForms TabControl                      |
//+------------------------------------------------------------------+
class CTabField : public CContainer
  {
private:
//--- Find and return a pointer to the header object corresponding to the number of this tab
   CWinFormBase     *GetHeaderObj(void);
//--- Create a new graphical object
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);
protected:

Visto que se especificamos o tipo do objeto-cabeçalho neste arquivo exatamente pelo seu tipo (CTabHeader), que é visível nesta classe, e tentamos compilar a biblioteca inteira compilando a classe principal da biblioteca CEngine, então obteremos um grande número de erros e avisos sobre tipo desconhecido de CTabHeader. Não vamos quebrar a cabeça em busca do que está "preso" em todos os meandros de todos os arquivos de biblioteca, mas simplesmente declarar o tipo de retorno como o objeto base de todos os objetos WinForms da biblioteca. Isso será suficiente para trabalhar com ele aqui. E fora desta classe, já podemos obtê-lo daqui com seu tipo correto.

O método virtual para criar elementos gráficos anexados é necessário para que, ao acessarmos o campo, possamos criar objetos subordinados nele.

Na seção protegida da classe, declaramos um construtor protegido:

protected:
//--- Protected constructor with object type, chart ID and subwindow
                     CTabField(const ENUM_GRAPH_ELEMENT_TYPE type,
                               const long chart_id,
                               const int subwindow,
                               const string descript,
                               const int x,
                               const int y,
                               const int w,
                               const int h);
public:


Na seção pública, declaramos métodos para trabalhar com a classe e um construtor paramétrico:

public:
//--- Draws a field frame depending on the location of the header
   virtual void      DrawFrame(void);
//--- (1) Set and (2) return the tab index
   void              SetPageNumber(const int value)         { this.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,value);         }
   int               PageNumber(void)                 const { return (int)this.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER);   }
//--- Clear the element filling it with color and opacity
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Clear the element with a gradient fill
   virtual void      Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);
//--- Constructors
                     CTabField(const long chart_id,
                               const int subwindow,
                               const string descript,
                               const int x,
                               const int y,
                               const int w,
                               const int h);
  };
//+------------------------------------------------------------------+

Todos os métodos virtuais aqui substituem os métodos das classes pai de mesmo nome, e os métodos para definir e retornar o número da guia à qual o campo pertence simplesmente definem o valor passado para a propriedade do objeto e o retornam.


Construtores protegidos e paramétricos :

//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CTabField::CTabField(const ENUM_GRAPH_ELEMENT_TYPE type,
                     const long chart_id,
                     const int subwindow,
                     const string descript,
                     const int x,
                     const int y,
                     const int w,
                     const int h) : CContainer(type,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD);
   this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true);
   this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true);
   this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN);
   this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER);
   this.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true);
   this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN);
   this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetPaddingAll(3);
  }
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CTabField::CTabField(const long chart_id,
                     const int subwindow,
                     const string descript,
                     const int x,
                     const int y,
                     const int w,
                     const int h) : CContainer(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD);
   this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true);
   this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true);
   this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN);
   this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER);
   this.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true);
   this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN);
   this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetPaddingAll(3);
  }
//+------------------------------------------------------------------+

A única diferença entre eles é que o construtor protegido recebe o tipo do objeto que está sendo criado (se herdado dele), e esse tipo é passado para o objeto pai. Em um construtor paramétrico público, o tipo passado para a classe pai é explicitamente especificado – o objeto-campo.

No corpo do construtor, o objeto criado é definido com os valores desejados de algumas propriedades por padrão. As propriedades restantes do objeto são definidas nas classes pai.

Método que localiza e retorna um ponteiro para o cabeçalho correspondente ao número da guia:

//+------------------------------------------------------------------+
//| Find and return a pointer to the header                          |
//| corresponding to the tab index                                   |
//+------------------------------------------------------------------+
CWinFormBase *CTabField::GetHeaderObj(void)
  {
//--- Get the pointer to the base object
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return NULL;
//--- From the base object, get the list of tab header objects
   CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
//--- Leave only the object whose tab index matches this one in the obtained list
   list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,this.PageNumber(),EQUAL);
//--- Return the pointer to the found object from the list
   return(list!=NULL ? list.At(0) : NULL);
  }
//+------------------------------------------------------------------+

O método é descrito em detalhes nos comentários ao código. Em resumo, para mapear este campo com seu cabeçalho armazenado na classe à qual este objeto e seus objetos de cabeçalho estão anexados, precisamos acessar o objeto base. Este método existe há muito tempo. Obtemos um ponteiro para o objeto base e, a partir dele, obtemos uma lista de todos os objetos vinculados a ele com o tipo de objeto-cabeçalho da guia. Filtramos a lista resultante para que ela contenha um objeto com o número da guia registrado neste objeto. Assim, encontramos o cabeçalho correspondente a este campo, e o ponteiro para ele é armazenado na lista resultante. Nós o devolvemos. Se o objeto não for encontrado, o método retornará NULL.


Método que desenha a borda do elemento dependendo da localização do cabeçalho:

//+------------------------------------------------------------------+
//| Draw the element frame depending on the header position          |
//+------------------------------------------------------------------+
void CTabField::DrawFrame(void)
  {
//--- Set the initial coordinates
   int x1=0;
   int y1=0;
   int x2=this.Width()-1;
   int y2=this.Height()-1;
//--- Get the tab header corresponding to the field
   CTabHeader *header=this.GetHeaderObj();
   if(header==NULL)
      return;
//--- Draw a rectangle that completely outlines the field
   this.DrawRectangle(x1,y1,x2,y2,this.BorderColor(),this.Opacity());
//--- Depending on the location of the header, draw a line on the edge adjacent to the header.
//--- The line size is calculated from the heading size and corresponds to it with a one-pixel indent on each side
//--- thus, visually the edge will not be drawn on the adjacent side of the header
   switch(header.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :
        this.DrawLine(header.CoordXRelative()+1,0,header.RightEdgeRelative()-2,0,this.BackgroundColor(),this.Opacity());
        break;
      case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
        this.DrawLine(header.CoordXRelative()+1,this.Height()-1,header.RightEdgeRelative()-2,this.Height()-1,this.BackgroundColor(),this.Opacity());
        break;
      case CANV_ELEMENT_ALIGNMENT_LEFT    :
        // { }
        break;
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :
        // { }
        break;
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

O método é descrito em detalhes nos comentários ao código. No momento, o método desenha uma moldura quando os cabeçalhos estão localizados na parte superior e inferior. Direita e esquerda serão feitas em artigos futuros. Em resumo, um cabeçalho é anexado ao campo, por exemplo, na parte superior. Não deve haver linha onde eles se encontram. Seria possível desenhar uma moldura de campo usando uma linha quebrada, mas há algum problema com o número de pontos a serem desenhados, dependendo da localização do cabeçalho. Se estiver à esquerda ou à direita do campo, o número de pontos de linha será um a menos se o cabeçalho não estiver na borda do campo.

Por isso, é mais fácil primeiro desenhar um retângulo que contorne completamente o campo e, depois de receber as coordenadas do cabeçalho, desenhar uma linha com a cor de fundo do campo onde o cabeçalho está em contato com o campo. Desta forma "apagamos" a linha onde o cabeçalho toca o campo para obter a exibição correta da guia.

Métodos virtuais que limpam um elemento, preenchendo-o com cor e opacidade:

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

Os métodos são idênticos aos mesmos métodos em outras classes, ou classes pai. E eles são redefinidos aqui para que a moldura seja desenhada exatamente pelo método que discutimos acima.

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

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

O método virtual também é idêntico aos métodos de mesmo nome em outras classes. Aqui está um novo bloco de código para criar um objeto-campo, cuja classe estamos considerando. Adicionaremos exatamente os mesmos blocos para criar este objeto em outras classes nos mesmos métodos.

Com este método, poderemos criar objetos subordinados no campo da guia.


Vamos refinar a classe do cabeçalho da guia no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\TabHeader.mqh.

Na seção privada, removemos o método para definir o tamanho e o deslocamento do objeto:

//--- Sets the width, height and shift of the element depending on the state
   void              SetWH(void);


No mesmo lugar, declaramos métodos para definir a linha do cabeçalho da guia selecionada na posição correta:

private:
   int               m_width_off;                        // Object width in the released state
   int               m_height_off;                       // Object height in the released state
   int               m_width_on;                         // Object width in the selected state
   int               m_height_on;                        // Object height in the selected state
   int               m_col;                              // Header column index
   int               m_row;                              // Header row index
//--- Adjust the size and location of the element depending on the state
   bool              WHProcessStateOn(void);
   bool              WHProcessStateOff(void);
//--- Draws a header frame depending on its position
   virtual void      DrawFrame(void);
//--- Set the string of the selected tab header to the correct position, (1) top, (2) bottom, (3) left and (4) right
   void              CorrectSelectedRowTop(void);
   void              CorrectSelectedRowBottom(void);
   void              CorrectSelectedRowLeft(void);
   void              CorrectSelectedRowRight(void);
   
protected:


Na seção pública, , anunciaremos/escreveremos novos métodos e refinaremos os existentes:

public:
//--- Find and return a pointer to the field object corresponding to the tab index
   CWinFormBase     *GetFieldObj(void);
//--- Set the control size in the (1) released and (2) selected state
   bool              SetSizeOff(void)  { return(CGCnvElement::SetWidth(this.m_width_off) && CGCnvElement::SetHeight(this.m_height_off) ? true : false);  }
   bool              SetSizeOn(void)   { return(CGCnvElement::SetWidth(this.m_width_on) && CGCnvElement::SetHeight(this.m_height_on)   ? true : false);  }
//--- Sets the size of the control element
   void              SetWidthOff(const int value)                                            { this.m_width_off=value;  }
   void              SetHeightOff(const int value)                                           { this.m_height_off=value; }
   void              SetWidthOn(const int value)                                             { this.m_width_on=value;   }
   void              SetHeightOn(const int value)                                            { this.m_height_on=value;  }
//--- Set all sizes of the control element
   bool              SetSizes(const int w,const int h);
//--- Returns the control size
   int               WidthOff(void)                                                    const { return this.m_width_off; }
   int               HeightOff(void)                                                   const { return this.m_height_off;}
   int               WidthOn(void)                                                     const { return this.m_width_on;  }
   int               HeightOn(void)                                                    const { return this.m_height_on; }
//--- (1) Set and (2) return the index of the tab title row
   void              SetRow(const int value)                                                 { this.m_row=value;        }
   int               Row(void)                                                         const { return this.m_row;       }
//--- (1) Set and (2) return the index of the tab title column
   void              SetColumn(const int value)                                              { this.m_col=value;        }
   int               Column(void)                                                      const { return this.m_col;       }
//--- Set the tab location
   void              SetTabLocation(const int row,const int col)
                       {
                        this.SetRow(row);
                        this.SetColumn(col);
                       }
//--- (1) Sets and (2) return the location of the object on the control
   void              SetAlignment(const ENUM_CANV_ELEMENT_ALIGNMENT alignment)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,alignment);
                       }
   ENUM_CANV_ELEMENT_ALIGNMENT Alignment(void)  const { return (ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT);  }
   
//--- (1) Set and (2) get the mode of setting the tab size
   void              SetTabSizeMode(const ENUM_CANV_ELEMENT_TAB_SIZE_MODE mode)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_TAB_SIZE_MODE,mode);
                       }
   ENUM_CANV_ELEMENT_TAB_SIZE_MODE TabSizeMode(void)const{ return (ENUM_CANV_ELEMENT_TAB_SIZE_MODE)this.GetProperty(CANV_ELEMENT_PROP_TAB_SIZE_MODE);}

//--- (1) Set and (2) return the tab index
   void              SetPageNumber(const int value)         { this.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,value);         }
   int               PageNumber(void)                 const { return (int)this.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER);   }

//--- Sets the state of the control
   virtual void      SetState(const bool flag);

//--- Clear the element filling it with color and opacity
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Clear the element with a gradient fill
   virtual void      Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);

//--- Last mouse event handler
   virtual void      OnMouseEventPostProcessing(void);
   
//--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control
   virtual void      SetPaddingLeft(const uint value)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_PADDING_LEFT,(value<1 ? 0 : value));
                       }
   virtual void      SetPaddingTop(const uint value)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_PADDING_TOP,(value<1 ? 0 : value));
                       }
   virtual void      SetPaddingRight(const uint value)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_PADDING_RIGHT,(value<1 ? 0 : value));
                       }
   virtual void      SetPaddingBottom(const uint value)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_PADDING_BOTTOM,(value<1 ? 0 : value));
                       }
   virtual void      SetPaddingAll(const uint value)
                       {
                        this.SetPaddingLeft(value); this.SetPaddingTop(value); this.SetPaddingRight(value); this.SetPaddingBottom(value);
                       }
   virtual void      SetPadding(const int left,const int top,const int right,const int bottom)
                       {
                        this.SetPaddingLeft(left); this.SetPaddingTop(top); this.SetPaddingRight(right); this.SetPaddingBottom(bottom);
                       }
protected:
//--- Protected constructor with object type, chart ID and subwindow

Anteriormente, o método SetAlignment(), além de definir a propriedade, também definia o tamanho da moldura. A moldura aqui é sempre do mesmo tamanho, isto é, 1 pixel, e, portanto, nada precisa ser alterado, portanto vamos excluir tudo:

//--- (1) Sets and (2) return the location of the object on the control
   void              SetAlignment(const ENUM_CANV_ELEMENT_ALIGNMENT alignment)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,alignment);
                        if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP)
                           this.SetBorderSize(1,1,1,0);
                        if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM)
                           this.SetBorderSize(1,0,1,1);
                        if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT)
                           this.SetBorderSize(1,1,0,1);
                        if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT)
                           this.SetBorderSize(0,1,1,1);
                       }


Construtores de classe, protegidos e paramétricos:

//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CTabHeader::CTabHeader(const ENUM_GRAPH_ELEMENT_TYPE type,
                       const long chart_id,
                       const int subwindow,
                       const string descript,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CButton(type,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP);
   this.SetToggleFlag(true);
   this.SetGroupButtonFlag(true);
   this.SetText(TypeGraphElementAsString(this.TypeGraphElement()));
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetOpacity(CLR_DEF_CONTROL_TAB_HEAD_OPACITY,true);
   this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true);
   this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN);
   this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER);
   this.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true);
   this.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON);
   this.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
   this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN);
   this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER);
   this.SetPadding(6,3,6,3);
   this.SetSizes(w,h);
   this.SetState(false);
  }
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CTabHeader::CTabHeader(const long chart_id,
                       const int subwindow,
                       const string descript,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CButton(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP);
   this.SetToggleFlag(true);
   this.SetGroupButtonFlag(true);
   this.SetText(TypeGraphElementAsString(this.TypeGraphElement()));
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetOpacity(CLR_DEF_CONTROL_TAB_HEAD_OPACITY,true);
   this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true);
   this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN);
   this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER);
   this.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true);
   this.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON);
   this.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
   this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN);
   this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER);
   this.SetPadding(6,3,6,3);
   this.SetSizes(w,h);
   this.SetState(false);
  }
//+------------------------------------------------------------------+

Agora, em vez de definir separadamente os tamanhos do cabeçalho:

   this.SetWidthOff(this.Width());
   this.SetHeightOff(this.Height());
   this.SetWidthOn(this.Width()+4);
   this.SetHeightOn(this.Height()+2);

é chamado o método para definir o tamanho do cabeçalho da guia, onde os tamanhos são definidos dependendo do modo de configuração do tamanho do cabeçalho:

//+------------------------------------------------------------------+
//| Set all header sizes                                             |
//+------------------------------------------------------------------+
bool CTabHeader::SetSizes(const int w,const int h)
  {
//--- If the passed width or height is less than 4 pixels, 
//--- make them equal to four pixels
   int width=(w<4 ? 4 : w);
   int height=(h<4 ? 4 : h);
//--- Depending on the header size setting mode
   switch(this.TabSizeMode())
     {
      //--- set the width and height for the Normal mode
      case CANV_ELEMENT_TAB_SIZE_MODE_NORMAL :
        this.TextSize(this.Text(),width,height);
        width+=this.PaddingLeft()+this.PaddingRight();
        height=h+this.PaddingTop()+this.PaddingBottom();
        break;
      //--- For the Fixed and Fill modes, the size remains
      //--- passed to the method and adjusted
      case CANV_ELEMENT_TAB_SIZE_MODE_FIXED  : break;
      //---CANV_ELEMENT_TAB_SIZE_MODE_FILL
      default: break;
     }
//--- Set the results of changing the width and height to 'res'
   bool res=true;
   res &=this.SetWidth(width);
   res &=this.SetHeight(height);
//--- If there is an error in changing the width or height, return 'false'
   if(!res)
      return false;
//--- Set the changed size for different button states
   this.SetWidthOn(this.Width()+4);
   this.SetHeightOn(this.Height()+2);
   this.SetWidthOff(this.Width());
   this.SetHeightOff(this.Height());
   return true;
  }
//+------------------------------------------------------------------+

A lógica do método é descrita nos comentários ao código. Os tamanhos são ajustados apenas para o modo onde a largura do cabeçalho corresponde à largura do texto exibido nele. Para o modo Fixed, o tamanho do cabeçalho deve ser fixo, de modo que permaneça aquele que foi passado para o método nas variáveis w e h, mas ajustado se os tamanhos forem menores que quatro pixeis (nas variáveis width e height). Faremos o modo de estiramento de largura de acordo com o tamanho do contêiner em um artigo posterior.

O método que define o estado do controle sofreu uma grande mudança:

//+------------------------------------------------------------------+
//| Set the state of the control                                     |
//+------------------------------------------------------------------+
void CTabHeader::SetState(const bool flag)
  {
//--- Get the button state and set the new one passed to the method
   bool state=this.State();
   CButton::SetState(flag);
//--- If the previous state of the button does not match the set
   if(state!=this.State())
     {
      //--- If the button is pressed
      if(this.State())
        {
         //--- Call the button resizing method and bring it to the foreground
         this.WHProcessStateOn();
         this.BringToTop();
         //--- Get the base object the tab title is attached to (TabControl)
         CWinFormBase *base=this.GetBase();
         if(base==NULL)
            return;
         //--- Set the index of the selected tab to the TabControl object
         base.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,this.PageNumber());
         //--- Get the list of tab field objects from the base object
         CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD);
         if(list==NULL)
            return;
         //--- In the loop through the received list, hide all fields that do not match the header
         for(int i=0;i<list.Total();i++)
           {
            //--- get the next tab field object
            CWinFormBase *obj=list.At(i);
            //--- If the object is not received or corresponds to the selected header, move on
            if(obj==NULL || obj.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER)==this.PageNumber())
               continue;
            //--- Set the ZOrder tab field as the base object and hide the field
            obj.SetZorder(base.Zorder(),false);
            obj.Hide();
           }
         //--- Get the field object corresponding to the field header (this object)
         CWinFormBase *field=this.GetFieldObj();
         if(field==NULL)
            return;
         //--- Display the field and set its ZOrder higher than other fields of the TabControl object,
         //--- draw the frame of the field object and bring it to the foreground
         field.Show();
         field.SetZorder(base.Zorder()+1,false);
         field.DrawFrame();
         field.BringToTop();
        }
      //--- If the button is not pressed, call the method to restore the title size
      else
         this.WHProcessStateOff();
     }
  }
//+------------------------------------------------------------------+

Se o botão estiver selecionado (clicado no cabeçalho da guia), precisamos aumentar o tamanho do botão (cabeçalho) e trazer o cabeçalho para a frente. Em seguida, precisamos ocultar todos os campos da guia que não correspondem ao cabeçalho selecionado, e o campo desse cabeçalho, ao contrário, deve ser exibido e trazido para o primeiro plano. Além disso, o campo da guia exibida deve ser clicável, portanto, seu parâmetro ZOrder deve ser maior que o dos demais objetos de controle, e para campos não selecionados, ao contrário, o ZOrder deve ser menor que o selecionado . Isso é tudo que esse método faz.


No método que ajusta o tamanho e a posição do elemento no estado "selecionado" dependendo de sua posição, precisamos chamar métodos que deslocarão a linha de cabeçalho selecionada para uma posição na qual o cabeçalho será anexado ao seu campo, porque se permitimos a posição dos cabeçalhos em várias linhas, o cabeçalho selecionado pode estar em uma linha que não seja adjacente ao campo:

//+------------------------------------------------------------------+
//| Adjust the element size and location                             |
//| in the "selected" state depending on its location                |
//+------------------------------------------------------------------+
bool CTabHeader::WHProcessStateOn(void)
  {
//--- If failed to set a new size, leave
   if(!this.SetSizeOn())
      return false;
//--- Get the base object
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return false;
//--- Depending on the title location,
   switch(this.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :
        //--- Adjust the location of the row with the selected header
        this.CorrectSelectedRowTop();
        //--- shift the header by two pixels to the new location coordinates and
        //--- set the new relative coordinates
        if(this.Move(this.CoordX()-2,this.CoordY()-2))
          {
           this.SetCoordXRelative(this.CoordXRelative()-2);
           this.SetCoordYRelative(this.CoordYRelative()-2);
          }
        break;
      case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
        //--- Adjust the location of the row with the selected header
        this.CorrectSelectedRowBottom();
        //--- shift the header by two pixels to the new location coordinates and
        //--- set the new relative coordinates
        if(this.Move(this.CoordX()-2,this.CoordY()))
          {
           this.SetCoordXRelative(this.CoordXRelative()-2);
           this.SetCoordYRelative(this.CoordYRelative());
          }
      case CANV_ELEMENT_ALIGNMENT_LEFT    :
        //--- Adjust the location of the row with the selected header
        this.CorrectSelectedRowLeft();
        //--- shift the header by two pixels to the new location coordinates and
        //--- set the new relative coordinates
        // {   }
        break;
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :
        //--- Adjust the location of the row with the selected header
        this.CorrectSelectedRowRight();
        //--- shift the header by two pixels to the new location coordinates and
        //--- set the new relative coordinates
        // {   }
        break;
      default:
        break;
     }
   return true;
  }
//+------------------------------------------------------------------+

Ainda não estamos processando a localização dos cabeçalhos das guias à esquerda e à direita aqui, faremos isso em artigos posteriores.


No método que ajusta o tamanho e a posição do elemento no estado "não selecionado" dependendo de sua posição, adicionamos blocos de código de stub para manipular os cabeçalhos esquerdo e direito:

//+------------------------------------------------------------------+
//| Adjust the element size and location                             |
//| in the "released" state depending on its location                |
//+------------------------------------------------------------------+
bool CTabHeader::WHProcessStateOff(void)
  {
//--- If failed to set a new size, leave
   if(!this.SetSizeOff())
      return false;
//--- Depending on the title location,
   switch(this.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :
        //--- shift the header to its original position and set the previous relative coordinates
        if(this.Move(this.CoordX()+2,this.CoordY()+2))
          {
           this.SetCoordXRelative(this.CoordXRelative()+2);
           this.SetCoordYRelative(this.CoordYRelative()+2);
          }
        break;
      case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
        //--- shift the header to its original position and set the previous relative coordinates
        if(this.Move(this.CoordX()+2,this.CoordY()))
          {
           this.SetCoordXRelative(this.CoordXRelative()+2);
           this.SetCoordYRelative(this.CoordYRelative());
          }
        break;
      case CANV_ELEMENT_ALIGNMENT_LEFT    :
        //--- shift the header to its original position and set the previous relative coordinates
        // {   }
        break;
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :
        //--- shift the header to its original position and set the previous relative coordinates
        // {   }
        break;
      default:
        break;
     }
   return true;
  }
//+------------------------------------------------------------------+

Esta é a base para futuras modificações.


Método que define a linha de cabeçalho da guia selecionada na posição correta na parte superior:

//+------------------------------------------------------------------+
//| Set the row of a selected tab header                             |
//| to the correct position at the top                               |
//+------------------------------------------------------------------+
void CTabHeader::CorrectSelectedRowTop(void)
  {
   int row_pressed=this.Row();      // Selected header row
   int y_pressed=this.CoordY();     // Coordinate where all headers with Row() equal to zero should be moved to
   int y0=0;                        // Zero row coordinate (Row == 0)
//--- If the zero row is selected, then nothing needs to be done - leave
   if(row_pressed==0)
      return;
      
//--- Get the tab field object corresponding to this header and set the Y coordinate of the zero line
   CWinFormBase *obj=this.GetFieldObj();
   if(obj==NULL)
      return;
   y0=obj.CoordY()-this.Height()+2;
   
//--- Get the base object (TabControl)
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return;
//--- Get the list of all tab headers from the base object
   CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
   if(list==NULL)
      return;
//--- Swap rows in the loop through all headers -
//--- set the row of the selected header to the zero position, while the zero one is set to the position of the selected header row
   for(int i=0;i<list.Total();i++)
     {
      CTabHeader *header=list.At(i);
      if(header==NULL)
         continue;
      //--- If this is a zero row
      if(header.Row()==0)
        {
         //--- move the header to the position of the selected row
         if(header.Move(header.CoordX(),y_pressed))
           {
            header.SetCoordXRelative(header.CoordX()-base.CoordX());
            header.SetCoordYRelative(header.CoordY()-base.CoordY());
            //--- Set the Row value to -1. It will be used as a label of the moved zero row instead of the selected one
            header.SetRow(-1);
           }
        }
      //--- If this is the clicked header line,
      if(header.Row()==row_pressed)
        {
         //--- move the header to the position of the zero row
         if(header.Move(header.CoordX(),y0))
           {
            header.SetCoordXRelative(header.CoordX()-base.CoordX());
            header.SetCoordYRelative(header.CoordY()-base.CoordY());
            //--- Set the Row value to -2. It will be used as a label of the moved selected row instead of the zero one
            header.SetRow(-2);
           }
        }
     }
//--- Set the correct Row and Col
   for(int i=0;i<list.Total();i++)
     {
      CTabHeader *header=list.At(i);
      if(header==NULL)
         continue;
      //--- If this is the former zero row moved to the place of the selected one, set Row of the selected row to it
      if(header.Row()==-1)
         header.SetRow(row_pressed);
      //--- If this is the selected row moved to the zero position, set Row of the zero row
      if(header.Row()==-2)
         header.SetRow(0);
     }
  }
//+------------------------------------------------------------------+

A lógica do método é descrita nos comentários ao código. Basicamente, e selecionamos uma guia (clique no botão de cabeçalho da guia) que está na linha zero (a linha zero está adjacente ao campo da guia, a primeira está acima de zero, a segunda está acima da primeira, etc.), então a linha não precisa ser movida para um novo local, ela já está em seu lugar. Se selecionamos uma guia, cujo cabeçalho não está na linha zero, precisamos mover todos os cabeçalhos dessa linha para o lugar do zero e mover a linha zero para o lugar do cabeçalho no qual clicamos com o mouse. Assim, as linhas zero e aquela em que se encontra o cabeçalho da guia selecionada sempre serão trocadas.

Este método trata apenas da situação em que os cabeçalhos das guias estão no topo. Eles também podem estar localizados abaixo, à esquerda e à direita. Mas manipuladores dessas situações serão feitos em artigos posteriores. Por enquanto, vamos apenas escrever métodos de stub para eles:

//+------------------------------------------------------------------+
//| Set the row of a selected tab header                             |
//| to the correct position at the bottom                            |
//+------------------------------------------------------------------+
void CTabHeader::CorrectSelectedRowBottom(void)
  {
  
  }
//+------------------------------------------------------------------+
//| Set the row of a selected tab header                             |
//| to the correct position on the left                              |
//+------------------------------------------------------------------+
void CTabHeader::CorrectSelectedRowLeft(void)
  {
  
  }
//+------------------------------------------------------------------+
//| Set the row of a selected tab header                             |
//| to the correct position on the right                             |
//+------------------------------------------------------------------+
void CTabHeader::CorrectSelectedRowRight(void)
  {
  
  }
//+------------------------------------------------------------------+


Método que pesquisa e retorna um ponteiro para o objeto-campo que corresponde ao número da guia:

//+------------------------------------------------------------------+
//| Find and return a pointer to the field object                    |
//| corresponding to the tab index                                   |
//+------------------------------------------------------------------+
CWinFormBase *CTabHeader::GetFieldObj(void)
  {
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return NULL;
   CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD);
   list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,this.PageNumber(),EQUAL);
   return(list!=NULL ? list.At(0) : NULL);
  }
//+------------------------------------------------------------------+

O método é idêntico ao método GetHeaderObj(), que procura e retorna um ponteiro para o cabeçalho da guia, que discutimos acima na classe de objeto-campo guia. Este método procura o campo de guia que corresponde a este cabeçalho.


No manipulador de eventos "O cursor está dentro da área ativa, o botão do mouse é liberado (esquerdo)", adicionamos um bloco de código onde, para o cabeçalho que foi clicado, o campo da guia correspondente é pesquisado e exibido:

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| left mouse button released                                       |
//+------------------------------------------------------------------+
void CTabHeader::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())
     {
      //--- If this is a simple button, set the initial background and text color
      if(!this.Toggle())
        {
         this.SetBackgroundColor(this.BackgroundColorInit(),false);
         this.SetForeColor(this.ForeColorInit(),false);
        }
      //--- If this is the toggle button, set the initial background and text color depending on whether the button is pressed or not
      else
        {
         this.SetBackgroundColor(!this.State() ? this.BackgroundColorInit() : this.BackgroundStateOnColorInit(),false);
         this.SetForeColor(!this.State() ? this.ForeColorInit() : this.ForeStateOnColorInit(),false);
        }
      //--- Set the initial frame color
      this.SetBorderColor(this.BorderColorInit(),false);
      //--- Send the test message to the journal
      Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel"));
     }
//--- The mouse button released within the element means a  click on the control
   else
     {
      //--- If this is a simple button, set the background and text color for "The cursor is over the active area" status
      if(!this.Toggle())
        {
         this.SetBackgroundColor(this.BackgroundColorMouseOver(),false);
         this.SetForeColor(this.ForeColorMouseOver(),false);
         this.Redraw(true);
        }
      //--- If this is the toggle button,
      else
        {
         //--- if the button does not work in the group, set its state to the opposite,
         if(!this.GroupButtonFlag())
            this.SetState(!this.State());
         //--- if the button is not pressed yet, set it to the pressed state
         else if(!this.State())
            this.SetState(true);
         //--- set the background and text color for "The cursor is over the active area" status depending on whether the button is clicked or not
         this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColorMouseOver() : this.BackgroundColorMouseOver(),false);
         this.SetForeColor(this.State() ? this.ForeStateOnColorMouseOver() : this.ForeColorMouseOver(),false);
         
         //--- Get the field object corresponding to the header
         CWinFormBase *field=this.GetFieldObj();
         if(field!=NULL)
           {
            //--- Display the field, bring it to the front and draw a frame
            field.Show();
            field.BringToTop();
            field.DrawFrame();
           }
         //--- Redraw an object and a chart
         this.Redraw(true);
        }
      //--- Send the test message to the journal
      Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.State()=",this.State(),", ID=",this.ID(),", Group=",this.Group());
      //--- Set the frame color for "The cursor is over the active area" status
      this.SetBorderColor(this.BorderColorMouseOver(),false);
     }
  }
//+------------------------------------------------------------------+

Se clicarmos no cabeçalho, o resultado deverá ser a exibição do campo da guia correspondente. Aqui o bloco de código destacado faz exatamente isso. Para um botão simples (no futuro, faremos com que pareçam cabeçalhos e haverá uma exibição na forma de botões), adicionaremos o redesenho de gráfico. Honestamente, não me lembro qual dos experimentos resultou nessa linha. Mas, por enquanto, deixe estar, ainda não chegamos aqui.


Todo controle de cabeçalhos e campos de guia deve ser feito a partir da classe de controle TabControl.

Vamos modificar a classe no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqh.

Incluímos o arquivo da classe de objeto-campo de guia recém-escrita ao arquivo e declaramos novas variáveis e métodos na seção privada:

//+------------------------------------------------------------------+
//|                                                   TabControl.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Container.mqh"
#include "GroupBox.mqh"
#include "..\TabHeader.mqh"
#include "..\TabField.mqh"
//+------------------------------------------------------------------+
//| TabHeader object class of WForms TabControl                      |
//+------------------------------------------------------------------+
class CTabControl : public CContainer
  {
private:
   int               m_item_width;                    // Fixed width of tab titles
   int               m_item_height;                   // Fixed height of tab titles
   int               m_header_padding_x;              // Additional header width if DrawMode==Fixed
   int               m_header_padding_y;              // Additional header height if DrawMode==Fixed
   int               m_field_padding_top;             // Padding of top tab fields
   int               m_field_padding_bottom;          // Padding of bottom tab fields
   int               m_field_padding_left;            // Padding of left tab fields
   int               m_field_padding_right;           // Padding of right tab fields
//--- Create a new graphical object
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);

//--- Return the list of (1) headers and (2) tab fields
   CArrayObj        *GetListHeaders(void)                { return this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);  }
   CArrayObj        *GetListFields(void)                 { return this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD);   }
//--- Set the tab as selected
   void              SetSelected(const int index);
//--- Set the tab as released
   void              SetUnselected(const int index);
//--- Set the number of a selected tab
   void              SetSelectedTabPageNum(const int value) { this.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,value);         }
//--- Arrange the tab headers according to the set modes
   void              ArrangeTabHeaders(void);
//--- Arrange the tab headers at the (1) top, (2) bottom, (3) left and (4) right
   void              ArrangeTabHeadersTop(void);
   void              ArrangeTabHeadersBottom(void);
   void              ArrangeTabHeadersLeft(void);
   void              ArrangeTabHeadersRight(void);
public:


Declaramos novos métodos na seção pública da classe:

public:
//--- Create the specified number of tabs
   bool              CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text="");
   
//--- Create a new attached element
   bool              CreateNewElement(const int tab_page,
                                      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);
 
//--- Return the number of (1) bound elements and (2) the bound element by the index in the list
   int               TabElementsTotal(const int tab_page);
   CGCnvElement     *GetTabElement(const int tab_page,const int index);
   
//--- Return by type the (1) list, (2) the number of bound controls, (3) the bound control by index in the list in the specified tab
   CArrayObj        *GetListTabElementsByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type);
   int               TabElementsTotalByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type);
   CGCnvElement     *GetTabElementByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type,const int index);
   
//--- Return a pointer to the (1) tab header, (2) field and (3) the number of tabs
   CTabHeader       *GetTabHeader(const int index)       { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,index);    }
   CWinFormBase     *GetTabField(const int index)        { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,index);     }
   int               TabPages(void)                      { return(this.GetListHeaders()!=NULL ? this.GetListHeaders().Total() : 0); }
//--- (1) Set and (2) return the location of tabs on the control
   void              SetAlignment(const ENUM_CANV_ELEMENT_ALIGNMENT alignment)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,alignment);
                        CArrayObj *list=this.GetListHeaders();
                        if(list==NULL)
                           return;
                        for(int i=0;i<list.Total();i++)
                          {
                           CTabHeader *header=list.At(i);
                           if(header==NULL)
                              continue;
                           header.SetAlignment(alignment);
                          }
                       }
   ENUM_CANV_ELEMENT_ALIGNMENT Alignment(void)  const { return (ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT);  }

//--- (1) Set and (2) get the mode of setting the tab size
   void              SetTabSizeMode(const ENUM_CANV_ELEMENT_TAB_SIZE_MODE mode)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_TAB_SIZE_MODE,mode);
                        CArrayObj *list=this.GetListHeaders();
                        if(list==NULL)
                           return;
                        for(int i=0;i<list.Total();i++)
                          {
                           CTabHeader *header=list.At(i);
                           if(header==NULL)
                              continue;
                           header.SetTabSizeMode(mode);
                          }
                       }
   ENUM_CANV_ELEMENT_TAB_SIZE_MODE TabSizeMode(void)const{ return (ENUM_CANV_ELEMENT_TAB_SIZE_MODE)this.GetProperty(CANV_ELEMENT_PROP_TAB_SIZE_MODE);}
   
//--- Sets all tab headers to the PaddingWidth and PaddingHeight values
   void              SetHeaderPadding(const int w,const int h);
//--- Set all tab fields to Padding values
   void              SetFieldPadding(const int top,const int bottom,const int left,const int right);
//--- Return the PaddingWidth and PaddingHeight values of the tab titles
   int               HeaderPaddingWidth(void)      const { return this.m_header_padding_x;      }
   int               HeaderPaddingHeight(void)     const { return this.m_header_padding_y;      }
//--- Return the Padding values of the tab fields
   int               FieldPaddingTop(void)         const { return this.m_field_padding_top;     }
   int               FieldPaddingBottom(void)      const { return this.m_field_padding_bottom;  }
   int               FieldPaddingLeft(void)        const { return this.m_field_padding_left;    }
   int               FieldPaddingRight(void)       const { return this.m_field_padding_right;   }
   
//--- (1) Set and (2) return the flag allowing multiple rows of tab headers on the control
   void              SetMultiline(const bool flag)       { this.SetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE,flag);         }
   bool              Multiline(void)               const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE); }
//--- (1) Set and (2) return the fixed width of tab headers
   void              SetItemWidth(const int value)       { this.m_item_width=value;             }
   int               ItemWidth(void)               const { return this.m_item_width;            }
//--- (1) Set and (2) return the fixed height of tab headers
   void              SetItemHeight(const int value)      { this.m_item_height=value;            }
   int               ItemHeight(void)              const { return this.m_item_height;           }
//--- Set a fixed tab size
   void              SetItemSize(const int w,const int h)
                       {
                        if(this.ItemWidth()!=w)
                           this.SetItemWidth(w);
                        if(this.ItemHeight()!=h)
                           this.SetItemHeight(h);
                       }
//--- Set the title text (1) of the specified tab and (2) by index
   void              SetHeaderText(CTabHeader *header,const string text);
   void              SetHeaderText(const int index,const string text);
   
//--- Set the tab specified by index to selected/not selected
   void              Select(const int index,const bool flag);
//--- Returns the (1) index, (2) the pointer to the selected tab
   int               SelectedTabPageNum(void)      const { return (int)this.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER);}
   CWinFormBase     *SelectedTabPage(void)               { return this.GetTabField(this.SelectedTabPageNum());             }
   
//--- Constructor
                     CTabControl(const long chart_id,
                                 const int subwindow,
                                 const string descript,
                                 const int x,
                                 const int y,
                                 const int w,
                                 const int h);
  };
//+------------------------------------------------------------------+


No construtor da classe, definimos os valores padrão para o modo de dimensionamento de guias e definimos os valores de Padding para os cabeçalhos e margens:

//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CTabControl::CTabControl(const long chart_id,
                         const int subwindow,
                         const string descript,
                         const int x,
                         const int y,
                         const int w,
                         const int h) : CContainer(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL);
   this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
   this.SetBorderSizeAll(0);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetOpacity(0,true);
   this.SetBackgroundColor(CLR_CANV_NULL,true);
   this.SetBackgroundColorMouseDown(CLR_CANV_NULL);
   this.SetBackgroundColorMouseOver(CLR_CANV_NULL);
   this.SetBorderColor(CLR_CANV_NULL,true);
   this.SetBorderColorMouseDown(CLR_CANV_NULL);
   this.SetBorderColorMouseOver(CLR_CANV_NULL);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP);
   this.SetItemSize(58,18);
   this.SetTabSizeMode(CANV_ELEMENT_TAB_SIZE_MODE_NORMAL);
   this.SetHeaderPadding(6,3);
   this.SetFieldPadding(3,3,3,3);
  }
//+------------------------------------------------------------------+


No método que cria o número especificado de guias, definiremos o ponteiro para o objeto base, número da guia e grupo nos objetos de cabeçalhos e campos criados. Para cabeçalhos e campos, definimos o valor de Padding, adicionamos o texto da guia, se for passado texto vazio para o método para definir os cabeçalhos das guias, definimos o modo para definir o tamanho dos cabeçalhos e definimos seus tamanhos:

//+------------------------------------------------------------------+
//| Create the specified number of tabs                              |
//+------------------------------------------------------------------+
bool CTabControl::CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text="")
  {
//--- Calculate the size and initial coordinates of the tab title
   int w=(tab_w==0 ? this.ItemWidth()  : tab_w);
   int h=(tab_h==0 ? this.ItemHeight() : tab_h);
   
//--- In the loop by the number of tabs
   CTabHeader *header=NULL;
   CTabField  *field=NULL;
   for(int i=0;i<total;i++)
     {
      //--- Depending on the location of tab titles, set their initial coordinates
      int header_x=2;
      int header_y=0;
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP)
         header_y=0;
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM)
         header_y=this.Height()-h;
      //--- Set the current X coordinate
      header_x=(header==NULL ? header_x : header.RightEdgeRelative());
      //--- Create the TabHeader object
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,header_x,header_y,w,h,clrNONE,255,this.Active(),false))
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1));
         return false;
        }
      header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,i);
      if(header==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1));
         return false;
        }
      header.SetBase(this.GetObject());
      header.SetPageNumber(i);
      header.SetGroup(this.Group()+1);
      header.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true);
      header.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN);
      header.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER);
      header.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true);
      header.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON);
      header.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON);
      header.SetBorderStyle(FRAME_STYLE_SIMPLE);
      header.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
      header.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN);
      header.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER);
      header.SetAlignment(this.Alignment());
      header.SetPadding(this.HeaderPaddingWidth(),this.HeaderPaddingHeight(),this.HeaderPaddingWidth(),this.HeaderPaddingHeight());
      if(header_text!="" && header_text!=NULL)
         this.SetHeaderText(header,header_text+string(i+1));
      else
         this.SetHeaderText(header,"TabPage"+string(i+1));
      header.SetTabSizeMode(this.TabSizeMode());
      header.SetSizes(w,h);
      
      //--- Depending on the location of the tab headers, set the initial coordinates of the tab fields
      int field_x=0;
      int field_y=0;
      int field_h=this.Height()-header.Height();
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP)
         field_y=header.BottomEdgeRelative();
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM)
         field_y=0;
      //--- Create the TabField object (tab field)
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,field_x,field_y,this.Width(),field_h,clrNONE,255,true,false))
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1));
         return false;
        }
      field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,i);
      if(field==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1));
         return false;
        }
      field.SetBase(this.GetObject());
      field.SetPageNumber(i);
      field.SetGroup(this.Group()+1);
      field.SetBorderSizeAll(1);
      field.SetBorderStyle(FRAME_STYLE_SIMPLE);
      field.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true);
      field.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true);
      field.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN);
      field.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER);
      field.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true);
      field.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN);
      field.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER);
      field.SetForeColor(CLR_DEF_FORE_COLOR,true);
      field.SetPadding(this.FieldPaddingLeft(),this.FieldPaddingTop(),this.FieldPaddingRight(),this.FieldPaddingBottom());
      field.Hide();
     }
//--- Arrange all titles in accordance with the specified display modes and select the specified tab
   this.ArrangeTabHeaders();
   this.Select(selected_page,true);
   return true;
  }
//+------------------------------------------------------------------+

Após criar o número especificado de guias, chamamos o método que posiciona os cabeçalhos de acordo com os modos de exibição definidos.


Método que cria um novo elemento vinculado:

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CTabControl::CreateNewElement(const int tab_page,
                                   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)
  {
   CTabField *field=this.GetTabField(tab_page);
   if(field==NULL)
     {
      CMessage::ToLog(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_TAB_OBJ);
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_TAB_OBJ)," (Tab",(string)tab_page,")");
      return false;
     }
   return field.CreateNewElement(element_type,x,y,w,h,colour,opacity,activity,redraw);
  }
//+------------------------------------------------------------------+

Obtemos o objeto-campo de guia pelo número de guia especificado e retornamos o resultado de chamar seu método para criar um novo elemento vinculado.


Método que posiciona os cabeçalhos das guias de acordo com os modos definidos:

//+------------------------------------------------------------------+
//| Arrange the tab headers                                          |
//| according to the specified modes                                 |
//+------------------------------------------------------------------+
void CTabControl::ArrangeTabHeaders(void)
  {
   switch(this.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :  this.ArrangeTabHeadersTop();     break;
      case CANV_ELEMENT_ALIGNMENT_BOTTOM  :  this.ArrangeTabHeadersBottom();  break;
      case CANV_ELEMENT_ALIGNMENT_LEFT    :  this.ArrangeTabHeadersLeft();    break;
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :  this.ArrangeTabHeadersRight();   break;
      default: break;
     }
  }
//+------------------------------------------------------------------+

Dependendo do modo de layout da guia definida, chamamos os métodos apropriados.


Método que posiciona os cabeçalhos das guias no topo:

//+------------------------------------------------------------------+
//| Arrange tab headers on top                                       |
//+------------------------------------------------------------------+
void CTabControl::ArrangeTabHeadersTop(void)
  {
//--- Get the list of tab headers
   CArrayObj *list=this.GetListHeaders();
   if(list==NULL)
      return;
//--- Declare the variables
   int col=0;                                // Column
   int row=0;                                // Row
   int x1_base=2;                            // Initial X coordinate
   int x2_base=this.RightEdgeRelative()-2;   // Final X coordinate
   int x_shift=0;                            // Shift the tab set for calculating their exit beyond the container
   int n=0;                                  // The variable for calculating the column index relative to the loop index
//--- In a loop by the list of headers,
   for(int i=0;i<list.Total();i++)
     {
      //--- get the next tab header object
      CTabHeader *header=list.At(i);
      if(header==NULL)
         continue;
      //--- If the flag for positioning headers in several rows is set
      if(this.Multiline())
        {
         //--- CANV_ELEMENT_TAB_SIZE_MODE_FIXED and CANV_ELEMENT_TAB_SIZE_MODE_NORMAL
         if(this.TabSizeMode()<CANV_ELEMENT_TAB_SIZE_MODE_FILL)
           {
            //--- Calculate the value of the right edge of the header, taking into account that
            //---  the origin always comes from the left edge of TabControl + 2 pixels
            int x2=header.RightEdgeRelative()-x_shift;
            //--- If the calculated value does not go beyond the right edge of the TabControl - 2 pixels, 
            //--- set the column number equal to the loop index minus the value in the n variable
            if(x2<x2_base)
               col=i-n;
            //--- If the calculated value goes beyond the right edge of the TabControl - 2 pixels,
            else
              {
               //--- Increase the row index, calculate the new shift (so that the next object is compared with the TabControl left edge + 2 pixels),
               //--- set the loop index for the n variable, while the column index is set to zero, this is the start of the new row
               row++;
               x_shift=header.CoordXRelative()-2;
               n=i;
               col=0;
              }
            //--- Assign the row and column indices to the tab header and shift it to the calculated coordinates
            header.SetTabLocation(row,col);
            if(header.Move(header.CoordX()-x_shift,header.CoordY()-header.Row()*header.Height()))
              {
               header.SetCoordXRelative(header.CoordX()-this.CoordX());
               header.SetCoordYRelative(header.CoordY()-this.CoordY());
              }
           }
         //--- Stretch the headers along the container width
         //--- CANV_ELEMENT_TAB_SIZE_MODE_FILL
         else
           {
            
           }
        }
      //--- If only one row of headers is allowed
      else
        {
         
        }
     }
//--- The location of all tab titles is set. Now place them all together with the fields
//--- according to the header row and column indices
//--- Get the last title in the list
   CTabHeader *last=this.GetTabHeader(list.Total()-1);
//--- If the object is received and its row value is greater than zero
   if(last!=NULL && last.Row()>0)
     {
      //--- Calculate the offset of the tab field Y coordinate
      int y_shift=last.Row()*last.Height();
      //--- In a loop by the list of headers,
      for(int i=0;i<list.Total();i++)
        {
         //--- get the next object
         CTabHeader *header=list.At(i);
         if(header==NULL)
            continue;
         //--- get the tab field corresponding to the received header
         CTabField  *field=header.GetFieldObj();
         if(field==NULL)
            continue;
         //--- shift the tab header by the calculated row coordinates
         if(header.Move(header.CoordX(),header.CoordY()+y_shift))
           {
            header.SetCoordXRelative(header.CoordX()-this.CoordX());
            header.SetCoordYRelative(header.CoordY()-this.CoordY());
           }
         //--- shift the tab field by the calculated shift
         if(field.Move(field.CoordX(),field.CoordY()+y_shift))
           {
            field.SetCoordXRelative(field.CoordX()-this.CoordX());
            field.SetCoordYRelative(field.CoordY()-this.CoordY());
            //--- change the size of the shifted field by the value of its shift
            field.Resize(field.Width(),field.Height()-y_shift,false);
           }
        }
     }
  }
//+------------------------------------------------------------------+

A lógica do método é descrita nos comentários ao código. O método foi implementado até agora apenas para colocar cabeçalhos na parte superior do contêiner em várias linhas. Aqui verificamos se cada próximo cabeçalho no loop é colocado na próxima posição da linha para que não ultrapasse a borda do contêiner. Se ultrapassa, devemos iniciar uma nova linha acima da anterior. Recalculamos a origem das coordenadas de referência para que, ao nos referirmos às coordenadas do objeto menos o deslocamento calculado, a linha, por assim dizer, comece novamente a partir da borda esquerda do contêiner. e então novamente é calculado se os objetos cabem dentro do contêiner e, se não, então novamente vamos para uma nova linha. Após cada nova linha, os valores de Row (linha) aumentados são gravados nos objetos, e o cálculo dos valores de Col (coluna) começa novamente. No final do loop, temos uma lista na qual estão escritos os valores das linhas e colunas, na qual devem estar localizados os cabeçalhos.

Além disso, em um novo laço percorrendo a lista de cabeçalhos, os colocamos em novas coordenadas correspondentes aos valores da linha e coluna registrados no objeto, e os objetos-campos de guia correspondentes a eles se movem uma distância calculada a partir do valor máximo da linha e diminuem em altura proporcionalmente. Após a conclusão do loop, obteremos os cabeçalhos colocados corretamente e seus campos correspondentes.

Nos artigos que virão, complementaremos o método com a localização dos cabeçalhos em outros modos.

Os métodos semelhantes restantes são implementados como métodos de stub até agora:

//+------------------------------------------------------------------+
//| Arrange tab headers at the bottom                                |
//+------------------------------------------------------------------+
void CTabControl::ArrangeTabHeadersBottom(void)
  {
  
  }
//+------------------------------------------------------------------+
//| Arrange tab headers on the left                                  |
//+------------------------------------------------------------------+
void CTabControl::ArrangeTabHeadersLeft(void)
  {
  
  }
//+------------------------------------------------------------------+
//| Arrange tab headers to the right                                 |
//+------------------------------------------------------------------+
void CTabControl::ArrangeTabHeadersRight(void)
  {
  
  }
//+------------------------------------------------------------------+


Método que define os valores de Padding para todos os cabeçalhos das guias:

//+------------------------------------------------------------------+
//| Set all tab titles to Padding values                             |
//+------------------------------------------------------------------+
void CTabControl::SetHeaderPadding(const int w,const int h)
  {
   this.m_header_padding_x=w;
   this.m_header_padding_y=h;
   CArrayObj *list=this.GetListHeaders();
   if(list==NULL)
      return;
   for(int i=0;i<list.Total();i++)
     {
      CTabHeader *header=list.At(i);
      if(header==NULL)
         continue;
      header.SetPadding(this.m_header_padding_x,this.m_header_padding_y,this.m_header_padding_x,this.m_header_padding_y);
     }
  }
//+------------------------------------------------------------------+

O método recebe todos os valores, que são adicionados de forma complementar à largura e altura do cabeçalho no modo de configuração de tamanho Normal. Os valores passados para o método são imediatamente gravados nas variáveis correspondentes. Em seguida, obtemos uma lista de todos os cabeçalhos e, em um loop pela lista resultante, configuramos os valores de Padding passados ao método para cada cabeçalho da lista.


Método que define todos valores de Padding para os campos das guias:

//+------------------------------------------------------------------+
//| Set all tab fields to Padding                                    |
//+------------------------------------------------------------------+
void CTabControl::SetFieldPadding(const int top,const int bottom,const int left,const int right)
  {
   this.m_field_padding_top=top;
   this.m_field_padding_bottom=bottom;
   this.m_field_padding_left=left;
   this.m_field_padding_right=right;
   CArrayObj *list=this.GetListFields();
   if(list==NULL)
      return;
   for(int i=0;i<list.Total();i++)
     {
      CTabField *field=list.At(i);
      if(field==NULL)
         continue;
      field.SetPadding(left,top,right,bottom);
     }
  }
//+------------------------------------------------------------------+

O método é o mesmo acima. Mas aqui os valores de Padding são passados da parte superior, inferior, direita e esquerda da margem da guia. Esses valores são definidos para as variáveis correspondentes e, em seguida, em um loop, para cada objeto-campo de guia.


Método que define a guia selecionada agora redesenhado:

//+------------------------------------------------------------------+
//| Set the tab as selected                                          |
//+------------------------------------------------------------------+
void CTabControl::SetSelected(const int index)
  {
//--- Get the header by index and
   CTabHeader *header=this.GetTabHeader(index);
   if(header==NULL)
      return;
//--- set it to the "selected" state
   if(!header.State())
      header.SetState(true);
//--- save the index of the selected tab
   this.SetSelectedTabPageNum(index);
  }
//+------------------------------------------------------------------+

Agora todas as manipulações com mover o objeto para o primeiro plano e selecionar o campo correspondente são realizadas no método SetState() da classe de objeto-cabeçalho da guia discutida acima.


O método que define a guia como não selecionada foi reformulado de maneira semelhante:

//+------------------------------------------------------------------+
//| Select the tab as released                                       |
//+------------------------------------------------------------------+
void CTabControl::SetUnselected(const int index)
  {
//--- Get the header by index and
   CTabHeader *header=this.GetTabHeader(index);
   if(header==NULL)
      return;
//--- set it to the "released" state
   if(header.State())
      header.SetState(false);
  }
//+------------------------------------------------------------------+


Método que retorna o número de elementos vinculados na guia especificada:

//+------------------------------------------------------------------+
//| Get the number of bound elements in the specified tab            |
//+------------------------------------------------------------------+
int CTabControl::TabElementsTotal(const int tab_page)
  {
   CTabField *field=this.GetTabField(tab_page);
   return(field!=NULL ? field.ElementsTotal() : 0);
  }
//+------------------------------------------------------------------+

Obtemos o objeto-campo de guia pelo número especificado e retornamos o número de objetos vinculados a ele.
O método permite descobrir quantos objetos estão anexados à guia com o número especificado.


Método que retorna o elemento vinculado por índice na lista na guia especificada:

//+------------------------------------------------------------------+
//| Returns the bound element by index in the list                   |
//| in the specified tab                                             |
//+------------------------------------------------------------------+
CGCnvElement *CTabControl::GetTabElement(const int tab_page,const int index)
  {
   CTabField *field=this.GetTabField(tab_page);
   return(field!=NULL ? field.GetElement(index) : NULL);
  }
//+------------------------------------------------------------------+

Obtemos o objeto-campo pelo número especificado e retornamos um ponteiro para o elemento anexado a partir da lista pelo índice especificado.
O método permite obter um ponteiro para o elemento desejado por seu índice na guia especificada.


Método que retorna, por tipo de objeto, uma lista de elementos vinculados na guia especificada:

//+------------------------------------------------------------------+
//| Return the list of bound controls by type                        |
//| in the specified tab                                             |
//+------------------------------------------------------------------+
CArrayObj *CTabControl::GetListTabElementsByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   CTabField *field=this.GetTabField(tab_page);
   return(field!=NULL ? field.GetListElementsByType(type) : NULL);
  }
//+------------------------------------------------------------------+

Obtemos o objeto-campo pelo número especificado e retornamos a lista de elementos vinculados pelo tipo especificado.
O método permite obter uma lista de elementos do mesmo tipo especificado na guia necessária.


Método que retorna, por tipo de objeto, o número de elementos vinculados na guia especificada:

//+------------------------------------------------------------------+
//| Get the list of bound elements by type                           |
//| in the specified tab                                             |
//+------------------------------------------------------------------+
int CTabControl::TabElementsTotalByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   CTabField *field=this.GetTabField(tab_page);
   return(field!=NULL ? field.ElementsTotalByType(type) : 0);
  }
//+------------------------------------------------------------------+

Obtemos o objeto-campo pelo número especificado e retornamos o número de elementos do tipo especificado localizado na guia.
O método permite descobrir quantos elementos do tipo especificado são colocados na guia especificada.


Método que retorna o elemento vinculado por tipo de objeto por índice na lista na guia especificada:

//+------------------------------------------------------------------+
//| Get (by type) the bound element by index in the list             |
//| in the specified tab                                             |
//+------------------------------------------------------------------+
CGCnvElement *CTabControl::GetTabElementByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type,const int index)
  {
   CTabField *field=this.GetTabField(tab_page);
   return(field!=NULL ? field.GetElementByType(type,index) : NULL);
  }
//+------------------------------------------------------------------+

Obtemos o objeto-campo pelo número especificado e retornamos o elemento do tipo necessário pelo índice especificado na lista.
O método permite obter um elemento do tipo necessário por seu número na guia especificada.


No método que cria um novo objeto gráfico, no final dele, adicionamos um bloco de código para criar um objeto-campo de guia (trecho de código):

      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER        :
         element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD         :
         element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL       :
         element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//|                                                        Panel.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Container.mqh"
#include "..\TabField.mqh"
#include "GroupBox.mqh"
#include "TabControl.mqh"
#include "..\..\WForms\Common Controls\ListBox.mqh"
#include "..\..\WForms\Common Controls\CheckedListBox.mqh"
#include "..\..\WForms\Common Controls\ButtonListBox.mqh"
//+------------------------------------------------------------------+


Ao final do método que cria um novo objeto gráfico, escrevemos um bloco de código para criar um objeto-campo de guia:

      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER        :
         element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD         :
         element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL       :
         element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+

Agora será possível criar objetos deste tipo no objeto painel e seus descendentes.


No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh da classe do objeto-contêiner base, no método que define os parâmetros do objeto anexado, inserimos a configuração dos parâmetros do objeto objeto-campo de guia no bloco de código para definir parâmetros dos objetos Button, TabHeader e ListBoxItem (trecho de código):

      //--- For "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 "Button", "TabHeader", TabField and "ListBoxItem" WinForms objects
      case GRAPH_ELEMENT_TYPE_WF_BUTTON            :
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER        :
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD         :
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM     :
        //--- 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 "ListBox", "CheckedListBox" and "ButtonListBox" WinForms object
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX          :
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX  :
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX   :

Após a criação do objeto neste método, as propriedades especificadas aqui serão atribuídas a ele. Elas podem ser alteradas posteriormente.


No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh da classe de objeto GroupBox, vamos tornar o método de desenho de moldura virtual:

//+------------------------------------------------------------------+
//| GroupBox object class of the WForms controls                     |
//+------------------------------------------------------------------+
class CGroupBox : public CContainer
  {
private:
//--- Draw a frame
   virtual void      DrawFrame(void);
//--- Create a new graphical object

Como declaramos esse método no objeto base de todos os objetos WinForms como virtual, agora todos esses métodos de mesmo nome nas classes descendentes também devem ser tornados virtuais, para redefini-los corretamente e acessá-los desde outras classes.


No método para criar um novo objeto gráfico, também adicionaremos um bloco de código para criar um objeto-campo de guia:

      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER        :
         element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD         :
         element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL       :
         element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+


Na classe-coleção de elementos gráficos no arquivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh , nos métodos para criação de elementos gráficos na tela, os nomes das variáveis foram alterados de "name" para "descript" - estes são ecos da mudança no algoritmo de nomeação de elementos gráficos no artigo anterior. Já funcionou sem erros, pois o tipo da variável é string, mas para os nomes corretos dos parâmetros formais dos métodos, eles foram alterados para "descrição" ao invés de "nome", que está correto. Como um exemplo:

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

Não veremos o restante das alterações aqui, pois elas são idênticas, todas já foram feitas no arquivo da biblioteca, que pode ser encontrada nos arquivos anexados ao artigo.

No método que procura objetos de interação, vamos adicionar uma verificação da visibilidade e acessibilidade do objeto. Se estiver invisível ou indisponível - tal objeto não pode ser processado, porque deve estar indisponível para interação com o mouse:

//+------------------------------------------------------------------+
//| Search for interaction objects                                   |
//+------------------------------------------------------------------+
CForm *CGraphElementsCollection::SearchInteractObj(CForm *form,const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- If a non-empty pointer is passed
   if(form!=NULL)
     {
      //--- Create the list of interaction objects
      int total=form.CreateListInteractObj();
      //--- In the loop by the created list
      for(int i=total-1;i>WRONG_VALUE;i--)
        {
         //--- get the next form object
         CForm *obj=form.GetInteractForm(i);
         //--- If the object is received and the mouse cursor is located above the object, return the pointer to the found object
         if(obj==NULL)
            continue;
         if(!obj.IsVisible())
           {
            continue;
           }
         if(!obj.Enabled())
           {
            continue;
           }
         if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL)
           {
            CTabControl *tab_ctrl=obj;
            CForm *elm=tab_ctrl.SelectedTabPage();
            if(elm!=NULL && elm.MouseFormState(id,lparam,dparam,sparam)>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
               return elm;
           }
         if(obj.MouseFormState(id,lparam,dparam,sparam)>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
            return obj;
        }
     }
//--- Return the same pointer
   return form;
  }
//+------------------------------------------------------------------+

Aqui: se o objeto estiver oculto ou inacessível, nós o ignoramos. Se for um objeto TabControl, obtemos a guia selecionada a partir dele.
Se o cursor estiver sobre a guia selecionada, retornamos um ponteiro para o objeto-campo da guia.


No método de pós-processamento da antiga forma ativa sob o cursor, pulamos todos os objetos ocultos e inacessíveis, porque eles não precisam ser processados:

//+------------------------------------------------------------------+
//| Post-processing of the former active form under the cursor       |
//+------------------------------------------------------------------+
void CGraphElementsCollection::FormPostProcessing(CForm *form,const int id, const long &lparam, const double &dparam, const string &sparam)
  {
//--- Get the main object the form is attached to
   CForm *main=form.GetMain();
   if(main==NULL)
      main=form;
//--- Get all the elements attached to the form
   CArrayObj *list=main.GetListElements();
   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
         CWinFormBase *elm=obj.GetInteractForm(j);
         if(elm==NULL || !elm.IsVisible() || !elm.Enabled())
            continue;
         //--- determine the location of the cursor relative to the object 
         //--- and call the mouse event handling method for the object
         elm.MouseFormState(id,lparam,dparam,sparam);
         elm.OnMouseEventPostProcessing();
        }
     }
   ::ChartRedraw(main.ChartID());
  }
//+------------------------------------------------------------------+


No arquivo \MQL5\Include\DoEasy\Engine.mqh do objeto da biblioteca principal, renomeamos o método GetWFPanel() que retorna um objeto por nome para GetWFPanelByName(), e fazemos com que o método GetWFPanel() retorne um objeto por sua descrição:

//--- Return the WForm Panel object by object name on the current chart
   CPanel              *GetWFPanelByName(const string name)
                          {
                           string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Return the WForm Panel object according to the description of the object on the current chart
   CPanel              *GetWFPanel(const string descript)
                          {
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_DESCRIPTION,descript,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Return the WForm Panel object by chart ID and object name

Como ambos os métodos possuem parâmetros formais do mesmo tipo, a sobrecarga de métodos não é possível nesta situação. É por esse motivo que renomeamos um dos métodos.

Da mesma forma que na classe-coleção de elementos gráficos, todas as ocorrências de "name" nos parâmetros formais dos métodos que criam objetos WinForms são renomeadas para "descript".

Por exemplo:

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

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


Teste

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

Para poder selecionar o modo para definir os tamanhos dos cabeçalhos das guias, sendo que na versão em inglês da compilação, as constantes de enumeração estavam em inglês e na versão russa, em russo, vamos criar novas enumerações para compilação condicional:

enum ENUM_ELEMENT_ALIGNMENT
  {
   ELEMENT_ALIGNMENT_TOP=CANV_ELEMENT_ALIGNMENT_TOP,                    // Top
   ELEMENT_ALIGNMENT_BOTTOM=CANV_ELEMENT_ALIGNMENT_BOTTOM,              // Bottom
   ELEMENT_ALIGNMENT_LEFT=CANV_ELEMENT_ALIGNMENT_LEFT,                  // Left
   ELEMENT_ALIGNMENT_RIGHT=CANV_ELEMENT_ALIGNMENT_RIGHT,                // Right
  };
enum ENUM_ELEMENT_TAB_SIZE_MODE
  {
   ELEMENT_TAB_SIZE_MODE_NORMAL=CANV_ELEMENT_TAB_SIZE_MODE_NORMAL,      // Fit to tab title text width
   ELEMENT_TAB_SIZE_MODE_FIXED=CANV_ELEMENT_TAB_SIZE_MODE_FIXED,        // Fixed size
   ELEMENT_TAB_SIZE_MODE_FILL=CANV_ELEMENT_TAB_SIZE_MODE_FILL,          // Fit TabControl Width
  };
#else 
enum ENUM_AUTO_SIZE_MODE
  {
   AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW,                // Increase only
   AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK   // Increase and decrease
  };
enum ENUM_BORDER_STYLE
  {
   BORDER_STYLE_NONE=FRAME_STYLE_NONE,                                  // No frame
   BORDER_STYLE_SIMPLE=FRAME_STYLE_SIMPLE,                              // Simple frame
   BORDER_STYLE_FLAT=FRAME_STYLE_FLAT,                                  // Flat frame
   BORDER_STYLE_BEVEL=FRAME_STYLE_BEVEL,                                // Embossed (convex)
   BORDER_STYLE_STAMP=FRAME_STYLE_STAMP,                                // Embossed (concave)
  };
enum ENUM_CHEK_STATE
  {
   CHEK_STATE_UNCHECKED=CANV_ELEMENT_CHEK_STATE_UNCHECKED,              // Unchecked
   CHEK_STATE_CHECKED=CANV_ELEMENT_CHEK_STATE_CHECKED,                  // Checked
   CHEK_STATE_INDETERMINATE=CANV_ELEMENT_CHEK_STATE_INDETERMINATE,      // Undefined
  };
enum ENUM_ELEMENT_ALIGNMENT
  {
   ELEMENT_ALIGNMENT_TOP=CANV_ELEMENT_ALIGNMENT_TOP,                    // Top
   ELEMENT_ALIGNMENT_BOTTOM=CANV_ELEMENT_ALIGNMENT_BOTTOM,              // Bottom
   ELEMENT_ALIGNMENT_LEFT=CANV_ELEMENT_ALIGNMENT_LEFT,                  // Left
   ELEMENT_ALIGNMENT_RIGHT=CANV_ELEMENT_ALIGNMENT_RIGHT,                // Right
  };
enum ENUM_ELEMENT_TAB_SIZE_MODE
  {
   ELEMENT_TAB_SIZE_MODE_NORMAL=CANV_ELEMENT_TAB_SIZE_MODE_NORMAL,      // By tab title width
   ELEMENT_TAB_SIZE_MODE_FIXED=CANV_ELEMENT_TAB_SIZE_MODE_FIXED,        // Fixed size
   ELEMENT_TAB_SIZE_MODE_FILL=CANV_ELEMENT_TAB_SIZE_MODE_FILL,          // By TabControl width
  };
#endif 
//--- input parameters


Vamos adicionar uma nova variável aos parâmetros de entrada do Expert Advisor, que define o modo para definir os tamanhos dos cabeçalhos das guias:

//--- input parameters
sinput   bool                          InpMovable           =  true;                   // Panel Movable flag
sinput   ENUM_INPUT_YES_NO             InpAutoSize          =  INPUT_YES;              // Panel Autosize
sinput   ENUM_AUTO_SIZE_MODE           InpAutoSizeMode      =  AUTO_SIZE_MODE_GROW;    // Panel Autosize mode
sinput   ENUM_BORDER_STYLE             InpFrameStyle        =  BORDER_STYLE_SIMPLE;    // Label border style
sinput   ENUM_ANCHOR_POINT             InpTextAlign         =  ANCHOR_CENTER;          // Label text align
sinput   ENUM_INPUT_YES_NO             InpTextAutoSize      =  INPUT_NO;               // Label autosize
sinput   ENUM_ANCHOR_POINT             InpCheckAlign        =  ANCHOR_LEFT;            // Check flag align
sinput   ENUM_ANCHOR_POINT             InpCheckTextAlign    =  ANCHOR_LEFT;            // Check label text align
sinput   ENUM_CHEK_STATE               InpCheckState        =  CHEK_STATE_UNCHECKED;   // Check flag state
sinput   ENUM_INPUT_YES_NO             InpCheckAutoSize     =  INPUT_YES;              // CheckBox autosize
sinput   ENUM_BORDER_STYLE             InpCheckFrameStyle   =  BORDER_STYLE_NONE;      // CheckBox border style
sinput   ENUM_ANCHOR_POINT             InpButtonTextAlign   =  ANCHOR_CENTER;          // Button text align
sinput   ENUM_INPUT_YES_NO             InpButtonAutoSize    =  INPUT_YES;              // Button autosize
sinput   ENUM_AUTO_SIZE_MODE           InpButtonAutoSizeMode=  AUTO_SIZE_MODE_GROW;    // Button Autosize mode
sinput   ENUM_BORDER_STYLE             InpButtonFrameStyle  =  BORDER_STYLE_NONE;      // Button border style
sinput   bool                          InpButtonToggle      =  true ;                  // Button toggle flag
sinput   bool                          InpListBoxMColumn    =  true;                   // ListBox MultiColumn flag
sinput   bool                          InpButtListMSelect   =  false;                  // ButtonListBox Button MultiSelect flag
//sinput   ENUM_ELEMENT_ALIGNMENT        InpHeaderAlignment   =  ELEMENT_ALIGNMENT_TOP;  // TabHeader Alignment
sinput   ENUM_ELEMENT_TAB_SIZE_MODE    InpTabPageSizeMode   =  ELEMENT_TAB_SIZE_MODE_NORMAL; // TabHeader Size Mode
//--- global variables


A criação de objetos WinForms no manipulador OnInit() do Expert Advisor agora ficará assim:

//+------------------------------------------------------------------+
//| 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.SetBackgroundStateOnColor(C'0xE2,0xC5,0xB1',true);
                        butt.SetBackgroundStateOnColorMouseOver(butt.ChangeColorLightness(butt.BackgroundStateOnColor(),-5));
                        butt.SetBackgroundStateOnColorMouseDown(butt.ChangeColorLightness(butt.BackgroundStateOnColor(),-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 TabControl object
            gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,4,12,gbox2.Width()-12,gbox2.Height()-20,clrNONE,255,true,false);
            //--- get the pointer to the TabControl object by its index in the list of bound objects of the TabControl type
            CTabControl *tab_ctrl=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0);
            //--- If TabControl is created and the pointer to it is received
            if(tab_ctrl!=NULL)
              {
               //--- Set the location of the tab titles on the element and the tab text, as well as create nine tabs
               tab_ctrl.SetTabSizeMode((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)InpTabPageSizeMode);
               tab_ctrl.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP/*(ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment*/);
               tab_ctrl.SetMultiline(true);
               tab_ctrl.SetHeaderPadding(6,0);
               tab_ctrl.CreateTabPages(9,0,50,16,TextByLanguage("Вкладка","TabPage"));

               //--- Create the CheckedListBox object on the first tab
               tab_ctrl.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,4,12,160,20,clrNONE,255,true,false);
               //--- get the pointer to the CheckedListBox object from the first tab by its index in the list of bound objects of the CheckBox type
               CCheckedListBox *check_lbox=tab_ctrl.GetTabElementByType(0,GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,0);
               //--- If CheckedListBox is created and the pointer to it is received
               if(check_lbox!=NULL)
                 {
                  check_lbox.SetBackgroundColor(tab_ctrl.BackgroundColor(),true);
                  check_lbox.SetMultiColumn(InpListBoxMColumn);
                  check_lbox.SetColumnWidth(0);
                  check_lbox.CreateCheckBox(4,66);
                 }
               
               //--- Create the ButtonListBox object on the second tab
               tab_ctrl.CreateNewElement(1,GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,4,12,160,30,clrNONE,255,true,false);
               //--- get the pointer to the ButtonListBox object from the first tab by its index in the list of attached objects of the Button type
               CButtonListBox *butt_lbox=tab_ctrl.GetTabElementByType(1,GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,0);
               //--- If ButtonListBox is created and the pointer to it is received
               if(butt_lbox!=NULL)
                 {
                  butt_lbox.SetBackgroundColor(tab_ctrl.BackgroundColor(),true);
                  butt_lbox.SetMultiColumn(true);
                  butt_lbox.SetColumnWidth(0);
                  butt_lbox.CreateButton(4,66,16);
                  butt_lbox.SetMultiSelect(InpButtListMSelect);
                  butt_lbox.SetToggle(InpButtonToggle);
                  for(int i=0;i<butt_lbox.ElementsTotal();i++)
                    {
                     butt_lbox.SetButtonGroup(i,(i % 2==0 ? butt_lbox.Group()+1 : butt_lbox.Group()+2));
                     butt_lbox.SetButtonGroupFlag(i,(i % 2==0 ? true : false));
                    }
                 }
               
               //--- Create the ListBox object on the third tab
               int lbw=146;
               if(!InpListBoxMColumn)
                  lbw=100;
               tab_ctrl.CreateNewElement(2,GRAPH_ELEMENT_TYPE_WF_LIST_BOX,4,12,lbw,60,clrNONE,255,true,false);
               //--- get the pointer to the ListBox object from the third tab by its index in the list of attached objects of the ListBox type
               CListBox *list_box=tab_ctrl.GetTabElementByType(2,GRAPH_ELEMENT_TYPE_WF_LIST_BOX,0);
               //--- If ListBox has been created and the pointer to it has been received
               if(list_box!=NULL)
                 {
                  list_box.SetBackgroundColor(tab_ctrl.BackgroundColor(),true);
                  list_box.SetMultiColumn(InpListBoxMColumn);
                  list_box.CreateList(8,68);
                 }
               
               //--- On the remaining tabs (3 - 8), place text labels with the name of the tab
               for(int i=3;i<9;i++)
                 {
                  CTabField *field=tab_ctrl.GetTabField(i);
                  if(field==NULL)
                     continue;
                  tab_ctrl.CreateNewElement(i,GRAPH_ELEMENT_TYPE_WF_LABEL,1,1,field.Width()-2,field.Height()-2,clrNONE,255,true,false);
                  CLabel *label=tab_ctrl.GetTabElementByType(i,GRAPH_ELEMENT_TYPE_WF_LABEL,0);
                  if(label!=NULL)
                    {
                     label.SetTextAlign(ANCHOR_CENTER);
                     label.SetText(tab_ctrl.GetTabHeader(i).Text());
                    }
                 }
              }
           }
        }
      //--- Redraw all objects according to their hierarchy
      pnl.Redraw(true);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Espero que a sequência de criação de objetos seja suficientemente clara nos comentários ao código. Aqui criamos um controle TabControl com nove guias no segundo contêiner GroupBox, isso propositadamente para verificar como elas serão organizadas em linhas. Nas três primeiras guias, vamos criar os objetos que fizemos anteriormente no contêiner GroupBox2. Agora todos esses três controles serão colocados cada um em sua própria guia. Nas guias restantes, colocaremos etiquetas de texto com uma descrição da guia, retirada do texto em seus cabeçalhos.

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


Bem... O tempo necessário para criar objetos é bastante grande. Em breve será necessário mudar a lógica de exibição de objetos durante sua criação em massa. Trataremos disso em breve. Ao escolher um tamanho fixo para os cabeçalhos das guias e um tamanho que se ajuste à largura da fonte, você pode ver que os tamanhos das guias são diferentes. A escolha da guia correta e o rearranjo das linhas de guias funcionam corretamente. Os objetos nas guias estão disponíveis para interação com o mouse. Até agora, tudo está correto, o que significa que podemos continuar desenvolvendo a funcionalidade do controle.


O que virá a seguir?

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

Todos os arquivos da versão atual da biblioteca, os arquivos do EA de teste e o indicador de controle de eventos 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 10): Objetos WinForms, dando vida à interface
DoEasy. Controles (Parte 11): Objetos WinForms - grupos, objeto WinForms CheckedListBox
DoEasy. Controles (Parte 12): Objeto base lista, objetos WinForms ListBox e ButtonListBox
DoEasy. Controles (Parte 13): Otimizando a interação de objetos WinForms com o mouse, dando início ao desenvolvimento do objeto WinForms TabControl
DoEasy. Controles (Parte 14): Novo algoritmo para nomear elementos gráficos. Continuando o trabalho no objeto WinForms TabControl



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

Arquivos anexados |
MQL5.zip (4432.19 KB)
Operações com Matrizes e Vetores em MQL5 Operações com Matrizes e Vetores em MQL5
Matrizes e vetores foram introduzidos na MQL5 para operações eficientes com soluções matemáticas. Os novos tipos oferecem métodos integrados para a criação de código conciso e compreensível que se aproxima da notação matemática. Os arrays fornecem recursos extensos, mas há muitos casos em que as matrizes são muito mais eficientes.
Redes neurais de maneira fácil (Parte 24): Melhorando a ferramenta para transferência de aprendizado Redes neurais de maneira fácil (Parte 24): Melhorando a ferramenta para transferência de aprendizado
No último artigo, elaboramos uma ferramenta para criar e editar a arquitetura de redes neurais. E hoje quero convidá-lo a continuar trabalhando nela, para torná-la mais amigável. De certa forma, ao fazer isso, estamos nos afastando um pouco do nosso tópico. Mas convenhamos que a organização do espaço de trabalho desempenha um papel importante na obtenção do resultado.
Redes neurais de maneira fácil (Parte 25): Exercícios práticos de transferência de aprendizado Redes neurais de maneira fácil (Parte 25): Exercícios práticos de transferência de aprendizado
Nos dois últimos artigos, criamos uma ferramenta que permite criar e editar modelos de redes neurais. E agora é hora de avaliar o uso potencial da transferência de aprendizado (transfer learning, em inglês) usando exemplos práticos.
Aprendendo a construindo um EA que opera de forma automática (Parte 06): Tipos de contas (I) Aprendendo a construindo um EA que opera de forma automática (Parte 06): Tipos de contas (I)
Aprenda como criar um EA que opera de forma automática, isto de forma simples e o mais seguro possível. Nosso EA, até o momento consegue trabalhar, em qualquer tipo de situação, mas para torná-lo automatizado, ele não está adequado, precisamos fazer algumas coisas.