English Русский 中文 Español Deutsch 日本語
preview
DoEasy. Controles (Parte 17): Recorte de seções invisíveis de objetos, objetos-botões WinForms auxiliares com setas

DoEasy. Controles (Parte 17): Recorte de seções invisíveis de objetos, objetos-botões WinForms auxiliares com setas

MetaTrader 5Exemplos | 6 dezembro 2022, 09:51
172 0
Artyom Trishkin
Artyom Trishkin

Conteúdo


Ideia

Cada elemento gráfico anexado ao seu contêiner só pode ser visto dentro do escopo de seu contêiner. Se alguma parte do elemento se projetar além do contêiner, essa parte saliente deverá ser ocultada. Não o objeto inteiro (se sair parcialmente além do contêiner), mas apenas a parte localizada fora dos limites de visibilidade do objeto pai ao qual está anexado. Esses limites de visibilidade dos objetos anexados a um contêiner são geralmente as bordas do mesmo. Mas se o objeto tiver uma moldura, esta não deverá ser sobreposta pelo objeto anexo, e o limite de visibilidade para o objeto saliente será então a borda interna da moldura do contêiner.

Em MQL, para recortar elementos gráficos criados com base em uma imagem bmp (OBJ_BITMAP_LABEL e OBJ_BITMAP), existem propriedades especiais de objetos gráficos que permitem exibir apenas uma parte da imagem contornada por uma área de visibilidade retangular:

Quanto aos objetos OBJ_BITMAP_LABEL e OBJ_BITMAP você pode definir um modo especial de exibição de imagem programaticamente. Neste modo, apenas a parte da imagem inicial sobre a qual a área de visibilidade retangular está sobreposta é mostrada, o resto da imagem se torna invisível. O tamanho do escopo deve ser definido usando as propriedades OBJPROP_XSIZE e OBJPROP_YSIZE. A área de visibilidade só pode ser "movida" dentro da imagem inicial usando as propriedades OBJPROP_XOFFSET e OBJPROP_YOFFSET.

Infelizmente, esse recorte e posicionamento da parte visível do objeto dentro do escopo retangular não funciona ao usar canvas. Embora o recurso para a tela seja criado como uma imagem bitmap na memória, "algo não permite" o uso dessa tecnologia, feita para objetos baseados em arquivos físicos bmp com imagens construídas na memória como recurso.

Portanto, seguiremos nosso próprio caminho, e criaremos tal escopo nós mesmos, cortaremos independentemente as partes do objeto (leia as imagens) que vão além do escopo de nosso contêiner. Inicialmente, o escopo retangular quer seja será igual à largura e altura do contêiner ou ficará dentro da borda deste objeto (caso tenha borda). Iremos dotar cada elemento gráfico de um método que lerá a posição dele mesmo em relação ao seu contêiner e aparará seu excesso visível (basta pintar sobre seu fundo com uma cor transparente com total transparência).

Além da implementação da funcionalidade dos elementos gráficos descrita acima, hoje vamos criar várias classes de elementos gráficos auxiliares. Estes serão objetos-botões com setas. Esses botões são necessários para implementar controles, como, por exemplo, barras de rolagem, listas suspensas e outros semelhantes.

Com relação ao controle TabControl que está sendo desenvolvido, é necessário que estes botões providenciem os deslocamentos dos cabeçalhos das guias no caso de estarem dispostos em uma única fileira e de haver muitos demais para caber todos eles no controle. As guias que se estendem além do controle devem ser escondidas (do que tratamos hoje), e apenas dois botões com setas esquerda-direita são usados para percorrer a fileira de cabeçalhos para encontrar e exibir o cabeçalho desejado. Portanto, hoje, depois de criar esses botões-objetos com setas, criaremos mais dois objetos com dois botões, "esquerda-direita" e "para cima-para baixo". Usaremos esses objetos para localizar a guia oculta no objeto TabControl no próximo artigo.


Modificando as classes da biblioteca

No arquivo \MQL5\Include\DoEasy\Defines.mqh, adicionaremos uma macro-substituição que especificará o tamanho dos lados dos botões com setas por padrão:

#define DEF_FONT                       ("Calibri")                // Default font
#define DEF_FONT_SIZE                  (8)                        // Default font size
#define DEF_CHECK_SIZE                 (12)                       // Verification flag default size
#define DEF_ARROW_BUTTON_SIZE          (15)                       // Default arrow button size
#define OUTER_AREA_SIZE                (16)                       // Size of one side of the outer area around the form workspace
#define DEF_FRAME_WIDTH_SIZE           (3)                        // Default form/panel/window frame width

Ao criar tal objeto-botão, você sempre pode especificar qualquer outro tamanho para seus lados, mas o tamanho padrão do lado será de 15 pixels.


Na lista de tipos de objetos da biblioteca, na seção de objetos WinForms, adicionamos um novo tipo, que será um objeto auxiliar:

//+------------------------------------------------------------------+
//| List of library object types                                     |
//+------------------------------------------------------------------+
enum ENUM_OBJECT_DE_TYPE
  {
//--- Graphics
   OBJECT_DE_TYPE_GBASE =  COLLECTION_ID_LIST_END+1,              // "Base object of all library graphical objects" object type
   OBJECT_DE_TYPE_GELEMENT,                                       // "Graphical element" object type
   OBJECT_DE_TYPE_GFORM,                                          // Form object type
   OBJECT_DE_TYPE_GFORM_CONTROL,                                  // "Form for managing pivot points of graphical object" object type
   OBJECT_DE_TYPE_GSHADOW,                                        // Shadow object type
   //--- WinForms
   OBJECT_DE_TYPE_GWF_BASE,                                       // WinForms Base object type (base abstract WinForms object)
   OBJECT_DE_TYPE_GWF_CONTAINER,                                  // WinForms container object type
   OBJECT_DE_TYPE_GWF_COMMON,                                     // WinForms standard control object type
   OBJECT_DE_TYPE_GWF_HELPER,                                     // WinForms auxiliary control object type
//--- Animation
   //---...
   //---...
  }

Posteriormente, com este tipo de objetos de biblioteca gráfica, só poderemos selecionar objetos auxiliares para fazer algo com eles.

Adicionamos novos tipos à lista de tipos de elementos gráficos, cujas classes de objeto criaremos hoje:

//+------------------------------------------------------------------+
//| The list of graphical element types                              |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_STANDARD,                       // Standard graphical object
   GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED,              // Extended standard graphical object
   GRAPH_ELEMENT_TYPE_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
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON,                // Windows Forms ArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,             // Windows Forms UpArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,           // Windows Forms DownArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,           // Windows Forms LeftArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,          // Windows Forms RightArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,        // Windows Forms UpDownArrowButtonsBox
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,        // Windows Forms LeftRightArrowButtonsBox
  };
//+------------------------------------------------------------------+


Adicionamos quatro novas propriedades à lista de propriedades inteiras do elemento gráfico na tela para especificar as coordenadas e tamanhos da área de visibilidade do elemento gráfico e aumentamos o número total de propriedades inteiras de 92 para 96:

//+------------------------------------------------------------------+
//| Integer properties of the graphical element on the canvas        |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_PROP_INTEGER
  {
   CANV_ELEMENT_PROP_ID = 0,                          // Element ID
   CANV_ELEMENT_PROP_TYPE,                            // Graphical element type

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

   CANV_ELEMENT_PROP_ACT_RIGHT,                       // Right border of the element active area
   CANV_ELEMENT_PROP_ACT_BOTTOM,                      // Bottom border of the element active area
   CANV_ELEMENT_PROP_VISIBLE_AREA_X,                  // Visibility scope X coordinate
   CANV_ELEMENT_PROP_VISIBLE_AREA_Y,                  // Visibility scope Y coordinate
   CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,              // Visibility scope width
   CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,             // Visibility scope height
   CANV_ELEMENT_PROP_GROUP,                           // Group the graphical element belongs to
   CANV_ELEMENT_PROP_ZORDER,                          // Priority of a graphical object for receiving the event of clicking on a chart

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

  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (96)          // 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_ACT_RIGHT,                    // Sort by the right border of the element active area
   SORT_BY_CANV_ELEMENT_ACT_BOTTOM,                   // Sort by the bottom border of the element active area
   SORT_BY_CANV_ELEMENT_VISIBLE_AREA_X,               // Sort by visibility scope X coordinate
   SORT_BY_CANV_ELEMENT_VISIBLE_AREA_Y,               // Sort by visibility scope Y coordinate
   SORT_BY_CANV_ELEMENT_VISIBLE_AREA_WIDTH,           // Sort by visibility scope width
   SORT_BY_CANV_ELEMENT_VISIBLE_AREA_HEIGHT,          // Sort by visibility scope height
   SORT_BY_CANV_ELEMENT_GROUP,                        // Sort by a group the graphical element belongs to
   SORT_BY_CANV_ELEMENT_ZORDER,                       // Sort by the priority of a graphical object for receiving the event of clicking on a chart

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

   SORT_BY_CANV_ELEMENT_TAB_PAGE_COLUMN,              // Sort by tab column index
   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 selecionar, classificar e filtrar elementos gráficos por propriedades recém-adicionadas.


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

   MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,               // TabControl tab field
   MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,             // TabControl
   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON,            // ArrowButton control
   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,         // UpArrowButton control
   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,       // DownArrowButton control
   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,       // LeftArrowButton control
   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,      // RightArrowButton control
   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,    // UpDownArrowBox control
   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,    // LeftRightArrowBox control
   MSG_GRAPH_OBJ_BELONG_PROGRAM,                      // Graphical object belongs to a program
   MSG_GRAPH_OBJ_BELONG_NO_PROGRAM,                   // Graphical object does not belong to a program

...

   MSG_CANV_ELEMENT_PROP_ACT_RIGHT,                   // Right border of the element active area
   MSG_CANV_ELEMENT_PROP_ACT_BOTTOM,                  // Bottom border of the element active area
   MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_X,              // Visibility scope X coordinate
   MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_Y,              // Visibility scope Y coordinate
   MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,          // Visibility scope width
   MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,         // Visibility scope height
   MSG_CANV_ELEMENT_PROP_ENABLED,                     // Element availability flag
   MSG_CANV_ELEMENT_PROP_FORE_COLOR,                  // Default text color for all control objects

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

   {"Поле вкладки элемента управления \"TabControl\"","Tab field of the Control element \"TabControl\""},
   {"Элемент управления \"TabControl\"","Control element \"TabControl\""},
   {"Элемент управления \"ArrowButton\"","Control element \"ArrowButton\""},
   {"Элемент управления \"UpArrowButton\"","Control element \"UpArrowButton\""},
   {"Элемент управления \"DownArrowButton\"","Control element \"DownArrowButton\""},
   {"Элемент управления \"LeftArrowButton\"","Control element \"LeftArrowButton\""},
   {"Элемент управления \"RightArrowButton\"","Control element \"RightArrowButton\""},
   {"Элемент управления \"UpDownArrowBox\"","Control element \"UpDownArrowBox\""},
   {"Элемент управления \"LeftRightArrowBox\"","Control element \"LeftRightArrowBox\""},
   {"Графический объект принадлежит программе","The graphic object belongs to the program"},
   {"Графический объект не принадлежит программе","The graphic object does not belong to the program"},

...

   {"Правая граница активной зоны элемента","Right border of the element's active area"},
   {"Нижняя граница активной зоны элемента","Bottom border of the element's active area"},
   {"Координата X области видимости","X-coordinate of object visibility area"},
   {"Координата Y области видимости","Y-coordinate of object visibility area"},
   {"Ширина области видимости","Width of object visibility area"},
   {"Высота области видимости","Height of object visibility area"},
   {"Флаг доступности элемента","Element Availability Flag"},
   {"Цвет текста по умолчанию для всех объектов элемента управления","Default text color for all objects in the control"},


No arquivo de funções de serviço da biblioteca \MQL5\Include\DoEasy\Services\DELib.mqh, na função que retorna o tipo do objeto gráfico como uma string, faremos pequenas melhorias:

//+------------------------------------------------------------------+
//| Return the graphical object type as string                       |
//+------------------------------------------------------------------+
string TypeGraphElementAsString(const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   ushort array[];
   int total=StringToShortArray(StringSubstr(EnumToString(type),18),array);
   for(int i=0;i<total-1;i++)
     {
      if(array[i]==95)
        {
         i+=1;
         continue;
        }
      else
         array[i]+=0x20;
     }
   string txt=ShortArrayToString(array);
   StringReplace(txt,"_Wf_Base","WFBase");
   StringReplace(txt,"_Wf_","");
   StringReplace(txt,"_Obj","");
   StringReplace(txt,"_","");
   StringReplace(txt,"Groupbox","GroupBox");
   StringReplace(txt,"ButtonsUdBox","ButtonsUDBox");
   StringReplace(txt,"ButtonsLrBox","ButtonsLRBox");
   return txt;
  }
//+------------------------------------------------------------------+

Como vamos criar novos elementos gráficos, precisamos mudar ligeiramente o nome do objeto gráfico para criar seu nome gerado automaticamente. A função, ao criar uma string de nome, inicialmente não permite dois ou mais caracteres maiúsculos consecutivos. E o nome do objeto deve conter três desses caracteres. Portanto, simplesmente substituímos a string de nome gerada automaticamente pela desejada.
Agora os nomes dos novos objetos criados hoje estarão corretos.


Nos construtores de classe do objeto ListBoxItem auxiliar no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\ListBoxItem.mqh, digitamos o tipo do objeto gráfico da biblioteca como "objeto WinForms auxiliar":

//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CListBoxItem::CListBoxItem(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)
  {
//--- Set the specified graphical element type for the object and assign the library object type to the current object
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetTextAlign(ANCHOR_LEFT);
   this.SetTextShiftSpace(1);
  }
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CListBoxItem::CListBoxItem(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_LIST_BOX_ITEM,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetTextAlign(ANCHOR_LEFT);
   this.SetTextShiftSpace(1);
  }
//+------------------------------------------------------------------+


 Ao trabalhar com canvas há problemas para usar meios regulares que permitem exibir apenas a parte visível do objeto gráfico delineada por uma área de visibilidade retangular (nós mesmos faremos isso), e os objetos gráficos têm tais propriedades, e por isso precisamos criar funcionalidades para definir e colocar essas propriedades no objeto gráfico. Também usaremos tais funcionalidades com o propósito de criar um escopo para elementos gráficos na tela.

No arquivo do objeto gráfico base da biblioteca \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh adicionaremos métodos virtuais para definir e obter novas propriedades de objetos gráficos e renomearemos o método para definir a visibilidade sinalizador para que fique claro que o método define o sinalizador:

//--- Set the priority of a graphical object for receiving the event of clicking on a chart 
   virtual bool      SetZorder(const long value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_ZORDER,value)) || only_prop)
                          {
                           this.m_zorder=value;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
                       
//--- (1) Set and (2) return the X coordinate of the upper left corner of the rectangle visibility scope of the OBJ_BITMAP_LABEL and OBJ_BITMAP graphical object
   virtual bool      SetXOffset(const long value)
                       {
                        ::ResetLastError();
                        if(!::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_XOFFSET,value))
                          {
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                           return false;
                          }
                        return true;
                       }
   virtual int       XOffset(void)  const { return (int)::ObjectGetInteger(this.m_chart_id,this.m_name,OBJPROP_XOFFSET);   }
