English Русский 中文 Español Deutsch 日本語
preview
DoEasy. Controles (Parte 1): Primeiros passos

DoEasy. Controles (Parte 1): Primeiros passos

MetaTrader 5Exemplos | 29 julho 2022, 08:48
701 0
Artyom Trishkin
Artyom Trishkin

Conteúdo


Ideia

Este artigo é o início de um novo ciclo dedicado à criação de controles no estilo do Windows Forms. Naturalmente, não é possível reproduzir todos os elementos da lista de controles do MS Visual Studio. Mas faremos os elementos mais populares para a construção de uma GUI para programas MQL5.

A razão pela qual mudamos para um novo tópico sem terminar os anteriores foi a necessidade de utilizar esse tipo de controles para continuar o desenvolvimento dos objetos gráficos da biblioteca, que foram abordados nos tópicos anteriores. Tudo já está se tornando difícil sem a presença de controles. Por isso, vamos criar todos os controles possíveis no estilo do Windows Forms, para depois retornar aos tópicos anteriores, mas já com as ferramentas necessárias para continuar desenvolvendo-os.

Se abrirmos a caixa de ferramentas no MS Visual Studio, veremos uma lista de grupos de controle:

  • Todos os Windows Forms, aqui estão todas as formas disponíveis
  • Controles padrão
  • Contêineres
  • Menus e barras de ferramentas
  • Dados
  • Componentes
  • Imprimir
  • Caixa de diálogo

Esses não são todos os grupos disponíveis que estão na lista do painel de elementos do MS Visual Studio. Cada um desses grupos contém um grande conjunto de elementos. E não vamos fazer todos eles pela biblioteca. Mas é claro que faremos os mais essenciais.

Bem, começaremos a criar controles a partir do elemento "Panel", pois os elementos da janela serão construídos sobre este elemento, e o painel é um contêiner para colocar outros controles dentro dele, e o próprio painel com os elementos colocados nele pode ser colocado no painel pai, e este último pode ser um objeto dentro de outro painel, e assim por diante.

Já criamos uma classe de objeto-elemento gráfico posicionado na tela, que é a classe pai para todos os outros objetos gráficos baseados na classe CCanvas. E precisamente o objeto da classe-forma é construído com base no elemento gráfico. O objeto-forma já possui um conjunto de funções para manipulá-lo e movê-lo. O objeto de painel será criado com base no objeto-forma que recebe novas propriedades que ampliam sua funcionalidade.

O painel será dotado da capacidade de receber qualquer controle dentro dele, controle esse que criaremos como parte desta seção, que descreve a criação da biblioteca. Por sua vez, com base nisso, poderemos fazer as caixas de diálogo e janelas principais do programa que rodará no terminal.

Mas antes de prosseguir criando a classe-painel, vamos finalizar as classes de objetos de biblioteca já criadas. Pensando bem, ainda não terminamos de trabalhar nos tópicos anteriores, e, aos poucos, passo a passo, temos estado concluindo os objetos de biblioteca existentes e corrigindo os erros ao trabalhar com esses objetos. Francamente, durante o desenvolvimento deste tópico, vamos estar corrigindo erros nas classes já criadas e as melhorando, inclusive para trabalhar com objetos de controles que elaboraremos nesta nova seção.


Modificando as classes da biblioteca

Na última atualização da versão beta do terminal (3245), foram adicionadas novas propriedades para o símbolo e a conta:

  • MQL5: valor SYMBOL_SUBSCRIPTION_DELAY adicionado à enumeração ENUM_SYMBOL_INFO_INTEGER, que é a quantidade de atraso das cotações transmitidas consoante o símbolo.
    Ele é usado apenas para instrumentos que dependem de assinatura, geralmente ao transmitir dados em um modo de teste gratuito.
    A propriedade só pode ser solicitada para os símbolos selecionados na Observação do Mercado. Caso contrário, você receberá o erro ERR_MARKET_NOT_SELECTED (4302).

  • MQL5: propriedade ACCOUNT_HEDGE_ALLOWED adicionada à enumeração ENUM_ACCOUNT_INFO_INTEGER, que é a permissão para abrir posições opostas e ordens pendentes. É usada apenas para contas de cobertura, permitindo implementar as exigências de alguns reguladores, quando é proibido ter posições opostas na conta, mas é permitido ter várias posições de um símbolo em uma direção.
    Se esta opção estiver desabilitada, será proibido ter posições e ordens multidirecionais nas contas do mesmo instrumento ao mesmo tempo. Por exemplo, se houver uma posição de compra na conta, o usuário não poderá abrir uma posição de venda ou colocar uma ordem de venda pendente. Ao tentar fazer isso, o usuário receberá o erro TRADE_RETCODE_HEDGE_PROHIBITED.

Vamos adicionar essas propriedades aos objetos do símbolo e da conta da biblioteca.

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

