English Русский 中文 Español Deutsch 日本語
Interfaces Gráficas I: Preparação da Estrutura da Biblioteca (Capítulo 1)

Interfaces Gráficas I: Preparação da Estrutura da Biblioteca (Capítulo 1)

MetaTrader 5Exemplos | 20 julho 2016, 12:58
3 337 0
Anatoli Kazharski
Anatoli Kazharski

Conteúdo

 

Introdução

Este artigo é o começo de uma outra série sobre o desenvolvimento de interfaces gráficas. Atualmente, não há uma única biblioteca de código que permite a criação rápida e fácil de interfaces gráficas de alta qualidade dentro das aplicações em MQL. Me refiro as interfaces gráficas que estamos acostumados nos sistemas operacionais comuns.

O objetivo deste projeto consiste em fornecer ao usuário final esta oportunidade e mostrar como usar esta biblioteca de auxílio. Eu pretendi deixá-la o mais simples possível para facilitar os estudos e com a possibilidade para desenvolvimentos futuros.

Vale a pena mencionar a biblioteca de código de Dmitry Fedoseev, que é compartilhado por ele em uma série de seus artigos:

Embora ambas as bibliotecas, sugerida por Dmitry e a biblioteca padrão, possuírem suas vantagens, há também algumas desvantagens. Dmitry forneceu uma descrição detalhada sob a forma de um manual de instruções e uma variedade mais ampla de elementos de interface do usuário do que a biblioteca padrão. No entanto, eu não concordo com algumas partes da funcionalidade e do mecanismo de controle implementado. Alguns membros do fórum compartilharam suas ideias interessantes sobre o artigo em seus comentários e eu levei em consideração algumas delas.

A biblioteca padrão não apresentam uma descrição detalhada, e, na minha opinião, a sua estrutura está melhor implementada, mas não em um nível suficiente ainda. Somando a isso, a biblioteca padrão não contém muitos controles que podem ser necessários para a criação de interfaces gráficas. Em ambas as implementações, quando surge uma pergunta sobre a modernização e melhoria da qualidade, chega-se a conclusão de que eles devem ser construídos a partir do zero. Em meus artigos, eu fiz uma tentativa de reunir todas as vantagens e eliminar todas as desvantagens.

Anteriormente, eu escrevi dois artigos simples e introdutórios, que são relativos aos controles:

Estes são escritos num estilo processual e tem como objetivo a familiarização da linguagem MQL. Agora, está na hora de exibir uma estrutura mais complexa utilizando um exemplo de um projeto bem grande, que será implementada de forma orientada a objetos.

O que o leitor irá ganhar após a leitura desses artigos?

  • O objetivo deste projeto é criar interfaces o mais intuitivo possível para o usuário final. Será fornecido para os desenvolvedores dessas interfaces um modo fácil de estudo e uso da biblioteca de código, que poderá ainda ser desenvolvida posteriormente.
  • Esses desenvolvedores, que estão apenas começando a dar os primeiros passos na implementação de projetos de larga escala utilizando métodos orientados a objetos ou começando a aprender programação orientada a objetos, irão encontrar um material específico para estudo com vários exemplos do começo até sua implementação.
  • Os desenvolvedores mais experientes encontrarão uma implementação a mais da biblioteca de interfaces gráficas do usuário para que eles possam desenvolver as suas ideias. É bom quando há uma escolha.
  • Aqueles profissionais que podem criar tais bibliotecas e que, provavelmente, possuem bibliotecas semelhantes, podem criticar e desafiar a implementação sugerida. Eles podem ser capazes de sugerir uma abordagem mais adequada e eficiente para a implementação de tais projetos, que será interessante para os leitores menos experientes. Às vezes, tais discussões são tão interessantes quanto o próprio artigo.

Eu chamei o método de narração que será utilizado nesta série de artigos de "uma tentativa de imitação da sequência ideal". Muitas vezes, no processo de desenvolvimento de projetos de grande escala da vida real, a ordem sequencial das ações e a linha de pensamento são mais caóticas e consistem de muitos experimentos, tentativas e erros. Aqui, nós vamos ignorar todas essas complicações. Para aqueles que se depararem com projetos dessa escala pela primeira vez, recomenda-se repetir todas as ações para uma melhor compreensão do material, quando se estuda esta biblioteca e o processo de seu desenvolvimento. Os artigos desta série permitem representar uma linha de pensamento em uma sequência ideal, quando todas as respostas para a maioria das questões já estão apresentadas e todas as partes do projeto são criadas conforme necessário.

 

Lista de Controles

Então, como esta biblioteca deve ser? Como deve ser a estrutura de programação orientada a objetos (POO) do código desta biblioteca? Como o que começamos? Na verdade, há várias perguntas pela frente. Vamos definir quais controles e elementos de interface são necessários para a criação de um aplicativo MQL conveniente. Todo mundo possui suas próprias exigências e a escala das ideias variam de pessoa para pessoa. Para uma pessoa, um par de botões e caixas de seleção são suficientes. Outros têm em mente a utilização de interfaces multi-janelas, com a possibilidade de selecionar um conjunto de dados e controles.

Eu gostaria de mencionar que a aplicação descrita nesta série de artigos poderá ser usada como um produto final nas plataformas de negociação MetaTrader 4 e MetaTrader 5 logo após a sua publicação. Se compararmos este assunto através de algum ideal, então, é evidente que há espaço para crescimento. Após a publicação dos artigos desta série, compartilharei meus pensamentos de como dever ser uma implementação ideal de uma biblioteca para a criação de interfaces gráficas em MQL.