//--- (1) Set and (2) return the Y coordinate of the upper left corner of the rectangle visibility scope of the OBJ_BITMAP_LABEL and OBJ_BITMAP graphical object
   virtual bool      SetYOffset(const long value)
                       {
                        ::ResetLastError();
                        if(!::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_YOFFSET,value))
                          {
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                           return false;
                          }
                        return true;
                       }
   virtual int       YOffset(void)  const { return (int)::ObjectGetInteger(this.m_chart_id,this.m_name,OBJPROP_YOFFSET);   }
//--- (1) Set and (2) return the width of OBJ_LABEL (read only), OBJ_BUTTON, OBJ_CHART, OBJ_BITMAP, OBJ_BITMAP_LABEL, OBJ_EDIT, OBJ_RECTANGLE_LABEL
   virtual bool      SetXSize(const long value)
                       {
                        ::ResetLastError();
                        if(!::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_XSIZE,value))
                          {
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                           return false;
                          }
                        return true;
                       }
   virtual int       XSize(void)    const { return (int)::ObjectGetInteger(this.m_chart_id,this.m_name,OBJPROP_XSIZE);     }
//--- (1) Set and (2) return the height of OBJ_LABEL (read only), OBJ_BUTTON, OBJ_CHART, OBJ_BITMAP, OBJ_BITMAP_LABEL, OBJ_EDIT and OBJ_RECTANGLE_LABEL objects
   virtual bool      SetYSize(const long value)
                       {
                        ::ResetLastError();
                        if(!::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_YSIZE,value))
                          {
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                           return false;
                          }
                        return true;
                       }
   virtual int       YSize(void)    const { return (int)::ObjectGetInteger(this.m_chart_id,this.m_name,OBJPROP_YSIZE);     }
                       
//--- Set object visibility on all timeframes
   bool              SetVisibleFlag(const bool flag,const bool only_prop)   
                       {
                        long value=(flag ? OBJ_ALL_PERIODS : OBJ_NO_PERIODS);
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,value)) || only_prop)
                          {
                           this.m_visible=flag;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }

Os métodos para definir propriedades retornam um sinalizador que informa se o valor de propriedade especificado do objeto gráfico foi colocado com sucesso na fila do gráfico, ou seja, a função é assíncrona e retorna apenas um sinalizador que indica se o comando foi colocado com sucesso na fila, e não se a propriedade requerida foi colocada com sucesso. Para verificar, devemos ler a propriedade alterada a partir do objeto e verificar seu valor. Isto é feito para todas as funções ObjectSetXXX, mas ainda não notamos nenhum atraso na execução da fila de comando ao usar a biblioteca, portanto, por enquanto usamos esta estrutura para definir as propriedades dos objetos gráficos.

No método que retorna a descrição do tipo elemento gráfico, escreveremos o retorno da descrição de novos objetos, nomeadamente dos botões com setas, que faremos hoje:

//+------------------------------------------------------------------+
//| 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)             :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON)          :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP)       :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN)     :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT)     :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT     ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT)    :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX)  :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX)  :
      "Unknown"
     );
  }  
//+------------------------------------------------------------------+

Agora podemos obter descrições de novos tipos de objetos auxiliares quando estiverem prontos e podemos criá-los.


No arquivo de objeto base de objetos gráficos padrão da biblioteca \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh, vamos corrigir a chamada para o método de configuração do sinalizador de visibilidade, já que este método agora é renomeado:

//--- Object visibility on timeframes
   bool              Visible(void)                 const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_TIMEFRAMES,0);                    }
   bool              SetFlagVisible(const bool flag,const bool only_prop)
                       {
                        if(!CGBaseObj::SetVisibleFlag(flag,only_prop))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_TIMEFRAMES,0,flag);
                        return true;
                       }
//--- Background object


No elemento gráfico do objeto da tela MQL5\Include\DoEasy\Objects\Graph\arquivo GCnvElement.mqh, adicionamos novas propriedades de número inteiro à estrutura privada do objeto:

private:
   int               m_shift_coord_x;                          // Offset of the X coordinate relative to the base object
   int               m_shift_coord_y;                          // Offset of the Y coordinate relative to the base object
   struct SData
     {
      //--- Object integer properties
      int            id;                                       // Element ID
      int            type;                                     // Graphical element type

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

      int            tab_alignment;                            // Location of tabs inside the control
      int            alignment;                                // Location of the object inside the control
      int            visible_area_x;                           // Visibility scope X coordinate
      int            visible_area_y;                           // Visibility scope Y coordinate
      int            visible_area_w;                           // Visibility scope width
      int            visible_area_h;                           // Visibility scope height
      //--- Object real properties

      //--- Object string properties
      uchar          name_obj[64];                             // Graphical element object name
      uchar          name_res[64];                             // Graphical resource name
      uchar          text[256];                                // Graphical element text
      uchar          descript[256];                            // Graphical element description
     };
   SData             m_struct_obj;                             // Object structure

Precisamos da estrutura de propriedades do objeto para salvar e ler corretamente as propriedades do objeto a partir do arquivo.

Todos os métodos que acessam o método renomeado anteriormente no arquivo de objeto gráfico base agora devem acessar esse método por seu novo nome:

//--- Set the object above all
   virtual void      BringToTop(void)                          { CGBaseObj::SetVisibleFlag(false,false); CGBaseObj::SetVisibleFlag(true,false);}
//--- (1) Show and (2) hide the element
   virtual void      Show(void)                                { CGBaseObj::SetVisibleFlag(true,false);                                }
   virtual void      Hide(void)                                { CGBaseObj::SetVisibleFlag(false,false);                               }
   
//--- Priority of a graphical object for receiving the event of clicking on a chart


Vamos escrever métodos virtuais para trabalhar com as novas propriedades do elemento gráfico:

//--- Graphical object group
   virtual int       Group(void)                         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_GROUP);                }
   virtual void      SetGroup(const int value)
                       {
                        CGBaseObj::SetGroup(value);
                        this.SetProperty(CANV_ELEMENT_PROP_GROUP,value);
                       }
//--- Visibility scope X coordinate
   virtual int       XOffset(void)                       const { return (int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X);       }
   virtual bool      SetXOffset(const int value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && CGBaseObj::SetXOffset(value)) || only_prop)
                          {
                           this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X,value);
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Visibility scope Y coordinate
   virtual int       YOffset(void)                       const { return (int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y);       }
   virtual bool      SetYOffset(const int value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && CGBaseObj::SetYOffset(value)) || only_prop)
                          {
                           this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y,value);
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Visibility scope width
   virtual int       XSize(void)                         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH);   }
   virtual bool      SetXSize(const int value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && CGBaseObj::SetXSize(value)) || only_prop)
                          {
                           this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,value);
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Visibility scope height
   virtual int       YSize(void)                         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT);  }
   virtual bool      SetYSize(const int value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && CGBaseObj::SetYSize(value)) || only_prop)
                          {
                           this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,value);
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
                       
//--- Visibility scope X coordinate
   virtual int       VisibleAreaX(void)                  const { return this.XOffset();                                                }
   virtual bool      SetVisibleAreaX(const int value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && CGBaseObj::SetXOffset(value)) || only_prop)
                          {
                           this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X,value);
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Visibility scope Y coordinate
   virtual int       VisibleAreaY(void)                  const { return this.YOffset();                                                }
   virtual bool      SetVisibleAreaY(const int value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && CGBaseObj::SetYOffset(value)) || only_prop)
                          {
                           this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y,value);
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Visibility scope width
   virtual int       VisibleAreaWidth(void)              const { return this.XSize();                                                  }
   virtual bool      SetVisibleAreaWidth(const int value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && CGBaseObj::SetXSize(value)) || only_prop)
                          {
                           this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,value);
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Visibility scope height
   virtual int       VisibleAreaHeight(void)             const { return this.YSize();                                                  }
   virtual bool      SetVisibleAreaHeight(const int value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && CGBaseObj::SetYSize(value)) || only_prop)
                          {
                           this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,value);
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
                       
//--- Return the (1) X coordinate, (2) right border, (3) Y coordinate, (4) bottom border of the visible area 
   int               CoordXVisibleArea(void)             const { return this.CoordX()+this.VisibleAreaX();                             }
   int               RightEdgeVisibleArea(void)          const { return this.CoordXVisibleArea()+this.VisibleAreaWidth();              }
   int               RightEdgeVisibleAreaRelative(void)  const { return this.VisibleAreaX()+this.VisibleAreaWidth();                   }
   int               CoordYVisibleArea(void)             const { return this.CoordY()+this.VisibleAreaY();                             }
   int               BottomEdgeVisibleArea(void)         const { return this.CoordYVisibleArea()+this.VisibleAreaHeight();             }
   int               BottomEdgeVisibleAreaRelative(void) const { return this.VisibleAreaY()+this.VisibleAreaHeight();                  }

//--- Graphical element description

Os métodos para definir o valor de propriedade primeiro definem a propriedade diretamente no próprio objeto gráfico e, se a operação for bem-sucedida, definem o valor na propriedade da classe do objeto. São retornados aqueles valores provenientes das propriedades do objeto que foram previamente definidos ali, utilizando os métodos de configuração de propriedade.
Os métodos auxiliares retornam o valor calculado da borda desejada ou a coordenada do canto superior esquerdo da área de visibilidade do objeto e simplificam o acesso à leitura das propriedades necessárias das bordas da área de visibilidade, pois são nomeadas de acordo com métodos semelhantes de elementos gráficos, e retornar os valores necessários sem a necessidade de calculá-los independentemente.

Quando um objeto-elemento gráfico é criado, ele deve ser preenchido com a cor definida para ele, as legendas ou imagens necessárias devem ser desenhadas nele e, em seguida, o objeto deve calcular sua localização dentro do contêiner e cortar suas partes salientes fora do contêiner . O corte será feito preenchendo as áreas a serem escondidas com uma cor transparente. Afinal, primeiro precisamos chamar o método Erase(), que preenche o fundo com cor e no qual algo mais é desenhado sobre o objeto, e depois temos que apagar as partes invisíveis da imagem. E tudo isso deve ser colocado novamente no método Erase(). Isso significa que o primeiro preenchimento com cor deve ocorrer sem recortar as áreas ocultas, e chamaremos esse método de EraseNoCrop(). Vamos criar o método Crop() para recortar áreas ocultas. E já no método Erase() existente, chamaremos sequencialmente esses métodos.

Vamos declarar novos métodos nas seções protegidas e públicas da classe:

//+------------------------------------------------------------------+
//| The methods of filling, clearing and updating raster data        |
//+------------------------------------------------------------------+
//--- Clear the element filling it with color and opacity
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Clear the element with a gradient fill
   virtual void      Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);
//--- Clear the element completely
   virtual void      Erase(const bool redraw=false);
protected:
//--- Clear the element filling it with color and opacity without cropping and updating
   virtual void      EraseNoCrop(const color colour,const uchar opacity,const bool redraw=false);
//--- Clears the element with a gradient fill without cropping and updating
   virtual void      EraseNoCrop(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);
public:
//--- Crops the image outlined by (1) the specified and (2) previously set rectangular visibility scope
   void              Crop(const uint coord_x,const uint coord_y,const uint width,const uint height);
   virtual void      Crop(void);
//--- Update the element
   void              Update(const bool redraw=false)           { this.m_canvas.Update(redraw);                                         }
   
//+------------------------------------------------------------------+


Em ambos os construtores de classe, definimos os valores padrão para as coordenadas e tamanhos da área de visibilidade do objeto para que a área de visibilidade retangular seja do tamanho de todo o objeto e, após definir todas as propriedades, definimos o sinalizador de visibilidade do objeto como "oculto", o que nos permitirá não observar como os objetos são gradualmente construídos no gráfico, todos os objetos dos elementos GUI do programa (além de ocultar o objeto-forma principal e sua exibição posterior após a construção de todos os objetos gráficos no próprio programa ao construir seu componente gráfico):

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           const int      element_id,
                           const int      element_num,
                           const long     chart_id,
                           const int      wnd_num,
                           const string   descript,
                           const int      x,
                           const int      y,
                           const int      w,
                           const int      h,
                           const color    colour,
                           const uchar    opacity,
                           const bool     movable=true,
                           const bool     activity=true,
                           const bool     redraw=false) : m_shadow(false)
  {
   this.SetTypeElement(element_type);
   this.m_type=OBJECT_DE_TYPE_GELEMENT; 
   this.m_element_main=NULL;
   this.m_element_base=NULL;
   this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND);
   this.m_name=this.CreateNameGraphElement(element_type);
   this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id);
   this.m_subwindow=wnd_num;
   this.SetFont(DEF_FONT,DEF_FONT_SIZE);
   this.m_text_anchor=0;
   this.m_text_x=0;
   this.m_text_y=0;
   this.SetBackgroundColor(colour,true);
   this.SetOpacity(opacity);
   this.m_shift_coord_x=0;
   this.m_shift_coord_y=0;
   if(::ArrayResize(this.m_array_colors_bg,1)==1)
      this.m_array_colors_bg[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_dwn,1)==1)
      this.m_array_colors_bg_dwn[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_ovr,1)==1)
      this.m_array_colors_bg_ovr[0]=this.BackgroundColor();
   if(this.Create(chart_id,wnd_num,x,y,w,h,redraw))
     {
      this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Graphical resource name
      this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID());         // Chart ID

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

      this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.ActiveAreaRight());      // Right border of the element active area
      this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.ActiveAreaBottom());    // Bottom border of the element active area
      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X,0);                      // Visibility scope X coordinate
      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y,0);                      // Visibility scope Y coordinate
      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,w);                  // Visibility scope width
      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,h);                 // Visibility scope height

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

      this.SetProperty(CANV_ELEMENT_PROP_BELONG,ENUM_GRAPH_OBJ_BELONG::GRAPH_OBJ_BELONG_PROGRAM);  // Graphical element affiliation
      this.SetProperty(CANV_ELEMENT_PROP_ZORDER,0);                              // Priority of a graphical object for receiving the event of clicking on a chart

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

      this.SetProperty(CANV_ELEMENT_PROP_ALIGNMENT,CANV_ELEMENT_ALIGNMENT_TOP);                       // Location of an object inside the control
      this.SetProperty(CANV_ELEMENT_PROP_TEXT,"");                                                    // Graphical element text
      this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,descript);                                       // Graphical element description
      this.SetVisibleFlag(false,false);
     }
   else
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj());
     }
  }