//+------------------------------------------------------------------+
//| List of the library's text message indices                       |
//+------------------------------------------------------------------+
enum ENUM_MESSAGES_LIB
  {
   MSG_LIB_PARAMS_LIST_BEG=ERR_USER_ERROR_FIRST,      // Beginning of the parameter list
   MSG_LIB_PARAMS_LIST_END,                           // End of the parameter list
   MSG_LIB_PROP_NOT_SUPPORTED,                        // Property not supported
   MSG_LIB_PROP_NOT_SUPPORTED_MQL4,                   // Property not supported in MQL4
   MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_2155,          // Property not supported in MetaTrader 5 versions lower than 2155
   MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3245,          // Property not supported in MetaTrader 5 versions lower than 3245
   MSG_LIB_PROP_NOT_SUPPORTED_POSITION,               // Property not supported for position

...

  
   MSG_SYM_PROP_BACKGROUND_COLOR,                     // Background color of the symbol in Market Watch
   MSG_SYM_PROP_SUBSCRIPTION_DELAY,                   // Delay for quotes passed by symbol for instruments working on subscription basis
   //---

...

   MSG_ACC_PROP_FIFO_CLOSE,                           // Flag of a position closure by FIFO rule only
   MSG_ACC_PROP_HEDGE_ALLOWED,                        // Permission to open opposite positions and set pending orders
   //---
   MSG_ACC_PROP_BALANCE,                              // Account balance

...

   MSG_GRAPH_ELEMENT_TYPE_FORM,                       // Form
   MSG_GRAPH_ELEMENT_TYPE_WINDOW,                     // Window
   MSG_GRAPH_ELEMENT_TYPE_PANEL,                      // Panel control
   MSG_GRAPH_OBJ_BELONG_PROGRAM,                      // Graphical object belongs to a program
   MSG_GRAPH_OBJ_BELONG_NO_PROGRAM,                   // Graphical object does not belong to a program
//---

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

   {"Свойство не поддерживается в MetaTrader5 версии ниже 2155","The property is not supported in MetaTrader5, build lower than 2155"},
   {"Свойство не поддерживается в MetaTrader5 версии ниже 3245","The property is not supported in MetaTrader5, build lower than 3245"},
   {"Свойство не поддерживается у позиции","Property not supported for position"},

...

   {"Цвет фона символа в Market Watch","Background color of the symbol in Market Watch"},
   {"Размер задержки у котировок, передаваемых по символу, для инструментов, работающих по подписке","Delay size for quotes transmitted per symbol for instruments working by subscription"},
   {"Максимальный Bid за день","Maximum Bid of the day"},

...

   {"Тип торгового сервера","Type of trading server"},
   {"Признак закрытия позиций только по правилу FIFO","Sign of closing positions only according to the FIFO rule"},
   {"Разрешение на открытие встречных позиций и отложенных ордеров","Permission to open opposite positions and pending orders"},
   //---
   {"Баланс счета","Account balance"},

...

   {"Форма","Form"},
   {"Окно","Window"},
   {"Элемент управления \"Panel\"","Control element \"Panel\""},
   {"Графический объект принадлежит программе","The graphic object belongs to the program"},
   {"Графический объект не принадлежит программе","The graphic object does not belong to the program"},


A nível de mensagens de texto, qualquer objeto-painel que criarmos hoje terá configurações padrão. Esses parâmetros serão usados para qualquer texto exibido no painel ou em seus objetos descendentes, ou anexados ao painel se for um contêiner para esses objetos. Precisamos definir valores padrão para o nome, tamanho e cor da fonte.

Vamos abrir o arquivo \MQL5\Include\DoEasy\Defines.mqh e adicionar novas macro-substituições para estas propriedades de texto do painel:

//--- Canvas parameters
#define PAUSE_FOR_CANV_UPDATE          (16)                       // Canvas update frequency
#define CLR_CANV_NULL                  (0x00FFFFFF)               // Zero for the canvas with the alpha channel
#define CLR_FORE_COLOR                 (C'0x2D,0x43,0x48')        // Default color for texts of objects on canvas
#define DEF_FONT                       ("Calibri")                // Default font
#define DEF_FONT_SIZE                  (8)                        // Default font size
#define OUTER_AREA_SIZE                (16)                       // Size of one side of the outer area around the form workspace
//--- Graphical object parameters

Adicionamos um novo tipo à lista de tipos de objetos de biblioteca:

//+------------------------------------------------------------------+
//| 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_PANEL,                                      // WinForms Panel object type
//--- Animation
   OBJECT_DE_TYPE_GFRAME,                                         // "Single animation frame" object type
   OBJECT_DE_TYPE_GFRAME_TEXT,                                    // "Single text animation frame" object type
   OBJECT_DE_TYPE_GFRAME_QUAD,                                    // "Single rectangular animation frame" object type
   OBJECT_DE_TYPE_GFRAME_GEOMETRY,                                // "Single geometric animation frame" object type
   OBJECT_DE_TYPE_GANIMATIONS,                                    // "Animations" object type
//--- Managing graphical objects

Nesta seção (WinForms) adicionaremos mais e mais tipos de objetos à medida que forem criados.

Vamos adicionar uma nova propriedade à lista de propriedades inteiras da conta e aumentar o número de propriedades inteiras do objeto de 11 para 12:

//+------------------------------------------------------------------+
//| Account integer properties                                       |
//+------------------------------------------------------------------+
enum ENUM_ACCOUNT_PROP_INTEGER
  {
   ...
   ACCOUNT_PROP_FIFO_CLOSE,                                 // Flag of a position closure by FIFO rule only
   ACCOUNT_PROP_HEDGE_ALLOWED                               // Permission to open opposite positions and set pending orders
  };
#define ACCOUNT_PROP_INTEGER_TOTAL    (12)                  // Total number of integer properties
#define ACCOUNT_PROP_INTEGER_SKIP     (0)                   // Number of integer account properties not used in sorting
//+------------------------------------------------------------------+

Vamos adicionar esta nova propriedade à lista de critérios para classificação de contas:

//+------------------------------------------------------------------+
//| Possible account sorting criteria                                |
//+------------------------------------------------------------------+
#define FIRST_ACC_DBL_PROP            (ACCOUNT_PROP_INTEGER_TOTAL-ACCOUNT_PROP_INTEGER_SKIP)
#define FIRST_ACC_STR_PROP            (ACCOUNT_PROP_INTEGER_TOTAL-ACCOUNT_PROP_INTEGER_SKIP+ACCOUNT_PROP_DOUBLE_TOTAL-ACCOUNT_PROP_DOUBLE_SKIP)
enum ENUM_SORT_ACCOUNT_MODE
  {
   ... 
   SORT_BY_ACCOUNT_FIFO_CLOSE,                              // Sort by the flag of a position closure by FIFO rule only
   SORT_BY_ACCOUNT_HEDGE_ALLOWED,                           // Sort by permission to open opposite positions and set pending orders
//--- Sort by real properties
   SORT_BY_ACCOUNT_BALANCE = FIRST_ACC_DBL_PROP,            // Sort by an account balance in the deposit currency
   SORT_BY_ACCOUNT_CREDIT,                                  // Sort by credit in a deposit currency
   ... 
   SORT_BY_ACCOUNT_COMPANY                                  // Sort by a name of a company serving an account
  };
//+------------------------------------------------------------------+


Vamos adicionar uma nova propriedade à lista de propriedades inteiras do símbolo e aumentar o número de propriedades inteiras de 40 para 41:

//+------------------------------------------------------------------+
//| Symbol integer properties                                        |
//+------------------------------------------------------------------+
enum ENUM_SYMBOL_PROP_INTEGER
  {
   //--- ...
   SYMBOL_PROP_OPTION_MODE,                                 // Option type (from the ENUM_SYMBOL_OPTION_MODE enumeration)
   SYMBOL_PROP_OPTION_RIGHT,                                // Option right (Call/Put) (from the ENUM_SYMBOL_OPTION_RIGHT enumeration)
   SYMBOL_PROP_SUBSCRIPTION_DELAY,                          // Delay for quotes passed by symbol for instruments working on subscription basis
   //--- skipped property
   SYMBOL_PROP_BACKGROUND_COLOR                             // The color of the background used for the symbol in Market Watch
  }; 
#define SYMBOL_PROP_INTEGER_TOTAL    (41)                   // Total number of integer properties
#define SYMBOL_PROP_INTEGER_SKIP     (1)                    // Number of symbol integer properties not used in sorting
//+------------------------------------------------------------------+

Na enumeração de possíveis critérios para classificar símbolos, adicionamos a classificação por uma propriedade:

//+------------------------------------------------------------------+
//| Possible symbol sorting criteria                                 |
//+------------------------------------------------------------------+
#define FIRST_SYM_DBL_PROP          (SYMBOL_PROP_INTEGER_TOTAL-SYMBOL_PROP_INTEGER_SKIP)
#define FIRST_SYM_STR_PROP          (SYMBOL_PROP_INTEGER_TOTAL-SYMBOL_PROP_INTEGER_SKIP+SYMBOL_PROP_DOUBLE_TOTAL-SYMBOL_PROP_DOUBLE_SKIP)
enum ENUM_SORT_SYMBOLS_MODE
  {
   ... 
   SORT_BY_SYMBOL_OPTION_MODE,                              // Sort by option type (from the ENUM_SYMBOL_OPTION_MODE enumeration)
   SORT_BY_SYMBOL_OPTION_RIGHT,                             // Sort by option right (Call/Put) (from the ENUM_SYMBOL_OPTION_RIGHT enumeration)
   SORT_BY_SYMBOL_SUBSCRIPTION_DELAY,                       // Sort by delay for quotes passed by symbol for instruments working on subscription basis


Vamos adicionar um novo tipo de elemento à lista de tipos de elementos gráficos:

//+------------------------------------------------------------------+
//| The list of graphical element types                              |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_STANDARD,                       // Standard graphical object
   GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED,              // Extended standard graphical object
   GRAPH_ELEMENT_TYPE_ELEMENT,                        // Element
   GRAPH_ELEMENT_TYPE_SHADOW_OBJ,                     // Shadow object
   GRAPH_ELEMENT_TYPE_FORM,                           // Form
   GRAPH_ELEMENT_TYPE_WINDOW,                         // Window
   //--- WinForms
   GRAPH_ELEMENT_TYPE_PANEL,                          // Windows Forms Panel
  };
//+------------------------------------------------------------------+

Ao criar cada controle, o tipo de controle será inserido nesta subseção da enumeração (WinForms).

Se adicionarmos outro objeto ao objeto-painel que seja maior do que o painel contêiner e se o painel tiver permissão para redimensioná-lo automaticamente, haverá duas opções de redimensionamento:

  1. aumento do tamanho do painel
  2. aumento e diminuição do tamanho do painel

No primeiro caso, os lados do painel ultrapassados pelo objeto colocado dentro do painel serão ampliados para que caiba completamente. No segundo caso, além do aumento do tamanho do painel conforme indicado no ponto nº 1, os lados maiores que o tamanho do objeto colocado dentro dele também serão reduzidos.

Após listar os critérios de classificação dos objetos gráficos, inserimos uma nova enumeração na qual indicaremos os modos de redimensionamento automático do elemento de interface:

//+------------------------------------------------------------------+
//| Mode of automatic interface element resizing                     |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_AUTO_SIZE_MODE
  {
   CANV_ELEMENT_AUTO_SIZE_MODE_GROW,                  // Increase only
   CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,           // Increase and decrease
  };
//+------------------------------------------------------------------+

Ao colocar um objeto dentro de um painel, este objeto pode ser fixado em qualquer lado de seu contêiner, seja ele superior, inferior, direito e esquerdo. Com esta fixação, o lado mais próximo do objeto "gruda" no lado correspondente do objeto contêiner, e as dimensões do objeto anexado são esticadas para os lados do contêiner que são perpendiculares ao lado ao qual o objeto está fixado. Por exemplo, se um objeto estiver fixado à borda superior de seu contêiner, sua borda superior será encaixada na borda superior do contêiner e os lados esquerdo e direito do objeto serão esticados para os respectivos lados do contêiner. Com isto, a altura do objeto não muda. A fixação aos outros lados do contêiner funciona de maneira semelhante.

Logo após a enumeração dos modos de redimensionamento automático, escrevemos uma nova enumeração:

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

Aqui, além dos quatro métodos de fixação do objeto ao contêiner discutidos acima, existem mais dois: preenchimento, quando as dimensões do objeto são ajustadas às dimensões do contêiner, e sem fixação, quando o objeto é fixado apenas às coordenadas especificadas dentro de seu contêiner, sem alterar suas dimensões.

Se o controle possui funcionalidade para interação com o usuário, então sob certas condições tal objeto pode ser considerado não disponível para interação (o botão não está ativo, por exemplo). Para indicar a possibilidade de interação com o elemento, introduzimos uma nova propriedade do elemento gráfico.

Vamos adicionar uma nova propriedade à enumeração de propriedades inteiras do elemento gráfico e aumentar o número de propriedades inteiras do objeto de 24 para 25:

//+------------------------------------------------------------------+
//| Integer properties of the graphical element on the canvas        |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_PROP_INTEGER
  {
   ... 
   CANV_ELEMENT_PROP_ZORDER,                          // Priority of a graphical object for receiving the event of clicking on a chart
   CANV_ELEMENT_PROP_ENABLED,                         // Element availability flag
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (25)          // Total number of integer properties
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Number of integer properties not used in sorting
//+------------------------------------------------------------------+


Na lista de critérios para classificar elementos gráficos na tela, vamos adicionar a classificação por uma nova propriedade:

//+------------------------------------------------------------------+
//| 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_CANV_ELEMENT_ZORDER,                       // Sort by the priority of a graphical object for receiving the event of clicking on a chart
   SORT_BY_CANV_ELEMENT_ENABLED,                      // Sort by the element availability flag
   ... 
  };
//+------------------------------------------------------------------+


Como agora temos novas propriedades para o símbolo e para a conta, precisamos modificar as classes desses objetos.

Vamos abrir o arquivo \MQL5\Include\DoEasy\Objects\Accounts\Account.mqh e introduzir modificações na classe de objeto conta.

Vamos adicionar uma nova propriedade inteira à estrutura de propriedades do objeto:

//+------------------------------------------------------------------+
//| Account class                                                    |
//+------------------------------------------------------------------+
class CAccount : public CBaseObjExt
  {
private:
   struct SData
     {
      //--- Account integer properties
      ... 
      bool           fifo_close;                   // ACCOUNT_FIFO_CLOSE (The flag indicating that positions can be closed only by the FIFO rule)
      bool           hedge_allowed;                // ACCOUNT_HEDGE_ALLOWED (Permission to open opposite positions and set pending orders)
      ... 
     };
   SData             m_struct_obj;                                      // Account object structure
   uchar             m_uchar_array[];                                   // uchar array of the account object structure
   
//--- Object properties


Vamos adicionar um novo método ao bloco de métodos para acesso simplificado às propriedades do objeto conta:

//+------------------------------------------------------------------+
//| Methods of a simplified access to the account object properties  |
//+------------------------------------------------------------------+
//--- Return the account's integer properties
   ... 
   bool              FIFOClose(void)                                 const { return (bool)this.GetProperty(ACCOUNT_PROP_FIFO_CLOSE);                              }
   bool              IsHedge(void)                                   const { return this.MarginMode()==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING;                        }
   bool              HedgeAllowed(void)                              const { return (bool)this.GetProperty(ACCOUNT_PROP_HEDGE_ALLOWED);                           }
   ... 

O método simplesmente retorna o valor armazenado no array de propriedades do objeto.

No construtor de classe, inserimos este valor no array de propriedades do objeto:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CAccount::CAccount(void)
  {
   this.m_type=OBJECT_DE_TYPE_ACCOUNT;
//--- Initialize control data
   this.SetControlDataArraySizeLong(ACCOUNT_PROP_INTEGER_TOTAL);
   this.SetControlDataArraySizeDouble(ACCOUNT_PROP_DOUBLE_TOTAL);
   this.ResetChangesParams();
   this.ResetControlsParams();
   ... 
   this.m_long_prop[ACCOUNT_PROP_SERVER_TYPE]                        = (::TerminalInfoString(TERMINAL_NAME)=="MetaTrader 5" ? 5 : 4);
   this.m_long_prop[ACCOUNT_PROP_FIFO_CLOSE]                         = (#ifdef __MQL5__::TerminalInfoInteger(TERMINAL_BUILD)<2155 ? false : ::AccountInfoInteger(ACCOUNT_FIFO_CLOSE) #else false #endif );
   this.m_long_prop[ACCOUNT_PROP_HEDGE_ALLOWED]                      = (#ifdef __MQL5__::TerminalInfoInteger(TERMINAL_BUILD)<3245 ? false : ::AccountInfoInteger(ACCOUNT_HEDGE_ALLOWED) #else false #endif );  
   ... 
//--- Update the base object data and search for changes
   CBaseObjExt::Refresh();
  }
//+-------------------------------------------------------------------+

Aqui, apenas para MQL5: quando a versão do terminal é inferior a 3245 é porque não existe tal propriedade, portanto definimos o valor como false. Se a versão do terminal for maior ou igual a 3245, obtemos esse valor a partir da nova propriedade da conta e o gravamos no array de propriedades inteiras do objeto. Para MQL4, sempre inserimos false, indicando que não existem essas e muitas outras propriedades.

No método que atualiza todos os dados da conta, escrevemos o valor na nova propriedade do objeto exatamente da mesma forma:

//+------------------------------------------------------------------+
//| Update all account data                                          |
//+------------------------------------------------------------------+
void CAccount::Refresh(void)
  {
//--- Initialize event data
   this.m_is_event=false;
   this.m_hash_sum=0;
   ... 
   this.m_long_prop[ACCOUNT_PROP_FIFO_CLOSE]                            = (#ifdef __MQL5__::TerminalInfoInteger(TERMINAL_BUILD)<2155 ? false : ::AccountInfoInteger(ACCOUNT_FIFO_CLOSE) #else false #endif );
   this.m_long_prop[ACCOUNT_PROP_HEDGE_ALLOWED]                         = (#ifdef __MQL5__::TerminalInfoInteger(TERMINAL_BUILD)<3245 ? false : ::AccountInfoInteger(ACCOUNT_HEDGE_ALLOWED) #else false #endif );
   ... 
   CBaseObjExt::Refresh();
   this.CheckEvents();
  }
//+------------------------------------------------------------------+


No método que cria a estrutura do objeto conta, escrevemos um registro de dados em dois campos da estrutura:

//+------------------------------------------------------------------+
//| Create the account object structure                              |
//+------------------------------------------------------------------+
bool CAccount::ObjectToStruct(void)
  {
//--- Save integer properties
   ... 
   this.m_struct_obj.server_type=(int)this.ServerType();
   this.m_struct_obj.fifo_close=this.FIFOClose();
   this.m_struct_obj.hedge_allowed=this.HedgeAllowed();
   ... 
   //--- Save the structure to the uchar array
   ::ResetLastError();
   if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array))
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY),(string)::GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

Aqui, nos campos inteiros da estrutura do objeto, inserimos a nova propriedade e a propriedade da conta FIFOclose, que foi adicionada na versão 2155, porém, que aqui de alguma forma perdemos...

No método que cria o objeto conta a partir da estrutura, inserimos o registro de valor desde o campo da estrutura na propriedade do objeto para a nova propriedade:

//+------------------------------------------------------------------+
//| Create the account object from the structure                     |
//+------------------------------------------------------------------+
void CAccount::StructToObject(void)
  {
//--- Save integer properties
   ... 
   this.m_long_prop[ACCOUNT_PROP_FIFO_CLOSE]                         = this.m_struct_obj.fifo_close;
   this.m_long_prop[ACCOUNT_PROP_HEDGE_ALLOWED]                      = this.m_struct_obj.hedge_allowed;
   ... 
  }
//+------------------------------------------------------------------+


No método que retorna a descrição de propriedade inteira da conta, vamos escrever um bloco de código para exibir a descrição da nova propriedade:

//+------------------------------------------------------------------+
//| Return the description of the account integer property           |
//+------------------------------------------------------------------+
string CAccount::GetPropertyDescription(ENUM_ACCOUNT_PROP_INTEGER property)
  {
   return
     (
      ... 
      property==ACCOUNT_PROP_FIFO_CLOSE      ?  CMessage::Text(MSG_ACC_PROP_FIFO_CLOSE)+": "+
                                                   (this.GetProperty(property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))  :
      property==ACCOUNT_PROP_HEDGE_ALLOWED   ?  CMessage::Text(MSG_ACC_PROP_HEDGE_ALLOWED)+": "+
                                                   (this.GetProperty(property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))  :
      ""
     );
  }
//+------------------------------------------------------------------+


Vamos adicionar modificações semelhantes ao arquivo de objeto-símbolo em \MQL5\Include\DoEasy\Objects\Symbols\Symbol.mqh.

Na seção protegida da classe, vamos declarar um método que retorna o valor da nova propriedade do símbolo:

protected:
//--- Protected parametric constructor
                     CSymbol(ENUM_SYMBOL_STATUS symbol_status,const string name,const int index);

//--- Get and return integer properties of a selected symbol from its parameters
   ... 
   long              SymbolCalcMode(void)                const;
   long              SymbolSwapMode(void)                const;
   long              SymbolSubscriptionDelay(void)       const;
   long              SymbolDigitsLot(void);
   int               SymbolDigitsBySwap(void);
   ... 
//--- Search for a symbol and return the flag indicating its presence on the server
   bool              Exist(void)                         const;

public:


Na seção pública, no bloco de métodos para acesso simplificado às propriedades do objeto-símbolo, escrevemos um método que retorne o valor da nova propriedade:

//+------------------------------------------------------------------+
//| Methods for a simplified access to symbol object properties      |
//+------------------------------------------------------------------+
//--- Integer properties
   ... 
   ENUM_SYMBOL_OPTION_MODE OptionMode(void)                       const { return (ENUM_SYMBOL_OPTION_MODE)this.GetProperty(SYMBOL_PROP_OPTION_MODE);        }
   ENUM_SYMBOL_OPTION_RIGHT OptionRight(void)                     const { return (ENUM_SYMBOL_OPTION_RIGHT)this.GetProperty(SYMBOL_PROP_OPTION_RIGHT);      }
   long              SubscriptionDelay(void)                      const { return this.GetProperty(SYMBOL_PROP_SUBSCRIPTION_DELAY);                          }
//--- Real properties

Aqui nós simplesmente retornamos o valor escrito no array de propriedades inteiras do objeto-símbolo por meio do método GetProperty().

No construtor paramétrico privado, escrevemos a nova propriedade no array de propriedades inteiras do objeto:

//+------------------------------------------------------------------+
//| Closed parametric constructor                                    |
//+------------------------------------------------------------------+
CSymbol::CSymbol(ENUM_SYMBOL_STATUS symbol_status,const string name,const int index)
  {
//--- Save integer properties
   ... 
   this.m_long_prop[SYMBOL_PROP_BOOKDEPTH_STATE]                                    = this.m_book_subscribed;
   this.m_long_prop[SYMBOL_PROP_SUBSCRIPTION_DELAY]                                 = this.SymbolSubscriptionDelay();
   ... 
//--- Initializing default values of a trading object
   this.m_trade.Init(this.Name(),0,this.LotsMin(),5,0,0,false,this.GetCorrectTypeFilling(),this.GetCorrectTypeExpiration(),LOG_LEVEL_ERROR_MSG);
  }
//+------------------------------------------------------------------+


Método que retorna o tamanho do atraso para cotações transmitidas consoante o símbolo para instrumentos por assinatura:

//+------------------------------------------------------------------+
//| Return the delay size for quotes passed by symbol                |
//| in case of subscription-based symbols                            |
//+------------------------------------------------------------------+
long CSymbol::SymbolSubscriptionDelay(void) const
  {
   return
     (
      #ifdef __MQL5__ (::TerminalInfoInteger(TERMINAL_BUILD)>=3245 ? ::SymbolInfoInteger(this.m_name,SYMBOL_SUBSCRIPTION_DELAY) : 0)
      #else 0
      #endif 
     );
  }
//+------------------------------------------------------------------+

Aqui: para MQL5, se a versão do terminal for igual ou superior a 3245, retornamos o valor da nova propriedade do símbolo, caso contrário, retornamos zero.
Para MQL4 sempre retornamos zero
, porque não existe tal propriedade.

No método que retorna a descrição da propriedade inteira do símbolo, vamos escrever um bloco de código que retorna a descrição da nova propriedade:

//+------------------------------------------------------------------+
//| Return the description of the symbol integer property            |
//+------------------------------------------------------------------+
string CSymbol::GetPropertyDescription(ENUM_SYMBOL_PROP_INTEGER property)
  {
   return
     (
      ... 
      property==SYMBOL_PROP_BACKGROUND_COLOR    ?  CMessage::Text(MSG_SYM_PROP_BACKGROUND_COLOR)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
         #ifdef __MQL5__
         (this.GetProperty(property)==CLR_MW_DEFAULT || this.GetProperty(property)==CLR_NONE ?  ": ("+CMessage::Text(MSG_LIB_PROP_EMPTY)+")" : ": "+::ColorToString((color)this.GetProperty(property),true))
         #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif 
         )  :
      property==SYMBOL_PROP_SUBSCRIPTION_DELAY  ?  CMessage::Text(MSG_SYM_PROP_SUBSCRIPTION_DELAY)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
         #ifdef __MQL5__
         (::TerminalInfoInteger(TERMINAL_BUILD)<3245 ?  ": ("+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3245)+")" : ": "+(string)this.GetProperty(property))
         #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif 
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+


Para os esquemas de cores dos elementos GUI no arquivo \MQL5\Include\DoEasy\GraphINI.mqh, adicionamos o valor da cor do texto, aumentamos o número de parâmetros no esquema de cores de 4 para 5 e adicionamos valores de cor de texto aos arrays de valores de esquema de cores:

//+------------------------------------------------------------------+
//| List of indices of color scheme parameters                       |
//+------------------------------------------------------------------+
enum ENUM_COLOR_THEME_COLORS
  {
   COLOR_THEME_COLOR_FORM_BG,                   // Form background color
   COLOR_THEME_COLOR_FORM_FRAME,                // Form frame color
   COLOR_THEME_COLOR_FORM_RECT_OUTER,           // Form outline rectangle color
   COLOR_THEME_COLOR_FORM_SHADOW,               // Form shadow color
   COLOR_THEME_COLOR_FORM_TEXT,                 // Form text color
  };
#define TOTAL_COLOR_THEME_COLORS       (5)      // Number of parameters in the color theme
//+------------------------------------------------------------------+
//| The array containing color schemes                               |
//+------------------------------------------------------------------+
color array_color_themes[TOTAL_COLOR_THEMES][TOTAL_COLOR_THEME_COLORS]=
  {
//--- Parameters of the "Blue steel" color scheme
   {
      C'134,160,181',                           // Form background color
      C'134,160,181',                           // Form frame color
      clrDimGray,                               // Form outline rectangle color
      clrGray,                                  // Form shadow color
      C'0x3E,0x3E,0x3E',                        // Form text color
   },
//--- Parameters of the "Light cyan gray" color scheme
   {
      C'181,196,196',                           // Form background color
      C'181,196,196',                           // Form frame color
      clrGray,                                  // Form outline rectangle color
      clrGray,                                  // Form shadow color
      C'0x3E,0x3E,0x3E',                        // Form text color
   },
  };
//+------------------------------------------------------------------+


Na enumeração de estilos de bordas, adicionamos um campo indicando a ausência de «moldura»:

//+------------------------------------------------------------------+
//| Frame styles                                                     |
//+------------------------------------------------------------------+
enum ENUM_FRAME_STYLE
  {
   FRAME_STYLE_NONE,                            // No frame
   FRAME_STYLE_SIMPLE,                          // Simple frame
   FRAME_STYLE_FLAT,                            // Flat frame
   FRAME_STYLE_BEVEL,                           // Embossed (convex)
   FRAME_STYLE_STAMP,                           // Embossed (concave)
  };
//+------------------------------------------------------------------+


Vamos finalizar a classe de objeto base de todos os objetos gráficos da biblioteca no arquivo \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh.

Para poder especificar 0 ou NULL ao definir o identificador do gráfico atual em nossos programas em vez de especificar o valor numérico do identificador ou passar a função ChartID(), escreveremos uma verificação do valor passado ao método SetChartID():

public:
//--- Return the prefix name
   string            NamePrefix(void)                    const { return this.m_name_prefix;           }
//--- Set the values of the class variables
   void              SetObjectID(const long value)             { this.m_object_id=value;              }
   void              SetBelong(const ENUM_GRAPH_OBJ_BELONG belong){ this.m_belong=belong;             }
   void              SetTypeGraphObject(const ENUM_OBJECT obj) { this.m_type_graph_obj=obj;           }
   void              SetTypeElement(const ENUM_GRAPH_ELEMENT_TYPE type) { this.m_type_element=type;   }
   void              SetSpecies(const ENUM_GRAPH_OBJ_SPECIES species){ this.m_species=species;        }
   void              SetGroup(const int group)                 { this.m_group=group;                  }
   void              SetName(const string name)                { this.m_name=name;                    }
   void              SetDigits(const int value)                { this.m_digits=value;                 }
   void              SetChartID(const long chart_id)
                       { this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id);    }
   
//--- Set the "Background object" flag

Aqui verificamos o valor passado para o método, e, se for 0 ou NULL, atribuímos o valor do identificador do gráfico atual à variável, caso contrário, o valor passado ao método.

No método que retorna a descrição do tipo do elemento gráfico, escrevemos o retorno da descrição do objeto Panel:

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


Na classe do objeto do elemento gráfico, no arquivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, adicionamos um novo campo para a propriedade de acessibilidade do elemento à estrutura do objeto:

//+------------------------------------------------------------------+
//| Class of the graphical element object                            |
//+------------------------------------------------------------------+
class CGCnvElement : public CGBaseObj
  {
protected:
   CCanvas           m_canvas;                                 // CCanvas class object
   CPause            m_pause;                                  // Pause class object
   bool              m_shadow;                                 // Shadow presence
   color             m_chart_color_bg;                         // Chart background color
   uint              m_duplicate_res[];                        // Array for storing resource data copy

//--- Create (1) the object structure and (2) the object from the structure
   virtual bool      ObjectToStruct(void);
   virtual void      StructToObject(void);
   
private:
   struct SData
     {
      //--- Object integer properties
      ... 
      int            coord_act_bottom;                         // Bottom border of the element active area
      long           zorder;                                   // Priority of a graphical object for receiving the event of clicking on a chart
      bool           enabled;                                  // Element availability flag
      //--- Object real properties

      //--- Object string properties
      uchar          name_obj[64];                             // Graphical element object name
      uchar          name_res[64];                             // Graphical resource name
     };
   SData             m_struct_obj;                             // Object structure
   uchar             m_uchar_array[];                          // uchar array of the object structure


No bloco de métodos para acesso simplificado às propriedades do objeto, vamos escrever novos métodos para definir e retornar o valor de acessibilidade do elemento:

//+------------------------------------------------------------------+
//| Methods of simplified access to object properties                |
//+------------------------------------------------------------------+
   ... 
//--- Set (1) object movability, (2) activity, (3) interaction,
//--- (4) element ID, (5) element index in the list, (6) availability and (7) shadow flag
   void              SetMovable(const bool flag)               { this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,flag);                     }
   void              SetActive(const bool flag)                { this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,flag);                      }
   void              SetInteraction(const bool flag)           { this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,flag);                 }
   void              SetID(const int id)                       { this.SetProperty(CANV_ELEMENT_PROP_ID,id);                            }
   void              SetNumber(const int number)               { this.SetProperty(CANV_ELEMENT_PROP_NUM,number);                       }
   void              SetEnabled(const bool flag)               { this.SetProperty(CANV_ELEMENT_PROP_ENABLED,flag);                     }
   void              SetShadow(const bool flag)                { this.m_shadow=flag;                                                   } 
   ... 
//--- Return the (1) element movability, (2) activity, (3) interaction and (4) availability flag
   bool              Movable(void)                       const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_MOVABLE);             }
   bool              Active(void)                        const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE);              }
   bool              Interaction(void)                   const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_INTERACTION);         }
   bool              Enabled(void)                       const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_ENABLED);             }