A versão inicial da biblioteca irá conter os elementos de interface a partir da lista abaixo. Alguns deles serão implementados de várias maneiras e apresentados como classes separadas do código, onde cada um deles será destinado para determinadas tarefas. Isso significa que alguns deles serão mais adequados para um tipo de tarefa e outros para um outro tipo.

  • Janela, na qual poderá ser adicionado vários controles em qualquer ordem e sequência.
  • Barra do menu com listas suspensas.
  • Menu contextual.
  • Barra de estado.
  • Botões:
    • Botão simples.
    • Botão de ícone com funcionalidade estendida.
    • Botão split com várias funções.
  • Grupos de botões:
    • Grupo dos botões simples.
    • Grupo dos botões do tipo radio.
    • Grupo dos botões de ícone com funcionalidade estendida.
  • Caixa de seleção (checkbox).
  • Spin edit.
  • Caixa de seleção com Spin edit.
  • Rolagem:
    • Rolagem vertical.
    • Rolagem horizontal.
  • Lista (list view).
  • Caixa de combinação (combobox) com uma lista suspensa.
  • Caixa de combinação (combobox) com caixa de seleção e uma lista suspensa.
  • Caixa de combinação (combobox) com uma lista suspensa e campo de texto.
  • Deslizador (slider) com campo de texto:
    • Deslizador (slider) de lado único.
    • Deslizador (slider) de lado duplo.
  • Calendário:
    • Calendário estático.
    • Calendário suspenso.
  • Dica (Tooltip).
  • Barra de progresso.
  • Tabelas:
    • Rótulo tabela.
    • Tabela de campos de texto.
    • Tabela em canvas.
  • Abas.
  • Exibição da lista em árvore ou hierárquica.

A lista apresentada acima contém controles, que compreendem de outros elementos desta lista para o seu funcionamento. Por exemplo, na enumeração acima, os controles do tipo caixa de combinação (combobox) incluem o controle lista (list view) e a lista, por sua vez, inclui uma rolagem vertical. As barras de rolagem horizontal e vertical também são incluídas em todos os tipos de tabelas. O calendário suspenso também inclui um calendário, que pode ser utilizado como um controle separado. A criação de cada elemento será aprofundada após termos definido a estrutura do projeto.

 

Classes Base da Biblioteca Padrão como Objetos Primitivos

De qualquer maneira, nós vamos utilizar algumas classes do código da biblioteca padrão. Elas pertencem as primitivas gráficas base que serão componentes de todos os controles. Cada uma dessas classes permite criar ou remover rapidamente um objeto gráfico, obter ou alterar qualquer uma de suas propriedades.

Os arquivos de classe para se trabalhar com as primitivas gráficas estão localizados:

  • MetaTrader 4: <Pasta de dados>\MQL4\Include\ChartObjects
  • MetaTrader 5: <Pasta de dados>\MQL5\Include\ChartObjects

O artigo Crie Seu Próprio Market Watch Usando as Classes da Biblioteca Padrão oferece uma descrição abrangente e exemplos de como usar essas classes, portanto, nós não vamos discuti-los detalhadamente aqui. Deixe-me apenas lembrá-lo que a classe base deste grupo de classe é a CObject. A classe CChartObject é derivada dela. Ela contém os métodos comuns que são aplicáveis ​​a todos os objetos gráficos. Todas as outras classes são derivadas da classe CChartObject, contendo métodos para gerenciar propriedades únicas para cada objeto gráfico único.

A estrutura comum de interconexões da biblioteca de classes padrão, que pertencem aos objetos gráficos, podem ser apresentadas da maneira abaixo. Vamos concordar que uma seta azul indica uma conexão de uma classe base com uma derivada.

Fig. 1. A estrutura comum de interconexões da biblioteca da classe padrão.

Fig. 1. A estrutura comum de interconexões da biblioteca da classe padrão.

À medida que avançamos com o desenvolvimento da biblioteca, mostraremos a sua estrutura nos artigos com diagramas parecidos. Por razões de brevidade, vamos usar uma versão mais curta, que é exibido na imagem abaixo. Isso significa que o último elemento no diagrama apresentado pode ser qualquer objeto gráfico (...) do diagrama acima.

Fig. 2. Versão reduzida da estrutura dos objetos gráficos da biblioteca padrão.

Fig. 2. Versão reduzida da estrutura dos objetos gráficos da biblioteca padrão

Para a construção de interfaces gráficas, nós vamos precisar de apenas algumas dessas classes:

A propriedade que eles compartilham em comum é que eles não estão ligados à uma escala de tempo, ou seja, eles não se deslocam junto com o gráfico quando este é deslocado. Logo, para cada uma destas primitivas, será necessário a criação de classes derivadas que armazenam algumas propriedades que serão consultadas frequentemente:

  • Coordenadas.
  • Tamanho.
  • Recuo a partir da borda do elemento contido, uma parte de sua formação.
  • Foco quando o cursor estiver pairando sobre o objeto.

Essas classes também devem conter os métodos correspondentes para obter e manter os valores dessas propriedades. Na realidade, os valores destas propriedades podem ser obtidos com a ajuda das classes base de níveis mais altos. Esta abordagem, no entanto, não será eficiente em termos de recursos.

 

Classes Derivadas dos Objetos Primitivos com Métodos Adicionais

Na pasta <pasta de dados>\MQL5\Include (para MetaTrader 4: <pasta de dados>\MQL4\Include) crie uma pasta chamada EasyAndFastGUI. Nós vamos colocar todos os arquivos de nossa biblioteca nesta pasta. Para encontrar uma pasta de dados, vá até o menu principal do MetaTrader ou do MetaEditor e selecione Arquivo > Abrir Pasta de Dados. Na pasta EasyAndFastGUI, todos os arquivos que pertencem à biblioteca para a criação de interfaces gráficas serão armazenados na sub-pasta Controls. Então, na sub-pasta Controls crie um arquivo chamado Objects.mqh. Ele irá conter as classes derivadas mencionados anteriormente.

