English Русский 中文 Español Deutsch 日本語
Usando Layouts e Containers para Controles de GUI: A Classe CBox

Usando Layouts e Containers para Controles de GUI: A Classe CBox

MetaTrader 5Exemplos | 16 setembro 2015, 09:35
2 838 0
Enrico Lambino
Enrico Lambino

Índice


1. Introdução

O posicionamento absoluto de controles dentro de uma janela de diálogo do aplicativo é a maneira mais direta de criar uma interface gráfica do usuário para um aplicativo. No entanto, em alguns casos, esta abordagem para o projeto GUI Interface gráfica do usuário pode ser inconveniente, ou mesmo impraticável. Este artigo apresenta um método alternativo de criação de GUI (Interface Gráfica do Usuário) baseado em layouts e containers, usando um gerenciador de layout - a classe CBox.

A classe de gerenciamento de layout implementado e usado neste artigo é aproximadamente equivalente aos encontrados em algumas linguagens de programação convencionais, como BoxLayout (Java) e o Pack geometry manager (Python/Tkinter).


2. Objetivos

Olhando para os exemplos SimplePanel e os Controls disponíveis no MetaTrader 5, podemos ver que os controles dentro destes painéis são posicionados pixel por pixel (posicionamento absoluto). Cada controlo é criado e atribuído uma posição definida na área do cliente, e a posição de cada controle dependerá do controle criado antes disso, com alguns deslocamentos adicionais. Embora esta seja a forma natural, tal nível de precisão não é necessário na maioria dos casos, e utilizar este método pode ser desvantajoso em vários níveis.

Qualquer programador com habilidade suficiente seria capaz de criar interfaces de usuário gráficas usando posições de pixel precisas para controles gráficos. No entanto, ela tem as seguintes desvantagens:

  • Ela é geralmente impossível de impedir que outros componentes sejam afetados quando o tamanho ou a posição de um componente é modificado.
  • A maior parte do código não é reutilizável - pequenas mudanças na interface podem, por vezes, exigir grandes mudanças no código.
  • Ela pode ser demorada, especialmente ao criar interfaces mais complexas.

Isto nos leva a criar um sistema de layout com os seguintes objetivos:

  • O código deve ser reutilizável.
  • Alterar uma parte da interface deve ter um impacto mínimo sobre os outros componentes.
  • O posicionamento dos componentes no interior da interface deve ser calculada automaticamente.

Uma implementação de tal sistema é introduzida neste artigo usando um container - a classe CBox.


3. A Classe CBox

Uma instância da classe CBox atua como um container ou uma caixa - os controles são adicionados para aquela caixa, e CBox irá calcular automaticamente o posicionamento dos controles dentro do seu espaço alocado. Um exemplo típico da classe CBox teria o seguinte layout:

Layout de CBox

Figura 1. Layout de CBox

A caixa exterior representa o tamanho de todo o container, enquanto qua a caixa pontilhada representa os limites do preenchimento. A área azul representa o espaço de preenchimento. O espaço em branco remanescente seria de todo o espaço disponível para o posicionamento dos controles no interior do container.

Dependendo da complexidade do painel, a classe CBox pode ser usada de várias maneiras. Por exemplo, é possível que um container (CBox) segure outro container que contém um conjunto de controles. Ou um container contendo um controle e um outro container. No entanto, usar containers apenas como irmãos em um determinado container pai é altamente recomendado.

Construímos a CBox estendendo a CWndClient (Sem as barras de posicionamento), como mostrado no seguinte fragmento:

#include <Controls\WndClient.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CBox : public CWndClient
  {
public:
                     CBox();
                    ~CBox();   
   virtual bool      Create(const long chart,const string name,const int subwin,
                           const int x1,const int y1,const int x2,const int y2);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CBox::CBox() 
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CBox::~CBox()
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CBox::Create(const long chart,const string name,const int subwin,
                  const int x1,const int y1,const int x2,const int y2)
  {
   if(!CWndContainer::Create(chart,name,subwin,x1,y1,x2,y2))
      return(false);
   if(!CreateBack())
      return(false);
   if(!ColorBackground(CONTROLS_DIALOG_COLOR_CLIENT_BG))
      return(false);
   if(!ColorBorder(clrNONE))
      return(false);
   return(true);
  }
//+------------------------------------------------------------------+

É também possível para a classe CBox herdar diretamente a partir de CWndContainer. No entanto, isso privaria a classe de alguns recursos úteis, como o fundo e a borda. Alternativamente, uma versão mais simples pode ser conseguida por extensão direta de CWndObj, Mas você precisará adicionar uma instância de CArrayObj como um dos seus membros privados ou protegidos e recriar os métodos de classe envolvendo os objetos que serão armazenados nessa instância.


3.1. Layout de Estilos

CBox tem dois estilos de layout: estilo vertical e estilo horizontal.

O estilo horizontal teria o seguinte layout básico:

Estilo Horizontal para Cbox

Figura 2. Estilo Horizontal (Centrado)

O estilo vertical teria o seguinte layout básico:

Estilo para Cbox

Figura 3. Estilo Vertical (Centrado)

CBox usa um estilo horizontal por padrão.

Usando uma combinação destes dois layouts (possivelmente usando múltiplos containers), é possível recriar virtualmente qualquer tipo de concepção do painel GUI. Além disso, a colocação de controles dentro dos containers permitiria uma concepção segmentada. Isto é, ela permite personalizar as dimensões e a posição dos controles num determinado container, sem afetar aquelas realizadas por outros containers.

A fim de implementar os estilos horizontais e verticais em CBox, seria preciso declarar uma enumeração, que nós vamos, então, armazenar como um dos membros da referida classe:

enum LAYOUT_STYLE
  {
   LAYOUT_STYLE_VERTICAL,
   LAYOUT_STYLE_HORIZONTAL
  };

3.2. Calculando o Espaço entre os Controles

CBox irá maximizar o espaço disponível atribuído a ele, e usá-lo para posicionar os controles que ele contém uniformemente, como mostrado nas figuras anteriores.

Olhando para os números acima, podemos derivar a fórmula para calcular o espaço entre os controles em um determinado recipiente CBox, usando o pseudocódigo a seguir:

para o layout horizontal:
espaço x = ((espaço x disponível)-(tamanho x total de todos os controles))/(número total de controles + 1)
espaço y = ((espaço y disponível)-(tamanho y de controle))/2

para o layout vertical:
espaço x = ((espaço x disponível)-(tamanho x de controle))/2
espaço y = ((espaço y disponível)-(tamanho y total de todos os controles))/(número total de controles + 1)

3.3. Alinhamento

O cálculo do espaço entre os controlos, tal como mencionado na seção anterior, só se aplica a um alinhamento centrado. Queremos que a classe CBox acomode mais alinhamentos também, e por isso vamos precisar de algumas pequenas alterações no cálculo.

Para o alinhamento horizontal, as opções disponíveis, além do container centrado, são a esquerda, direita, e no centro (sem lados), como mostrado nas figuras seguintes:

Caixa Horizontal - alinhamento à esquerda

Figura 4. Estilo Horizontal (Alinhamento à esquerda)

Caixa Horizontal - alinhamento à direita

Figura 5. Estilo Horizontal (Alinhamento à direita)

Caixa Horizontal - Alinhamento central (sem lados)

Figura 6. Estilo Horizontal (Centralizado, sem lados)


Para o alinhamento vertical, as opções disponíveis, além do alinhamento centralizado, são a parte superior, inferior, centro e centro (sem lado), como mostrado abaixo:

Caixa Vertical - alinhamento superior Caixa Vertical - Alinhamento central (sem lados) Caixa Vertical - alinhamento inferior

Figura 7. Alinhamentos do Estilo Vertical: (Esquerda) Alinhamento Superior, (Centro) Alinhamento Central - Sem Lados, (Direito) Alinhamento Inferior

Observe que a classe CBox deve calcular automaticamente o espaçamento x e y entre os controles com base nesses alinhamentos. Assim, em vez de utilizar o divisor de

(Número total de controles + 1)

para obter o espaço entre os controles, usamos o número total de controles para alinhamentos distorcidos (alinhamento da direita, esquerda, superior e inferior) como divisor, e (número total de controles - 1) para um container centrado sem margem nem lados.

Similar aos estilos de layout, a implementação de recursos de alinhamento para a classe CBox irá exigir enumerações. Vamos declarar uma enumeração para cada estilo de alinhamento, como segue:

enum VERTICAL_ALIGN
  {
   VERTICAL_ALIGN_CENTER,
   VERTICAL_ALIGN_CENTER_NOSIDES,
   VERTICAL_ALIGN_TOP,
   VERTICAL_ALIGN_BOTTOM
  };
enum HORIZONTAL_ALIGN
  {
   HORIZONTAL_ALIGN_CENTER,
   HORIZONTAL_ALIGN_CENTER_NOSIDES,
   HORIZONTAL_ALIGN_LEFT,
   HORIZONTAL_ALIGN_RIGHT
  };

3.4. Componentes de Renderização

Normalmente, nós criamos controles especificando os parâmetros x1, y1, x2, y2, tais como o seguinte trecho ao criar um botão:

CButton m_button;
int x1 = currentX;
int y1 = currentY;
int x2 = currentX+BUTTON_WIDTH; 
int y2 = currentY+BUTTON_HEIGHT
if(!m_button.Create(m_chart_id,m_name+"Button",m_subwin,x1,y1,x2,y2))
      return(false);

onde x2 menos x1 e y2 menos y1 equivale à largura e altura do controle, respectivamente. Em vez de utilizar este método, nós podemos criar o mesmo botão com a CBox usando um método muito mais simples, como se mostra no seguinte fragmento:

if(!m_button.Create(m_chart_id,m_name+"Button",m_subwin,0,0,BUTTON_WIDTH,BUTTON_HEIGHT))
      return(false);

A classe CBox mais tarde reposicionará automaticamente o componente na criação da janela do painel. O método Pack(), que chama o método Render(), deve ser chamado para o reposicionamento dos controles e recipientes:

bool CBox::Pack(void)
  {
   GetTotalControlsSize();
   return(Render());
  }

O método Pack() simplesmente obtém o tamanho combinado dos containers, e, em seguida, chama o método Render(), onde a maior parte da ação acontece. O fragmento abaixo mostra o processamento real dos controles no interior do recipiente, através do método de Render():

bool CBox::Render(void)
  {
   int x_space=0,y_space=0;
   if(!GetSpace(x_space,y_space))
      return(false);
   int x=Left()+m_padding_left+
      ((m_horizontal_align==HORIZONTAL_ALIGN_LEFT||m_horizontal_align==HORIZONTAL_ALIGN_CENTER_NOSIDES)?0:x_space);
   int y=Top()+m_padding_top+
      ((m_vertical_align==VERTICAL_ALIGN_TOP||m_vertical_align==VERTICAL_ALIGN_CENTER_NOSIDES)?0:y_space);
   for(int j=0;j<ControlsTotal();j++)
     {
      CWnd *control=Control(j);
      if(control==NULL) 
         continue;
      if(control==GetPointer(m_background)) 
         continue;
      control.Move(x,y);     
      if (j<ControlsTotal()-1)
         Shift(GetPointer(control),x,y,x_space,y_space);      
     }
   return(true);
  }

3.5. Componente de Redimensionamento

Quando o tamanho de um controle é maior do que o espaço disponível dentro do seu container, o controle deve ser redimensionado, a fim de se ajustar ao espaço disponível. Caso contrário, o controle irá derramar sobre o recipiente, causando problemas na aparência de todo o painel. Esta abordagem também é conveniente quando você quer um certo controle para maximizar o seu espaço e tomar toda a largura ou altura da área do cliente, ou o do seu container. Se a largura ou a altura de um determinado controle exceder a largura ou a altura do recipiente menos o preenchimento (ambos os lados), o controle será redimensionado para a largura máxima ou altura máxima disponível.

Note que o CBox não redimensionará o container quando o tamanho total de todos os controles que ele detém exceder o espaço disponível. Neste caso, tanto o tamanho da janela de diálogo principal (CDialog ou CAppDialog), ou a dos controles individuais iria necessitar de ser ajustado manualmente.


3.6. Renderização Recursiva

Para o uso simples da CBox, uma única chamada para o método Pack() já seria o suficiente. No entanto, para containers encaixados, o mesmo método terá de ser chamado de modo que todos os recipientes irá ser capaz de posicionar os seus controles individuais ou os outros containers. Podemos evitar isso adicionando um método para a função de implementação para o mesmo método de seus próprios controles, se e somente se o controle gráfico em questão é uma instância da classe CBox ou qualquer classe de layout. Para fazer isso, em primeiro lugar, definimos uma macro e atribui-lhe um valor único:

#define CLASS_LAYOUT 999

Então, nós substituímos o método Type() da classe CObject para que ele retorne o valor da macro que acabou de preparar:

virtual int       Type() const {return CLASS_LAYOUT;}

Finalmente, no âmbito do método Pack() da classe CBox, vamos realizar o método de renderização para seus recipientes filhos que são instâncias de uma classe de layout:

for(int j=0;j<ControlsTotal();j++)
     {
      CWnd *control=Control(j);
      if(control==NULL) 
         continue;
      if(control==GetPointer(m_background)) 
         continue;
      control.Move(x,y);

      //Chama o método Pack() de controle se é uma classe de layout
      if(control.Type()==CLASS_LAYOUT)
        {
         CBox *container=control;
         container.Pack();
        }     
   
      if (j<ControlsTotal()-1)
         Shift(GetPointer(control),x,y,x_space,y_space);      
     }

O método de renderização começa pelo cálculo do espaço disponível para os controles dentro do container. Estes valores são armazenados em m_total_x e m_total_y, respectivamente. O próximo passo é calcular o espaço entre os controles, com base no estilo do esquema e alinhamento. O último passo é implementar o reposicionamento real dos controles no interior do container.

CBox mantém uma contagem de comandos a ser reposicionados, como existem objetos dentro do container que não exigem o reposicionamento, como o objeto nativo de fundo CWndClient, ou possivelmente alguns outros controles que devem ser estendidos de CBox.

CBox também mantém o tamanho mínimo do controle dentro do container (exceto o fundo), definida pelo m_min_size (estrutura CSize). A sua finalidade é manter os controles empilhados de modo uniforme no interior do container, quer na horizontal ou na vertical. A sua definição é bastante contra-intuitiva, uma vez que este é, na verdade, o tamanho do maior controle. No entanto, aqui nós vamos defini-lo como o mínimo, uma vez que CBox diria que este tamanho é o tamanho mínimo, e gostaria de calcular o espaço disponível com base neste tamanho.

Note que o método Shift() segue uma rotina semelhante à execução habitual de controle do posicionamento (posicionamento absoluto). Os métodos de renderização mantêm referências a ambas as coordenadas X e Y que são lembradas e atualizadas como CBox reposiciona cada controle. No entanto, com CBox, isto é feito automaticamente, deixando o desenvolvedor do painel simplesmente definir o tamanho real para cada controle usado.


4. Implementação em uma Janela de Diálogo

Ao utilizar a CBox, nós praticamente substituímos a funcionalidade da área do cliente nativo CDialog ou CAppDialog, M_client_area, que é uma instância de CWndClient. Assim, temos pelo menos três opções neste caso:

  1. Estender/reescrever CAppDialog ou CDialog para que CBox substitua a área do cliente.
  2. Usar container e adicioná-los na área do cliente.
  3. Usar o container CBox principal para sediar outros containers pequenos.

Usando a primeira opção poderá dar bastante trabalho, como nós vamos precisar reescrever os objetos de diálogo para fazê-lo usar o novo objeto de área de cliente. Como alternativa, os objetos de diálogo podem ser estendidos para usar a classe container personalizada, mas vai nos deixar com uma instância de CWndClient (M_client_area) não utilizada, ocupando memória desnecessariamente.

A segunda opção também é viável. Nós simplesmente colocamos os controles dentro do container CBox e usamos o posicionamento de pixels, a fim de adicioná-los à área do cliente. Mas esta opção não utilizar plenamente o potencial da classe CBox, que é a concepção de painéis sem ter que se incomodar sobre o posicionamento dos controles individuais e dos containers.

A terceira opção é recomendada. Ou seja, nós criamos um container CBox principal para armazenar todos os outros containers e controles menores. Este recipiente principal ocuparia a largura de toda a área nativa do cliente, e será adicionado a isso como seu único filho. Isto faria com que a área nativa do cliente ficasse um pouco redundante, mas, pelo menos, ela ainda está sendo utilizada. Além disso, podemos evitar uma grande quantidade de codificação/recodificação usando esta opção.


5. Exemplos


5.1. Exemplo #1: Uma Simples Calculadora de Pips

Agora, usamos a classe CBox para implementar um painel simples: uma calculadora pips. O diálogo da calculadora de pips irá conter três campos do tipo CEdit, a saber:

  • nome do símbolo ou instrumento;
  • tamanho de 1 pip para o símbolo ou um instrumento de entrada;
  • valor de 1 pip para o símbolo ou instrumento.

Isso nos dá um total de 7 controles diferentes, incluindo os rótulos (CLabel) para cada campo, e um botão (CButton) para a execução de um cálculo. Uma screenshot da calculadora é mostrada abaixo:

Calculadora de Pips - screenshot

Figura 8. Calculadora de Pips

Ao olhar para o painel da calculadora, podemos deduzir que ela irá usar 5 containers diferentes de CBox. Deve haver três containers horizontais para cada um dos campos, e um outro container horizontal, alinhado à direita, para o botão. Todos estes containers vão ser embalados dentro do container principal com um estilo vertical. E, finalmente, este container principal deve ser anexado à área do cliente da instância CAppDialog. A figura a seguir mostra o layout dos containers. As caixas de violeta representam as linhas horizontais. As caixas brancas representam os controles essenciais, enquanto a grande caixa cinza que contém todas as caixas menores é a janela da caixa principal.

Calculadora de Pips - layout de diálogo

Figura 9. Layout da Calculadora de Pips

Observe que o uso dos containers CBox, nós não declaramos quaisquer macros para as lacunas e recortes. Em vez disso, simplesmente nós declaramos as macros para tamanhos de controle, configuramos cada instância CBox, e deixamos ela organizar os controles em conformidade.

Para construir este painel, primeiro começamos por criar um arquivo de cabeçalho, 'PipValueCalculator.mqh', que deve estar na mesma pasta que o arquivo de origem principal que vamos preparar mais tarde (PipValueCalculator.mq5). Nós incluímos neste arquivo o arquivo de cabeçalho da classe CBox, bem como outros includes que precisamos para este painel. Nós também exigimos a classe CSymbolInfo , que vamos utilizar para o cálculo real do valor de pip para um determinado símbolo:

#include <Trade\SymbolInfo.mqh>
#include <Layouts\Box.mqh>
#include <Controls\Dialog.mqh>
#include <Controls\Label.mqh>
#include <Controls\Button.mqh>

O próximo passo é especificar a largura e a altura para os controles que vamos usar. É possível especificar um determinado tamanho para cada controle, mas para este painel, iremos utilizar um tamanho de controle genérico. Isto é, todos os controles essenciais terão a mesma largura e altura:

#define CONTROL_WIDTH   (100)
#define CONTROL_HEIGHT  (20)

Agora, vamos passar para a criação do objeto da classe painel atual. Isto é feito de maneira usual, por ter uma nova classe herdada de CAppDialog:

class CPipValueCalculatorDialog : public CAppDialog

A estrutura inicial da classe será semelhante a seguinte:

class CPipValueCalculatorDialog : public CAppDialog
  {
protected:
//membros da classe protegidas aqui
public:
                     CPipValueCalculatorDialog();
                    ~CPipValueCalculatorDialog();

protected:
//métodos de classe protegidos aqui
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CPipValueCalculatorDialog::CPipValueCalculatorDialog(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CPipValueCalculatorDialog::~CPipValueCalculatorDialog(void)
  {
  }

A partir do trecho de código acima, temos agora um modelo inicial para a classe painel da calculadora de pips (na verdade, isso pode ser reutilizado para criar painéis semelhantes). Agora, nós avançamos com a criação do membro da classe container principal, que servirá como um container pai de todos os outros containers CBox encontrados no painel:

class CPipValueCalculatorDialog : public CAppDialog
  {
protected:
   CBox              m_main;
//Mais código aqui...

Nós definimos o recipiente principal CBox para o painel, mas não a verdadeira função da sua criação. Para fazer isto, adicionamos outro método de classe para classe painel, como mostrado a seguir:

//Início da definição de classe
// ...
public:
                     CPipValueCalculatorDialog();
                    ~CPipValueCalculatorDialog();
protected:
   virtual bool      CreateMain(const long chart,const string name,const int subwin);
//o resto da definição
// ...

Então, fora da classe, definimos o corpo real do método de classe (semelhante ao como o corpo do construtor e destrutor da classe são definidos):

bool CPipValueCalculatorDialog::CreateMain(const long chart,const string name,const int subwin)
  {   
   //cria um container principal de CBox
   if(!m_main.Create(chart,name+"main",subwin,0,0,CDialog::m_client_area.Width(),CDialog::m_client_area.Height()))
      return(false);   

   //aplica o layout vertical
   m_main.LayoutStyle(LAYOUT_STYLE_VERTICAL);
   
   //define o preenchimento para 10 px de todos os lados
   m_main.Padding(10);
   return(true);
  }

Nós usamos CDialog::m_client_area.Width() e CDialog::m_client_area.Height() para especificar a largura e a altura do container. Ou seja, ele toma todo o espaço da área do painel do cliente. Também aplicamos algumas modificações para o container: a aplicação de um estilo vertical, e definindo o preenchimento de 10 pixels em todos os lados. Estas funções são fornecidas pela Classe CBox.

Agora que temos definido o principal membro da classe container e como ele deve ser criado, então, criamos os membros para as linhas como mostrado na Fig. 9. Para a linha de nível superior, que é a linha para o símbolo, nós declaramos através da criação do primeiro container, em seguida, os controles essenciais contidos nele, logo abaixo da declaração para o principal membro da classe container mostrado no trecho anterior:

CBox              m_main;
CBox              m_symbol_row;   //linha do container
CLabel            m_symbol_label; //controle do rótulo
CEdit             m_symbol_edit;  //controle de edição

Similar ao container principal, que também define uma função para a criação da linhas do container vigentes:

bool CPipValueCalculatorDialog::CreateSymbolRow(const long chart,const string name,const int subwin)
  {
   //cria um container CBox para esta linha (linha do símbolo)
   if(!m_symbol_row.Create(chart,name+"symbol_row",subwin,0,0,CDialog::m_client_area.Width(),CONTROL_HEIGHT*1.5))
      return(false);

   //Cria o controle de rótulo
   if(!m_symbol_label.Create(chart,name+"symbol_label",subwin,0,0,CONTROL_WIDTH,CONTROL_HEIGHT))
      return(false);
   m_symbol_label.Text("Symbol");
   
   //Cria o controle de edição
   if(!m_symbol_edit.Create(chart,name+"symbol_edit",subwin,0,0,CONTROL_WIDTH,CONTROL_HEIGHT))
      return(false);
   m_symbol_edit.Text(m_symbol.Name());

   //Adiciona os controles essenciais ao seu container pai (linha)
   if(!m_symbol_row.Add(m_symbol_label))
      return(false);
   if(!m_symbol_row.Add(m_symbol_edit))
      return(false);
   return(true);
  }

Nesta função, primeiro criamos o recipiente linha do símbolo. Observe que usamos toda a largura da área do cliente como a sua largura, enquanto fazemos a sua altura 50% maior do que a altura do controle que foram definidos anteriormente.

Após a criação da linha, então, nós criamos os controles individuais. Desta vez, eles usam a largura e a altura do controle de macros que definimos anteriormente. Além disso, observe como nós criamos esses controles:

Create(chart,name+"symbol_edit",subwin,0,0,CONTROL_WIDTH,CONTROL_HEIGHT))

Os valores em vermelho são as coordenadas x1 e y1. Isto significa que, no momento da criação, todos os controles são colocados no lado superior esquerdo do gráfico. Estes são então rearranjados assim que chamar o método Pack() de CBox.

Nós criamos o container linha. Também criamos os controles essenciais dentro do container. O próximo passo foi adicionar os controles que acabamos de criar para o container de linha, representado nos últimas várias linhas da função:

if(!m_symbol_row.Add(m_symbol_label))
   return(false);
if(!m_symbol_row.Add(m_symbol_edit))
   return(false);

Para as demais linhas (tamanho do pip, valor do pip, e linhas de botões), vamos implementar aproximadamente o mesmo método como fizemos para o linha de símbolo.

A criação do recipiente principal e de outras linhas filho é necessária para utilizar a classe CBox. Agora, vamos passar para uma área mais familiar, que é a criação do próprio painel do objeto. Isso é feito (usando ou não CBox), substituindo o método virtual Create() da classe CAppDialog . É sob esse método que os dois métodos que definimos anteriormente finalmente farão sentido, uma vez que nós vamos chamar esses métodos aqui:

bool CPipValueCalculatorDialog::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2)
  {
   //cria o painel CAppDialog
   if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
      return(false);
   
   //cria o container principal CBox utilizando a função que definimos anteriormente  
   if(!CreateMain(chart,name,subwin))
      return(false);  

   //criar a linha símbolo do container CBox utilizando a função que definimos anteriormente  
   if(!CreateSymbolRow(chart,name,subwin))
      return(false);

   //adiciona o container CBox da linha de símbolo como um filho do container principal CBox
   if(!m_main.Add(m_symbol_row))
      return(false);

   //renderiza o container principal CBox e todos os seus recipientes filhos (de forma recursiva)
   if (!m_main.Pack())
      return(false);
   
   //adiciona o container principal CBox como a única filha da área do painel do cliente
   if (!Add(m_main))
      return(false);
   return(true);
  }

Não se esqueça de declarar o método substituído Create() na classe CPipValueCalculatorDialog, como segue:

public:
                     CPipValueCalculatorDialog();
                    ~CPipValueCalculatorDialog();
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);

Como mostrado no código acima, ela deve ser um método de classe pública, uma vez que vamos chamá-la de fora da classe. Para ser mais específico, este será necessário no arquivo de origem principal: PipValueCalculator.mq5:

#include "PipValueCalculator.mqh"
CPipValueCalculatorDialog ExtDialog;
//+------------------------------------------------------------------+
//| Função de inicialização do Expert                                |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
//---Cria o diálogo do aplicativo
   if(!ExtDialog.Create(0,"Pip Value Calculator",0,50,50,279,250))
      return(INIT_FAILED);
//---Execução da aplicação
   if(!ExtDialog.Run())
      return(INIT_FAILED);
//--- OK
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Função de desinicialização do Expert                             |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   ExtDialog.Destroy(reason);
  }