//--- Return (1) the object name, (2) the graphical resource name, (3) the chart ID and (4) the chart subwindow index



Em um dos três métodos de limpeza de tela, removemos os valores padrão dos sinalizadores:

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

anteriormente o método era:

void              Erase(color &colors[],const uchar opacity,const bool vgradient=true,const bool cycle=false,const bool redraw=false);

e era inutilizável porque o compilador não podia escolher o método sobrecarregado correto.

No construtor paramétrico, escreveremos a verificação do valor passado do identificador do gráfico e a definição do sinalizador de disponibilidade do elemento e os valores padrão para a fonte:

//+------------------------------------------------------------------+
//| 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   name,
                           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.m_type=OBJECT_DE_TYPE_GELEMENT; 
   this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND);
   this.m_name=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
   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.m_color_bg=colour;
   this.m_opacity=opacity;
   if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,colour,opacity,redraw))
     {
      ... 
      this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,activity);                       // Element activity flag
      this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,false);                     // Flag of interaction with the outside environment
      this.SetProperty(CANV_ELEMENT_PROP_ENABLED,true);                          // Element availability flag
      this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge());                // Element right border
   ... 
  }
//+------------------------------------------------------------------+

No construtor protegido, faremos o mesmo:

//+------------------------------------------------------------------+
//| Protected constructor                                            |
//+------------------------------------------------------------------+
CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           const long    chart_id,
                           const int     wnd_num,
                           const string  name,
                           const int     x,
                           const int     y,
                           const int     w,
                           const int     h) : m_shadow(false)
  {
   this.m_type=OBJECT_DE_TYPE_GELEMENT; 
   this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND);
   this.m_name=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
   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.m_color_bg=CLR_CANV_NULL;
   this.m_opacity=0;
   if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,this.m_color_bg,this.m_opacity,false))
     {
      ... 
      this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,false);                          // Element activity flag
      this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,false);                     // Flag of interaction with the outside environment
      this.SetProperty(CANV_ELEMENT_PROP_ENABLED,true);                          // Element availability flag
      this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge());                // Element right border
      ... 

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