No início do arquivo Objects.mqh, faça a inclusão dos arquivos necessários da biblioteca padrão:

//+------------------------------------------------------------------+
//|                                                      Objects.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include <ChartObjects\ChartObjectsBmpControls.mqh>
#include <ChartObjects\ChartObjectsTxtControls.mqh>

Todas as classes dos objetos listados acima serão do mesmo tipo e, portanto, eu mostrarei o código de apenas um deles. Na verdade, todos estes métodos se encaixam em uma única linha. Para economizar espaço no arquivo e apresentar as informações de forma mais compactado, tais métodos serão localizados diretamente no corpo da classe.

A inicialização de todas as variáveis ​​ocorre no construtor da classe. Parte delas está na lista de inicialização do construtor (antes do corpo da classe). Na realidade, isso não é importante já que durante o desenvolvimento de toda a biblioteca não houve um único caso sequer em que foi necessário a inicialização dos campos (variáveis) das classes derivadas e da base. É por isso que no corpo da classe todas as variáveis ​​podem ser inicializados ou apenas aqueles que exigem alguns comentários.

//+------------------------------------------------------------------+
//| Classe com propriedades adicionais para o objeto Rectagle Label  |
//+------------------------------------------------------------------+
class CRectLabel : public CChartObjectRectLabel
  {
protected:
   int               m_x;
   int               m_y;
   int               m_x2;
   int               m_y2;
   int               m_x_gap;
   int               m_y_gap;
   int               m_x_size;
   int               m_y_size;
   bool              m_mouse_focus;
public:
                     CRectLabel(void);
                    ~CRectLabel(void);
   //--- Coordenadas
   int               X(void)                      { return(m_x);           }
   void              X(const int x)               { m_x=x;                 }
   int               Y(void)                      { return(m_y);           }
   void              Y(const int y)               { m_y=y;                 }
   int               X2(void)                     { return(m_x+m_x_size);  }
   int               Y2(void)                     { return(m_y+m_y_size);  }
   //--- Recuos do ponto da borda (xy)
   int               XGap(void)                   { return(m_x_gap);       }
   void              XGap(const int x_gap)        { m_x_gap=x_gap;         }
   int               YGap(void)                   { return(m_y_gap);       }
   void              YGap(const int y_gap)        { m_y_gap=y_gap;         }
   //--- Tamanhos
   int               XSize(void)                  { return(m_x_size);      }
   void              XSize(const int x_size)      { m_x_size=x_size;       }
   int               YSize(void)                  { return(m_y_size);      }
   void              YSize(const int y_size)      { m_y_size=y_size;       }
   //--- Foco
   bool              MouseFocus(void)             { return(m_mouse_focus); }
   void              MouseFocus(const bool focus) { m_mouse_focus=focus;   }
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CRectLabel::CRectLabel(void) : m_x(0),
                               m_y(0),
                               m_x2(0),
                               m_y2(0),
                               m_x_gap(0),
                               m_y_gap(0),
                               m_x_size(0),
                               m_y_size(0),
                               m_mouse_focus(false)
  {
  }
//+------------------------------------------------------------------+
//| Destrutor                                                        |
//+------------------------------------------------------------------+
CRectLabel::~CRectLabel(void)
  {
  }

Um arquivo irá conter várias classes. Para uma navegação rápida entre eles, bem no início do arquivo, logo após os arquivos de inclusão da biblioteca padrão, vamos escrever o conteúdo do arquivo, que representará uma lista de classes sem um corpo:

//--- Lista de classes no arquivo para uma navegação rápida (Alt+G)
class CRectLabel;
class CEdit;
class CLabel;
class CBmpLabel;
class CButton;

Agora, a classe necessária no arquivo pode ser navegada da mesma maneira que você pode mover-se entre as funções e métodos, colocando o cursor no nome da classe na lista e pressionando Alt+G.

Na fase atual, este esquema pode ser representado da maneira que ele é exibido no diagrama abaixo. Aqui, o retângulo com uma estrutura azul espessa é o arquivo Objects.mqh com as classes que ele contém (retângulos com uma estrutura azul fina), que foram descritos acima. As estruturas em azul significam que todas as classes nesse arquivo são derivadas de uma das classes que representadas pelo retângulo CChartObject..., a partir da qual a última seta azul teve origem.

Fig. 3. Expansão da estrutura através da criação de classes derivadas para um objeto primitivo.

Fig. 3. Expansão da estrutura através da criação de classes derivadas para um objeto primitivo

A questão sobre primitivas gráficas está resolvida. Então, nós precisamos decidir como gerenciar esses objetos quando eles estão agrupados já que praticamente todos os controles consistem de vários objetos simples. Cada controle é único, mas todos eles possuem um conjunto comum de propriedades. Vamos criar uma classe base chamada CElement que conterá um conjunto comum de propriedades para cada controle.

 

Classe Base para todos os Controles

A classe CElement estará localizada no arquivo Element.mqh, e o arquivo Objects.mqh será incluído na classe mediante o comando #include:

//+------------------------------------------------------------------+
//|                                                      Element.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Objects.mqh"
//+------------------------------------------------------------------+
//| Classe base do controle                                          |
//+------------------------------------------------------------------+
class CElement
  {
public:
                     CElement(void);
                    ~CElement(void);
  };

Para economizar espaço do artigo, vou mostrar os métodos desta classe em grupos separados no corpo da classe. No final do artigo, você pode baixar uma versão completa da classe com todos os métodos que serão descritos adiante. Nós vamos manter esta abordagem para todas as outras classes.

Deve-se notar que nenhuma das classes no arquivo Objects.mqh são uma classe base para a classe CElement e nenhuma delas estarão entre os objetos incluídos nesta classe. Posteriormente, elas serão utilizadas como objetos em todas as classes derivadas da classe CElement e armazenada na classe base apenas como um array de ponteiros para objetos. Quando o arquivo Objects.mqh for incluído no arquivo Element.mqh, futuramente, não precisaremos incluí-lo em outros arquivos. Como resultado, em vez de incluir dois arquivos (Objects.mqh e Element.mqh), precisaremos incluir apenas um, que é o Element.mqh.

Na pasta Controls criaremos mais um arquivo, onde nós iremos armazenar nas diretivas #define algumas propriedades que são comuns a todo o programa:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//--- Nome da classe
#define CLASS_NAME ::StringSubstr(__FUNCTION__,0,::StringFind(__FUNCTION__,"::"))
//--- Nome do programa
#define PROGRAM_NAME ::MQLInfoString(MQL_PROGRAM_NAME)
//--- Tipo do programa
#define PROGRAM_TYPE (ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE)
//--- Prevenção de exceder o tamanho do array
#define PREVENTING_OUT_OF_RANGE __FUNCTION__," > Prevenção de exceder o tamanho do array."

//--- Fonte
#define FONT      ("Calibri")
#define FONT_SIZE (8)

Por favor, note que, antes das funções no código acima, há dois pontos duplos. Eles não são obrigatórios e tudo irá funcionar corretamente sem eles. Na programação, no entanto, são boas práticas colocar os dois pontos duplos antes das funções do sistema da linguagem. Isso torna inequívoco que a função é uma função do sistema.

Inclua o arquivo Defines.mqh no arquivo Objects.mqh, assim, ele estrará disponível para toda a cadeia de arquivos incluídos por cada um (Defines.mqh -> Objects.mqh -> Element.mqh):

//+------------------------------------------------------------------+
//|                                                      Objects.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Defines.mqh"
#include <ChartObjects\ChartObjectsBmpControls.mqh>
#include <ChartObjects\ChartObjectsTxtControls.mqh>

Agora, nós temos que definir quais propriedades devem ser comuns entre os controladores. Cada controle é um módulo separado do programa, que funcionará de forma independente de todos os outros módulos semelhantes. No entanto, já que alguns controles serão reunidos em grupos, que são elementos (compostos) de controles mais complexos, haverá situações em que os controles estarão enviando mensagens para o módulo principal do programa e para outros controles. Isso pode causar uma necessidade ocasional de definir se a mensagem foi de fato recebida de um controle de nosso programa, já pode haver várias aplicações em MQL executando no mesmo gráfico.

Também será necessário definir:

  • Nome do controle, que será o nome da classe.
  • Tipo do programa (EA, indicador).
  • Se o controle está atualmente oculto.
  • Se há uma parte suspensa do controle.
  • Se este é um controle ligado a um grupo de abas.
  • Se o cursor do mouse se encontra sobre o controle.
class CElement
  {
protected:
   //--- (1) Nome da classe e (2) do programa, (3) tipo do programa
   string            m_class_name;
   string            m_program_name;
   ENUM_PROGRAM_TYPE m_program_type;
   //--- Estado do Controle
   bool              m_is_visible;
   bool              m_is_dropdown;
   int               m_is_object_tabs;
   //--- Foco
   bool              m_mouse_focus;
   //---
public:
                     CElement(void);
                    ~CElement(void);
   //--- (1) Obtendo e definindo o nome da classe, (2) obtendo o nome do programa, 
   //    (3) obtendo o tipo do programa
   string            ClassName(void)                    const { return(m_class_name);           }
   void              ClassName(const string class_name)       { m_class_name=class_name;        }
   string            ProgramName(void)                  const { return(m_program_name);         }
   ENUM_PROGRAM_TYPE ProgramType(void)                  const { return(m_program_type);         }
   //--- Estado do Controle
   void              IsVisible(const bool flag)               { m_is_visible=flag;              }
   bool              IsVisible(void)                    const { return(m_is_visible);           }
   void              IsDropdown(const bool flag)              { m_is_dropdown=flag;             }
   bool              IsDropdown(void)                   const { return(m_is_dropdown);          }
   void              IsObjectTabs(const int index)            { m_is_object_tabs=index;         }
   int               IsObjectTabs(void)                 const { return(m_is_object_tabs);       }
   //--- Foco
   bool              MouseFocus(void)                   const { return(m_mouse_focus);          }
   void              MouseFocus(const bool focus)             { m_mouse_focus=focus;            }
  };

Já que pode haver vários controles idênticos na interface do programa, como vários botões ou caixas de seleção, cada um deles deve ter um número exclusivo ou identificador (id). Ao mesmo tempo, se um elemento de controle for composto por um array cheio de outros controles, cada um deles deve ter o seu número de índice.

class CElement
  {
protected:
   //--- Identificador e o índice do controle
   int               m_id;
   int               m_index;
   //---
public:
   //--- Definir e obter o identificador do controle
   void              Id(const int id)                         { m_id=id;                      }
   int               Id(void)                           const { return(m_id);                 }
   //--- Definir e obter o índice do controle
   void              Index(const int index)                   { m_index=index;                }
   int               Index(void)                        const { return(m_index);              }
  };

Como mencionado anteriormente, todos os objetos dos elementos gráficos do controle serão armazenados em um array do tipo CChartObject como ponteiros para esses objetos. Para isso, nós vamos precisar de um método para inserir os ponteiros do objeto no array, após eles terem sido criados com êxito. Nós também precisamos (1) obter um ponteiro a partir de um array tendo indicado o seu índice, (2) obter o tamanho do array de objetos e (3) esvaziar o buffer do array.

class CElement
  {
protected:
   //--- Array comum de ponteiros para todos os objetos deste controle
   CChartObject     *m_objects[];
   //---
public:
   //--- Obter o ponteiro do objeto pelo índice indicado
   CChartObject     *Object(const int index);
   //--- (1) Obter o número de objetos do controle, (2) esvaziar o array de objetos
   int               ObjectsElementTotal(void)          const { return(::ArraySize(m_objects)); }
   void              FreeObjectsArray(void)                   { ::ArrayFree(m_objects);         }
   //---
protected:
   //--- Método para adicionar os ponteiros de objetos primitivos em um array comum
   void              AddToArray(CChartObject &object);
  };
//+------------------------------------------------------------------+
//| Retorna o ponteiro do objeto de controle pelo índice             |
//+------------------------------------------------------------------+
CChartObject *CElement::Object(const int index)
  {
   int array_size=::ArraySize(m_objects);
//--- Verifica o tamanho do array de objetos
   if(array_size<1)
     {
      ::Print(__FUNCTION__," > No ("+m_class_name+") objects in this element!");
      return(NULL);
     }
//--- Corrige em caso do tamanho ter sido excedido
   int i=(index>=array_size)? array_size-1 : (index<0)? 0 : index;
//--- Retorna o ponteiro do objeto
   return(m_objects[i]);
  }
//+------------------------------------------------------------------+
//| Adiciona um ponteiro do objeto no array                          |
//+------------------------------------------------------------------+
void CElement::AddToArray(CChartObject &object)
  {
   int size=ObjectsElementTotal();
   ::ArrayResize(m_objects,size+1);
   m_objects[size]=::GetPointer(object);
  }

Cada controle terá o seu próprio manipulador de eventos para os eventos do gráfico e o seu próprio timer. Na classe CElement estes métodos serão virtuais uma vez que eles não podem ser universais, já que cada controle é único. Nós vamos discutir este assunto em detalhe quando desenvolvemos uma classe, que será um container para todos os objetos (controles). Os métodos a seguir também serão virtuais:

  • Mover um controle.
  • Exibir um controle.
  • Ocultar um controle.
  • Reiniciar (resetar). Usado quando é necessário que todos os objetos relevantes do controle estejam acima daqueles que são irrelevantes.
  • Remover todos os objetos do controle gráfico.
  • Definir os valores das prioridades para o clique esquerdo do mouse.
  • Zerar os valores das prioridades para o clique esquerdo do mouse.
class CElement
  {
public:
   //--- Manipulador de eventos do gráfico
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) {}
   //--- Timer
   virtual void      OnEventTimer(void) {}
   //--- Mover o controle
   virtual void      Moving(const int x,const int y) {}
   //--- (1) Exibir, (2) ocultar, (3) resetar, (4) remover
   virtual void      Show(void) {}
   virtual void      Hide(void) {}
   virtual void      Reset(void) {}
   virtual void      Delete(void) {}
   //--- (1) Definir (2), resetar as prioridades para o clique esquerdo do mouse
   virtual void      SetZorders(void) {}
   virtual void      ResetZorders(void) {}
  };