//+------------------------------------------------------------------+
//| Protected constructor                                            |
//+------------------------------------------------------------------+
CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           const long    chart_id,
                           const int     wnd_num,
                           const string  descript,
                           const int     x,
                           const int     y,
                           const int     w,
                           const int     h) : m_shadow(false)
  {
   this.m_type=OBJECT_DE_TYPE_GELEMENT; 
   this.m_element_main=NULL;
   this.m_element_base=NULL;
   this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND);
   this.m_name=this.CreateNameGraphElement(element_type);
   this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id);
   this.m_subwindow=wnd_num;
   this.m_type_element=element_type;
   this.SetFont(DEF_FONT,DEF_FONT_SIZE);
   this.m_text_anchor=0;
   this.m_text_x=0;
   this.m_text_y=0;
   this.SetBackgroundColor(CLR_CANV_NULL,true);
   this.SetOpacity(0);
   this.m_shift_coord_x=0;
   this.m_shift_coord_y=0;
   if(::ArrayResize(this.m_array_colors_bg,1)==1)
      this.m_array_colors_bg[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_dwn,1)==1)
      this.m_array_colors_bg_dwn[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_ovr,1)==1)
      this.m_array_colors_bg_ovr[0]=this.BackgroundColor();
   if(this.Create(chart_id,wnd_num,x,y,w,h,false))
     {
      this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Graphical resource name
      this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID());         // Chart ID

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

      this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.ActiveAreaRight());      // Right border of the element active area
      this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.ActiveAreaBottom());    // Bottom border of the element active area
      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X,0);                      // Visibility scope X coordinate
      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y,0);                      // Visibility scope Y coordinate
      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,w);                  // Visibility scope width
      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,h);                 // Visibility scope height

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

      this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,CANV_ELEMENT_ALIGNMENT_TOP);                   // Location of tabs inside the control
      this.SetProperty(CANV_ELEMENT_PROP_ALIGNMENT,CANV_ELEMENT_ALIGNMENT_TOP);                       // Location of an object inside the control

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

      this.SetProperty(CANV_ELEMENT_PROP_TEXT,"");                                                    // Graphical element text
      this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,descript);                                       // Graphical element description
      this.SetVisibleFlag(false,false);
     }
   else
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj());
     }
  }
//+------------------------------------------------------------------+


No método que cria a estrutura do objeto, escrevemos a configuração das propriedades do escopo de visibilidade:

//+------------------------------------------------------------------+
//| Create the object structure                                      |
//+------------------------------------------------------------------+
bool CGCnvElement::ObjectToStruct(void)
  {
//--- Save integer properties
   this.m_struct_obj.id=(int)this.GetProperty(CANV_ELEMENT_PROP_ID);                               // Element ID
   this.m_struct_obj.type=(int)this.GetProperty(CANV_ELEMENT_PROP_TYPE);                           // Graphical element type

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

   this.m_struct_obj.coord_act_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y);             // Y coordinate of the element active area
   this.m_struct_obj.coord_act_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_RIGHT);           // Right border of the element active area
   this.m_struct_obj.coord_act_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM);         // Bottom border of the element active area
   this.m_struct_obj.visible_area_x=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X);       // Visibility scope X coordinate
   this.m_struct_obj.visible_area_y=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y);       // Visibility scope Y coordinate
   this.m_struct_obj.visible_area_w=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH);   // Visibility scope width
   this.m_struct_obj.visible_area_h=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT);  // Visibility scope height
   this.m_struct_obj.zorder=this.GetProperty(CANV_ELEMENT_PROP_ZORDER);                            // Priority of a graphical object for receiving the on-chart mouse click event
   this.m_struct_obj.enabled=(bool)this.GetProperty(CANV_ELEMENT_PROP_ENABLED);                    // Element availability flag

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

   this.m_struct_obj.tab_alignment=(int)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT);                              // Location of tabs inside the control
   this.m_struct_obj.alignment=(int)this.GetProperty(CANV_ELEMENT_PROP_ALIGNMENT);                                      // Location of an object inside the control
//--- Save real properties

//--- Save string properties
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj);   // Graphical element object name
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res);   // Graphical resource name
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_TEXT),this.m_struct_obj.text);           // Graphical element text
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_DESCRIPTION),this.m_struct_obj.descript);// Graphical element description
   //--- Save the structure to the uchar array
   ::ResetLastError();
   if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array))
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY,true);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+


No método que cria um objeto a partir de uma estrutura, escrevemos um registro dos valores das propriedades do escopo de visibilidade nas propriedades do objeto:

//+------------------------------------------------------------------+
//| Create the object from the structure                             |
//+------------------------------------------------------------------+
void CGCnvElement::StructToObject(void)
  {
//--- Save integer properties
   this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id);                                    // Element ID
   this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type);                                // Graphical element type

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

   this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.m_struct_obj.coord_act_right);                // Right border of the element active area
   this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.m_struct_obj.coord_act_bottom);              // Bottom border of the element active area
   this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X,this.m_struct_obj.visible_area_x);            // Visibility scope X coordinate
   this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y,this.m_struct_obj.visible_area_y);            // Visibility scope Y coordinate
   this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,this.m_struct_obj.visible_area_w);        // Visibility scope width
   this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,this.m_struct_obj.visible_area_h);       // Visibility scope height
   this.SetProperty(CANV_ELEMENT_PROP_ZORDER,this.m_struct_obj.zorder);                            // Priority of a graphical object for receiving the event of clicking on a chart
   this.SetProperty(CANV_ELEMENT_PROP_ENABLED,this.m_struct_obj.enabled);                          // Element availability flag

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

   this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR,this.m_struct_obj.fore_color);                    // Default text color for all control objects
   this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_OPACITY,this.m_struct_obj.fore_color_opacity);    // Opacity of the default text color for all control objects
   
   //---...
   //---...

   this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,this.m_struct_obj.tab_alignment);                             // Location of tabs inside the control
   this.SetProperty(CANV_ELEMENT_PROP_ALIGNMENT,this.m_struct_obj.alignment);                                     // Location of an object inside the control
//--- Save real properties

//--- Save string properties
   this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj));   // Graphical element object name
   this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res));   // Graphical resource name
   this.SetProperty(CANV_ELEMENT_PROP_TEXT,::CharArrayToString(this.m_struct_obj.text));           // Graphical element text
   this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,::CharArrayToString(this.m_struct_obj.descript));// Graphical element description
  }
//+------------------------------------------------------------------+


Nos métodos para definir a largura e a altura do elemento, inserimos a configuração das propriedades de escopo retangular:

//+------------------------------------------------------------------+
//| Set a new width                                                  |
//+------------------------------------------------------------------+
bool CGCnvElement::SetWidth(const int width)
  {
   if(this.GetProperty(CANV_ELEMENT_PROP_WIDTH)==width)
      return true;
   if(!this.m_canvas.Resize(width,this.m_canvas.Height()))
     {
      CMessage::ToLog(DFUN+this.TypeElementDescription()+": width="+(string)width+": ",MSG_CANV_ELEMENT_ERR_FAILED_SET_WIDTH);
      return false;
     }
   this.SetProperty(CANV_ELEMENT_PROP_WIDTH,width);
   this.SetVisibleAreaX(0,true);
   this.SetVisibleAreaWidth(width,true);
   return true;
  }
//+------------------------------------------------------------------+
//| Set a new height                                                 |
//+------------------------------------------------------------------+
bool CGCnvElement::SetHeight(const int height)
  {
   if(this.GetProperty(CANV_ELEMENT_PROP_HEIGHT)==height)
      return true;
   if(!this.m_canvas.Resize(this.m_canvas.Width(),height))
     {
      CMessage::ToLog(DFUN+this.TypeElementDescription()+": height="+(string)height+": ",MSG_CANV_ELEMENT_ERR_FAILED_SET_HEIGHT);
      return false;
     }
   this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,height);
   this.SetVisibleAreaY(0,true);
   this.SetVisibleAreaHeight(height,true);
   return true;
  }
//+------------------------------------------------------------------+

Se o tamanho do elemento gráfico mudar, a área de visibilidade deve mudar de acordo para cobrir todo o objeto, que é o que estamos fazendo aqui - após cada mudança no tamanho do objeto, definimos um novo tamanho de área de visibilidade com sua coordenada inicial igual a zero, no canto superior esquerdo do objeto gráfico.

