English Русский 中文 Español Deutsch 日本語
Interfaces Gráficas IV: O Modo Multi-Janela e o Sistema de Prioridades (Capítulo 2)

Interfaces Gráficas IV: O Modo Multi-Janela e o Sistema de Prioridades (Capítulo 2)

MetaTrader 5Exemplos | 2 setembro 2016, 09:24
1 124 0
Anatoli Kazharski
Anatoli Kazharski

Conteúdo


Introdução

O primeiro artigo Interfaces gráficas I: Preparação da Estrutura da Biblioteca (Capítulo 1) considera em detalhes a finalidade desta biblioteca. Você irã encontrar uma lista de artigos com os links no final de cada capítulo. Lá, você também pode encontrar e baixar a versão completa da biblioteca, no estágio de desenvolvimento atual. Os arquivos devem estar localizados nas mesmas pastas que o arquivo baixado.  

No capítulo anterior, nós discutimos os seguintes elementos informativos da interface gráfica: A barra de status e a dica de contexto. Neste capítulo, nós vamos estender a implementação da biblioteca para possibilitar a criação de interfaces de multi-janela para as aplicações MQL. Nós também vamos desenvolver um sistema de prioridades para o clique do botão esquerdo do mouse sobre os objetos gráficos já que sem isso pode ocorrer com que os controles não respondam as ações do usuário.


O Modo Multi-Janela

Vamos considerar o modo multi-janela da interface gráfica da biblioteca em desenvolvimento. Até agora, a enumeração ENUM_WINDOW_TYPE fornecia dois identificadores para a janela principal (W_MAIN) e a de diálogo (W_DIALOG). O modo de janela única foi o único modo em uso. Depois que introduzirmos alguns incrementos, será possível habilitar o modo multi-janela apenas criando e adicionando a quantidade necessária de formulários de controle para a base.

Na classe principal para o tratamento de eventos CWndEvents crie um campo para armazenar o índice da janela ativa no momento.

class CWndEvents : public CWndContainer
  {
protected:
   //--- Índice da janela ativa
   int               m_active_window_index;
  };

Vamos ver como o índice da janela ativa será identificado. Por exemplo, o utilizador atribui a abertura de uma janela de diálogo (W_DIALOG) à algum botão. Quando o botão é pressionado, o evento personalizado ON_CLICK_BUTTON é gerado. Este evento pode ser rastreado no manipulador de eventos CProgram::OnEvent() da classe personalizada. Nós também vamos usar o método CWindow::Show() do formulário que é para ser exibido. Não é possível realizar isso com a implementação atual da biblioteca, portanto, nós vamos implementar os incrementos necessários.

Um evento personalizado terá de ser enviado a partir do método CWindow::Show() que irá indicar que uma janela foi aberta, assim os valores dos parâmetros do sistema de interface gráfica deve ser atualizado. Tal evento requer um identificador separado. Vamos chamá-lo de ON_OPEN_DIALOG_BOX e colocá-lo no arquivo Defines.mqh onde os outros identificadores de biblioteca estão localizados. 

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#define ON_OPEN_DIALOG_BOX        (11) // The opening of a dialog window event

Adicione uma linha no final do método CWindow::Show() como é mostrado no código abaixo. Esta é uma versão reduzida do método. Para a identificação inequívoca do iniciador do evento, o identificador do elemento e o nome do programa devem ser enviados juntos com o identificador de evento.

//+------------------------------------------------------------------+
//| Exibe a janela                                                   |
//+------------------------------------------------------------------+
void CWindow::Show(void)
  {
//--- Faz com que todos os objetos sejam visíveis
//--- Estado de visibilidade
//--- Zera o foco
//--- Envia uma mensagem sobre ele
   ::EventChartCustom(m_chart_id,ON_OPEN_DIALOG_BOX,(long)CElement::Id(),0,m_program_name);
  }

Este evento será tratado na classe CWndEvents. Antes da implementação do método para manipulação, nós precisamos criar mais três métodos na classe CWindow. Estes são dois métodos para armazenar e receber o índice do formulário onde uma janela de diálogo será aberta e o terceiro método é para a gestão do estado do formulário. 

O índice da janela anteriormente ativa deve ser armazenado como várias janelas, podendo serem abertas simultaneamente. É por isso que quando se fecha uma janela de diálogo, é importante saber qual delas terá que voltar para o estado ativo. 

class CWindow : public CElement
  {
private:
   //--- Índice da janela anteriormente ativa
   int               m_prev_active_window_index;
   //---
public:
   //--- (1) Armazenando e (2) obtendo o índice da janela anteriormente ativa
   void              PrevActiveWindowIndex(const int index)                  { m_prev_active_window_index=index;   }
   int               PrevActiveWindowIndex(void)                       const { return(m_prev_active_window_index); }
  };

Para a gestão do estado do formulário, os formulários desativados terão uma cor diferente de cabeçalho, podendo ser alterado pelo usuário. A cor dos elementos não irá mudar quando o cursor do mouse estiver sobre eles já que o formulário estará bloqueado. Além disso, no momento da desativação do formulário, será gerado um evento personalizado. Isto irá avisar que o formulário está bloqueado e os focos e cores de seus elementos devem ser zerados. Quando o formulário é bloqueado, o foco sobre os elementos não é monitorado. No momento da abertura de uma janela de diálogo, a cor do elemento que traz a janela será o mesmo de quando o cursor do mouse está sobre ele. 