Você já viu que, ambas as classes das primitivas gráficas no arquivo Objects.mqh e na classe CElement, há propriedades e métodos que nos permitam obter as bordas do objeto. Por conseguinte, haverá uma oportunidade de descobrir se o cursor do mouse está na área de controle e se ele for superior a um ou a outro objeto primitivo individualmente. Por que nós precisamos disso? Isto irá nos permitir fazer uma interface gráfica altamente intuitiva para o usuário.

Quando o cursor está sobre um elemento da interface, a cor do seu fundo ou o quadro mudará indicando que este elemento da interface pode ser clicado, ou seja, ele vai reagir com um clique do mouse. Para implementar essa funcionalidade na classe CElement, nós vamos precisar dos métodos para se trabalhar com a cor. O array da cor será definido em uma delas e, para isso, apenas duas cores terão de ser passadas para este método. O gradiente será calculado a partir delas. O cálculo acontecerá apenas uma vez para cada objeto no momento da ligação do objeto com o gráfico. Para se trabalhar com a cor no segundo método, será feito apenas quando o array de cores estiver pronto, economizando significativamente os recursos.

Você pode criar um método para o cálculo do gradiente, mas nós estaremos usando um código de classe pronto, que poderá ser baixado na Base de Código. Serão utilizados muitos métodos da Base de Código neste projeto e em outras classes. Dmitry Fedoseev adicionou a sua versão da classe para trabalhar com as cores (IncColors) na Base de Código, mas eu sugiro utilizar a versão que eu corrigi um pouco. Ele pode ser baixado no final do artigo (Colors.mqh).