No método que cria a estrutura do objeto, escrevemos o preenchimento do novo campo da estrutura com um novo elemento sinalizador de acessibilidade:

//+------------------------------------------------------------------+
//| Create the object structure                                      |
//+------------------------------------------------------------------+
bool CGCnvElement::ObjectToStruct(void)
  {
//--- Save integer properties
   ... 
   this.m_struct_obj.active=(bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE);                   // Element activity flag
   this.m_struct_obj.interaction=(bool)this.GetProperty(CANV_ELEMENT_PROP_INTERACTION);         // Flag of interaction with the outside environment
   this.m_struct_obj.enabled=(bool)this.GetProperty(CANV_ELEMENT_PROP_ENABLED);                 // Element availability flag
   this.m_struct_obj.coord_act_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_X);          // X coordinate of the element active area
   this.m_struct_obj.coord_act_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y);          // Y coordinate of the element active area
   ... 
   return true;
  }
//+------------------------------------------------------------------+

 

No método que cria um objeto a partir de uma estrutura, escrevemos um registro na propriedade de acessibilidade do objeto de valor a partir do campo de estrutura correspondente:

//+------------------------------------------------------------------+
//| Create the object from the structure                             |
//+------------------------------------------------------------------+
void CGCnvElement::StructToObject(void)
  {
//--- Save integer properties
   ... 
   this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,this.m_struct_obj.active);                         // Element activity flag
   this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,this.m_struct_obj.interaction);               // Flag of interaction with the outside environment
   this.SetProperty(CANV_ELEMENT_PROP_ENABLED,this.m_struct_obj.enabled);                       // Element availability flag
   this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.m_struct_obj.coord_act_x);               // X coordinate of the element active area
   this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.m_struct_obj.coord_act_y);               // Y coordinate of the element active area
   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.m_color_bg=this.m_struct_obj.color_bg;                                                  // Element background color
   this.m_opacity=this.m_struct_obj.opacity;                                                    // Element opacity
   this.m_zorder=this.m_struct_obj.zorder;                                                      // Priority of a graphical object for receiving the on-chart mouse click event
   ... 