Para este evento, o identificador ON_RESET_WINDOW_COLORS será criado no arquivo Defines.mqh:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#define ON_RESET_WINDOW_COLORS    (13) // Zeroing colors of all elements on the form

O método para a gestão do estado do formulário é apresentado no código abaixo.

class CWindow : public CElement
  {
public:
   //--- Define o estado da janela
   void              State(const bool flag);
  };
//+------------------------------------------------------------------+
//| Define o estado da janela                                        |
//+------------------------------------------------------------------+
void CWindow::State(const bool flag)
  {
//--- Se a janela é para ser bloqueada
   if(!flag)
     {
      //--- Define o status
      m_is_locked=true;
      //--- Define a cor do cabeçalho
      m_caption_bg.BackColor(m_caption_bg_color_off);
      //--- Zera o sinal da cor. O reset será realizado também para os outros elementos.
      ::EventChartCustom(m_chart_id,ON_RESET_WINDOW_COLORS,(long)CElement::Id(),0,"");
     }
//--- Se a janela é para ser desbloqueada
   else
     {
      //--- Define o status
      m_is_locked=false;
      //--- Define a cor do cabeçalho
      m_caption_bg.BackColor(m_caption_bg_color);
      //--- Zera o foco
      CElement::MouseFocus(false);
     }
  }

Vamos voltar para a manipulação do evento ON_OPEN_DIALOG_BOX. Na classe principal para manipular com os eventos da interface gráfica (CWndEvents) crie o método CWndEvents::OnOpenDialogBox(), que será chamado no método comum CWndEvents::ChartEventCustom() para a manipulação de todos os eventos personalizados.

O método CWndEvents::OnOpenDialogBox() começa com duas verificações: um para o identificador do evento e outro para o nome do programa. Se elas forem verificadas com êxito, então, será realizado a iteração sobre todas as janelas para descobrir qual janela gerou o evento. O identificador do elemento que está contido nesta mensagem (lparam) facilitará este processo. Os formulários que não possuírem os identificadores correspondentes serão bloqueados em conjunto com todos os elementos que estão ligados a ele. As prioridades de todos os objetos serão zeradas com a ajuda do método ResetZorders(), não reagindo ao clique esquerdo do mouse. Tendo realizado isso para os formulários que tiverem os identificadores correspondentes, é armazenado o índice da janela ativa no momento como o índice da janela anteriormente ativa. Ative este formulário e restaure a prioridade do clique do botão esquerdo do mouse para todos os seus objetos. Armazene o índice desta janela como ativo no momento. Então, faça com que todos os elementos deste formulário seja visível e restaure as suas prioridades para o clique do botão esquerdo do mouse, omitindo o elemento do formulário (já que ele está visível) e os elementos suspensos. 

Se uma janela de diálogo é aberta quando uma dica de contexto está visível, então, a dica de contexto deve ser oculta. Ele não desaparecerá sozinho já que o formulário que ele está ligado já está bloqueado. O array privado para dicas de contexto foi criado anteriormente para atender tais casos. O acesso aos métodos de quaisquer elementos de uma base pode ser recebido na classe principal CWndEvents para o tratamento de eventos. 

class CWndEvents : public CWndContainer
  {
private:
   //--- Abrindo uma janela de diálogo
   bool              OnOpenDialogBox(void);
  };
//+------------------------------------------------------------------+
//| Evento CHARTEVENT_CUSTOM                                         |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventCustom(void)
  {
//--- Se o sinal for para minimizar o formulário
//--- Se o sinal for para maximizar o formulário
//--- Se o sinal é para ocultar os menus de contexto abaixo do elemento inicial
//--- Se o sinal for para esconder todos os menus de contexto

//--- Se o sinal é para abrir uma janela de diálogo
   if(OnOpenDialogBox())
      return;
  }