//+------------------------------------------------------------------+
//| Função tick do Expert                                            |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
  }
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
 //Comentado agora para ser discutido mais adiante na seção  
    //ExtDialog.ChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

Este código é muito semelhante ao que costumamos ver nos principais arquivos de origem do painel, com exceção de três coisas:

  1. Nós adicionamos 'PipValueCalculator.mqh' como um arquivo de cabeçalho, ao invés do arquivo include para CAppDalog. 'PipValueCalculator.mqh' já inclui o arquivo de cabeçalho para isso, então já não há qualquer necessidade de incluí-lo no arquivo de origem principal. 'PipValueCalculator.mqh' também é responsável por incluir o arquivo de cabeçalho da classe CBox.
  2. Declaramos ExtDialog como uma instância da classe que definimos em 'PipValueCalculator.mqh' (classe PipValueCalculator).
  3. Nós especificamos um tamanho personalizado para o painel que é mais adequado para isso, como definido no ExtDialog.Create().

Quando compilado com apenas a linha do símbolo, o painel seria semelhante à tela a seguir:

Painel calculadora de pips com uma linha

Figura 10. Painel calculadora de pips com uma linha

O container principal tem uma disposição vertical e está com alinhamento central, enquanto que a linha do símbolo mostrado como uma disposição horizontal (também centrado horizontalmente e verticalmente). Para fazer com que este painel se assemelhe a um painel mostrado na Fig. 8, nós precisamos adicionar as outras três linhas, usando basicamente o mesmo método que foi implementado para a criação da linha do símbolo. Uma exceção é a linha de botões, que contém apenas um único controle essencial (botão), e deve ser alinhado à direita:

m_button_row.HorizontalAlign(HORIZONTAL_ALIGN_RIGHT);

A manipulação de eventos está além do escopo deste artigo, mas por uma questão de completude para este exemplo, vamos discutir brevemente aqui. Começamos por declarar um novo membro da classe para a classe PipValueCalculator, m_symbol. Nós também incluímos dois membros adicionais, m_digits_adjust e m_points_adjust, que será usado posteriormente para converter o tamanho em pontos de pips.

CSymbolInfo      *m_symbol;
int               m_digits_adjust;
double            m_points_adjust;

Nós inicializamos m_symbol ou no construtor da classe ou no método Create(), usando o seguinte código:

if (m_symbol==NULL)
      m_symbol=new CSymbolInfo();
if(m_symbol!=NULL)
{
   if (!m_symbol.Name(_Symbol))
      return(false);
}   

Se o ponteiro do símbolo é nulo, criamos uma nova instância de CSymbolInfo. Se não for nulo, vamos atribuir o nome do símbolo igual ao nome do gráfico do símbolo.

O próximo passo seria definir um manipulador de eventos para o clique do botão Isso é implementado pelo método de classe OnClickButton(). Nós definimos o seu corpo como se segue:

void CPipValueCalculatorDialog::OnClickButton()
  {
   string symbol=m_symbol_edit.Text();
   StringToUpper(symbol);
   if(m_symbol.Name(symbol))
     {
      m_symbol.RefreshRates();
      m_digits_adjust=(m_symbol.Digits()==3 || m_symbol.Digits()==5)?10:1;
      m_points_adjust=m_symbol.Point()*m_digits_adjust;
      m_pip_size_edit.Text((string)m_points_adjust);      
      m_pip_value_edit.Text(DoubleToString(m_symbol.TickValue()*(StringToDouble(m_pip_size_edit.Text()))/m_symbol.TickSize(),2));
     }
   else Print("invalid input");
  }