Esta classe (CColors) tem vários métodos para todas as ocasiões. A única mudança que eu introduzi foi a possibilidade de navegação rápida quando os nomes dos métodos estão no corpo da classe e os próprios métodos estão fora do mesmo. Isto irá permitir que você descubra o método necessário de forma mais rápida e eficiente e mova o conteúdo para o método e vice-versa, utilizando a combinação Alt+G. Este arquivo deve ser localizado na pasta EasyAndFastGUI. Nós vamos incluir o arquivo em nossa biblioteca de interface através do arquivo Element.mqh na pasta ..\EasyAndFastGUI\Controls. Como este arquivo estará localizado um nível acima da pasta, ele deve ser incluído no caminho mostrado abaixo:

//+------------------------------------------------------------------+
//|                                                      Element.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Objects.mqh"
#include "..\Colors.mqh"

Incluímos o objeto da classe CColors para a classe CElement e, em seguida, (1) adicionamos uma variável e um método para especificar o número das cores do gradiente e (2) os métodos para inicializar o array do gradiente, mudando a cor do objeto especificado:

class CElement
  {
protected:
   //--- Instância da classe para trabalhar com a cor
   CColors           m_clr;
   //--- Número de cores no gradiente
   int               m_gradient_colors_total;
   //---
public:
   //--- Definir o tamanho do gradiente
   void              GradientColorsTotal(const int total)     { m_gradient_colors_total=total;  }
   //---
protected:
   //--- Inicializar o array do gradiente
   void              InitColorArray(const color outer_color,const color hover_color,color &color_array[]);
   //--- Mudança da cor do objeto
   void              ChangeObjectColor(const string name,const bool mouse_focus,const ENUM_OBJECT_PROPERTY_INTEGER property,
                                       const color outer_color,const color hover_color,const color &color_array[]);
  };