//+------------------------------------------------------------------+
//| ON_OPEN_DIALOG_BOX event                                         |
//+------------------------------------------------------------------+
bool CWndEvents::OnOpenDialogBox(void)
  {
//--- Se o sinal é para abrir uma janela de diálogo
   if(m_id!=CHARTEVENT_CUSTOM+ON_OPEN_DIALOG_BOX)
      return(false);
//--- Sai, se a mensagem é de outro programa
   if(m_sparam!=m_program_name)
      return(true);
//--- Itera sobre o array da janela
   int window_total=CWndContainer::WindowsTotal();
   for(int w=0; w<window_total; w++)
     {
      //--- Se os identificadores forem iguais
      if(m_windows[w].Id()==m_lparam)
        {
         //--- Armazena o índice da janela no formulário que este foi criado
         m_windows[w].PrevActiveWindowIndex(m_active_window_index);
         //--- Ativa o formulário
         m_windows[w].State(true);
         //--- Restaura as prioridades do botão esquerdo do mouse para os objetos do formulário
         m_windows[w].SetZorders();
         //--- Armazena o índice da janela ativada
         m_active_window_index=w;
         //--- Faz com que todos os elementos da janela ativada seja visível
         int elements_total=CWndContainer::ElementsTotal(w);
         for(int e=0; e<elements_total; e++)
           {
            //--- Pula os formulários e elementos suspensos
            if(m_wnd[w].m_elements[e].ClassName()=="CWindow" || 
               m_wnd[w].m_elements[e].IsDropdown())
               continue;
            //--- Faz com que o elemento seja visível
            m_wnd[w].m_elements[e].Show();
            //--- Restaurar a prioridade do clique do botão esquerdo do mouse para o elemento
            m_wnd[w].m_elements[e].SetZorders();
           }
         //--- Oculta as dicas de contexto
         int tooltips_total=CWndContainer::TooltipsTotal(m_windows[w].PrevActiveWindowIndex());
         for(int t=0; t<tooltips_total; t++)
            m_wnd[m_windows[w].PrevActiveWindowIndex()].m_tooltips[t].FadeOutTooltip();
        }
      //--- Outros formulários serão bloqueados até que a janela ativada seja fechada
      else
        {
         //--- Bloqueia o formulário
         m_windows[w].State(false);
         //--- Zera as prioridades do clique esquerdo do mouse para os elementos do formulário
         int elements_total=CWndContainer::ElementsTotal(w);
         for(int e=0; e<elements_total; e++)
            m_wnd[w].m_elements[e].ResetZorders();
        }
     }
//---
   return(true);
  }

Agora, nós vamos abordar o identificador ON_RESET_WINDOW_COLORS que foi criado no início deste artigo. Antes de gravar um método para manipular este evento, mais um método virtual padrão deve ser adicionado à classe base CElement de todos os elementos que serão designados para zerar a cor. Vamos nomeá-lo de CElement::ResetColors():

class CElement
  {
public:
   //--- Zera a cor do elemento
   virtual void      ResetColors(void) {}
  };

Os métodos ResetColors() com características específicas para cada elemento devem ser criados em todas as classes derivadas. O código a seguir mostra um exemplo para o elemento botão com ícone (CIconButton). O método ResetColors() para todos os outros elementos podem ser encontrados nos arquivos anexados neste artigo.

class CIconButton : public CElement
  {
public:
   //--- Zera a cor do elemento
   void              ResetColors(void);
  };
//+------------------------------------------------------------------+
//| Zera a cor                                                       |
//+------------------------------------------------------------------+
void CIconButton::ResetColors(void)
  {
//--- Sair, se este é o modo de dois estados e o botão é pressionado
   if(m_two_state && m_button_state)
      return;
//--- Zera a cor
   m_button.BackColor(m_back_color);
//--- Zera o foco
   m_button.MouseFocus(false);
   CElement::MouseFocus(false);
  }

Assim, um método virtual na classe base de elementos e a sua própria versão nas classes derivadas proporcionam a possibilidade de zerar as cores de todos os elementos em um loop a partir do manipulador de eventos da classe principal da biblioteca (CWndEvents).

Escreva o método CWndEvents::OnResetWindowColors() para manipular o evento ON_RESET_WINDOW_COLORS. Isso é bem simples. Procure pelo formulário que acaba de ser desativado pelo identificador de elemento que acaba de ser recebido por uma mensagem. Se foi encontrado, armazena o seu índice. Se o índice foi armazenado, reseta as cores de todos os elementos deste formulário. Os detalhes deste método pode ser encontrado no código abaixo. 

class CWndEvents : public CWndContainer
  {
private:
   //--- Reseta a cor da formulário e dos seus elementos
   bool              OnResetWindowColors(void);
  };
//+------------------------------------------------------------------+
//| Evento CHARTEVENT_CUSTOM                                         |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventCustom(void)
  {
//--- Se o sinal for para minimizar o formulário
//--- Se o sinal for para maximizar o formulário
//--- Se o sinal é para ocultar os menus de contexto abaixo do elemento inicial
//--- Se o sinal for para esconder todos os menus de contexto
//--- Se o sinal é para abrir uma janela de diálogo
//--- Se o sinal estiver é para resetar as cores de todos os elementos na formulário especificado
   if(OnResetWindowColors())
      return;
  }
//+------------------------------------------------------------------+
//| ON_RESET_WINDOW_COLORS event                                     |
//+------------------------------------------------------------------+
bool CWndEvents::OnResetWindowColors(void)
  {
//--- Se o sinal é para resetar a cor da janela
   if(m_id!=CHARTEVENT_CUSTOM+ON_RESET_WINDOW_COLORS)
      return(false);
//--- Para identificar o índice do formulário a partir do qual a mensagem foi recebida
   int index=WRONG_VALUE;
//--- Itera sobre o array da janela
   int window_total=CWndContainer::WindowsTotal();
   for(int w=0; w<window_total; w++)
     {
      //--- Se os identificadores forem iguais
      if(m_windows[w].Id()==m_lparam)
        {
         //--- Armazena o índice
         index=w;
         //--- Zera a cor do formulário
         m_windows[w].ResetColors();
         break;
        }
     }
//--- Sai, se o índice não foi identificado
   if(index==WRONG_VALUE)
      return(true);
//--- Reseta a cor de todos os seus elementos
   int elements_total=CWndContainer::ElementsTotal(index);
   for(int e=0; e<elements_total; e++)
      m_wnd[index].m_elements[e].ResetColors();
//--- Redesenho do gráfico
   m_chart.Redraw();
   return(true);
  }