//--- 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
  }
//+------------------------------------------------------------------+


No método que cria o elemento-objeto gráfico, também inseriremos a verificação do valor passado do identificador do gráfico:

//+------------------------------------------------------------------+
//| Create the graphical element object                              |
//+------------------------------------------------------------------+
bool CGCnvElement::Create(const long chart_id,     // Chart ID
                          const int wnd_num,       // Chart subwindow
                          const string name,       // Element name
                          const int x,             // X coordinate
                          const int y,             // Y coordinate
                          const int w,             // Width
                          const int h,             // Height
                          const color colour,      // Background color
                          const uchar opacity,     // Opacity
                          const bool redraw=false) // Flag indicating the need to redraw
                         
  {
   ::ResetLastError();
   if(this.m_canvas.CreateBitmapLabel((chart_id==NULL ? ::ChartID() : chart_id),wnd_num,name,x,y,w,h,COLOR_FORMAT_ARGB_NORMALIZE))
     {
      this.Erase(CLR_CANV_NULL);
      this.m_canvas.Update(redraw);
      this.m_shift_y=(int)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_WINDOW_YDISTANCE,wnd_num);
      return true;
     }
   CMessage::ToLog(DFUN,::GetLastError(),true);
   return false;
  }
//+------------------------------------------------------------------+


Vamos finalizar a classe de objeto-forma no arquivo \MQL5\Include\DoEasy\Objects\Graph\Form.mqh.

Método de inicialização de variável privada

//+------------------------------------------------------------------+
//| Form object class                                                |
//+------------------------------------------------------------------+
class CForm : public CGCnvElement
  {
private:
   CArrayObj         m_list_elements;                          // List of attached elements
   CAnimations      *m_animations;                             // Pointer to the animation object
   CShadowObj       *m_shadow_obj;                             // Pointer to the shadow object
   CMouseState       m_mouse;                                  // "Mouse status" class object
   ENUM_MOUSE_FORM_STATE m_mouse_form_state;                   // Mouse status relative to the form
   ushort            m_mouse_state_flags;                      // Mouse status flags
   color             m_color_frame;                            // Form frame color
   int               m_frame_width_left;                       // Form frame width to the left
   int               m_frame_width_right;                      // Form frame width to the right
   int               m_frame_width_top;                        // Form frame width at the top
   int               m_frame_width_bottom;                     // Form frame width at the bottom
   int               m_offset_x;                               // Offset of the X coordinate relative to the cursor
   int               m_offset_y;                               // Offset of the Y coordinate relative to the cursor
   
//--- Initialize the variables
   void              Initialize(void);
//--- Reset the array size of (1) text, (2) rectangular and (3) geometric animation frames
   void              ResetArrayFrameT(void);
   void              ResetArrayFrameQ(void);
   void              ResetArrayFrameG(void);

vamos movê-lo para a seção protegida da classe, já que este método será necessário nos objetos descendentes, e vamos declarar um novo método para desinicializar o objeto da classe:

//--- Reset the array size of (1) text, (2) rectangular and (3) geometric animation frames
   void              ResetArrayFrameT(void);
   void              ResetArrayFrameQ(void);
   void              ResetArrayFrameG(void);
   
//--- Return the name of the dependent object
   string            CreateNameDependentObject(const string base_name)  const
                       { return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+1)+"_"+base_name;   }
  
//--- Create a new graphical object
   CGCnvElement     *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                      const int element_num,
                                      const string name,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool movable,
                                      const bool activity);

//--- Create a shadow object
   void              CreateShadowObj(const color colour,const uchar opacity);
   
protected:
//--- Initialize the variables
   void              Initialize(void);
   void              Deinitialize(void);
   
public:
//--- Return (1) the mouse status relative to the form, as well as (2) X and (3) Y coordinate of the cursor


Vamos renomear o método GetList(), que retorna uma lista de objetos anexados, para GetListElements(), que é mais apropriado à sua finalidade:

//--- Return (1) the list of attached objects and (2) the shadow object
   CForm            *GetObject(void)                                          { return &this;                  }
   CArrayObj        *GetListElements(void)                                    { return &this.m_list_elements;  }
   CGCnvElement     *GetShadowObj(void)                                       { return this.m_shadow_obj;      }
//--- Return the pointer to (1) the animation object, the list of (2) text and (3) rectangular animation frames


Na seção pública da classe , vamos declarar um método que adiciona um novo elemento anexado à lista de elementos de forma anexados:

//--- Create a new attached element
   bool              CreateNewElement(const int element_num,
                                      const string name,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool movable,
                                      const bool activity);
//--- Add a new attached element
   bool              AddNewElement(CGCnvElement *obj,const int x,const int y);

//--- Draw an object shadow
   void              DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=4);


Desde o destruidor de classe vamos transferir o bloco de código que remove todos os objetos dinâmicos da classe usados

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CForm::~CForm()
  {
   if(this.m_shadow_obj!=NULL)
      delete this.m_shadow_obj;
   if(this.m_animations!=NULL)
      delete this.m_animations;
  }
//+------------------------------------------------------------------+

no novo método de desinicialização:

//+------------------------------------------------------------------+
//| Deinitialize the variables                                       |
//+------------------------------------------------------------------+
void CForm::Deinitialize(void)
  {
   if(this.m_shadow_obj!=NULL)
      delete this.m_shadow_obj;
   if(this.m_animations!=NULL)
      delete this.m_animations;
  }  
//+------------------------------------------------------------------+

Chamaremos esse método no destruidor:

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CForm::~CForm()
  {
   this.Deinitialize();
  }
//+------------------------------------------------------------------+

Isso é feito para que possamos remover objetos dinâmicos desnecessários da classe pai das classes herdadas.

Método que adiciona um novo elemento anexado à lista de elementos do objeto anexados:

//+------------------------------------------------------------------+
//| Add a new attached element                                       |
//+------------------------------------------------------------------+
bool CForm::AddNewElement(CGCnvElement *obj,const int x,const int y)
  {
   if(obj==NULL)
      return false;
   this.m_list_elements.Sort(SORT_BY_CANV_ELEMENT_NAME_OBJ);
   int index=this.m_list_elements.Search(obj);
   if(index>WRONG_VALUE)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_OBJ_ALREADY_IN_LIST),": ",obj.NameObj());
      return false;
     }
   if(!this.m_list_elements.Add(obj))
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST),": ",obj.NameObj());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

O método recebe um ponteiro para o objeto a ser adicionado à lista de objetos anexados.
Classificamos a lista de elementos pelo nome do objeto especificado e procuramos esse objeto na lista.
Se um objeto com o mesmo nome já estiver na lista, notificamos sobre isso e retornamos false.
Se o objeto não puder ser colocado na lista de objetos anexados, informamos isso e retornamos false.
Como resultado, retornamos true.