Agora os métodos Erase() ficarão assim:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//+------------------------------------------------------------------+
void CGCnvElement::Erase(const color colour,const uchar opacity,const bool redraw=false)
  {
   this.EraseNoCrop(colour,opacity,false);
   this.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element with a gradient fill                           |
//+------------------------------------------------------------------+
void CGCnvElement::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false)
  {
   this.EraseNoCrop(colors,opacity,vgradient,cycle,false);
   this.Crop();
//--- If specified, update the canvas
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Primeiro, é chamado o método EraseNoCrop(), no qual o elemento é limpo com a cor especificada, com a atualização desabilitada, depois é chamado o método Crop(), que corta as áreas ocultas e, em seguida, a tela é atualizada com o sinalizador especificado para atualização do gráfico.


Métodos para preencher a tela com cor sem recortar áreas ocultas:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//| without cropping and with the chart update by flag               |
//+------------------------------------------------------------------+
void CGCnvElement::EraseNoCrop(const color colour,const uchar opacity,const bool redraw=false)
  {
   color arr[1];
   arr[0]=colour;
   this.SaveColorsBG(arr);
   this.m_canvas.Erase(::ColorToARGB(colour,opacity));
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element with a gradient fill without cropping          |
//| but with updating the chart by flag                              |
//+------------------------------------------------------------------+
void CGCnvElement::EraseNoCrop(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false)
  {
//--- Set the vertical and cyclic gradient filling flags
   this.m_gradient_v=vgradient;
   this.m_gradient_c=cycle;
//--- Check the size of the color array
   int size=::ArraySize(colors);
//--- If there are less than two colors in the array
   if(size<2)
     {
      //--- if the array is empty, erase the background completely and leave
      if(size==0)
        {
         this.Erase(redraw);
         return;
        }
      //--- in case of one color, fill the background with this color and opacity, and leave
      this.EraseNoCrop(colors[0],opacity,redraw);
      return;
     }
//--- Declare the receiver array
   color out[];
//--- Set the gradient size depending on the filling direction (vertical/horizontal)
   int total=(vgradient ? this.Height() : this.Width());
//--- and get the set of colors in the receiver array
   CColors::Gradient(colors,out,total,cycle);
   total=::ArraySize(out);
//--- In the loop by the number of colors in the array
   for(int i=0;i<total;i++)
     {
      //--- depending on the filling direction
      switch(vgradient)
        {
         //--- Horizontal gradient - draw vertical segments from left to right with the color from the array
         case false :
            DrawLineVertical(i,0,this.Height()-1,out[i],opacity);
           break;
         //--- Vertical gradient - draw horizontal segments downwards with the color from the array
         default:
            DrawLineHorizontal(0,this.Width()-1,i,out[i],opacity);
           break;
        }
     }
//--- Save the background color array
   this.SaveColorsBG(colors);
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Na verdade, esses são os métodos Erase() anteriores, que agora são complementados com um método para cortar áreas ocultas, e os novos métodos Erase() recebem a chamada de métodos e o recorte de áreas fora do contêiner.


Método que corta a imagem contornada pela área retangular especificada:

//+--------------------------------------------------------------------+
//| Crop the image outlined by a specified rectangular visibility scope|
//+--------------------------------------------------------------------+
void CGCnvElement::Crop(const uint coord_x,const uint coord_y,const uint width,const uint height)
  {
//--- If the passed coordinates and the size of the visibility scope match the size of the object, leave
   if(coord_x==0 && coord_y==0 && width==this.Width() && height==this.Height())
      return;
//--- Set the coordinates and size of the visibility scope in the object properties
   this.SetVisibleAreaX(coord_x,true);
   this.SetVisibleAreaY(coord_y,true);
   this.SetVisibleAreaWidth(width,true);
   this.SetVisibleAreaHeight(height,true);
//--- If the object in the current state has not yet been saved,
//--- save its bitmap to the array for subsequent restoration
   if(::ArraySize(this.m_duplicate_res)==0)
      this.ResourceStamp(DFUN);
//--- In the loop through the image lines of the graphical object
   for(int y=0;y<this.Height();y++)
     {
      //--- go through each pixel of the current line
      for(int x=0;x<this.Width();x++)
        {
         //--- If the string and its pixel are in the visibility scope, skip the pixel
         if(y>=this.VisibleAreaY() && y<=this.BottomEdgeVisibleAreaRelative() && x>=this.VisibleAreaX() && x<=this.RightEdgeVisibleAreaRelative())
            continue;
         //--- If the line pixel is outside the visibility scope, set a transparent color for it
         this.SetPixel(x,y,CLR_CANV_NULL,0);
        }
     }
  }
//+------------------------------------------------------------------+

O método recebe as coordenadas iniciais da área visível do objeto em relação a seu contêiner e o tamanho dessa área. Os valores passados são definidos nas propriedades do objeto e então dois laços são usados para apagar (preencher com cor transparente) aqueles pixels da imagem que estão fora da área visível definida.


Método que recorta a imagem contornada pela área de visibilidade retangular calculada:

//+------------------------------------------------------------------+
//| Crop the image outlined by the calculated                        |
//| rectangular visibility scope                                     |
//+------------------------------------------------------------------+
void CGCnvElement::Crop(void)
  {
//--- Get the pointer to the base object
   CGCnvElement *base=this.GetBase();
//--- If the object does not have a base object it is attached to, then there is no need to crop the hidden areas - leave
   if(base==NULL)
      return;
//--- Set the initial coordinates and size of the visibility scope to the entire object
   int vis_x=0;
   int vis_y=0;
   int vis_w=this.Width();
   int vis_h=this.Height();
//--- Set the size of the top, bottom, left and right areas that go beyond the container
   int crop_top=0;
   int crop_bottom=0;
   int crop_left=0;
   int crop_right=0;
//--- Calculate the boundaries of the container area, inside which the object is fully visible
   int top=fmax(base.CoordY()+(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_TOP),base.CoordYVisibleArea());
   int bottom=fmin(base.BottomEdge()-(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_BOTTOM),base.BottomEdgeVisibleArea()+1);
   int left=fmax(base.CoordX()+(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_LEFT),base.CoordXVisibleArea());
   int right=fmin(base.RightEdge()-(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_RIGHT),base.RightEdgeVisibleArea()+1);
//--- Calculate the values of the top, bottom, left and right areas, at which the object goes beyond
//--- the boundaries of the container area, inside which the object is fully visible
   crop_top=this.CoordY()-top;
   if(crop_top<0)
      vis_y=-crop_top;
   crop_bottom=bottom-this.BottomEdge()-1;
   if(crop_bottom<0)
      vis_h=this.Height()+crop_bottom-vis_y;
   crop_left=this.CoordX()-left;
   if(crop_left<0)
      vis_x=-crop_left;
   crop_right=right-this.RightEdge()-1;
   if(crop_right<0)
      vis_w=this.Width()+crop_right-vis_x;
//--- If there are areas that need to be hidden, call the cropping method with the calculated size of the object visibility scope
   if(crop_top<0 || crop_bottom<0 || crop_left<0 || crop_right<0)
      this.Crop(vis_x,vis_y,vis_w,vis_h);
  }
//+------------------------------------------------------------------+

A lógica do método é descrita nos comentários ao código. Primeiro, obtemos um ponteiro para o objeto contêiner ao qual esse elemento gráfico está anexado. Dependendo do tamanho do contêiner e das bordas da área em que os objetos anexados são visíveis, calculamos quanto o objeto anexado ao contêiner ultrapassa os limites dessa área de seu contêiner. Se o objeto ultrapassar os limites de qualquer um dos lados, chamamos o método de recorte das áreas da imagem que devem ser ocultadas.


Vamos fazer algumas modificações no arquivo de classe de objeto-sombra \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh .

No construtor da classe, ao criar um objeto , defina seu sinalizador de visibilidade como "oculto":

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CShadowObj::CShadowObj(const long chart_id,
                       const int subwindow,
                       const string name,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_SHADOW_OBJ,chart_id,subwindow,name,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GSHADOW; 
   CGCnvElement::SetBackgroundColor(clrNONE,true);
   CGCnvElement::SetOpacity(0);
   CGCnvElement::SetActive(false);
   this.m_opacity=CLR_DEF_SHADOW_OPACITY;
   this.m_blur=DEF_SHADOW_BLUR;
   color gray=CGCnvElement::ChangeColorSaturation(this.ChartBackgroundColor(),-100);
   this.m_color=CGCnvElement::ChangeColorLightness(gray,-50);
   this.m_shadow=false;
   this.SetVisibleFlag(false,false);
   CGCnvElement::Erase();
  }
//+------------------------------------------------------------------+


No método de desenho de sombra, primeiro verificamos a visibilidade do objeto e, se o objeto estiver oculto, não há nada para desenhar - saímos:

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


No arquivo de classe do objeto-forma \MQL5\Include\DoEasy\Objects\Graph\Form.mqh, no método que cria um novo elemento encaixado e o adiciona à lista de objetos anexados, corrigiremos o nome do anterior método renomeado:

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


Como agora a sombra é desenhada apenas se o sinalizador de visibilidade estiver definido, no método para desenhar a sombra, trocamos o desenho da sombra e definimos o sinalizador de visibilidade:

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

Anteriormente, esses métodos eram chamados na ordem inversa e a sombra não era desenhada.


No arquivo de classe de objeto base de todos os objetos WinForms \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh, vamos modificar os métodos Erase():

//+------------------------------------------------------------------+
//| 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::EraseNoCrop(colour,opacity,false);
//--- 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.Crop();
   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::EraseNoCrop(colors,opacity,vgradient,cycle,false);
//--- 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.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Agora, primeiro chamamos o método EraseNoCrop() do objeto widget, depois desenhamos a moldura e cortamos as áreas ocultas.


No método que retorna a descrição da propriedade integer do elemento, vamos adicionar um bloco de código para retornar a descrição das novas propriedades do objeto — as coordenadas e tamanhos do escopo:

//+------------------------------------------------------------------+
//| Return the description of the control integer property           |
//+------------------------------------------------------------------+
string CWinFormBase::GetPropertyDescription(ENUM_CANV_ELEMENT_PROP_INTEGER property,bool only_prop=false)
  {
   return
     (
      property==CANV_ELEMENT_PROP_ID                           ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_ID)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_TYPE                         ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TYPE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.TypeElementDescription()
         )  :

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

      property==CANV_ELEMENT_PROP_ACT_RIGHT                    ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_ACT_RIGHT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_ACT_BOTTOM                   ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_ACT_BOTTOM)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_VISIBLE_AREA_X               ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_X)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_VISIBLE_AREA_Y               ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_Y)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH           ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT          ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_GROUP                        ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_GROUP)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_ZORDER                       ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_ZORDER)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :

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

      property==CANV_ELEMENT_PROP_TAB_PAGE_COLUMN              ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_PAGE_COLUMN)+
         (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))
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

O objeto agora poderá gerar o nome das novas propriedades criadas hoje.


No arquivo de classe do objeto base dos controles padrão \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CommonBase.mqh, vamos modificar os métodos Erase() de acordo com o novo conceito de construção visto:

//+------------------------------------------------------------------+
//| 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::EraseNoCrop(colour,opacity,false);
//--- 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.Crop();
   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::EraseNoCrop(colors,opacity,vgradient,cycle,false);
//--- 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.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Aqui, primeiro chamamos o método EraseNoCrop() do objeto elemento gráfico, depois desenhamos uma moldura e cortamos as áreas ocultas.


No arquivo de classe do objeto WinForms CheckBox \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh, vamos alterar o método Redraw():

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

Aqui primeiro limpamos a imagem com a cor do objeto não cortamos as áreas ocultas, depois desenhamos tudo o que precisamos na tela (como fizemos antes) e, antes de atualizar, chamamos o método para cortar as áreas ocultas da imagem.


No arquivo de classe do objeto WinForms Label \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Label.mqh, no método de definição do texto do elemento depois de definido, chamamos o método de corte de áreas ocultas para que o texto desenhado seja cortado com base nos limites da área visível:

//--- Set the element text
   virtual void      SetText(const string text)
                       {
                        CWinFormBase::SetText(text);
                        if(this.AutoSize())
                           this.AutoSetWH();
                        this.Crop();
                       }


No método que redesenha o objeto, substituiremos a chamada ao método Erase() pela chamada ao método EraseNoCrop() e, após acabada a aparência do objeto, chamaremos o método de recorte das áreas ocultas da imagem:

//+------------------------------------------------------------------+
//| Redraw the object                                                |
//+------------------------------------------------------------------+
void CLabel::Redraw(bool redraw)
  {
//--- Fill the object with the background color having full transparency
   this.EraseNoCrop(this.BackgroundColor(),0,redraw);
//--- Declare the variables for X and Y coordinates and set their values depending on the text alignment
   int x=0,y=0;
   this.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.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+


No arquivo de objeto WinForms Button \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh, modificaremos de forma semelhante o método de redesenho do objeto:

//+------------------------------------------------------------------+
//| Redraw the object                                                |
//+------------------------------------------------------------------+
void CButton::Redraw(bool redraw)
  {
//--- Fill the object with the background color
   this.EraseNoCrop(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.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+


Classes de objetos-botões auxiliares com setas

Caso o objeto WinForms esteja configurado em modo cabeçalho de uma única linha, e houver mais guias que possam caber na largura ou altura do objeto, então os cabeçalhos das guias que se estendem além de seu contêiner serão escondidos.  Para poder deslocar a linha de cabeçalhos, precisamos criar botões de seta que deslocam a linha de cabeçalhos da esquerda para a direita ou para cima para baixo. Precisaremos desses botões também em outros controles, de modo que serão listados como objetos WinForms auxiliares - não são controles autônomos, ao contrário, são usados para construir outros controles.

Tais botões objetos com setas serão feitos da seguinte forma: criamos um objeto base de todos esses botões, que conterá métodos para definir suas propriedades, e os objetos herdeiros criarão um botão específico - com uma seta para esquerda, direita, para cima ou para baixo.

Além disso, com base nos objetos gerados, criaremos mais dois objetos que serão usados para criar o objeto WinForms TabControl: um terá dois botões horizontais com setas esquerda-direita, e o segundo terá dois botões verticais com setas para cima e para baixo. Estes objetos serão usados para rolar horizontal e verticalmente através da linha de cabeçalhos das guias.

Na pasta biblioteca \MQL5\Include\DoEasy\Objects\Graph\WForms\, criamos um novo arquivo WinFormBase.mqh da classe CWinFormBase.
A classe deve ser herdada da classe objeto-botão e seu arquivo deve ser anexado ao arquivo da classe que está sendo criada:

//+------------------------------------------------------------------+
//|                                                  ArrowButton.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 "Common Controls\Button.mqh"
//+------------------------------------------------------------------+
//| Arrow Button object class of WForms controls                     |
//+------------------------------------------------------------------+
class CArrowButton : public CButton
  {
  }


Na seção privada declaramos uma variável para armazenar a cor da seta e na seção protegida declaramos um método virtual para desenhar a seta e um construtor protegido. Na seção pública, declaramos métodos para definir e retornar a cor da seta, um construtor paramétrico e métodos para redesenhar o objeto e desenhar sua moldura:

//+------------------------------------------------------------------+
//| Arrow Button object class of WForms controls                     |
//+------------------------------------------------------------------+
class CArrowButton : public CButton
  {
private:
   color             m_arrow_color;                      // Arrow color
protected:
   //--- Draw the arrow
   virtual void      DrawArrow(void){return;}
//--- Protected constructor with object type, chart ID and subwindow
                     CArrowButton(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:
//--- (1) Set and (2) return the arrow color
   void              SetArrowColor(const color clr)      { this.m_arrow_color=clr;     }
   color             ArrowColor(void)              const { return this.m_arrow_color;  }
//--- Constructor
                     CArrowButton(const long chart_id,
                                  const int subwindow,
                                  const string descript,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h);
//--- Redraw the object
   virtual void      Redraw(bool redraw);
//--- Clear the element filling it with color and opacity
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Clear the element with a gradient fill
   virtual void      Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);
//--- Draw the button frame
   virtual void      DrawFrame(void);
  };
//+------------------------------------------------------------------+

O método virtual DrawArrow() nesta classe não desenha nada e, como é virtual, será substituído nas classes herdadas, cada uma delas criará seu próprio método para desenhar setas - esquerda, direita, para cima e para baixo.

A finalidade dos outros métodos, penso eu, é clara - eles estão todos em outros objetos de biblioteca e os vimos muitas vezes.

Construtor protegido com especificação do tipo de objeto, identificador do gráfico e da subjanela:

//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CArrowButton::CArrowButton(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)
  {
//--- Set the specified graphical element type for the object and assign the library object type to the current object
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetPaddingAll(0);
   this.SetMarginAll(0);
   this.SetBorderSizeAll(1);
   this.SetArrowColor(CLR_DEF_FORE_COLOR);
  }
//+------------------------------------------------------------------+

O construtor recebe o tipo de objeto criado, que por sua vez é passado ao longo da cadeia para o resto dos objetos pais. O corpo do construtor define o tipo de elemento gráfico, o tipo de objeto gráfico da biblioteca, valores zero de Padding e Margin, o tamanho da moldura de um pixel e a cor da seta como a cor do texto dos controles padrão.

Depois de criar um objeto, todos esses parâmetros (exceto os tipos de objeto) podem ser alterados.

Fazemos o mesmo no construtor paramétrico, exceto que o tipo de objeto a ser criado não é passado para ele, e o tipo "Botão de Seta" é passado para o construtor do objeto pai na linha de inicialização:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CArrowButton::CArrowButton(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_ARROW_BUTTON,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetPaddingAll(0);
   this.SetMarginAll(0);
   this.SetBorderSizeAll(1);
   this.SetArrowColor(CLR_DEF_FORE_COLOR);
  }
//+------------------------------------------------------------------+


Método que redesenha o objeto:

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

Basicamente aqui é chamado o método Erase(), que é onde acontece o redesenho:

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

Aqui tudo acontece como para todos os outros objetos de acordo com o novo conceito de recorte das áreas ocultas da imagem: primeiro é chamado o método EraseNoCrop(), onde o objeto é preenchido com a cor de fundo, depois uma moldura e uma seta são desenhadas e as áreas ocultas são recortadas.


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

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

Tudo é exatamente igual ao método acima. Aqui é chamado o método EraeNoCrop() sobrecarregado, que preenche o plano de fundo com uma cor gradiente.


Método que desenha a borda do elemento:

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

Aqui, simplesmente desenhamos um retângulo ao redor das bordas do objeto com a cor de fundo e a opacidade definidas.

Se criarmos este objeto, simplesmente será desenhado um botão comum sem qualquer tipo de legenda ou flecha. As setas serão desenhadas nos objetos herdeiros desta classe.


Objeto-botão com seta para a esquerda.

Na pasta da biblioteca \MQL5\Include\DoEasy\Objects\Graph\WForms\, criamos um novo arquivo ArrowLeftButton.mqh da classe CArrowLeftButton. A classe deve ser herdada da classe base do botão de seta que acabamos de criar e seu arquivo deve ser incluído no arquivo de classe gerado:

//+------------------------------------------------------------------+
//|                                              ArrowLeftButton.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 "ArrowButton.mqh"
//+------------------------------------------------------------------+
//| Left Arrow Button object class of WForms controls                |
//+------------------------------------------------------------------+
class CArrowLeftButton : public CArrowButton
  {
  }


Na seção protegida da classe, declaramos um método para desenhar uma seta e um construtor protegido e, na seção pública, um construtor paramétrico:

//+------------------------------------------------------------------+
//| Left Arrow Button object class of WForms controls                |
//+------------------------------------------------------------------+
class CArrowLeftButton : public CArrowButton
  {
private:

protected:
   //--- Draw the arrow
   virtual void      DrawArrow(void);
//--- Protected constructor with object type, chart ID and subwindow
                     CArrowLeftButton(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:
//--- Constructor
                     CArrowLeftButton(const long chart_id,
                                      const int subwindow,
                                      const string descript,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h);
  };
//+------------------------------------------------------------------+


No construtor protegido, definimos o tipo do elemento gráfico passado para o método, e no construtor paramétrico, na linha de inicialização, passamos o tipo de objeto para o construtor da classe pai como "botão com seta para a esquerda" e definimos este objeto como do mesmo tipo:

//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CArrowLeftButton::CArrowLeftButton(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) : CArrowButton(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);
  }
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CArrowLeftButton::CArrowLeftButton(const long chart_id,
                                   const int subwindow,
                                   const string descript,
                                   const int x,
                                   const int y,
                                   const int w,
                                   const int h) : CArrowButton(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT);
  }
//+------------------------------------------------------------------+


Método para desenhar a seta:

//+------------------------------------------------------------------+
//| Draw the arrow                                                   |
//+------------------------------------------------------------------+
void CArrowLeftButton::DrawArrow(void)
  {
//--- Create X and Y coordinate arrays for drawing a triangle
   double x=(double)this.Width()/2;
   double y=(double)this.Height()/2;
   double w=(double)this.Width();
   double h=(double)this.Height();
//--- Calculate coordinates as double values and write them to arrays as integers
   int array_x[]={int(w*0.7), int(w*0.7), int(w*0.3)};
   int array_y[]={int(h*0.3), int(h*0.7), int(h*0.5)};
//--- Draw a filled triangle followed by a smoothed one on top of it
   this.DrawTriangleFill(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor());
   this.DrawTriangleWu(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor());
  }
//+------------------------------------------------------------------+

Este método virtual será diferente para cada um dos objetos que desenham setas em diferentes direções. Eles vão diferir apenas nos valores das coordenadas dos vértices dos triângulos desenhados. A diferença entre esta classe e outras que desenham setas em botões em outras direções está apenas no tipo de elemento gráfico e no método virtual que desenha a seta de acordo com suas coordenadas individuais - para cada seta, a sua própria.

Vamos analisá-las em sua totalidade sem explicação, já que a classe acima é completamente idêntica às outras, e todas essas classes estão localizadas na mesma pasta da biblioteca \MQL5\Include\DoEasy\Objects\Graph\WForms\.


Classe do objeto-botão de seta para a direita no arquivo ArrowRightButton.mqh:

//+------------------------------------------------------------------+
//|                                             ArrowRightButton.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 "ArrowButton.mqh"
//+------------------------------------------------------------------+
//| Right Arrow Button object class of WForms controls               |
//+------------------------------------------------------------------+
class CArrowRightButton : public CArrowButton
  {
private:

protected:
   //--- Draw the arrow
   virtual void      DrawArrow(void);
//--- Protected constructor with object type, chart ID and subwindow
                     CArrowRightButton(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:
//--- Constructor
                     CArrowRightButton(const long chart_id,
                                       const int subwindow,
                                       const string descript,
                                       const int x,
                                       const int y,
                                       const int w,
                                       const int h);
  };
//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CArrowRightButton::CArrowRightButton(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) : CArrowButton(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);
  }
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CArrowRightButton::CArrowRightButton(const long chart_id,
                                   const int subwindow,
                                   const string descript,
                                   const int x,
                                   const int y,
                                   const int w,
                                   const int h) : CArrowButton(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT);
  }
//+------------------------------------------------------------------+
//| Draw the arrow                                                   |
//+------------------------------------------------------------------+
void CArrowRightButton::DrawArrow(void)
  {
//--- Create X and Y coordinate arrays for drawing a triangle
   double x=(double)this.Width()/2;
   double y=(double)this.Height()/2;
   double w=(double)this.Width();
   double h=(double)this.Height();
//--- Calculate coordinates as double values and write them to arrays as integers
   int array_x[]={int(w*0.3), int(w*0.7), int(w*0.3)};
   int array_y[]={int(h*0.3), int(h*0.5), int(h*0.7)};
//--- Draw a filled triangle followed by a smoothed one on top of it
   this.DrawTriangleFill(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor());
   this.DrawTriangleWu(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor());
  }
//+------------------------------------------------------------------+


Classe de objeto-botão de seta para cima no arquivo ArrowUpButton.mqh:

//+------------------------------------------------------------------+
//|                                                ArrowUpButton.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 "ArrowButton.mqh"
//+------------------------------------------------------------------+
//| Up Arrow Button object class of WForms controls                  |
//+------------------------------------------------------------------+
class CArrowUpButton : public CArrowButton
  {
private:

protected:
   //--- Draw the arrow
   virtual void      DrawArrow(void);
//--- Protected constructor with object type, chart ID and subwindow
                     CArrowUpButton(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:
//--- Constructor
                     CArrowUpButton(const long chart_id,
                                    const int subwindow,
                                    const string descript,
                                    const int x,
                                    const int y,
                                    const int w,
                                    const int h);
  };
//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CArrowUpButton::CArrowUpButton(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) : CArrowButton(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);
  }
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CArrowUpButton::CArrowUpButton(const long chart_id,
                               const int subwindow,
                               const string descript,
                               const int x,
                               const int y,
                               const int w,
                               const int h) : CArrowButton(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP);
  }