Nós já esclarecemos o processo de abertura das janelas. Agora, nós temos que implementar os métodos para fechar e restaurar a janela anteriormente ativa. Para lidar com este evento, nós temos que criar o identificador ON_CLOSE_DIALOG_BOX no arquivo Defines.mqh:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#define ON_CLOSE_DIALOG_BOX       (12) // Fecha o evento da janela de diálogo

Na classe CWindow nós estamos usando o método CWindow::CloseWindow() para fechar o formulário e o programa junto. Neste método, a seção que fecha as janelas de diálogo (W_DIALOG) não foi implementada ainda. Vamos escrever um método adicional que irá gerar um evento para que as janelas de diálogo se fechem. Além do (1) identificador do evento, a mensagem irá conter também (2) o identificador do elemento, (3) o índice da janela previamente ativa e (4) o texto do cabeçalho. Vamos chamar esse método de CWindow::CloseDialogBox(). Mais tarde, nós também vamos usar isso em controles complexos, onde o fechamento de uma janela será realizado por outros elementos além do botão de fechamento.

class CWindow : public CElement
  {
public:
   //--- Fechamento de uma janela de diálogo
   void              CloseDialogBox(void);
  };
//+------------------------------------------------------------------+
//| Fechamento de uma janela de diálogo                              |
//+------------------------------------------------------------------+
void CWindow::CloseDialogBox(void)
  {
//--- Estado de visibilidade
   CElement::IsVisible(false);
//--- Envia uma mensagem sobre ele
   ::EventChartCustom(m_chart_id,ON_CLOSE_DIALOG_BOX,CElement::Id(),m_prev_active_window_index,m_caption_text);
  }

Na classe CWindow, o método CWindow::CloseDialogBox() é para ser chamado no método CWindow::CloseWindow(), como é mostrado na versão reduzida do código abaixo. Uma versão completa pode ser encontrada nos arquivos anexados neste artigo.

//+------------------------------------------------------------------+
//| Fechando a janela de diálogo ou o programa                       |
//+------------------------------------------------------------------+
bool CWindow::CloseWindow(const string pressed_object)
  {
//--- Se o pressionamento não foi no botão de fechar a janela
   if(pressed_object!=m_button_close.Name())
      return(false);
//--- Se está é a janela principal
   if(m_window_type==W_MAIN)
     {
      //--- ...
     }
//--- Se isto é uma janela de diálogo
   else if(m_window_type==W_DIALOG)
     {
      //--- Fecha-o
      CloseDialogBox();
     }
//---
   return(false);
  }

Depois da mensagem com o identificador ON_CLOSE_DIALOG_BOX ser enviado, ele tem que ser monitorado e tratado no manipulador da classe CWndEvents. Para isso, vamos escrever o método CWndEvents::OnCloseDialogBox(). Itere sobre todas as janelas na base e procure por aquele com o identificador correspondente ao identificador na mensagem. Se tal janela é encontrada, ela deve ser desativada. Em seguida, oculte-o em conjunto com todos os elementos ligados a ele e ative o formulário pelo índice passado na mensagem. Depois disso, armazene o índice da janela ativa no momento e restaure as prioridades do botão esquerdo do mouse para os elementos.

class CWndEvents : public CWndContainer
  {
private:
   //--- Fechamento de uma janela de diálogo
   bool              OnCloseDialogBox(void);
  };
//+------------------------------------------------------------------+
//| Evento CHARTEVENT_CUSTOM                                         |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventCustom(void)
  {
//--- Se o sinal for para minimizar o formulário
//--- Se o sinal for para maximizar o formulário
//--- Se o sinal é para ocultar os menus de contexto abaixo do elemento inicial
//--- Se o sinal for para esconder todos os menus de contexto
//--- Se o sinal é para abrir uma janela de diálogo
//--- Se o sinal é para fechar uma janela de diálogo
   if(OnCloseDialogBox())
      return;
//--- Se o sinal estiver é para resetar as cores de todos os elementos na formulário especificado
  }
//+------------------------------------------------------------------+
//| Evento ON_CLOSE_DIALOG_BOX                                       |
//+------------------------------------------------------------------+
bool CWndEvents::OnCloseDialogBox(void)
  {
//--- Se o sinal é para fechar uma janela de diálogo
   if(m_id!=CHARTEVENT_CUSTOM+ON_CLOSE_DIALOG_BOX)
      return(false);
//--- Itera sobre o array da janela
   int window_total=CWndContainer::WindowsTotal();
   for(int w=0; w<window_total; w++)
     {
      //--- Se os identificadores forem iguais
      if(m_windows[w].Id()==m_lparam)
        {
         //--- Bloqueia o formulário
         m_windows[w].State(false);
         //--- Oculta o formulário
         int elements_total=CWndContainer::ElementsTotal(w);
         for(int e=0; e<elements_total; e++)
            m_wnd[w].m_elements[e].Hide();
         //--- Ativa o formulário anterior
         m_windows[int(m_dparam)].State(true);
         //--- Redesenho do gráfico
         m_chart.Redraw();
         break;
        }
     }
//--- Define o índice da janela anterior
   m_active_window_index=int(m_dparam);
//--- Restaura as prioridades do botão esquerdo do mouse para a janela ativada
   int elements_total=CWndContainer::ElementsTotal(m_active_window_index);
   for(int e=0; e<elements_total; e++)
      m_wnd[m_active_window_index].m_elements[e].SetZorders();
//---
   return(true);
  }