Para inicializar o array do gradiente, use o método Gradient() da classe CColors. Deve ser passado os seguintes parâmetros para este método: (1) array de cores, que será utilizado para o cálculo do gradiente, (2) o array que contém a sequência de cores do gradiente e (3) o tamanho exigido do array, que é o suposto número de passos no gradiente.

//+------------------------------------------------------------------+
//| Inicializando o array do gradiente                               |
//+------------------------------------------------------------------+
void CElement::InitColorArray(const color outer_color,const color hover_color,color &color_array[])
  {
//--- Array das cores do gradiente
   color colors[2];
   colors[0]=outer_color;
   colors[1]=hover_color;
//--- Formação do array da cor
   m_clr.Gradient(colors,color_array,m_gradient_colors_total);
  }

No método, para mudar a cor do objeto haverá os parâmetros que permitirão especificar:

  • O nome do objeto.
  • Se o cursor do mouse se encontra sobre o controle.
  • Qual parte do objeto a cor deve ser alterada (por exemplo: fundo, quadro etc.).
  • À medida que o gradiente for se formado pelas duas cores, então, estas cores devem ser passadas como dois parâmetros para verificação.
  • O array do gradiente da cor, a qual é formada na função InitColorArray().

Por favor, veja abaixo os comentários adicionais no código do método ChangeObjectColor():

//+------------------------------------------------------------------+
//| Mudar a cor do objeto quando pairar o cursor sobre o objeto      |
//+------------------------------------------------------------------+
void CElement::ChangeObjectColor(const string name,const bool mouse_focus,const ENUM_OBJECT_PROPERTY_INTEGER property,
                                 const color outer_color,const color hover_color,const color &color_array[])
  {
   if(::ArraySize(color_array)<1)
      return;
//--- Obter a cor do objeto atual
   color current_color=(color)::ObjectGetInteger(m_chart_id,name,property);
//--- Se o cursor estiver sobre o objeto
   if(mouse_focus)
     {
      //--- Sair, se a cor especificada foi alcançada
      if(current_color==hover_color)
         return;
      //--- Mover do primeiro ao último
      for(int i=0; i<m_gradient_colors_total; i++)
        {
         //--- Se as cores não combinarem, vá para o seguinte
         if(color_array[i]!=current_color)
            continue;
         //---
         color new_color=(i+1==m_gradient_colors_total)? color_array[i] : color_array[i+1];
         //--- Mudar a cor
         ::ObjectSetInteger(m_chart_id,name,property,new_color);
         break;
        }
     }
//--- Se o cursor não está na área do objeto
   else
     {
      //--- Sair, se a cor especificada foi alcançada
      if(current_color==outer_color)
         return;
      //--- Mover do último para o primeiro
      for(int i=m_gradient_colors_total-1; i>=0; i--)
        {
         //--- Se as cores não combinarem, vá para o seguinte
         if(color_array[i]!=current_color)
            continue;
         //---
         color new_color=(i-1<0)? color_array[i] : color_array[i-1];
         //--- Mudar a cor
         ::ObjectSetInteger(m_chart_id,name,property,new_color);
         break;
        }
     }
  }

Outras propriedades comuns de todos os controles são os pontos âncoras do objeto e o canto do gráfico.

class CElement
  {
protected:
   //--- Canto do gráfico e ponto âncora
   ENUM_BASE_CORNER  m_corner;
   ENUM_ANCHOR_POINT m_anchor;
  }

Nós completamos a criação da classe CElement. No final do artigo, você pode baixar a versão completa desta classe. Atualmente, a estrutura da biblioteca se parece com o diagrama abaixo. Vamos supor, que as setas amarelas indiquem o arquivo de inclusão. Se ele contém uma classe, porém, esta não será uma classe base para as classes que se encontram no arquivo em que ela está incluída. Será utilizado como um objeto incluído na classe da mesma maneira como foi mostrado acima entre as classes CElement e CColors.

Fig. 4. Incluindo a classe CColors para trabalhar com a cor.

Fig. 4. Classe base para os controles CElement

 

Classes Base para uma Aplicação com uma Interface Gráfica

Antes de começarmos a criar os elementos de interface, nós devemos definir como a será implementada a interação entre os objetos. O esquema deve ser definido de tal maneira que o acesso a cada objeto poderá ser estabelecido a partir de uma classe, onde os objetos não são apenas armazenados, mas também categorizados. Assim, há uma oportunidade não só saber quantos e quais objetos estarão presentes neste container, mas também como gerenciá-los.

Colocar todas as funcionalidades em uma classe só não é muito conveniente já que esta ficará sobrecarregada. Tais classes (objetos) são chamadas de objetos mestre, que são anti-padrões da programação orientada a objetos, porque eles têm muitas tarefas. A medida que a estrutura do projeto cresce, será difícil introduzir alterações ou adições. Portanto, os objetos serão armazenados separadamente da classe onde os eventos são manipulados. Os objetos estarão contidos na classe base CWndContainer e os eventos serão tratados na classe CWndEvents, que é derivada dela.

Agora, vamos criar as classes CWndContainer e CWndEvents. À medida que nós criamos todos os controles listados no início do artigo, nós estaremos enchendo essas classes com as funcionalidades necessárias. Por enquanto, nós vamos determinar a estrutura geral do projeto.