//+------------------------------------------------------------------+
//| Draw the arrow                                                   |
//+------------------------------------------------------------------+
void CArrowUpButton::DrawArrow(void)
  {
//--- Create X and Y coordinate arrays for drawing a triangle
   double x=(double)this.Width()/2;
   double y=(double)this.Height()/2;
   double w=(double)this.Width();
   double h=(double)this.Height();
//--- Calculate coordinates as double values and write them to arrays as integers
   int array_x[]={int(w*0.3), int(w*0.5), int(w*0.7)};
   int array_y[]={int(h*0.7), int(h*0.3), int(h*0.7)};
//--- Draw a filled triangle followed by a smoothed one on top of it
   this.DrawTriangleFill(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor());
   this.DrawTriangleWu(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor());
  }
//+------------------------------------------------------------------+


Classe do objeto-botão de seta para baixo no arquivo ArrowDownButton.mqh:

//+------------------------------------------------------------------+
//|                                              ArrowDownButton.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 "ArrowButton.mqh"
//+------------------------------------------------------------------+
//| Down Arrow Button object class of WForms controls                |
//+------------------------------------------------------------------+
class CArrowDownButton : public CArrowButton
  {
private:

protected:
   //--- Draw the arrow
   virtual void      DrawArrow(void);
//--- Protected constructor with object type, chart ID and subwindow
                     CArrowDownButton(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:
//--- Constructor
                     CArrowDownButton(const long chart_id,
                                      const int subwindow,
                                      const string descript,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h);
  };
//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CArrowDownButton::CArrowDownButton(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) : CArrowButton(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);
  }
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CArrowDownButton::CArrowDownButton(const long chart_id,
                                   const int subwindow,
                                   const string descript,
                                   const int x,
                                   const int y,
                                   const int w,
                                   const int h) : CArrowButton(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN);
  }
//+------------------------------------------------------------------+
//| Draw the arrow                                                   |
//+------------------------------------------------------------------+
void CArrowDownButton::DrawArrow(void)
  {
//--- Create X and Y coordinate arrays for drawing a triangle
   double x=(double)this.Width()/2;
   double y=(double)this.Height()/2;
   double w=(double)this.Width();
   double h=(double)this.Height();
//--- Calculate coordinates as double values and write them to arrays as integers
   int array_x[]={int(w*0.3), int(w*0.5), int(w*0.7)};
   int array_y[]={int(h*0.3), int(h*0.7), int(h*0.3)};
//--- Draw a filled triangle followed by a smoothed one on top of it
   this.DrawTriangleFill(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor());
   this.DrawTriangleWu(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor());
  }
//+------------------------------------------------------------------+


Todas essas classes são idênticas. Só é possível notar uma diferença nos tipos de objetos definidos nos construtores de classes e uma diferença nos valores das coordenadas dos vértices nos métodos DrawArrow().


Criaremos mais duas classes auxiliares com base nas classes criadas de botões-objeto de setas. Cada uma delas terá um contêiner ao qual serão anexados dois botões. Na primeira classe, o botão esquerdo e o botão direito dispostos horizontalmente; na segunda classe, o botão para cima e o botão para baixo dispostos verticalmente.

Na pasta da biblioteca \MQL5\Include\DoEasy\Objects\Graph\WForms\ criamos um novo arquivo ArrowLeftRightBox.mqh da classe CArrowLeftRightBox.
A classe deve ser herdada da classe WinForms do objeto contêiner e o arquivo de classe CPanel deve ser incluído no arquivo de classe gerado:

//+------------------------------------------------------------------+
//|                                            ArrowLeftRightBox.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"
//+------------------------------------------------------------------+
//| ArrowLeftRightBox object class of WForms controls                |
//+------------------------------------------------------------------+
class CArrowLeftRightBox : public CContainer
  {
  }


Na seção privada da classe, declararemos um método virtual para criar um objeto gráfico e um método para criar dois botões de seta. Na seção protegida da classe, declararemos um construtor protegido e, na seção pública, escreveremos dois métodos para obter ponteiros para objetos-botões com setas e declarar um construtor paramétrico:

//+------------------------------------------------------------------+
//| ArrowLeftRightBox object class of WForms controls                |
//+------------------------------------------------------------------+
class CArrowLeftRightBox : public CContainer
  {
private:
//--- 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);
//--- Create ArrowButton Up and Down objects
   void              CreateArrowButtons(const int width,const int height);

protected:
//--- Protected constructor with object type, chart ID and subwindow
                     CArrowLeftRightBox(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:
//--- Return the pointer to the (1) up and (2) down arrow button
   CArrowLeftButton *GetArrowUpButton(void)     { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,0);    }
   CArrowRightButton*GetArrowDownButton(void)   { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,0);   }
//--- Constructor
                     CArrowLeftRightBox(const long chart_id,
                                        const int subwindow,
                                        const string descript,
                                        const int x,
                                        const int y,
                                        const int w,
                                        const int h);
  };
//+------------------------------------------------------------------+


Vejamos a implementação dos métodos declarados.

Construtor protegido com especificação do tipo de objeto, identificador do gráfico e da subjanela:

//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CArrowLeftRightBox::CArrowLeftRightBox(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)
  {
//--- Set the specified graphical element type for the object and assign the library object type to the current object
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.CreateArrowButtons((w<DEF_ARROW_BUTTON_SIZE ? w : DEF_ARROW_BUTTON_SIZE),(h<DEF_ARROW_BUTTON_SIZE ? h : DEF_ARROW_BUTTON_SIZE));
  }
//+------------------------------------------------------------------+

Aqui, definimos o tipo de elemento gráfico passado para o construtor, o tipo de objeto gráfico da biblioteca como "objeto auxiliar", definimos o tamanho da moldura do objeto como um pixel, o tipo de moldura como plana, a cor da moldura padrão, a cor do texto do objeto WinForms como padrão e chamamos o método para criar dois botões de seta. Nesse caso, se a largura e altura passadas ao construtor for menor que o tamanho padrão do objeto de botão de seta padrão, então o objeto é construído com tamanho especificado, caso contrário - com tamanho padrão. Assim, o tamanho padrão do botão é o tamanho máximo possível para objetos com botões de seta.


Construtor paramétrico especificando o ID e a subjanela do gráfico:

//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CArrowLeftRightBox::CArrowLeftRightBox(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_ARROW_BUTTONS_LR_BOX,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.CreateArrowButtons((w<DEF_ARROW_BUTTON_SIZE ? w : DEF_ARROW_BUTTON_SIZE),(h<DEF_ARROW_BUTTON_SIZE ? h : DEF_ARROW_BUTTON_SIZE));
  }
//+------------------------------------------------------------------+

Aqui é o mesmo que o construtor protegido, exceto que o tipo de item gráfico não é definido no construtor, mas é codificado como ArrowLeftRightButtonBox.


Método que cria objetos ArrowButton Left e Right:

//+------------------------------------------------------------------+
//| Create ArrowButton Left and Right objects                        |
//+------------------------------------------------------------------+
void CArrowLeftRightBox::CreateArrowButtons(const int width,const int height)
  {
//--- Calculate the width of the object from the width of two buttons plus the size of the frame on the left and right
//--- and the height of the object from the height of the button plus the top and bottom frame sizes
   int w=width*2+this.BorderSizeLeft()+this.BorderSizeRight();
   int h=height+this.BorderSizeTop()+this.BorderSizeBottom();
//--- If the received width or height is greater than the width or height of the object, resize it
   if(w>this.Width() || h>this.Height())
      this.Resize((w>this.Width() ? w : this.Width()),(h>this.Height() ? h : this.Height()),false);
//--- Create two buttons next to each other starting from the 0 : 0 coordinate of the container
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,0,0,width,height,clrNONE,255,true,false);
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,width,0,width,height,clrNONE,255,true,false);
  }
//+------------------------------------------------------------------+