Agora, tudo está pronto para o teste do modo multi-janela.  

 


Teste do Modo Multi-Janela

Crie duas instâncias da classe CWindow no EA que usamos para testar os elementos informativos da interface. O resultado será de três formulários na interface gráfica do EA. O primeiro formulário será o principal (W_MAIN) e os dois outros terão um papel de janelas de diálogo (W_DIALOG). Anexe a primeira janela de diálogo para um dos botões no formulário principal. Crie três botões na primeira janela de diálogo e anexe a segunda janela de diálogo para um dos botões recém-criados. Desta forma, nós teremos três formulários abertos simultaneamente e só um deles estará ativo (disponível).

O código a seguir mostra o que precisa ser adicionado à classe personalizada CProgram da aplicação na fase atual de desenvolvimento. 

class CProgram : public CWndEvents
  {
private:
   //--- Formulário 2
   CWindow           m_window2;
   //--- Botões com Ícone
   CIconButton       m_icon_button6;
   CIconButton       m_icon_button7;
   CIconButton       m_icon_button8;

   //--- Formulário 3
   CWindow           m_window3;
   //---
private:
   //--- Formulário 2
   bool              CreateWindow2(const string text);
   //--- Botões com Ícone
#define ICONBUTTON6_GAP_X        (7)
#define ICONBUTTON6_GAP_Y        (25)
   bool              CreateIconButton6(const string text);
#define ICONBUTTON7_GAP_X        (7)
#define ICONBUTTON7_GAP_Y        (50)
   bool              CreateIconButton7(const string text);
#define ICONBUTTON8_GAP_X        (7)
#define ICONBUTTON8_GAP_Y        (75)
   bool              CreateIconButton8(const string text);

   //--- Formulário 3
   bool              CreateWindow3(const string text);
  };

Localize a chamada desses métodos no método principal de criação da interface gráfica da aplicação em desenvolvimento. Abaixo está uma versão reduzida deste método. 

//+------------------------------------------------------------------+
//| Cria o painel de negociação                                      |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- Criação do formulário 1 para os controles
//--- Criação dos controles:
//    Menu principal
//--- Menus de contexto
//--- Criando a barra de status
//--- Botões com Ícone

//--- Criação do formulário 2 para os controles
   if(!CreateWindow2("Icon Button 1"))
      return(false);
//--- Botões com Ícone
   if(!CreateIconButton6("Icon Button 6..."))
      return(false);
   if(!CreateIconButton7("Icon Button 7"))
      return(false);
   if(!CreateIconButton8("Icon Button 8"))
      return(false);

//--- Criação do formulário 3 para os controles
   if(!CreateWindow3("Icon Button 6"))
      return(false);

//--- Dicas de contexto
//--- Redesenho do gráfico
   m_chart.Redraw();
   return(true);
  }

Nós vamos considerar o método apenas para a primeira janela de diálogo (segundo formulário). Como você deve se lembrar, você precisa usar o método CWndContainer::AddWindow() para a adição de um formulário à base. Observe que como as coordenadas do formulário são definidas no código abaixo. Como as coordenadas padrão são zeradas quando o programa é carregado ao gráfico, as coordenadas que você considera adequadas serão definidas. Neste exemplo os valores são x=1, y=20. Depois disso, o formulário pode ser movido e, então, o período de tempo ou o símbolo do gráfico pode ser alterado. O código a seguir mostra que o formulário vai ficar onde ele estava pela última vez. Se você deseja que o formulário seja localizado onde estava o primeiro carregamento do programa ao gráfico, então, remova essas condições. Neste exemplo, todos os três formulários da interface gráfica do programa terão as mesmas condições.

Vamos providenciar para que os formulários de diálogo possam ser movidos sobre o gráfico. O tipo da janela deve ser definida como um diálogo (W_DIALOG), caso contrário, você vai encontrar erros no funcionamento da interface gráfica. O ícone da janela pode ser redefinido usando o método CWindow::IconFile(). No caso de janelas de diálogo, o mesmo ícone pode ser utilizado como o de o elemento que traz esta janela.