O método que cria o novo elemento anexado agora chamará o método que adiciona o objeto criado à lista:

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CForm::CreateNewElement(const int element_num,
                             const string element_name,
                             const int x,
                             const int y,
                             const int w,
                             const int h,
                             const color colour,
                             const uchar opacity,
                             const bool movable,
                             const bool activity)
  {
   CGCnvElement *obj=this.CreateNewGObject(GRAPH_ELEMENT_TYPE_ELEMENT,element_num,element_name,x,y,w,h,colour,opacity,movable,activity);
   if(obj==NULL)
      return false;
   if(!this.AddNewElement(obj,x,y))
     {
      delete obj;
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

Anteriormente, era neste método que adicionávamos o objeto criado à lista, o que não era sensato, pois podemos adicionar elementos gráficos à lista de objetos anexados desde outros locais do programa, e não apenas ao criar o objeto.

No método que cria o objeto de sombra, definimos o sinalizador de locomobilidade de sombra como true. Isso tornou o objeto sombra móvel. Eu acho que isso não é verdade, e é melhor herdar o valor dessa propriedade do objeto para o qual o objeto de sombra é construído. Vamos corrigir isso:

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

Concluímos todas as etapas preparatórias.


Classe do objeto WinForms Panel

O objeto de painel será o herdeiro da classe de objeto-forma. Ou seja, ele terá todas as funcionalidades e propriedades da forma e, com isso, adicionaremos novas propriedades e novos recursos a ele. O painel terá a capacidade de receber outros objetos, poderá se redimensionar para caber consoante o conteúdo interno e habilitar a rolagem automática se o conteúdo interno ultrapassar a borda do painel.

Hoje faremos apenas um arranjo preliminar do objeto-painel, pois definiremos todas as suas propriedades e criaremos métodos para defini-las e retorná-las. Nos próximos artigos, adicionaremos gradualmente todas as funcionalidades do objeto-painel. Hoje, só podemos criar um objeto-painel usando seu construtor.

Para todos os controles WinForms, definimos um novo diretório na biblioteca.

Vamos criar a pasta \MQL5\Include\DoEasy\Objects\Graph\WForms\, para, nela, criarmos subpastas de acordo com os nomes dos grupos de controles no MS Visual Studio consoante o número que definimos no início do artigo:

  • \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\
  • \MQL5\Include\DoEasy\Objects\Graph\WForms\Components\
  • \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\
  • \MQL5\Include\DoEasy\Objects\Graph\WForms\Data\
  • \MQL5\Include\DoEasy\Objects\Graph\WForms\Dialogs\
  • \MQL5\Include\DoEasy\Objects\Graph\WForms\Menu & Toolbars\
  • \MQL5\Include\DoEasy\Objects\Graph\WForms\Printing     

Como o painel é um contêiner para outros objetos, o arquivo de classe deste objeto estará localizado na pasta correspondente \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\.

Vamos criar um novo arquivo Panel.mqh da classe CPanel herdada da classe CForm na pasta especificada, classe essa cujo arquivo deve ser incluído:

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

Na seção privada da classe, declaramos todas as variáveis e arrays necessários:

//+------------------------------------------------------------------+
//| Panel object class of WForms controls                            |
//+------------------------------------------------------------------+
class CPanel : public CForm
  {
private:
   color             m_fore_color;                                   // Default text color for all panel objects
   ENUM_FRAME_STYLE  m_border_style;                                 // Panel frame style
   bool              m_autoscroll;                                   // Auto scrollbar flag
   int               m_autoscroll_margin[2];                         // Array of fields around the control during an auto scroll
   bool              m_autosize;                                     // Flag of the element auto resizing depending on the content
   ENUM_CANV_ELEMENT_AUTO_SIZE_MODE m_autosize_mode;                 // Mode of the element auto resizing depending on the content
   ENUM_CANV_ELEMENT_DOCK_MODE m_dock_mode;                          // Mode of binding element borders to the container
   int               m_margin[4];                                    // Array of gaps of all sides between the fields of the current and adjacent controls
   int               m_padding[4];                                   // Array of gaps of all sides inside controls
public:

Para explicar o que são Margin, Padding e AutoSize, mostrarei um exemplo da documentação do MS Windows Forms .NET Framework 4.X:

... as três propriedades mais importantes: Margin , Padding e AutoSize encontradas em todos os controles do Windows Forms.

A propriedade Margin define a margem em torno do controle, essa margem supre com certo espaço entre as bordas do controle e as de outros controles.

A propriedade Padding define uma margem dentro de um controle, essa margem supre com certo espaço entre o conteúdo do controle (por exemplo, o valor da propriedade Text) e suas bordas.

A propriedade AutoSize comunica ao controle o dimensionamento automático de acordo com seu conteúdo. O tamanho não será menor que o valor da propriedade Size inicial e respeitará o valor de sua propriedade Padding.


Na seção pública da classe, vamos escrever os métodos para definir e retornar os valores de todas as variáveis de classe declaradas:

public:
//--- (1) Set and (2) return the default text color of all panel objects
   void              ForeColor(const color clr)                      { this.m_fore_color=clr;               }
   color             ForeColor(void)                           const { return this.m_fore_color;            }

//--- (1) Set and (2) return the frame style
   void              BorderStyle(const ENUM_FRAME_STYLE style)       { this.m_border_style=style;           }
   ENUM_FRAME_STYLE  BorderStyle(void)                         const { return this.m_border_style;          }

//--- (1) Set and (2) return the auto scrollbar flag
   void              AutoScroll(const bool flag)                     { this.m_autoscroll=flag;              }
   bool              AutoScroll(void)                                { return this.m_autoscroll;            }
//--- Set the (1) field width, (2) height, (3) the height of all fields around the control during auto scrolling
   void              AutoScrollMarginWidth(const int value)          { this.m_autoscroll_margin[0]=value;   }
   void              AutoScrollMarginHeight(const int value)         { this.m_autoscroll_margin[1]=value;   }
   void              AutoScrollMarginAll(const int value)
                       {
                        this.AutoScrollMarginWidth(value); this.AutoScrollMarginHeight(value);
                       }
//--- Return the (1) field width and (2) height around the control during auto scrolling
   int               AutoScrollMarginWidth(void)               const { return this.m_autoscroll_margin[0];  }
   int               AutoScrollMarginHeight(void)              const { return this.m_autoscroll_margin[1];  }
   
//--- (1) Set and (2) return the flag of the element auto resizing depending on the content
   void              AutoSize(const bool flag)                       { this.m_autosize=flag;                }
   bool              AutoSize(void)                                  { return this.m_autosize;              }
//--- (1) Set and (2) return the mode of the element auto resizing depending on the content
   void              AutoSizeMode(const ENUM_CANV_ELEMENT_AUTO_SIZE_MODE mode) { this.m_autosize_mode=mode; }
   ENUM_CANV_ELEMENT_AUTO_SIZE_MODE AutoSizeMode(void)         const { return this.m_autosize_mode;         }
   
//--- (1) Set and (2) return the mode of binding element borders to the container
   void              DockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode){ this.m_dock_mode=mode;               }
   ENUM_CANV_ELEMENT_DOCK_MODE DockMode(void)                  const { return this.m_dock_mode;             }

//--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides between the fields of this and another control
   void              MarginLeft(const int value)                     { this.m_margin[0]=value;              }
   void              MarginTop(const int value)                      { this.m_margin[1]=value;              }
   void              MarginRight(const int value)                    { this.m_margin[2]=value;              }
   void              MarginBottom(const int value)                   { this.m_margin[3]=value;              }
   void              MarginAll(const int value)
                       {
                        this.MarginLeft(value); this.MarginTop(value); this.MarginRight(value); this.MarginBottom(value);
                       }
//--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields of this and another control
   int               MarginLeft(void)                          const { return this.m_margin[0];             }
   int               MarginTop(void)                           const { return this.m_margin[1];             }
   int               MarginRight(void)                         const { return this.m_margin[2];             }
   int               MarginBottom(void)                        const { return this.m_margin[3];             }

//--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control
   void              PaddingLeft(const int value)                     { this.m_padding[0]=value;            }
   void              PaddingTop(const int value)                      { this.m_padding[1]=value;            }
   void              PaddingRight(const int value)                    { this.m_padding[2]=value;            }
   void              PaddingBottom(const int value)                   { this.m_padding[3]=value;            }
   void              PaddingAll(const int value)
                       {
                        this.PaddingLeft(value); this.PaddingTop(value); this.PaddingRight(value); this.PaddingBottom(value);
                       }
//--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields inside the control
   int               PaddingLeft(void)                          const { return this.m_padding[0];           }
   int               PaddingTop(void)                           const { return this.m_padding[1];           }
   int               PaddingRight(void)                         const { return this.m_padding[2];           }
   int               PaddingBottom(void)                        const { return this.m_padding[3];           }
   
//--- Constructors
                     CPanel(const long chart_id,
                           const int subwindow,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CPanel(const int subwindow,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CPanel(const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CPanel(const string name) : CForm(::ChartID(),0,name,0,0,0,0)
                       {
                        CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL);
                        this.m_type=OBJECT_DE_TYPE_GWF_PANEL; 
                        this.m_fore_color=CLR_FORE_COLOR;
                        this.MarginAll(3);
                        this.PaddingAll(0);
                        this.Initialize(); 
                       }
//--- Destructor
                    ~CPanel();
  };
//+------------------------------------------------------------------+

Para alguns deles, é possível definir simultaneamente a propriedade correspondente a cada lado do objeto.

Por exemplo, para o valor Margin no MS Visual Studio, é possível definir cada propriedade separadamente e todas as quatro ao mesmo tempo:


Temos quatro construtores de classe: com (1) ID do gráfico, subjanela do gráfico, nome do objeto e coordenadas com dimensões, (2) subjanela do gráfico do gráfico atual, nome do objeto e coordenadas com dimensões, (3) nome do objeto e coordenadas com dimensões, (4) ) nome do objeto com coordenadas e dimensões zero:

//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CPanel::CPanel(const long chart_id,
               const int subwindow,
               const string name,
               const int x,
               const int y,
               const int w,
               const int h) : CForm(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL);
   this.m_type=OBJECT_DE_TYPE_GWF_PANEL;
   this.m_fore_color=CLR_FORE_COLOR;
   this.MarginAll(3);
   this.PaddingAll(0);
   this.Initialize();
  }
//+------------------------------------------------------------------+
//| Current chart constructor specifying the subwindow               |
//+------------------------------------------------------------------+
CPanel::CPanel(const int subwindow,
               const string name,
               const int x,
               const int y,
               const int w,
               const int h) : CForm(::ChartID(),subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL);
   this.m_type=OBJECT_DE_TYPE_GWF_PANEL; 
   this.m_fore_color=CLR_FORE_COLOR;
   this.MarginAll(3);
   this.PaddingAll(0);
   this.Initialize();
  }
//+------------------------------------------------------------------+
//| Constructor on the current chart in the main chart window        |
//+------------------------------------------------------------------+
CPanel::CPanel(const string name,
               const int x,
               const int y,
               const int w,
               const int h) : CForm(::ChartID(),0,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL);
   this.m_type=OBJECT_DE_TYPE_GWF_PANEL; 
   this.m_fore_color=CLR_FORE_COLOR;
   this.MarginAll(3);
   this.PaddingAll(0);
   this.Initialize();
  }
//+------------------------------------------------------------------+

Em cada construtor, na linha de inicialização, passamos os parâmetros necessários para o construtor da classe pai.
Em seguida, no corpo do construtor, definimos o tipo de elemento gráfico, o tipo de objeto da biblioteca, a cor dos textos para o painel por padrão, definimos o valor Margin para todas as linhas como 3, Padding como 0 e inicializamos as variáveis da classe pai.

Isso será suficiente para simplesmente criar um objeto-painel em um gráfico no terminal. Todo o resto para o objeto "Painel" será feito nos próximos artigos.

No destruidor de classe, chamamos o método de desinicialização da classe pai:

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CPanel::~CPanel()
  {
   CForm::Deinitialize();
  }
//+------------------------------------------------------------------+


Agora precisamos modificar a classe-coleção de elementos gráficos \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh.

Em vez do arquivo do objeto-forma, incluiremos o arquivo objeto-painel:

//+------------------------------------------------------------------+
//|                                      GraphElementsCollection.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Graph\WForms\Containers\Panel.mqh"
#include "..\Objects\Graph\Standard\GStdVLineObj.mqh"

Como o objeto-painel é um herdeiro do objeto-forma, todos os objetos de sua hierarquia pai estarão visíveis na classe-coleção.

Na seção pública da classe, escreveremos dois métodos que retornam uma lista de elementos gráficos por ID de gráfico e ID de objeto e por ID de gráfico e nome de objeto:

//--- Return the list of graphical objects by chart ID and group
   CArrayObj        *GetListStdGraphObjByGroup(const long chart_id,const int group)
                       {
                        CArrayObj *list=GetList(GRAPH_OBJ_PROP_CHART_ID,0,chart_id,EQUAL);
                        return CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_GROUP,0,group,EQUAL);
                       }
//--- Return the list of graphical elements by chart and object IDs
   CArrayObj        *GetListCanvElementByID(const long chart_id,const int element_id)
                       {
                        CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL);
                        return CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL);;
                       }
//--- Return the list of graphical elements by chart ID and object name
   CArrayObj        *GetListCanvElementByName(const long chart_id,const string name)
                       {
                        CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL);
                        return CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,name,EQUAL);;
                       }
   
//--- Constructor