O método recebe os botões a serem criados. Com base nas dimensões dos botões, a largura do contêiner é calculada como a largura dos dois botões, levando em conta o tamanho da moldura do objeto, e a altura do contêiner é tirada da altura do botão, levando em conta o tamanho da moldura do contêiner. Se o tamanho do botão calculado for maior que o tamanho do contêiner, o contêiner é aumentado para o tamanho calculado e os métodos são chamados para criar botões dispostos horizontalmente, um ao lado do outro.

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

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CArrowLeftRightBox::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_WF_ARROW_BUTTON_LEFT    :
         element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   :
         element=new CArrowRightButton(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. Ele permite criar apenas dois objetos, um botão de seta para a esquerda e um botão de seta para a direita.


A classe para criar um contêiner com dois botões verticais é idêntica à discutida acima.

Na pasta \MQL5\Include\DoEasy\Objects\Graph\WForms\ da biblioteca criamos um novo arquivo ArrowUpDownBox.mqh da classe CArrowUpDownBox.
A classe deve ser herdada da classe WinForms do objeto contêiner e o arquivo de classe CPanel deve ser incluído no arquivo de classe gerado:

//+------------------------------------------------------------------+
//|                                               ArrowUpDownBox.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"
//+------------------------------------------------------------------+
//| ArrowUpDownBox object class of the WForms controls               |
//+------------------------------------------------------------------+
class CArrowUpDownBox : public CContainer
  {
private:
//--- 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);
//--- Create ArrowButton Up and Down objects
   void              CreateArrowButtons(const int width,const int height);

protected:
//--- Protected constructor with object type, chart ID and subwindow
                     CArrowUpDownBox(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:
//--- Return the pointer to the (1) up and (2) down arrow button
   CArrowUpButton   *GetArrowUpButton(void)     { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,0);   }
   CArrowDownButton *GetArrowDownButton(void)   { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,0); }
//--- Constructor
                     CArrowUpDownBox(const long chart_id,
                                     const int subwindow,
                                     const string descript,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h);
  };
//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CArrowUpDownBox::CArrowUpDownBox(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)
  {
//--- Set the specified graphical element type for the object and assign the library object type to the current object
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.CreateArrowButtons((w<DEF_ARROW_BUTTON_SIZE ? w : DEF_ARROW_BUTTON_SIZE),(h<DEF_ARROW_BUTTON_SIZE ? h : DEF_ARROW_BUTTON_SIZE));
  }
//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CArrowUpDownBox::CArrowUpDownBox(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_ARROW_BUTTONS_UD_BOX,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.CreateArrowButtons((w<DEF_ARROW_BUTTON_SIZE ? w : DEF_ARROW_BUTTON_SIZE),(h<DEF_ARROW_BUTTON_SIZE ? h : DEF_ARROW_BUTTON_SIZE));
  }
//+------------------------------------------------------------------+
//| Create ArrowButton Up and Down objects                           |
//+------------------------------------------------------------------+
void CArrowUpDownBox::CreateArrowButtons(const int width,const int height)
  {
//--- Calculate the width of the object from the width of the button plus the size of the frame on the left and right
//--- and the height of the object from the height of two buttons plus the top and bottom frame sizes
   int w=width+this.BorderSizeLeft()+this.BorderSizeRight();
   int h=height*2+this.BorderSizeTop()+this.BorderSizeBottom();
//--- If the received width or height is greater than the width or height of the object, resize it
   if(w>this.Width() || h>this.Height())
      this.Resize((w>this.Width() ? w : this.Width()),(h>this.Height() ? h : this.Height()),false);
//--- Create two buttons one above the other starting from the 0 : 0 coordinate of the container
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,0,0,width,height,clrNONE,255,true,false);
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,0,height,width,height,clrNONE,255,true,false);
  }
//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CArrowUpDownBox::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_WF_ARROW_BUTTON_UP      :
         element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    :
         element=new CArrowDownButton(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;
  }
//+------------------------------------------------------------------+

A diferença entre as duas classes acima está apenas nos tipos de elementos gráficos definidos nos construtores das classes e em seus métodos de criação dos dois botões. Na segunda classe, o método cria botões dispostos verticalmente e redimensiona o contêiner de acordo com a altura dos dois botões. Ambos os métodos são comentados de forma bastante correta na lista e espero que não necessitem de mais explicações.

Usaremos ambos no próximo artigo para rolar a linha de cabeçalhos das guias quando estiverem posicionados horizontal e verticalmente em uma linha e quando estiverem fora do controle e, portanto, escondidos.


Agora que temos os novos elementos gráficos criados, precisamos refinar os objetos-contêineres, para que eles saibam dos novos objetos e possam criá-los.

No arquivo de classe do objeto-contêiner \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh, vamos renomear os métodos que retornam os tamanhos e coordenadas da área de trabalho para que seus nomes correspondam aos nomes de métodos, para isso removemos o prefixo "Get":

public:
//--- Return the size and coordinates of the working area
   int               WidthWorkspace(void)          const
                       {
                        return this.Width()-::fmax(this.BorderSizeLeft(),this.PaddingLeft())-::fmax(this.BorderSizeRight(),this.PaddingRight());
                       }
   int               HeightWorkspace(void)         const
                       {
                        return this.Height()-::fmax(this.BorderSizeTop(),this.PaddingTop())-::fmax(this.BorderSizeBottom(),this.PaddingBottom());
                       }
   int               CoordXWorkspace(void)         const
                       {
                        return this.CoordX()+::fmax(this.BorderSizeLeft(),this.PaddingLeft());
                       }
   int               CoordYWorkspace(void)         const
                       {
                        return this.CoordY()+::fmax(this.BorderSizeTop(),this.PaddingTop());
                       }
   int               RightEdgeWorkspace(void)      const
                       {
                        return this.RightEdge()-::fmax(this.BorderSizeRight(),this.PaddingRight());
                       }
   int               BottomEdgeWorkspace(void)     const
                       {
                        return this.BottomEdge()-::fmax(this.BorderSizeBottom(),this.PaddingBottom());
                       }

//--- Return the list of bound WinForms objects with (1) any and (2) specified WinForms object type (from the base one and higher)


Vamos declarar um método virtual para recortar as áreas ocultas do objeto:

   virtual void      SetBorderSizeBottom(const uint value)
                       {
                        CForm::SetBorderSizeBottom(value);
                        if(this.PaddingBottom()<this.BorderSizeBottom())
                           this.SetPaddingBottom(this.BorderSizeBottom());
                       }
                       
//--- Crop the image outlined by the specified rectangular visibility area
   virtual void      Crop(void);

protected:

Tais métodos devem estar em todos os objetos WinForms chave da biblioteca.


No método que cria o novo elemento anexado, adicionamos uma chamada ao método Crop() para o objeto recém-criado:

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CContainer::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h,
                                  const color colour,
                                  const uchar opacity,
                                  const bool activity,
                                  const bool redraw)
  {
//--- If the object type is less than the base WinForms object
   if(element_type<GRAPH_ELEMENT_TYPE_WF_BASE)
     {
      //--- report the error and return 'false'
      CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE);
      return false;
     }
//--- If failed to create a new graphical element, return 'false'
   CWinFormBase *obj=CForm::CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity);
   if(obj==NULL)
      return false;
//--- Set parameters for the created object
   this.SetObjParams(obj,colour);
//--- If the panel has auto resize enabled and features bound objects, call the resize method
   if(this.AutoSize() && this.ElementsTotal()>0)
      this.AutoSizeProcess(redraw);
//--- Crop along the edges of the visible part
   obj.Crop();
//--- return 'true'
   return true;
  }
//+------------------------------------------------------------------+

Depois de criar um novo objeto e redimensionar o contêiner para adaptá-lo aos objetos criados dentro dele (com o sinalizador de redimensionamento automático do contêiner), é necessário verificar se o objeto recém-criado está fora da área do contêiner dentro da qual os objetos devem ser visíveis, e se as partes do novo objeto que estão fora daquela área devem ser cortadas.

No método que define os parâmetros para o objeto anexado, vamos adicionar ao objeto anexado a definição de ponteiros para os objetos base e principal, e vamos escrever um bloco de código para definir os parâmetros para os objetos criados e anexados aos objetos de contêiner - botões com setas:

//+------------------------------------------------------------------+
//| Set parameters for the attached object                           |
//+------------------------------------------------------------------+
void CContainer::SetObjParams(CWinFormBase *obj,const color colour)
  {
   obj.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain());
   obj.SetBase(this.GetObject());
//--- Set the text color of the object to be the same as that of the base container
   obj.SetForeColor(this.ForeColor(),true);
//--- If the created object is not a container, set the same group for it as the one for its base object
   if(obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_CONTAINER || obj.TypeGraphElement()>GRAPH_ELEMENT_TYPE_WF_GROUPBOX)
      obj.SetGroup(this.Group());
//--- Depending on the object type
   switch(obj.TypeGraphElement())
     {
      //--- For the Container, Panel and GroupBox WinForms objects
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER            :
      case GRAPH_ELEMENT_TYPE_WF_PANEL                :
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX             :
        //--- set the frame color equal to the background color 
        obj.SetBorderColor(obj.BackgroundColor(),true);
        break;
      //--- For "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      :
        //--- set the object text color as a container text color depending on the one passed to the method:
        //--- set the background color depending on the one passed to the method:
        //--- either the default standard control background color, or the one passed to the method.
        //--- The frame color is set equal to the text color
        obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true);
        obj.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        break;
      //--- For the "TabControl", "ArrowButton" WinForms object
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL          :
        //--- set the object text color as a container text color depending on the one passed to the method:
        //--- set the background color depending on the one passed to the method:
        //--- either the default standard control background color, or the one passed to the method.
        //--- The frame color is set equal to the text color
        obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_TAB_BACK_COLOR : colour,true);
        obj.SetBorderColor(CLR_DEF_CONTROL_TAB_BORDER_COLOR,true);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        obj.SetOpacity(CLR_DEF_CONTROL_TAB_OPACITY);
        break;
      //--- For the "ArrowButton" WinForms object
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         :
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      :
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    :
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    :
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   :
        //--- 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.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
        obj.SetBorderStyle(FRAME_STYLE_SIMPLE);
        break;
      default:
        break;
     }
   obj.Crop();
  }
//+------------------------------------------------------------------+

Ao final do método, cortamos as áreas ocultas do objeto criado.

É provável que chamar os métodos Crop() ao criar o elemento anexado em um método anterior e depois de definir as propriedades padrão nesse método possa ser redundante. Testes posteriores mostrarão de qual método será possível remover a chamada para o método Crop().

Vamos escrever uma implementação do método que recorta a imagem contornada pela área de visibilidade retangular calculada:

//+------------------------------------------------------------------+
//| Crop the image outlined by the calculated                        |
//| rectangular visibility scope                                     |
//+------------------------------------------------------------------+
void CContainer::Crop(void)
  {
//--- Get the pointer to the base object
   CContainer *base=this.GetBase();
//--- If the object does not have a base object it is attached to, then there is no need to crop the hidden areas - leave
   if(base==NULL)
      return;
//--- Set the initial coordinates and size of the visibility scope to the entire object
   int vis_x=0;
   int vis_y=0;
   int vis_w=this.Width();
   int vis_h=this.Height();
//--- Set the size of the top, bottom, left and right areas that go beyond the container
   int crop_top=0;
   int crop_bottom=0;
   int crop_left=0;
   int crop_right=0;
//--- Calculate the boundaries of the container area, inside which the object is fully visible
   int top=fmax(base.CoordYWorkspace(),base.CoordYVisibleArea());
   int bottom=fmin(base.BottomEdgeWorkspace(),base.BottomEdgeVisibleArea()+1);
   int left=fmax(base.CoordXWorkspace(),base.CoordXVisibleArea());
   int right=fmin(base.RightEdgeWorkspace(),base.RightEdgeVisibleArea()+1);
//--- Calculate the values of the top, bottom, left and right areas, at which the object goes beyond
//--- the boundaries of the container area, inside which the object is fully visible
   crop_top=this.CoordY()-top;
   if(crop_top<0)
      vis_y=-crop_top;
   crop_bottom=bottom-this.BottomEdge()-1;
   if(crop_bottom<0)
      vis_h=this.Height()+crop_bottom-vis_y;
   crop_left=this.CoordX()-left;
   if(crop_left<0)
      vis_x=-crop_left;
   crop_right=right-this.RightEdge()-1;
   if(crop_right<0)
      vis_w=this.Width()+crop_right-vis_x;
//--- If there are areas that need to be hidden, call the cropping method with the calculated size of the object visibility scope
   if(crop_top<0 || crop_bottom<0 || crop_left<0 || crop_right<0)
      this.Crop(vis_x,vis_y,vis_w,vis_h);
  }
//+------------------------------------------------------------------+

O método é idêntico ao que escrevemos na classe de objeto do elemento gráfico CGCnvElement, mas em vez de obter o tipo de objeto base como CGCnvElement, obtemos o objeto base com o tipo de objeto contêiner CContainer, e para calcular os limites da área do contêiner onde o objeto é totalmente visível, usamos métodos que retornam os limites das áreas de contêiner do espaço de trabalho que não existem no elemento gráfico base.

No método ArrangeObjects(), os métodos renomeados anteriormente, dos quais o prefixo "Get" foi removido, já foram substituídos por seus nomes atuais.

Por exemplo:

//+------------------------------------------------------------------+
//| Place bound objects in the order of their Dock binding           |
//+------------------------------------------------------------------+
bool CContainer::ArrangeObjects(const bool redraw)
  {
//--- Get the list of bound objects with WinForms type basic and higher
   CArrayObj *list=this.GetListWinFormsObj();
   CWinFormBase *prev=NULL, *obj=NULL, *elm=NULL;
//--- In the loop by all bound objects,
   for(int i=0;i<list.Total();i++)
     {
      //--- Get the current and previous elements from the list
      obj=list.At(i);
      prev=list.At(i-1);
      //--- If the object is not received, move on
      if(obj==NULL)
         continue;
      int x=0, y=0; // Object binding coordinates
      //--- Depending on the current object binding mode...
      //--- Top
      if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_TOP)
        {
         //--- If failed to change the object size (for the entire working area width and by the initial object height), move on to the next one
         if(!obj.Resize(this.WidthWorkspace(),obj.GetHeightInit(),false))
            continue;
         //--- Get the object binding coordinates
         x=this.CoordXWorkspace();
         y=(prev!=NULL ? prev.BottomEdge()+1 : this.CoordYWorkspace());
         //--- If failed to move the object to the obtained coordinates, move on to the next one
         if(!obj.Move(x,y,false))
            continue;
        }
      //--- Bottom

Existem muitas substituições semelhantes no método, todas já foram feitas e não faz sentido descrevê-las aqui - são apenas melhorias d de usabilidade Intellisense ao escrever código e não afetam sua lógica.


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

Incluímos nela os arquivos de todas as novas classes criadas hoje:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Container.mqh"
#include "..\TabField.mqh"
#include "..\ArrowButton.mqh"
#include "..\ArrowUpButton.mqh"
#include "..\ArrowDownButton.mqh"
#include "..\ArrowLeftButton.mqh"
#include "..\ArrowRightButton.mqh"
#include "..\ArrowUpDownBox.mqh"
#include "..\ArrowLeftRightBox.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"
//+------------------------------------------------------------------+

Agora essas classes estarão visíveis em todos os objetos gráficos da biblioteca, onde poderão ser criadas.

No método que cria um novo objeto gráfico, adicionamos um bloco de código para criar novos objetos:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                       const int obj_num,
                                       const string descript,
                                       const int x,
                                       const int y,
                                       const int w,
                                       const int h,
                                       const color colour,
                                       const uchar opacity,
                                       const bool movable,
                                       const bool activity)
  {
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT :
         element=new CGCnvElement(type,this.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;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         :
         element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      :
         element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    :
         element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    :
         element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   :
         element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX :
         element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX :
         element=new CArrowLeftRightBox(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 método que cria o objeto substrato, vamos alterar os nomes dos métodos renomeados anteriormente chamados:

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


No arquivo de classe do controle GroupBox \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh, no método que cria um novo objeto gráfico, assim como na classe anterior, adicionamos blocos de código para criar todos novos objetos criados por nós hoje:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CGroupBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int obj_num,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity)
  {
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT                 :
         element=new CGCnvElement(type,this.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;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         :
         element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      :
         element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    :
         element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    :
         element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   :
         element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX :
         element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX :
         element=new CArrowLeftRightBox(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;
  }
//+------------------------------------------------------------------+


Os métodos de limpeza dos elementos foram apurados em conformidade com aqueles previamente aprimorados em outras classes:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//+------------------------------------------------------------------+
void CGroupBox::Erase(const color colour,const uchar opacity,const bool redraw=false)
  {
//--- Fill the element having the specified color and the redrawing flag
   CGCnvElement::EraseNoCrop(colour,opacity,false);
//--- Draw a frame encasing a group of objects
   this.DrawFrame();
//--- Draw a header above the frame
   CGCnvElement::Text(6,0,this.Text(),this.ForeColor(),this.ForeColorOpacity());
//--- Update the element having the specified redrawing flag
   this.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element with a gradient fill                           |
//+------------------------------------------------------------------+
void CGroupBox::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false)
  {
//--- Fill the element having the specified color array and the redrawing flag
   CGCnvElement::EraseNoCrop(colors,opacity,vgradient,cycle,false);
//--- Draw a frame encasing a group of objects
   this.DrawFrame();
//--- Draw a header above the frame
   CGCnvElement::Text(6,0,this.Text(),this.ForeColor(),this.ForeColorOpacity());
//--- Update the element having the specified redrawing flag
   this.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Tudo é como antes, preenchemos com a cor de fundo, desenhamos os elementos necessários e cortamos o excesso que vai além da área visível.


Modificamos a classe do objeto-cabeçalho da guia do controle TabControl no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\TabHeader.mqh.

Na seção pública da classe, declararemos um método para redesenhar o objeto:

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

//--- Redraw the object
   virtual void      Redraw(bool redraw);
//--- Clear the element filling it with color and opacity


Nos construtores de classe, para o elemento definimos o tipo do objeto gráfico da biblioteca como "objeto WinForms auxiliar":

//+------------------------------------------------------------------+
//| 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_HELPER;
   this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP);
   this.SetToggleFlag(true);

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

   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_HELPER;
   this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP);
   this.SetToggleFlag(true);

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

   this.SetSizes(w,h);
   this.SetState(false);
  }
//+------------------------------------------------------------------+


No método que define o estado do controle, adicionamos o cabeçalho ao primeiro plano somente se o objeto estiver visível. Processaremos o objeto-campo da guia da mesma maneira, isto é, vamos exibi-lo, trazê-lo para o primeiro plano, desenhar elementos nele e cortá-lo somente se o objeto estiver visível:

//+------------------------------------------------------------------+
//| 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();
         if(this.IsVisible())
            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.SetZorder(base.Zorder()+1,false);
         if(this.IsVisible())
           {
            field.Show();
            field.DrawFrame();
            field.Crop();
            field.BringToTop();
           }
        }
      //--- If the button is not pressed, call the method to restore the title size
      else
        {
         this.WHProcessStateOff();
         CWinFormBase *field=this.GetFieldObj();
         field.Hide();
        }
     }
  }
//+------------------------------------------------------------------+

Se o botão não for pressionado, o objeto-campo da guia ficará oculto.

Os métodos de limpeza dos elementos foram aprimorados de acordo com o novo conceito para todos os elementos gráficos:

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

Preenchemos com cor, desenhamos elementos de formatação e cortamos o excesso.


Método que redesenha o objeto:

//+------------------------------------------------------------------+
//| Redraw the object                                               |
//+------------------------------------------------------------------+
void CTabHeader::Redraw(bool redraw)
  {
//--- Fill the object with the background color
   this.Erase(this.BackgroundColor(),this.Opacity(),false);
//--- 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.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Aqui, chamamos o método de preenchimento do objeto com cor, definimos os parâmetros de saída do texto e desenhamos o texto, depois cortamos as áreas da imagem que vão além do contêiner.

No manipulador de eventos "Cursor dentro da área ativa, botão do mouse (esquerdo) liberado" (método MouseActiveAreaReleasedHandler())
adicionamos o recorte do campo da guia
ao exibi-lo em um elemento após clicar no cabeçalho da guia:

      //--- 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();
            field.Crop();
           }
         //--- 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);
     }
  }
//+------------------------------------------------------------------+


Vamos modificar a classe de objeto-campo de guia no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\TabField.mqh.

Nos construtores da classe, definimos o tipo do objeto gráfico da biblioteca como "objeto WinForms auxiliar":

//+------------------------------------------------------------------+
//| 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_HELPER;
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   
   //---...
   //---...

   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_HELPER;
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);

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

   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetPaddingAll(3);
  }
//+------------------------------------------------------------------+


Os métodos de limpeza de elementos foram aprimorados da mesma forma que em todas as classes anteriores de objetos WinForms:

//+------------------------------------------------------------------+
//| 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::EraseNoCrop(colour,opacity,false);
//--- 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.Crop();
   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::EraseNoCrop(colors,opacity,vgradient,cycle,false);
//--- 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.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+


No método que cria um novo objeto gráfico, vamos adicionar blocos de código para criar todos os novos objetos que fizemos hoje:

//+------------------------------------------------------------------+
//| 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;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         :
         element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      :
         element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    :
         element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    :
         element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   :
         element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX :
         element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX :
         element=new CArrowLeftRightBox(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;
  }
//+------------------------------------------------------------------+


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

Para este objeto, precisamos criar seus próprios métodos para trazê-lo para o primeiro plano e exibi-lo. O ponto é que estes métodos de classe pai se sobrepõem a todos os objetos anexados ao contêiner, trazendo-os todos para o primeiro plano, bem como exibindo-os todos. O ponto é que estes métodos de classe pai, após um loop sobre todos os objetos anexados ao contêiner, fazem com que todos eles fiquem em primeiro plano, além de exibi-los todos.Para o controle TabControl, essa lógica não é adequada - ela contém campos ocultos que são exibidos e trazidos para o primeiro plano pelos métodos das classes pai, o que é inaceitável. O que precisamos fazer aqui é verificar primeiro se o campo pertence à guia selecionada, e apenas esse campo deve ser exibido e trazido para o primeiro plano, deixando todos os outros escondidos.

Vamos declarar esses dois métodos virtuais na seção pública da classe:

//--- 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());             }
   
//--- Set the object above all
   virtual void      BringToTop(void);
//--- Show the control
   virtual void      Show(void);
   
//--- Constructor


É importante que o Padding seja zero no construtor da classe, caso contrário o tamanho da área dentro da qual os objetos anexados deveriam ser visíveis seria cortado em três pixels em cada lado (Padding era de três pixels por padrão) e os campos da guia seriam cortados onde eles não deveriam estar - ao longo do contorno do contêiner, medindo três pixels:

//+------------------------------------------------------------------+
//| 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.SetPaddingAll(0);
   this.SetHeaderPadding(6,3);
   this.SetFieldPadding(3,3,3,3);
  }
//+------------------------------------------------------------------+


No método que cria o número especificado de guias, precisamos ajustar as coordenadas dos cabeçalhos e o tamanho das margens das guias em dois pixels. Além disso, devemos definir o sinalizador de visibilidade para o cabeçalho da guia da mesma forma que para o controle - nesse caso, os cabeçalhos das guias não serão exibidos para o objeto oculto:

//+------------------------------------------------------------------+
//| 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=2;
      int header_w=w;
      int header_h=h;
      
      //--- Set the current X and Y coordinate depending on the location of the tab headers
      switch(this.Alignment())
        {
         case CANV_ELEMENT_ALIGNMENT_TOP     :
           header_w=w;
           header_h=h;
           header_x=(header==NULL ? 2 : header.RightEdgeRelative());
           header_y=2;
           break;
         case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
           header_w=w;
           header_h=h;
           header_x=(header==NULL ? 2 : header.RightEdgeRelative());
           header_y=this.Height()-header_h;
           break;
         case CANV_ELEMENT_ALIGNMENT_LEFT    :
           header_w=h;
           header_h=w;
           header_x=2;
           header_y=(header==NULL ? this.Height()-header_h-2 : header.CoordYRelative()-header_h);
           break;
         case CANV_ELEMENT_ALIGNMENT_RIGHT   :
           header_w=h;
           header_h=w;
           header_x=this.Width()-header_w-2;
           header_y=(header==NULL ? 2 : header.BottomEdgeRelative());
           break;
         default:
           break;
        }
      //--- Create the TabHeader object
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,header_x,header_y,header_w,header_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));
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT)
         header.SetFontAngle(90);
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT)
         header.SetFontAngle(270);
      header.SetTabSizeMode(this.TabSizeMode());
      
      //--- Save the initial height of the header and set its size in accordance with the header size setting mode
      int h_prev=header_h;
      header.SetSizes(header_w,header_h);
      //--- Get the Y offset of the header position after changing its height and
      //--- shift it by the calculated value only for headers on the left
      int y_shift=header.Height()-h_prev;
      if(header.Move(header.CoordX(),header.CoordY()-(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT ? y_shift : 0)))
        {
         header.SetCoordXRelative(header.CoordX()-this.CoordX());
         header.SetCoordYRelative(header.CoordY()-this.CoordY());
        }
      header.SetVisibleFlag(this.IsVisible(),false);

      //--- 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_w=this.Width();
      int field_h=this.Height()-header.Height()-2;
      int header_shift=0;
      
      switch(this.Alignment())
        {
         case CANV_ELEMENT_ALIGNMENT_TOP     :
           field_x=0;
           field_y=header.BottomEdgeRelative();
           field_w=this.Width();
           field_h=this.Height()-header.Height()-2;
           break;
         case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
           field_x=0;
           field_y=0;
           field_w=this.Width();
           field_h=this.Height()-header.Height();
           break;
         case CANV_ELEMENT_ALIGNMENT_LEFT    :
           field_x=header.RightEdgeRelative();
           field_y=0;
           field_h=this.Height();
           field_w=this.Width()-header.Width()-2;
           break;
         case CANV_ELEMENT_ALIGNMENT_RIGHT   :
           field_x=0;
           field_y=0;
           field_h=this.Height();
           field_w=this.Width()-header.Width()-2;
           break;
         default:
           break;
        }
      
      //--- Create the TabField object (tab field)
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,field_x,field_y,field_w,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;
  }
//+------------------------------------------------------------------+

O deslocamento dos cabeçalhos em dois pixels os coloca dentro do contêiner, já que o cabeçalho selecionado é aumentado em dois pixels, estendendo-se assim para fora do contêiner. É então cortado com segurança, pois sua porção ampliada em dois pixels se estende fora de seu contêiner e deve se tornar invisível. O deslocamento das coordenadas do cabeçalho resolve este problema, mas o campo da guia também deve ser reduzido em dois pixels, para que ele também caiba dentro do contêiner, pois o campo da guia tira suas coordenadas das coordenadas do cabeçalho, agora deslocadas em dois pixels.


No método que estica os cabeçalhos das guias com base na largura do controle ( StretchHeadersByWidth() ), mudaremos o cálculo da largura dos cabeçalhos - agora a largura deles será calculada arredondada para o inteiro mais próximo, o que tornará seu alinhamento um pouco mais agradável de ser vista:

      //--- Get the width of the container, as well as the number of headers in a row, and calculate the width of each header
      int base_size=this.Width()-4;
      int num=list_row.Total();
      int w=(int)round((double)base_size/double(num>0 ? num : 1));
      //--- In the loop by row headers
      for(int j=0;j<list_row.Total();j++)
        {


No método que define a guia selecionada, definiremos forçosamente as guias restantes para o estado "não selecionado":

//+------------------------------------------------------------------+
//| 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())
     {
      CArrayObj *list=this.GetListHeaders();
      if(list==NULL)
         return;
      for(int i=0;i<list.Total();i++)
        {
         if(i==index)
            continue;
         this.SetUnselected(i);
        }
      header.SetState(true);
     }
//--- save the index of the selected tab
   this.SetSelectedTabPageNum(index);
  }
//+------------------------------------------------------------------+

O índice da guia selecionada é passado para o método. Em um loop pela lista de cabeçalhos de guias, verificamos o índice do loop e, se for igual ao índice da guia selecionada, o loop segue para a próxima iteração. Se o índice do loop não for igual ao índice da guia selecionada, será chamado o método de configuração da guia especificada com base no índice do loop para o estado não selecionado.