//+------------------------------------------------------------------+
//| Cria o formulário 2 para os controles                            |
//+------------------------------------------------------------------+
bool CProgram::CreateWindow2(const string caption_text)
  {
//--- Adiciona o ponteiro da janela para o array de janela
   CWndContainer::AddWindow(m_window2);
//--- Coordenadas
   int x=(m_window2.X()>0) ? m_window2.X() : 1;
   int y=(m_window2.Y()>0) ? m_window2.Y() : 20;
//--- Propriedades
   m_window2.Movable(true);
   m_window2.WindowType(W_DIALOG);
   m_window2.XSize(160);
   m_window2.YSize(160);
   m_window2.IconFile("Images\\EasyAndFastGUI\\Icons\\bmp16\\coins.bmp");
   m_window2.CaptionBgColor(clrCornflowerBlue);
   m_window2.CaptionBgColorHover(C'150,190,240');
//--- Criando a formulário
   if(!m_window2.CreateWindow(m_chart_id,m_subwin,caption_text,x,y))
      return(false);
//---
   return(true);
  }

Deixe-me lembrá-lo sobre alguns detalhes de como anexar controles para uma determinada janela de diálogo. Como exemplo, vamos considerar um dos métodos de botão, destinados a este formulário. Eu gostaria de destacar apenas duas coisas. 

É preciso lembrar que:

  • O elemento deve ser passado para o ponteiro do formulário na qual ele é para ser ligado
  • Quando o ponteiro do elemento é armazenado na base, especifique o índice do formulário que o elemento é para ser anexado. Neste caso, é o índice 1. 
//+------------------------------------------------------------------+
//| Cria o botão com ícone 6                                         |
//+------------------------------------------------------------------+
bool CProgram::CreateIconButton6(const string button_text)
  {
//--- Armazena o ponteiro da janela
   m_icon_button6.WindowPointer(m_window2);
//--- Coordenadas
   int x=m_window2.X()+ICONBUTTON6_GAP_X;
   int y=m_window2.Y()+ICONBUTTON6_GAP_Y;
//--- Define as propriedades antes da criação
   m_icon_button6.TwoState(false);
   m_icon_button6.ButtonXSize(146);
   m_icon_button6.ButtonYSize(22);
   m_icon_button6.LabelColor(clrBlack);
   m_icon_button6.LabelColorPressed(clrBlack);
   m_icon_button6.BorderColorOff(clrWhite);
   m_icon_button6.BackColor(clrLightGray);
   m_icon_button6.BackColorHover(C'193,218,255');
   m_icon_button6.BackColorPressed(C'153,178,215');
   m_icon_button6.IconFileOn("Images\\EasyAndFastGUI\\Icons\\bmp16\\script.bmp");
   m_icon_button6.IconFileOff("Images\\EasyAndFastGUI\\Icons\\bmp16\\script_colorless.bmp");
//--- Cria o controle
   if(!m_icon_button6.CreateIconButton(m_chart_id,m_subwin,button_text,x,y))
      return(false);
//--- Adiciona o ponteiro do elemento para a base
   CWndContainer::AddToElementsArray(1,m_icon_button6);
   return(true);
  }

Cabe ao desenvolvedor do aplicativo gerenciar a exibição de janelas. Acompanhe o pressionamento em qualquer controle no manipulador de eventos da classe personalizada CProgram e mostre a janela relevante. Atribua a chamada da primeira janela do diálogo ao botão na janela principal do EA (segundo formulário), e a chamada da segunda janela do diálogo ao botão na primeira janela de diálogo (terceiro formulário).

//+------------------------------------------------------------------+
//| Manipulador de eventos                                           |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- O evento de pressionamento do botão
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- Se o texto corresponde
      if(sparam==m_icon_button1.Text())
        {
         //--- Mostra a janela 2
         m_window2.Show();
        }
      //--- Se o texto corresponde
      if(sparam==m_icon_button6.Text())
        {
         //--- Mostra a janela 3
         m_window3.Show();
        }
     }
  }

O resultado desejado é mostrado na imagem abaixo. Por favor, note os pontos suspensos no nomes dos botões «Icon Button 1...» e «Icon Button 6...». Esta é uma forma habitual de deixar o usuário saber que ao pressionar este elemento, irá abrir uma janela de diálogo.

Fig. 1. Teste do modo multi-janela.

Fig. 1. Teste do modo multi-janela.

 

Se você alternar o símbolo ou o tempo gráfico do gráfico quando vários formulários são abertos, você vai encontrar um problema. As janelas de diálogo irão desaparecer como deveria mas o gerenciamento não passará da janela principal. O formulário não irá responder a ações do usuário. A solução para isso é simples. Como você deve se lembrar, o CWndEvents::Destroy() é chamado no método CProgram::OnDeinitEvent() para desinicialização da classe personalizada. A interface gráfica da aplicação é excluída neste método. A gestão tem de ser dada para a janela principal no momento da exclusão da interface gráfica. Portanto, algumas adições devem ser introduzidas ao método CWndEvents::Destroy():

  • Defina o índice da janela principal como ativa.
  • Ative a janela principal e desative o resto.

Abaixo está o código da versão atual do método CWndEvents::Destroy(). 