O método de classe calcula o valor do pip por receber primeiramente o valor do controle m_symbol_edit. Em seguida, ele passa o nome do símbolo a uma instância da classe CSymbolInfo. A referida classe recebe o valor tick do símbolo escolhido, o qual é, então, ajustado por um certo multiplicador, a fim de calcular o valor de um pip

O passo final para permitir a manipulação do evento para a classe de eventos é a definição do processador de eventos (também dentro da classe PipValueCalculator). De acordo com os métodos públicos da classe, insira esta linha de código:

virtual bool      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);

Em seguida, definimos o corpo do método de classe fora da classe, usando o seguinte trecho:

EVENT_MAP_BEGIN(CPipValueCalculatorDialog)
   ON_EVENT(ON_CLICK,m_button,OnClickButton)
EVENT_MAP_END(CAppDialog)


5.2. Exemplo #2: Reconstruindo os Controles de Exemplo

O exemplo do painel de Controles é instalado automaticamente após uma nova instalação do MetaTrader. Na janela do navegador, ele pode ser encontrado em Expert Advisors\Examples\Controls. Uma screenshot deste painel é mostrado abaixo:

Controls - diálogo

Figura 11. Diálogo Controls (Original)

O layout da janela de diálogo mostrada acima é detalhado na figura a seguir. Para reconstruir o painel usando instâncias CBox, nós estamos vendo quatro linhas horizontais principais (em violeta) para o seguinte conjunto de controles:

  1. o controle Edit;
  2. os três controles Button;
  3. o SpinEdit e o DatePicker;
  4. o ComboBox, RadioGroup e CheckGroup (Coluna 1) e o ListView (Coluna 2).