A lógica desses métodos foi repetidamente considerada por nós anteriormente. Aqui nós simplesmente filtramos a lista pelos parâmetros requeridos e retornamos a lista resultante, que deve conter um ponteiro para o objeto encontrado na lista-coleção.
Se o objeto não foi encontrado, os métodos retornarão NULL.

No final do corpo da classe, escreveremos métodos para criar objetos-elementos gráficos, formas e painéis:

//--- Create a graphical element object on canvas on a specified chart and subwindow
   int               CreateElement(const long chart_id,
                                   const int subwindow,
                                   const string name,
                                   const int x,
                                   const int y,
                                   const int w,
                                   const int h,
                                   const color clr,
                                   const uchar opacity,
                                   const bool movable,
                                   const bool activity,
                                   const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,activity,redraw);
                        if(!this.AddCanvElmToCollection(obj))
                          {
                           delete obj;
                           return WRONG_VALUE;
                          }
                        obj.Erase(clr,opacity,redraw);
                        return obj.ID();
                       }
//--- Create a graphical element object on canvas on a specified chart and subwindow with the vertical gradient filling
   int               CreateElementVGradient(const long chart_id,
                                            const int subwindow,
                                            const string name,
                                            const int x,
                                            const int y,
                                            const int w,
                                            const int h,
                                            color &clr[],
                                            const uchar opacity,
                                            const bool movable,
                                            const bool activity,
                                            const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr[0],opacity,movable,activity,redraw);
                        if(!this.AddCanvElmToCollection(obj))
                          {
                           delete obj;
                           return WRONG_VALUE;
                          }
                        obj.Erase(clr,opacity,true,false,redraw);
                        return obj.ID();
                       }
//--- Create a graphical element object on canvas on a specified chart and subwindow with the horizontal gradient filling
   int               CreateElementHGradient(const long chart_id,
                                            const int subwindow,
                                            const string name,
                                            const int x,
                                            const int y,
                                            const int w,
                                            const int h,
                                            color &clr[],
                                            const uchar opacity,
                                            const bool movable,
                                            const bool activity,
                                            const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr[0],opacity,movable,activity,redraw);
                        if(!this.AddCanvElmToCollection(obj))
                          {
                           delete obj;
                           return WRONG_VALUE;
                          }
                        obj.Erase(clr,opacity,false,false,redraw);
                        return obj.ID();
                       }
//--- Create a graphical element object on canvas on a specified chart and subwindow with the cyclic vertical gradient filling
   int               CreateElementVGradientCicle(const long chart_id,
                                                 const int subwindow,
                                                 const string name,
                                                 const int x,
                                                 const int y,
                                                 const int w,
                                                 const int h,
                                                 color &clr[],
                                                 const uchar opacity,
                                                 const bool movable,
                                                 const bool activity,
                                                 const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr[0],opacity,movable,activity,redraw);
                        if(!this.AddCanvElmToCollection(obj))
                          {
                           delete obj;
                           return WRONG_VALUE;
                          }
                        obj.Erase(clr,opacity,true,true,redraw);
                        return obj.ID();
                       }
//--- Create a graphical element object on canvas on a specified chart and subwindow with the cyclic horizontal gradient filling
   int               CreateElementHGradientCicle(const long chart_id,
                                                 const int subwindow,
                                                 const string name,
                                                 const int x,
                                                 const int y,
                                                 const int w,
                                                 const int h,
                                                 color &clr[],
                                                 const uchar opacity,
                                                 const bool movable,
                                                 const bool activity,
                                                 const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr[0],opacity,movable,activity,redraw);
                        if(!this.AddCanvElmToCollection(obj))
                          {
                           delete obj;
                           return WRONG_VALUE;
                          }
                        obj.Erase(clr,opacity,false,true,redraw);
                        return obj.ID();
                       }
 
//--- Create a graphical object form object on canvas on a specified chart and subwindow
   int               CreateForm(const long chart_id,
                                const int subwindow,
                                const string name,
                                const int x,
                                const int y,
                                const int w,
                                const int h,
                                const color clr,
                                const uchar opacity,
                                const bool movable,
                                const bool activity,
                                const bool shadow=false,
                                const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h);
                        if(!this.AddCanvElmToCollection(obj))
                          {
                           delete obj;
                           return WRONG_VALUE;
                          }
                        obj.SetID(id);
                        obj.SetActive(activity);
                        obj.SetMovable(movable);
                        obj.SetColorBackground(clr);
                        obj.SetColorFrame(clr);
                        obj.SetOpacity(opacity,false);
                        obj.SetShadow(shadow);
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity());
                        obj.Done();
                        obj.Erase(clr,opacity,redraw);
                        return obj.ID();
                       }
//--- Create a graphical object form object on canvas on a specified chart and subwindow with the vertical gradient filling
   int               CreateFormVGradient(const long chart_id,
                                         const int subwindow,
                                         const string name,
                                         const int x,
                                         const int y,
                                         const int w,
                                         const int h,
                                         color &clr[],
                                         const uchar opacity,
                                         const bool movable,
                                         const bool activity,
                                         const bool shadow=false,
                                         const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h);
                        if(!this.AddCanvElmToCollection(obj))
                          {
                           delete obj;
                           return WRONG_VALUE;
                          }
                        obj.SetID(id);
                        obj.SetActive(activity);
                        obj.SetMovable(movable);
                        obj.SetColorBackground(clr[0]);
                        obj.SetColorFrame(clr[0]);
                        obj.SetOpacity(opacity,false);
                        obj.SetShadow(shadow);
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity());
                        obj.Done();
                        obj.Erase(clr,opacity,true,false,redraw);
                        return obj.ID();
                       }
//--- Create a graphical object form object on canvas on a specified chart and subwindow with the horizontal gradient filling
   int               CreateFormHGradient(const long chart_id,
                                         const int subwindow,
                                         const string name,
                                         const int x,
                                         const int y,
                                         const int w,
                                         const int h,
                                         color &clr[],
                                         const uchar opacity,
                                         const bool movable,
                                         const bool activity,
                                         const bool shadow=false,
                                         const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h);
                        if(!this.AddCanvElmToCollection(obj))
                          {
                           delete obj;
                           return WRONG_VALUE;
                          }
                        obj.SetID(id);
                        obj.SetActive(activity);
                        obj.SetMovable(movable);
                        obj.SetColorBackground(clr[0]);
                        obj.SetColorFrame(clr[0]);
                        obj.SetOpacity(opacity,false);
                        obj.SetShadow(shadow);
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity());
                        obj.Done();
                        obj.Erase(clr,opacity,false,false,redraw);
                        return obj.ID();
                       }
//--- Create a graphical object form object on canvas on a specified chart and subwindow with the cyclic vertical gradient filling
   int               CreateFormVGradientCicle(const long chart_id,
                                              const int subwindow,
                                              const string name,
                                              const int x,
                                              const int y,
                                              const int w,
                                              const int h,
                                              color &clr[],
                                              const uchar opacity,
                                              const bool movable,
                                              const bool activity,
                                              const bool shadow=false,
                                              const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h);
                        if(!this.AddCanvElmToCollection(obj))
                          {
                           delete obj;
                           return WRONG_VALUE;
                          }
                        obj.SetID(id);
                        obj.SetActive(activity);
                        obj.SetMovable(movable);
                        obj.SetColorBackground(clr[0]);
                        obj.SetColorFrame(clr[0]);
                        obj.SetOpacity(opacity,false);
                        obj.SetShadow(shadow);
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity());
                        obj.Done();
                        obj.Erase(clr,opacity,true,true,redraw);
                        return obj.ID();
                       }
//--- Create a graphical object form object on canvas on a specified chart and subwindow with the cyclic horizontal gradient filling
   int               CreateFormHGradientCicle(const long chart_id,
                                              const int subwindow,
                                              const string name,
                                              const int x,
                                              const int y,
                                              const int w,
                                              const int h,
                                              color &clr[],
                                              const uchar opacity,
                                              const bool movable,
                                              const bool activity,
                                              const bool shadow=false,
                                              const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h);
                        if(!this.AddCanvElmToCollection(obj))
                          {
                           delete obj;
                           return WRONG_VALUE;
                          }
                        obj.SetID(id);
                        obj.SetActive(activity);
                        obj.SetMovable(movable);
                        obj.SetColorBackground(clr[0]);
                        obj.SetColorFrame(clr[0]);
                        obj.SetOpacity(opacity,false);
                        obj.SetShadow(shadow);
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity());
                        obj.Done();
                        obj.Erase(clr,opacity,false,true,redraw);
                        return obj.ID();
                       }
 
//--- Create graphical object WinForms Panel object on canvas on a specified chart and subwindow
   int               CreatePanel(const long chart_id,
                                 const int subwindow,
                                 const string name,
                                 const int x,
                                 const int y,
                                 const int w,
                                 const int h,
                                 const color clr,
                                 const uchar opacity,
                                 const bool movable,
                                 const bool activity,
                                 const bool shadow=false,
                                 const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CPanel *obj=new CPanel(chart_id,subwindow,name,x,y,w,h);
                        if(!this.AddCanvElmToCollection(obj))
                          {
                           delete obj;
                           return WRONG_VALUE;
                          }
                        obj.SetID(id);
                        obj.SetActive(activity);
                        obj.SetMovable(movable);
                        obj.SetColorBackground(clr);
                        obj.SetColorFrame(clr);
                        obj.SetOpacity(opacity,false);
                        obj.SetShadow(shadow);
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity());
                        obj.Done();
                        obj.Erase(clr,opacity,redraw);
                        return obj.ID();
                       }

  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+

Os métodos para criar elementos e formas são quase idênticos. A diferença entre eles está apenas na forma como o fundo é preenchido com cores. Ou é preenchido com uma única cor permanente ou de gradiente. Vários tipos de gradiente são usados para tal preenchimento: vertical, horizontal e vertical e horizontal cíclico. Logo após a criação do objeto, ele é adicionado à coleção-lista de elementos gráficos, e as propriedades mínimas necessárias são definidas para ele, propriedades essas passadas ao método quando ele é chamado.

No método que redefine os sinalizadores de interação para todas as formas, exceto o especificado, alteramos o tipo de objeto para elemento, pois os elementos gráficos são o objeto mínimo para a construção de elementos da GUI:

//+---------------------------------------------------------------------+
//| Reset all interaction flags for all forms except the specified one  |
//+---------------------------------------------------------------------+
void CGraphElementsCollection::ResetAllInteractionExeptOne(CGCnvElement *form_exept)
  {
   //--- 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 pointer to the object
      CGCnvElement *obj=this.m_list_all_canv_elm_obj.At(i);
      //--- if failed to receive the pointer, or it is not a form, or it is not a form whose pointer has been passed to the method, move on
      if(obj==NULL || obj.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_FORM || (obj.Name()==form_exept.Name() && obj.ChartID()==form_exept.ChartID()))
         continue;
      //--- Reset the interaction flag for the current form in the loop
      obj.SetInteraction(false);
     }
  }
//+------------------------------------------------------------------+


Como agora temos métodos para criar elementos gráficos, formas e painéis, não precisamos mais do método que adiciona o elemento gráfico à lista-coleção no arquivo \MQL5\Include\DoEasy\Engine.mqh do objeto principal da biblioteca, isto é, do CEngine, por isso vamos excluí-lo:

//--- Return the list of graphical elements on canvas
   CArrayObj           *GetListCanvElement(void)                           { return this.m_graph_objects.GetListCanvElm();                }
//--- Add the graphical element on canvas to the collection
   bool                 GraphAddCanvElmToCollection(CGCnvElement *element) { return this.m_graph_objects.AddCanvElmToCollection(element); }
   
//--- Fill in the array with IDs of the charts opened in the terminal
   void              GraphGetArrayChartsID(long &array_charts_id[])

Em seu lugar, escreveremos dois métodos que retornam uma lista de elementos gráficos por ID de gráfico e ID de objeto e uma lista de elementos gráficos por ID de gráfico e nome de objeto:

//--- Return the list of graphical elements on canvas
   CArrayObj           *GetListCanvElement(void)                           { return this.m_graph_objects.GetListCanvElm();                }
//--- Return the list of graphical elements by chart and object IDs
   CArrayObj           *GetListCanvElementByID(const long chart_id,const int element_id)
                          {
                           return this.m_graph_objects.GetListCanvElementByID(chart_id,element_id);
                          }
//--- Return the list of graphical elements by chart ID and object name
   CArrayObj           *GetListCanvElementByName(const long chart_id,const string name)
                          {
                           return this.m_graph_objects.GetListCanvElementByName(chart_id,name);
                          }
   
//--- Fill in the array with IDs of the charts opened in the terminal
   void              GraphGetArrayChartsID(long &array_charts_id[])

Esses dois métodos simplesmente retornam o resultado de uma solicitação feita a partir dos métodos de mesmo nome da classe-coleção de elementos gráficos que consideramos acima.

Agora estamos prontos para o teste.


Teste

Para efetuar o teste, vamos pegar o Expert Advisor do artigo anterior e salvá-lo na nova pasta \MQL5\Experts\TestDoEasy\Part101| com o novo nome TestDoEasyPart101.mq5.

O que vamos testar? Deixamos a criação de três objetos-formas vistas no último artigo, adicionamos três objetos-elementos e um objeto-painel.

Em cada um dos objetos, exibiremos o texto com o nome do tipo desse objeto e seu identificador na coleção de elementos gráficos.

No manipulador OnInit() , escrevemos a criação de todos os objetos usando os métodos adicionados hoje à classe-coleção:

//+------------------------------------------------------------------+
//| 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 form objects
   int obj_id=WRONG_VALUE;
   CArrayObj *list=NULL;
   CForm *form=NULL;
   for(int i=0;i<FORMS_TOTAL;i++)
     {
      obj_id=engine.GetGraphicObjCollection().CreateFormVGradient(ChartID(),0,"Form_0"+string(i+1),30,(form==NULL ? 100 : form.BottomEdge()+20),100,30,array_clr,245,true,true);
      list=engine.GetListCanvElementByID(ChartID(),obj_id);
      form=list.At(0);
      if(form==NULL)
         continue;
      //--- Set ZOrder to zero, display the text describing the gradient type and update the form
      //--- Text parameters: the text coordinates and the anchor point in the form center
      //--- Create a new text animation frame with the ID of 0 and display the text on the form
      form.SetZorder(0,false);
      form.TextOnBG(0,"Form: ID "+(string)form.ID()+", ZOrder "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,false);
     }
//--- Create four graphical elements
   CGCnvElement *elm=NULL;
   array_clr[0]=C'0x65,0xA4,0xA9';
   array_clr[1]=C'0x48,0x75,0xA2';
//--- Vertical gradient
   obj_id=engine.GetGraphicObjCollection().CreateElementVGradient(NULL,0,"CElmVG",form.RightEdge()+50,20,200,50,array_clr,127,true,true,true);
   list=engine.GetGraphicObjCollection().GetListCanvElementByID(ChartID(),obj_id);
   elm=list.At(0);
   if(elm!=NULL)
     {
      elm.SetFontSize(10);
      elm.Text(elm.Width()/2,elm.Height()/2,"Element: ID "+(string)elm.ID(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER);
      elm.Update();
     }
//--- Vertical cyclic gradient
   obj_id=engine.GetGraphicObjCollection().CreateElementVGradientCicle(NULL,0,"CElmVGC",form.RightEdge()+50,80, 200,50,array_clr,127,true,true,true);
   list=engine.GetGraphicObjCollection().GetListCanvElementByID(ChartID(),obj_id);
   elm=list.At(0);
   if(elm!=NULL)
     {
      elm.SetFontSize(10);
      elm.Text(elm.Width()/2,elm.Height()/2,"Element: ID "+(string)elm.ID(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER);
      elm.Update();
     }
//--- Horizontal gradient
   obj_id=engine.GetGraphicObjCollection().CreateElementHGradient(NULL,0,"CElmHG",form.RightEdge()+50,140,200,50,array_clr,127,true,true,true);
   list=engine.GetGraphicObjCollection().GetListCanvElementByID(ChartID(),obj_id);
   elm=list.At(0);
   if(elm!=NULL)
     {
      elm.SetFontSize(10);
      elm.Text(elm.Width()/2,elm.Height()/2,"Element: ID "+(string)elm.ID(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER);
      elm.Update();
     }
//--- Horizontal cyclic gradient
   obj_id=engine.GetGraphicObjCollection().CreateElementHGradientCicle(NULL,0,"CElmHGC",form.RightEdge()+50,200,200,50,array_clr,127,true,true,false);
   list=engine.GetGraphicObjCollection().GetListCanvElementByID(ChartID(),obj_id);
   elm=list.At(0);
   if(elm!=NULL)
     {
      elm.SetFontSize(10);
      elm.Text(elm.Width()/2,elm.Height()/2,"Element: ID "+(string)elm.ID(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER);
      elm.Update();
     }

//--- Create WinForms Panel object
   CPanel *pnl=NULL;
   obj_id=engine.GetGraphicObjCollection().CreatePanel(ChartID(),0,"WFPanel",elm.RightEdge()+50,50,150,150,array_clr[0],200,true,true,false,true);
   list=engine.GetListCanvElementByID(ChartID(),obj_id);
   pnl=list.At(0);
   if(pnl!=NULL)
     {
      pnl.SetFontSize(10);
      pnl.TextOnBG(0,"WinForm Panel: ID "+(string)pnl.ID(),4,2,FRAME_ANCHOR_LEFT_TOP,pnl.ForeColor(),pnl.Opacity());
      pnl.Update(true);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Os objetos-formas são preenchidos com um gradiente vertical e os objetos-elementos são preenchidos com seu próprio tipo de gradiente. O objeto-painel é preenchido com uma cor.

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


As formas reagem ao movimento do mouse e são sempre colocadas em cima dos objetos gráficos adicionados ao gráfico. Os preenchimentos de gradiente de objetos-elementos são desenhados corretamente e a cor do objeto-painel é única. Mas no nosso caso, nem os elementos nem o painel reagem ao mouse e ficam em segundo plano sob todos os objetos gráficos. Isso ocorre porque apenas manipulamos eventos de mouse para objetos-formas. E mesmo o fato de o painel ser essencialmente também uma forma não importa, pois no manipulador processamos explicitamente apenas a classe CForm. Tudo isso será corrigido mais adiante.


O que virá a seguir?

No próximo artigo, continuaremos desenvolvendo a classe do objeto WinForms Panel.

Todos os arquivos da versão atual da biblioteca, 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

*Último artigo da série anterior:

Gráficos na biblioteca DoEasy (Parte 100): Eliminando bugs ao trabalhar com objetos gráficos padrão estendidos


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

Arquivos anexados |
MQL5.zip (4381.34 KB)
DoEasy. Controles (Parte 2): Continuamos trabalhando na classe CPanel DoEasy. Controles (Parte 2): Continuamos trabalhando na classe CPanel
Neste artigo, vamos nos livrar de alguns erros ao trabalhar com elementos gráficos e continuar desenvolvendo o controle CPanel. Isto último irá se tratar de métodos para definir os parâmetros da fonte, que é usada por padrão para todos os objetos de texto do painel, objetos esses que, por sua vez, podem ser localizados nele no futuro.
Desenvolvendo um EA de negociação do zero (Parte 25): Dado robustez ao sistema (II) Desenvolvendo um EA de negociação do zero (Parte 25): Dado robustez ao sistema (II)
Aqui vamos terminar de dar uma alavancada na performance do EA ... então preparem-se para uma longa leitura. A primeira coisa que iremos fazer para dar robustez ao nosso EA será retirar tudo e absolutamente tudo que não faça parte do sistema de negociação de entro do código.
Desenvolvendo um EA de negociação do zero (Parte 26): Em direção ao futuro (I) Desenvolvendo um EA de negociação do zero (Parte 26): Em direção ao futuro (I)
Vamos levar nosso sistema de ordens para um outro patamar, mas antes temos algumas coisas a resolver. O problema é que existem questões que são dependentes de como você deseja operar e que tipo de coisa você estará fazendo no momento em que estiver operando.
Como desenvolver um sistema de negociação baseado no indicador Estocástico Como desenvolver um sistema de negociação baseado no indicador Estocástico
Neste artigo, nós continuamos nossa série de aprendizado - desta vez, nós aprenderemos como projetar um sistema de negociação usando um dos indicadores mais populares e úteis, que é o indicador Oscilador Estocástico, para construir um novo bloco em nosso conhecimento básico.