//+------------------------------------------------------------------+
//| Removendo todos os objetos                                       |
//+------------------------------------------------------------------+
void CWndEvents::Destroy(void)
  {
//--- Define o índice da janela principal
   m_active_window_index=0;
//--- Obtém o número de janelas
   int window_total=CWndContainer::WindowsTotal();
//--- Itera sobre o array da janela
   for(int w=0; w<window_total; w++)
     {
      //--- Ativa a janela principal
      if(m_windows[w].WindowType()==W_MAIN)
         m_windows[w].State(true);
      //--- Bloqueia as janelas de diálogo
      else
         m_windows[w].State(false);
     }
//--- Esvazia os arrays do elemento
   for(int w=0; w<window_total; w++)
     {
      int elements_total=CWndContainer::ElementsTotal(w);
      for(int e=0; e<elements_total; e++)
        {
         //--- Se o ponteiro é inválido, passe para o próximo
         if(::CheckPointer(m_wnd[w].m_elements[e])==POINTER_INVALID)
            continue;
         //--- Remove os objetos do elemento
         m_wnd[w].m_elements[e].Delete();
        }
      //--- Esvazia os arrays do elemento
      ::ArrayFree(m_wnd[w].m_objects);
      ::ArrayFree(m_wnd[w].m_elements);
      ::ArrayFree(m_wnd[w].m_context_menus);
     }
//--- Esvazia os arrays de formulário
   ::ArrayFree(m_wnd);
   ::ArrayFree(m_windows);
  }

A primeira versão do modo multi-janela foi implementada. Tudo acabou se tornando menos complicado do que poderia parecer inicialmente.  

 


Aprimoramento do Sistema de Prioridades do Botão Esquerdo do Mouse

Até agora, a gestão de prioridades do clique esquerdo do mouse sobre os elementos de interface foram realizados pelos eventos com os identificadores ON_OPEN_DIALOG_BOX e ON_CLOSE_DIALOG_BOX. A razão para isso foi que, quando o próximo elemento suspenso foi desenvolvido, ficou a critério do usuário atribuir o valor da prioridade para cada objeto deste elemento. As prioridades de outros elementos que poderiam acontecer de estar abaixo dela foram levadas em conta. No entanto, ao criar controles compostos complexos, este sistema foi difícil e fácil de se confundir. Para facilitar as coisas, vamos criar mais dois identificadores para esses eventos:

  • ON_ZERO_PRIORITIES - Reseta as prioridades.
  • ON_SET_PRIORITIES - Restaura as prioridades.

Adicione-os no arquivo Defines.mqh.

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#define ON_ZERO_PRIORITIES        (14) // Redefinição das prioridades do botão esquerdo do mouse
#define ON_SET_PRIORITIES         (15) // Restaurando as prioridades do botão esquerdo do mouse

A geração de eventos com esses identificadores devem estar localizados nas classes dos elementos que são ou podem ser suspensos. No atual estágio de desenvolvimento, no presente conjunto de interface, este elemento é o menu de contexto. Portanto, adicione o código para os métodos Show() e Hide() da classe CContextMenucomo é mostrado abaixo nas versões reduzidas do código.

//+------------------------------------------------------------------+
//| Exibe o menu de contexto                                         |
//+------------------------------------------------------------------+
void CContextMenu::Show(void)
  {
//--- Retorna, se o elemento já for visível
//--- Mostra os objetos do menu de contexto
//--- Mostra os elementos de menu
//--- Atribui o estado de um elemento visível
//--- Estado do menu de contexto
//--- Registra o estado no nó anterior
//--- Bloqueia o formulário

//--- Envia um sinal para zerar as prioridades do botão esquerdo do mouse
   ::EventChartCustom(m_chart_id,ON_ZERO_PRIORITIES,CElement::Id(),0.0,"");
  }
//+------------------------------------------------------------------+
//| Oculta o menu de contexto                                        |
//+------------------------------------------------------------------+
void CContextMenu::Hide(void)
  {
//--- Sai se o elemento está oculto
//--- Oculta os objetos do menu de contexto
//--- Oculta os elementos de menu
//--- Zera o foco
//--- Estado do menu de contexto
//--- Registra o estado no nó anterior

//--- Envia um sinal para restaurar as prioridades do botão esquerdo do mouse
   ::EventChartCustom(m_chart_id,ON_SET_PRIORITIES,0,0.0,"");
  }

Nós vamos receber estas mensagens na classe principal para lidar com todas as mensagens (CWndEvents). Para isso, nós vamos escrever um método de tratamento separado para cada identificador. Estes métodos serão chamados no método principal para lidar com eventos personalizados CWndEvents::ChartEventCustom(). 

class CWndEvents : public CWndContainer
  {
private:
   //--- Resetando as prioridades do clique do botão esquerdo do mouse
   bool              OnZeroPriorities(void);
   //--- Restaura as prioridades do botão esquerdo do mouse
   bool              OnSetPriorities(void);
  };
//+------------------------------------------------------------------+
//| Evento CHARTEVENT_CUSTOM                                         |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventCustom(void)
  {
//--- Se o sinal for para minimizar o formulário
//--- Se o sinal for para maximizar o formulário
//--- Se o sinal é para ocultar os menus de contexto abaixo do elemento inicial
//--- Se o sinal for para esconder todos os menus de contexto
//--- Se o sinal é para abrir uma janela de diálogo
//--- Se o sinal é para fechar uma janela de diálogo
//--- Se o sinal estiver é para resetar as cores de todos os elementos na formulário especificado

//--- Se o sinal for para resetar as prioridades do clique do botão esquerdo do mouse
   if(OnZeroPriorities())
      return;
//--- Se o sinal for para restaurar as prioridades do clique do botão esquerdo do mouse
   if(OnSetPriorities())
      return;
  }