O último container horizontal é um caso especial, uma vez que ele é um container horizontal aninhada segurando dois outros containers (colunas 1 e 2, em verde). Estes containers devem ter layouts verticais.

Figura 12. Layout do Diálogo Controls

Figura 12. Layout do Diálogo Controls

Ao reconstruir o diálogo Controls, as linhas de código invocando o método Add(), deve ser removido, exceto para o container principal, que agiria como uma única filha da área de diálogo do cliente. Enquanto isso, os outros controles e containers devem ser adicionados aos seus containers principais que foram designados, a partir dos níveis mais profundos de cima para o container principal e, em seguida, finalmente, para a área nativa do cliente.

Uma vez instalado, compile e execute. Tudo deve funcionar bem, exceto para o selecionador da data, cujo incremento, decremento, e a lista de botões que se recusam a funcionar. Isto é devido ao fato da lista suspensa da classe CDatePicker ser definida no fundo dos outros containers. Para resolver este problema, procure o arquivo de classe CDatePicker localizado na pasta %Data Folder%\MQL5\Include\Controls\DatePicker.mqh. Encontre o método ListShow() e no início dessa função, insira esta linha de código:

BringToTop();

Recompile e teste. Isto faria com que a lista suspensa de Date Picker seja colocado em primeiro plano, dando prioridade para os eventos de clique sempre que for mostrado. Aqui está um trecho de toda a função:

bool CDatePicker::ListShow(void)
  {
   BringToTop();
//---Valor definido   
   m_list.Value(m_value);
//---Mostra a lista
   return(m_list.Show());
  }

Uma screenshot da caixa de diálogo Controls reconstruída é mostrada abaixo:

Controls - diálogo reconstruído

Figura 13. Diálogo Controls (Usando CBox)

Concentrando-se no panorama geral, ele parece quase idêntico ao original. Mas existe uma diferença marcante, o qual foi feito incidentalmente - a coluna 1 está perfeitamente alinhada com a coluna 2. No original, podemos ver que o CheckGroup é empilhado uniformemente com a ListView na parte de baixo. No entanto, na parte superior, o ComboBox não está alinhado com o topo da ListView. Naturalmente, as coordenadas podem ser reposicionadas no painel original, mas isso exigiria que o ajuste fosse feito não apenas no pixel de coordenadas para o ComboBox, mas as coordenadas de RadioGroup e as diferenças entre os três controles também Usando um container CBox, por outro lado, apenas exige definir o preenchimento superior e inferior para zero, e usando o alinhamento correto.

Isso não significa, no entanto, que o uso de CBox ou de layouts seja superior em termos de precisão. Embora se reconheça que é menos preciso do que a codificação das coordenadas exatas para os controles, usando recipientes e layouts ainda seria capaz de fornecer um nível decente de precisão, enquanto que, ao mesmo tempo, faz com que o design GUI seja um pouco mais fácil.