Na pasta Controls, crie os arquivos WndContainer.mqh e WndEvents.mqh. A classe CWndContainer estará vazia, já que não criamos nenhum controle.

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Classe para armazenar todos os objetos da interface              |
//+------------------------------------------------------------------+
class CWndContainer
  {
protected:
                     CWndContainer(void);
                    ~CWndContainer(void);
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CWndContainer::CWndContainer(void)
  {
  }
//+------------------------------------------------------------------+
//| Destrutor                                                        |
//+------------------------------------------------------------------+
CWndContainer::~CWndContainer(void)
  {
  }
//+------------------------------------------------------------------+

O arquivo WndContainer.mqh deve ser incluído no arquivo WndEvents.mqh já que a classe CWndEvents será derivada da classe CWndContainer:

//+------------------------------------------------------------------+
//|                                                    WndEvents.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "WndContainer.mqh"
//+------------------------------------------------------------------+
//| Classe para a manipulação de eventos                             |
//+------------------------------------------------------------------+
class CWndEvents : public CWndContainer
  {
protected:
                     CWndEvents(void);
                    ~CWndEvents(void);
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CWndEvents::CWndEvents(void)
  {
  }
//+------------------------------------------------------------------+
//| Destrutor                                                        |
//+------------------------------------------------------------------+
CWndEvents::~CWndEvents(void)
  {
  }
//+------------------------------------------------------------------+

As classes CWndContainer e CWndEvents serão as classes base para qualquer aplicação em MQL que requer uma interface gráfica.

Para mais testes desta biblioteca durante o processo de seu desenvolvimento, nós vamos criar um EA. Ele tem que ser criado em uma pasta separada, pois além do arquivo do programa principal, haverá o arquivo de inclusão Program.mqh com a classe do nosso programa (CProgram). Esta classe será derivada da classe CWndEvents.

//+------------------------------------------------------------------+
//|                                                      Program.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include <EasyAndFastGUI\Controls\WndEvents.mqh>
//+------------------------------------------------------------------+
//| Classe para a criação do painel de negociação                    |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
public:
                     CProgram(void);
                    ~CProgram(void);
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CProgram::CProgram(void)
  {
  }
//+------------------------------------------------------------------+
//| Destrutor                                                        |
//+------------------------------------------------------------------+
CProgram::~CProgram(void)
  {
  }
//+------------------------------------------------------------------+

Nós vamos precisar dos métodos para a manipulação de eventos, que mais tarde será chamado no arquivo principal do programa, que está na função principal dos manipuladores de eventos da aplicação em MQL:

class CProgram : public CWndEvents
  {
public:
   //--- Inicialização/desinicialização
   void              OnInitEvent(void);
   void              OnDeinitEvent(const int reason);
   //--- Timer
   void              OnTimerEvent(void);
   //---
protected:
   //--- Manipulador de eventos virtual do gráfico
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
  };

O timer e o manipulador de eventos do gráfico também devem ser criados no nível mais elevado da classe base CWndEvents:

class CWndEvents : public CWndContainer
  {
protected:
   //--- Timer
   void              OnTimerEvent(void);
   //--- Manipulador de eventos virtual do gráfico
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) {}
  };

Note que no código acima, tanto a classe base CWndEvents e a classe derivada CProgram, o manipulador do gráfico OnEvent foi declarado como virtual. Dito isto, na classe CWndEvents , este método é fictício "{}". Isto irá nos permitir dirigir o fluxo de eventos da classe base para uma derivada quando necessário. Os métodos virtuais OnEvent() nestas classes são destinados para uso interno. Para as chamadas do arquivo do programa principal, um outro método da classe CWndEvents será usado. Vamos chamá-la de ChartEvent(). Nós também vamos criar os métodos auxiliares para cada tipo de evento principal, deixando o código mais claro e legível.

Será necessário um método para verificar os eventos nos controles junto com os métodos auxiliares, que irão incluir a verificação de eventos personalizados. Vamos chamá-lo de CheckElementsEvents(). Ele está destacado abaixo na cor verde:

class CWndEvents : public CWndContainer
  {
public:
   //--- Manipuladores de eventos do gráfico
   void              ChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //---
private:
   void              ChartEventCustom(void);
   void              ChartEventClick(void);
   void              ChartEventMouseMove(void);
   void              ChartEventObjectClick(void);
   void              ChartEventEndEdit(void);
   void              ChartEventChartChange(void);
   //--- Verificação dos eventos nos controles
   void              CheckElementsEvents(void);
  };

Os métodos auxiliares serão utilizados dentro do método ChartEvent () e apenas na classe CWndEvents. Para evitar passar os mesmos parâmetros a elas, nós vamos criar variáveis ​​semelhantes na forma de membros de classe e também um método para a sua inicialização, que será utilizado no início do método ChartEvent(). Eles serão localizados na seção privada (private), já que eles serão usados somente nesta classe.

class CWndEvents : public CWndContainer
  {
private:
   //--- Parâmetros do evento
   int               m_id;
   long              m_lparam;
   double            m_dparam;
   string            m_sparam;
   //--- Inicialização dos parâmetros do evento
   void              InitChartEventsParams(const int id,const long lparam,const double dparam,const string sparam);
  };
//+------------------------------------------------------------------+
//| Inicialização das variáveis do evento                            |
//+------------------------------------------------------------------+
void CWndEvents::InitChartEventsParams(const int id,const long lparam,const double dparam,const string sparam)
  {
   m_id     =id;
   m_lparam =lparam;
   m_dparam =dparam;
   m_sparam =sparam;
  }

Agora, no arquivo principal do programa, (1) inclua o arquivo que contém a classe CProgram, (2) crie sua instância e (3) conecte-o com as principais funções do programa:

//+------------------------------------------------------------------+
//|                                                  TestLibrary.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2015, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
//--- Incluindo a classe do painel de negociação
#include "Program.mqh"
CProgram program;
//+------------------------------------------------------------------+
//| Função de inicialização do Expert                                |
//+------------------------------------------------------------------+
int OnInit(void)
  {
   program.OnInitEvent();
//--- Inicialização bem sucedida
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Função de desinicialização do Expert                             |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   program.OnDeinitEvent(reason);
  }
//+------------------------------------------------------------------+
//| Função tick do Expert                                            |
//+------------------------------------------------------------------+
void OnTick(void)
  {
  }
//+------------------------------------------------------------------+
//| Funçao timer                                                     |
//+------------------------------------------------------------------+
void OnTimer(void)
  {
   program.OnTimerEvent();
  }
//+------------------------------------------------------------------+
//| Função Trade                                                     |
//+------------------------------------------------------------------+
void OnTrade(void)
  {
  }
//+------------------------------------------------------------------+
//| Função ChartEvent                                                |
//+------------------------------------------------------------------+
void OnChartEvent(const int    id,
                  const long   &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   program.ChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

Se necessário, os métodos de outros manipuladores de eventos, tais como o OnTick(), OnTrade(), etc poderão ser criados na classe CProgram .

 

Teste dos Manipuladores de Eventos da biblioteca e das Classes do Programa

Foi mencionado anteriormente que o método virtual OnEvent() da classe CProgram pode ser chamada a partir da classe base CWndEvents no método ChartEvent(). Nós queremos ter certeza de que isso funciona e, agora, poderemos testar este mecanismo. Para isso, no método CWndEvents::ChartEvent() chame o método CProgram::OnEvent() da maneira que é exibido abaixo:

//+------------------------------------------------------------------+
//| Manipulação de eventos do programa                               |
//+------------------------------------------------------------------+
void CWndEvents::ChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   OnEvent(id,lparam,dparam,sparam);
  }

Em seguida, no método CProgram::OnEvent(), escreva o código abaixo:

//+------------------------------------------------------------------+
//| Manipulador de eventos                                           |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_CLICK)
     {
      ::Comment("x: ",lparam,"; y: ",(int)dparam);
     }
  }