No método CWndEvents::OnZeroPriorities(), itere sobre todos os elementos da janela ativa e resete as prioridades de todos eles, exceto aquele com o identificador do elemento contido na mensagem (parâmetro lparam), com exceção os elementos de menu e menus de contexto. A razão por que excluímos os elementos de menu e menus de contexto é que vários menus de contexto podem ser abertos ao mesmo tempo (um do outro).

//+------------------------------------------------------------------+
//| Evento ON_ZERO_PRIORITIES                                        |
//+------------------------------------------------------------------+
bool CWndEvents::OnZeroPriorities(void)
  {
//--- Se o sinal estiver para resetar as prioridades do botão esquerdo do mouse
   if(m_id!=CHARTEVENT_CUSTOM+ON_ZERO_PRIORITIES)
      return(false);
//---
   int elements_total=CWndContainer::ElementsTotal(m_active_window_index);
   for(int e=0; e<elements_total; e++)
     {
      //--- Reseta as prioridades de todos os elementos, exceto aquele com o id passado no evento e...
      if(m_lparam!=m_wnd[m_active_window_index].m_elements[e].Id())
        {
         //--- ...exceto os menus de contexto
         if(m_wnd[m_active_window_index].m_elements[e].ClassName()=="CMenuItem" ||
            m_wnd[m_active_window_index].m_elements[e].ClassName()=="CContextMenu")
            continue;
         //---
         m_wnd[m_active_window_index].m_elements[e].ResetZorders();
        }
     }
//---
   return(true);
  }

Se a mensagem recebida contém o identificador de evento ON_SET_PRIORITIES, então, restaure as prioridades do clique do botão esquerdo do mouse para todos os elementos da janela ativa. 

//+------------------------------------------------------------------+
//| Evento ON_SET_PRIORITIES                                         |
//+------------------------------------------------------------------+
bool CWndEvents::OnSetPriorities(void)
  {
//--- Se o sinal for para restaurar as prioridades do clique do botão esquerdo do mouse
   if(m_id!=CHARTEVENT_CUSTOM+ON_SET_PRIORITIES)
      return(false);
//---
   int elements_total=CWndContainer::ElementsTotal(m_active_window_index);
   for(int e=0; e<elements_total; e++)
      m_wnd[m_active_window_index].m_elements[e].SetZorders();
//---
   return(true);
  }

 


Conclusão

A biblioteca para a criação de interfaces gráficas no atual estágio de desenvolvimento se parece com o esquema abaixo.

Fig. 2. Estrutura da biblioteca no atual estágio de desenvolvimento.

Fig. 2. Estrutura da biblioteca no atual estágio de desenvolvimento.

 

Este é o artigo final da quarta parte da série sobre interfaces gráficas. No primeiro capítulo desta parte, nós discutimos o desenvolvimento da barra de status e a dica de contexto da interface informativa. No segundo capítulo, foram discutidos o modo multi-janela e um sistema de prioridade do botão esquerdo do mouse.

Você pode encontrar e baixar o material da primeira parte ou toda a série nos arquivos anexados, assim, você pode testar o 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 dos artigos (capítulos) da quarta parte:

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

Arquivos anexados |
Mais uma vez vamos falar sobre mapas de Kohonen Mais uma vez vamos falar sobre mapas de Kohonen
O artigo descreve as técnicas para trabalhar com mapas de Kohonen. Ele vai ser do interesse tanto para exploradores do mercado, com habilidades básicas nas plataformas MQL4 e MQL5, quanto para programadores experientes que enfrentam dificuldades com a conexão dos mapas de Kohonen aos seus projetos.
Interfaces Gráficas IV: Elementos Informativos da Interface (Capítulo 1) Interfaces Gráficas IV: Elementos Informativos da Interface (Capítulo 1)
No atual estágio de desenvolvimento, a biblioteca para a criação de interfaces gráficas contém um formulário e vários controles que podem ser ligados a ele. Foi mencionado antes que um dos artigos futuros seria dedicado ao modo multi-janela. Agora, nós já temos tudo preparado para considerar tal questão, desse modo, nós vamos lidar com isso no capítulo seguinte. Neste capítulo, nós vamos escrever as classes para criar os elementos de interface barra de status e dica de contexto.
Tratamento de erros e registro em log na MQL5 Tratamento de erros e registro em log na MQL5
Este artigo aborda questões sobre o tratamento de erros comuns no software. Além disso, trata do registro em log e monstra uma forma de empregar o registrador de logs via MQL5.
Teste de estratégias de negociação em ticks reiais Teste de estratégias de negociação em ticks reiais
Neste artigo mostraremos os resultados de teste de uma estratégia de negociação simples em três modos: "OHLC em M1", "Todos os ticks" e "Cada tick baseado em ticks reais" usando os ticks gravados a partir do histórico.