6. Vantagens e Desvantagens

Vantagens:

  • Seu código é reutilizável - você pode reutilizar CBox ou qualquer classe de layout em diferentes aplicações e diálogos.
  • Ele é escalável - embora usá-lo pode tornar o código fonte maior em pequenas aplicações, seus benefícios podem ser mais apreciados nos painéis e diálogos mais complexos.
  • Segmentação de conjuntos de controle - ele permite que você modifique um conjunto de controles sem afetar muito o posicionamento de outros controles.
  • Posicionamentos automatizados - travessões, lacunas e espaçamentos não são necessários para ser codificado manualmente, estes são calculados automaticamente pela classe layout.

Desvantagens:

  • Controlos adicionais terão de ser criados para os containers, bem como a criação de funções adicionais, que os utilizam.
  • Menos preciso - o posicionamento é limitado aos layouts e as opções de alinhamento disponíveis.
  • Pode tornar-se problemática, ou demasiadamente complexo quando usado para conter os controles com tamanhos variados - neste caso, tanto as diferenças nos tamanhos podem ser mantidas a um mínimo, ou fazer o uso de containers encaixados.


7. Conclusão

Neste artigo, nós consideramos a possibilidade de usar layouts e containers na concepção dos painéis gráficos. Esta abordagem permitiu-nos automatizar o processo de posicionar vários controles usando os estilos de layout e alinhamento. Ele pode gerar o design gráfico dos painéis facilmente, e, em alguns casos, reduzir o tempo de codificação.

A classe CBox é um controle auxiliar que atua como um container para controles essenciais em um painel de GUI. Neste artigo, demonstramos o seu funcionamento e como ele pode ser utilizado em aplicações reais. Apesar de ser menos preciso do que o posicionamento absoluto, ele ainda pode fornecer um nível de precisão que viria a ser conveniente em muitas aplicações.

Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/1867

Arquivos anexados |
Box.mqh (11.88 KB)
Controls2.mq5 (2.13 KB)
ControlsDialog2.mqh (20.18 KB)
Sistema de Negociação mecânica "Chuvashov's Fork" Sistema de Negociação mecânica "Chuvashov's Fork"
Este artigo, numa breve revisão, chama a sua atenção ao método e programa de código do sistema de negociação mecânica baseado na técnica proposta por Stanislav Chuvashov. A análise de mercado considerada no artigo tem algo em comum com a abordagem de Thomas DeMark, desenhando linhas de tendência ao último intervalo de tempo mais próximo, fractais são os pontos de referência na construção de linhas de tendências.
Análise Fractal dos Movimentos Conjuntos de Moedas Análise Fractal dos Movimentos Conjuntos de Moedas
O quanto independente são as cotações das moedas? São os seus movimentos coordenados ou não? O movimento de uma moeda sugere o movimento de outra? O artigo tenta resolver esta questão usando uma dinâmica não-linear e métodos geométricos fractais.
Barras sintéticas - Uma Nova Dimensão para Exibição de Informações Gráficas dos Preços Barras sintéticas - Uma Nova Dimensão para Exibição de Informações Gráficas dos Preços
A principal desvantagem dos métodos tradicionais para a exibição de informações dos preços utilizando as barras e as velas japonesas é que estão vinculadas ao período de tempo. Estes métodos eram ideais ao momento em que foram criados, mas hoje com os movimentos do mercado demasiadamente rápidos, os preços exibidos no gráfico desta forma não contribuem para uma resposta rápida ao novo movimento. O método de exibição dos gráficos de preços proposto não tem esta desvantagem e oferece um layout muito familiar.
Métodos de Análise Técnica e Previsão do Mercado Métodos de Análise Técnica e Previsão do Mercado
O artigo demonstra as possibilidades e potencialidades de um método matemático bem conhecido juntamente com o pensamento visual e perspectivas de mercado "fora da caixa". Por um lado, serve para atrair a atenção de um grande público, pois incentiva as mentes criativas a reconsiderarem o paradigma da negociação como tal. Por outro, pode dar origem a desenvolvimentos de alternativas e implementações de códigos a respeito de uma ampla gama de ferramentas de análise e previsão.