No método que define o texto do cabeçalho da guia especificada, após definir o texto, chamamos o método para recortar as áreas ocultas do cabeçalho:

//+------------------------------------------------------------------+
//| Set the title text of the specified tab                          |
//+------------------------------------------------------------------+
void CTabControl::SetHeaderText(CTabHeader *header,const string text)
  {
   if(header==NULL)
      return;
   header.SetText(text);
   header.Crop();
  }
//+------------------------------------------------------------------+

Isto irá aparar o texto desenhado no cabeçalho, caso o cabeçalho fique parcialmente fora do escopo.


No método que cria um novo objeto gráfico, adicionamos um bloco de código para criar novos objetos:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CTabControl::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;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         :
         element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      :
         element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    :
         element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    :
         element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   :
         element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX :
         element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX :
         element=new CArrowLeftRightBox(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;
  }
//+------------------------------------------------------------------+


Método que exibe o controle:

//+------------------------------------------------------------------+
//| Show the control                                                 |
//+------------------------------------------------------------------+
void CTabControl::Show(void)
  {
//--- Get the list of all tab headers
   CArrayObj *list=this.GetListHeaders();
   if(list==NULL)
      return;
//--- If the object has a shadow, display it
   if(this.m_shadow_obj!=NULL)
      this.m_shadow_obj.Show();
//--- Display the container
   CGCnvElement::Show();
//--- Move all elements of the object to the foreground
   this.BringToTop();
  }
//+------------------------------------------------------------------+

O contêiner do objeto é exibido primeiro (normalmente é transparente, mas pode ser ajustado a qualquer outra cor pelo usuário), depois é chamado o método para trazer todos os elementos do objeto para o primeiro plano, que discutiremos a seguir.


Método que coloca um objeto acima de todos os outros (em primeiro plano):

//+------------------------------------------------------------------+
//| Set the object above all the rest                                |
//+------------------------------------------------------------------+
void CTabControl::BringToTop(void)
  {
//--- Move all elements of the object to the foreground
   CForm::BringToTop();
//--- Get the index of the selected tab
   int selected=this.SelectedTabPageNum();
//--- Declare the pointers to tab header objects and tab fields
   CTabHeader *header=NULL;
   CTabField  *field=NULL;
//--- Get the list of all tab headers
   CArrayObj *list=this.GetListHeaders();
   if(list==NULL)
      return;
//--- In a loop by the list of tab headers,
   for(int i=0;i<list.Total();i++)
     {
      //--- get the next header, and if failed to get the object,
      //--- or this is the header of the selected tab, skip it
      header=list.At(i);
      if(header==NULL || header.PageNumber()==selected)
         continue;
      //--- bring the header to the foreground
      header.BringToTop();
      //--- get the tab field corresponding to the current header
      field=header.GetFieldObj();
      if(field==NULL)
         continue;
      //--- Hide the tab field
      field.Hide();
     }
//--- Get the pointer to the title of the selected tab
   header=this.GetTabHeader(selected);
   if(header!=NULL)
     {
      //--- bring the header to the front
      header.BringToTop();
      //--- get the tab field corresponding to the selected tab header
      field=header.GetFieldObj();
      //--- Display the tab field on the foreground
      if(field!=NULL)
         field.BringToTop();
     }
  }
//+------------------------------------------------------------------+

A lógica do método é detalhada nos comentários ao código. Em resumo, precisamos colocar em primeiro plano todos os cabeçalhos das guias e somente aquele campo da guia que está atualmente selecionado. Para fazer isso, colocamos em primeiro plano todos os cabeçalhos, exceto o da guia escolhida, e escondemos os campos dessas guias.
No final, colocamos em primeiro plano o cabeçalho da guia selecionada e seu campo.

Depois de tais ajustes, o controle TabControl funcionará sem a troca errada dos campos não selecionados da guia de estado oculto para o estado de primeiro plano e será exibido corretamente quando for trocado do estado oculto para o estado visível.


Na classe-coleção de elementos gráficos no arquivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh, no método que retorna um ponteiro para a forma sob o cursor, adicionamos uma verificação para o sinalizador de visibilidade do objeto para evitar a seleção de objetos ocultos :

//+------------------------------------------------------------------+
//| Return the pointer to the form located under the cursor          |
//+------------------------------------------------------------------+
CForm *CGraphElementsCollection::GetFormUnderCursor(const int id, 
                                                    const long &lparam, 
                                                    const double &dparam, 
                                                    const string &sparam,
                                                    ENUM_MOUSE_FORM_STATE &mouse_state,
                                                    long &obj_ext_id,
                                                    int &form_index)
  {
//--- Set the ID of the extended standard graphical object to -1 
//--- and the index of the anchor point managed by the form to -1
   obj_ext_id=WRONG_VALUE;
   form_index=WRONG_VALUE;
//--- Initialize the mouse status relative to the form
   mouse_state=MOUSE_FORM_STATE_NONE;
//--- Declare the pointers to graphical element collection class objects
   CGCnvElement *elm=NULL;
   CForm *form=NULL;
//--- Get the list of objects the interaction flag is set for (there should be only one object)
   CArrayObj *list=CSelect::ByGraphCanvElementProperty(GetListCanvElm(),CANV_ELEMENT_PROP_INTERACTION,true,EQUAL);
//--- If managed to obtain the list and it is not empty,
   if(list!=NULL && list.Total()>0)
     {
      //--- Get the only graphical element there
      elm=list.At(0);
      //--- If the element is a form object or its descendants
      if(elm.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE && elm.IsVisible())
        {
         //--- Assign the pointer to the element for the form object pointer
         form=elm;
         //--- Get the mouse status relative to the form
         mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
         //--- If the cursor is inside the form,
         if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
           {
            //--- Find the interaction object.
            //--- This will be either the found object or the same form
            form=this.SearchInteractObj(form,id,lparam,dparam,sparam);
            //--- Return the form object
            //Comment(form.TypeElementDescription()," ",form.Name());
            return form;
           }
        }
     }
//--- If there is no a single form object with a specified interaction flag,
//--- in the loop by all graphical element collection class objects
   int total=this.m_list_all_canv_elm_obj.Total();
   for(int i=0;i<total;i++)
     {
      //--- get the next element
      elm=this.m_list_all_canv_elm_obj.At(i);
      if(elm==NULL || !elm.IsVisible() || !elm.Enabled())
         continue;
      //--- if the obtained element is a form object or its descendants
      if(elm.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE)
        {
         //--- Assign the pointer to the element for the form object pointer
         form=elm;
         //--- Get the mouse status relative to the form
         mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
         //--- If the cursor is within the form, return the pointer to the form
         if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
           {
            //--- Find the interaction object.
            //--- This will be either the found object or the same form
            form=this.SearchInteractObj(form,id,lparam,dparam,sparam);
            //--- Return the form object
            //Comment(form.TypeElementDescription()," ",form.Name());
            return form;
           }
        }
     }
//--- ...
//--- ...

//---...
//--- Nothing is found - return NULL
   .return NULL;
  }
//+------------------------------------------------------------------+


No método de pós-processamento do antigo formulário ativo sob o cursor, também adicionamos uma verificação para o sinalizador de visibilidade e o processamento do objeto TabControl:

//+------------------------------------------------------------------+
//| 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 || !obj.IsVisible() || !obj.Enabled())
         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;
         if(elm.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL)
           {
            CTabControl *tab_ctrl=elm;
            CForm *selected=tab_ctrl.SelectedTabPage();
            if(selected!=NULL)
               elm=selected;
           }
         //--- 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());
  }
//+------------------------------------------------------------------+

Se o objeto atual no loop for um TabControl, precisamos encontrar apenas sua guia selecionada para processamento. Depois de localizá-la e atribuir um ponteiro a ela, apenas a guia selecionada será processada pelo manipulador de eventos do mouse.

Hoje, essas são todas as melhorias e mudanças nas classes da biblioteca.

Teste

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

O que vamos testar? Vamos criar um controle TabControl no painel principal. Em sua primeira guia, colocaremos todos os objetos das classes de objetos-botões de seta criados hoje. As setas horizontais duplas esquerda-direita terão uma largura menor que o padrão - para ver se podem ser redimensionadas. Vamos adicionar duas variáveis de entrada para as coordenadas de localização TabControl. Então podemos definir valores iniciais de coordenadas para que o objeto que criamos possa se estender além de seu contêiner para testar métodos de recorte de uma imagem que esteja fora do escopo. Entretanto, isto já será evidente pelos cabeçalhos das guias, que serão 11, e não caberão no controle quando colocados em uma única fileira.

Vamos declarar duas novas variáveis no bloco de variáveis de entrada:

//--- 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                          InpButtListMSelect   =  false;                  // ButtonListBox Button MultiSelect flag
sinput   bool                          InpListBoxMColumn    =  true;                   // ListBox MultiColumn flag
sinput   bool                          InpTabCtrlMultiline  =  true;                   // Tab Control Multiline 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
sinput   int                           InpTabControlX       =  10;                     // TabControl X coord
sinput   int                           InpTabControlY       =  20;                     // TabControl Y coord
//--- global variables
CEngine        engine;
color          array_clr[];
//+------------------------------------------------------------------+


No manipulador OnInit() do Expert Advisor, vamos escrever o seguinte código para criar um painel e anexar um controle TabControl a ele:

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

//--- Create WinForms Panel object
   CPanel *pnl=NULL;
   pnl=engine.CreateWFPanel("WFPanel",50,50,410,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false);
   if(pnl!=NULL)
     {
      pnl.Hide();
      Print(DFUN,"Panel visibility: ",pnl.IsVisible(),": ",pnl.TypeElementDescription()," ",pnl.Name());
      //--- Set Padding to 4
      pnl.SetPaddingAll(3);
      //--- 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);

      pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,InpTabControlX,InpTabControlY,pnl.Width()-30,pnl.Height()-40,clrNONE,255,true,false);
      CTabControl *tc=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0);
      if(tc!=NULL)
        {
         tc.SetTabSizeMode((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)InpTabPageSizeMode);
         tc.SetAlignment((ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment);
         tc.SetMultiline(InpTabCtrlMultiline);
         tc.SetHeaderPadding(6,0);
         tc.CreateTabPages(11,0,56,16,TextByLanguage("Вкладка","TabPage"));
         //---
         tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,10,10,15,15,clrNONE,255,true,false);
         tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,30,10,15,15,clrNONE,255,true,false);
         tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,50,10,15,15,clrNONE,255,true,false);
         tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,70,10,15,15,clrNONE,255,true,false);
         tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,10,30,13,13,clrNONE,255,true,false);
         tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,40,30,9,13,clrNONE,255,true,false);
        }
        
      //--- Redraw all objects according to their hierarchy
      pnl.Show();
      pnl.Redraw(true);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Imediatamente após a criação do painel, nós o ocultamos, e todas as outras construções nele ocorrem no modo oculto: criamos um controle TabControl no painel, criamos 11 guias, e em sua primeira guia criamos todos os objetos-botões com setas, cujas as classes escrevemos hoje. Depois de adicionar todos os elementos necessários, exibimos o painel e o redesenhamos.

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


O que vemos? O recorte fora do escopo funciona corretamente, os cabeçalhos fora do contêiner são recortados por sua borda, mas se você definir as coordenadas TabControl para que o elemento seja deixado fora do contêiner, ele também é recortado corretamente - o elemento em si é recortado pela borda do painel, e botões localizados no controle também são recortados pela borda do painel, não pela borda do contêiner. Aqui tudo funciona corretamente. Os botões horizontais esquerda-direita são menores do que a largura padrão (9 pixels), e ainda são exibidos corretamente.
O que mais está errado até agora? O objeto-sombra aparece diante do painel que o projeta. Isto será tratado à medida que a biblioteca se desenvolver.


O que virá a seguir?

No próximo artigo, continuaremos trabalhando no controle TabControl e geraremos a rolagem dos cabeçalhos das guias que se estendem além do controle.

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 ListBox e ButtonListBox do WinForms
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
DoEasy. Controles (Parte 15): Objeto WinForms TabControl - múltiplas fileiras de cabeçalhos de guias, métodos de manuseio de guias
DoEasy. Controles (Parte 16): Objeto WinForms TabControl - múltiplas fileiras de cabeçalhos de guias, modo esticamento de cabeçalhos consoante o tamanho do contêiner

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

Arquivos anexados |
MQL5.zip (4449.76 KB)
DoEasy. Controles (Parte 18): Preparando a funcionalidade para rolagem de guias no TabControl DoEasy. Controles (Parte 18): Preparando a funcionalidade para rolagem de guias no TabControl
Neste artigo colocaremos os botões de controle de rolagem de cabeçalhos no objeto WinForms TabControl caso a fileira de cabeçalhos não se ajuste ao tamanho do controle, e faremos o deslocamento da linha de cabeçalho quando clicamos no cabeçalho de uma guia cortada.
Aprendendo a construindo um EA que opera de forma automática (Parte 08): OnTradeTransaction Aprendendo a construindo um EA que opera de forma automática (Parte 08): OnTradeTransaction
Neste artigo, mostrei como você pode usar o sistema de tratamento de eventos, a fim de conseguir lidar com mais agilidade, e de uma forma melhor com questões envolvendo o sistema de ordens, a fim de deixar o EA mais rápido. Assim ele não precisará, ficar procurando informações a todo o momento.
Aprendendo a construindo um Expert Advisor que opera de forma automática (Parte 09): Automação (I) Aprendendo a construindo um Expert Advisor que opera de forma automática (Parte 09): Automação (I)
Criar um Expert Advisor automático não é uma tarefa muito complicada. Mas sem o devido conhecimento você pode acabar fazendo muita bobagem. Neste artigo iremos ver como se dá a construção do primeiro nível de automação. Então a questão aqui é aprender a criar o gatilho para promover o Breakeven e o Trailing Stop.
Como desenvolver um sistema de negociação baseado no indicador Awesome Oscilador Como desenvolver um sistema de negociação baseado no indicador Awesome Oscilador
Neste novo artigo da nossa série, nós aprenderemos sobre uma nova ferramenta técnica que pode ser útil em nossas negociações e ele é o indicador Awesome Oscillator (AO). Nós aprenderemos como desenvolver um sistema de negociação por este indicador.