Compile os arquivos e carregue o EA no gráfico. Se você clicar à esquerda do gráfico, será exibido no canto superior esquerdo as coordenadas do cursor no momento da liberação do botão do mouse. Após os testes foram feitos, o código em destaque nas duas últimas listas poderão ser excluídos dos métodos CWndEvents::ChartEvent() e CProgram::OnEvent().

 

Conclusão

Para resumir onde estamos até agora, deixe-nos mostrar tudo o que nós falamos em um diagrama:

Fig. 5. Inclusão no projeto de classes para armazenar os ponteiros e a manipulação de eventos.

Fig. 5. Inclusão no projeto de classes para armazenar os ponteiros e a manipulação de eventos

Atualmente, o sistema consiste de duas partes não interligadas entre si. Para conectá-las, primeiramente, é necessário criar o elemento principal da interface do usuário. O elemento principal é a forma ou a janela para qual será ligado todos os outros controles. Assim, nós vamos escrever uma classe e chamá-la de CWindow. Conecte o arquivo com a classe Element.mqh para o arquivo Window.mqh já que a classe CElement será base para a classe CWindow.

Você pode encontrar o material da Parte I e baixá-la para testar como é seu funcionamento. Se você tiver dúvidas sobre a utilização do material a partir desses arquivos, você poderá consultar a descrição detalhada do desenvolvimento da biblioteca em um dos artigos da lista abaixo ou fazer sua pergunta nos comentários deste artigo.

Lista de artigos (capítulos) da primeira parte:

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

Arquivos anexados |
Expert Advisor Universal: O Modelo de Evento e o Protótipo da Estratégia de Negociação (Parte 2) Expert Advisor Universal: O Modelo de Evento e o Protótipo da Estratégia de Negociação (Parte 2)
Este artigo continua a série de publicações do modelo universal de um Expert Advisor. Esta parte descreve em detalhes o modelo de eventos original, baseado no processamento de dados centralizado e considera a estrutura da classe base CStrategy.
Expert Advisor Universal: Modos de Negociação das Estratégias (Parte 1) Expert Advisor Universal: Modos de Negociação das Estratégias (Parte 1)
Qualquer desenvolvedor de Expert Advisor, independentemente de suas habilidades de programação, diariamente é confrontado com as mesmas tarefas de negociação e problemas algorítmicos, que devem ser resolvidos para organizar um processo de negociação confiável. O artigo descreve as possibilidades do motor de negociação CStrategy que possibilita a solução destas tarefas e fornece ao usuário um mecanismo eficaz para descrever uma idéia de negociação personalizada.
Interfaces Gráficas I: Formulário para os Controles (Capítulo 2) Interfaces Gráficas I: Formulário para os Controles (Capítulo 2)
Neste artigo, nós vamos criar o primeiro e o principal elemento da interface gráfica - o formulário para os controles. Vários controles podem ser anexados a este formulário, podendo ser de qualquer lugar e qualquer combinação.
Expert Advisor Universal: Estratégias Personalizadas e Classes Auxiliares de Negociação (Parte 3) Expert Advisor Universal: Estratégias Personalizadas e Classes Auxiliares de Negociação (Parte 3)
Neste artigo, vamos continuar a análise dos algoritmos do motor de negociação CStrategy. A terceira parte da série contém uma análise detalhada com exemplos de como desenvolver estratégias de negociação específicas usando esta abordagem. É dada uma atenção especial aos algoritmos auxiliares - sistema de registro Expert Advisor e acesso a dados usando um indexador convencional (Close[1], Open[0], etc).