Interfaces Gráficas I: Formulário para os Controles (Capítulo 2)
Conteúdo
- Introdução
- A Classe do Formulário para os Controles
- Métodos para a Criação de um Formulário
- Anexar um Formulário no Gráfico
- Conclusão
Introdução
Este artigo é a continuação da primeira parte da série sobre as interfaces gráficas. O primeiro artigo Interfaces gráficas I: Preparação da Estrutura da Biblioteca (Capítulo 1) considera em detalhes a finalidade desta biblioteca. A lista completa dos links para os artigos da primeira parte se encontram 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 a estrutura da biblioteca para a criação das interfaces gráficas. Nós criamos (1) as classes derivadas para os objetos primitivos, (2) uma classe base para todos os controles, (3) as classes principais para armazenar os ponteiros do controle e a gestão desses controles no manipulador comum de eventos.
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. O formulário será móvel e todos os controles ligados a ele serão deslocados da mesma maneira.
A Classe do Formulário para os Controles
Como discutido anteriormente, a descrição da classe CElement já continha alguns métodos virtuais, que serão únicos para cada controle. Vamos colocar as suas duplicatas na classe CWindow e em toda descrição futura de outros controles que deverão fazer o mesmo. O código para esses métodos será considerado após a criação dos métodos para a construção de um formulário (janela) para os controles.
//+------------------------------------------------------------------+ //| Window.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" //+------------------------------------------------------------------+ //| Classe para a criação do formulário para os controles | //+------------------------------------------------------------------+ class CWindow : public CElement { public: CWindow(void); ~CWindow(void); //--- 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); //--- Exibir, ocultar, resetar, remover virtual void Show(void); virtual void Hide(void); virtual void Reset(void); virtual void Delete(void); //--- Definir, resetar as prioridades para o clique esquerdo do mouse virtual void SetZorders(void); virtual void ResetZorders(void); }; //+------------------------------------------------------------------+ //| Construtor | //+------------------------------------------------------------------+ CWindow::CWindow(void) { } //+------------------------------------------------------------------+ //| Destrutor | //+------------------------------------------------------------------+ CWindow::~CWindow(void) { } //+------------------------------------------------------------------+
Na classe CWindow e em algumas outras classes, serão utilizados muitos modos de diferentes tipos para variadas ações. As listas dos identificadores das constantes nomeadas para todos os modos e tipos de dados serão armazenadas no arquivo separado Enums.mqh. Inclua-o no arquivo Objects.mqh, assim, todas os modos e tipos de enumerações estarão disponíveis em todos os arquivos de biblioteca:
//+------------------------------------------------------------------+ //| Objects.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Enums.mqh" #include "Defines.mqh" #include <ChartObjects\ChartObjectsBmpControls.mqh> #include <ChartObjects\ChartObjectsTxtControls.mqh>
Quais partes que constituirão a janela que nós vamos criar?
- Fundo. Todos os controles estarão localizados nesta área.
- Cabeçalho. Esta parte permite mover a janela que contém os controles de interface listados abaixo.
- Ícone. Atributos adicionais para a identificação visual.
- Legenda. Nome da janela.
- O botão de "Dica". Pressionando este botão, é habilitado o modo de exibição de dicas para os controles que esta funcionalidade está presente.
- Botão para minimizar/maximizar a janela.
- Botão para fechar a janela.
Fig. 1. Partes que compõem o formulário para os controles
Será implementado o modo multi-janela na biblioteca da interface gráfica, que nós estamos desenvolvendo. Um aplicativo pode ter apenas uma janela. No entanto, quando um programa possui muitas opções, não havendo possibilidade de colocá-las em um único formulário, é necessário adicionar mais um formulário. Ela pode ser usada para algumas configurações comuns da aplicação, manual do usuário, uma janela para abrir/salvar o arquivo e até mesmo uma paleta para selecionar uma cor. Assim, ideias variadas podem exigir janelas adicionais. Normalmente, elas são chamadas de "janelas de diálogo".
A classe CWindow deve apresentam uma oportunidade para definir o tipo da janela, isto é, qual janela é a principal do aplicativo e qual dela é a de diálogo. Durante o processo de desenvolvimento, pode acontecer de surgir algumas ideias sobre novos tipos de janelas, mas no momento, vamos criar uma enumeração no arquivo Enums.mqh contendo apenas dois tipos para a janela (1) principal e (2) de diálogo:
//+------------------------------------------------------------------+ //| Enumeração dos tipos de janelas | //+------------------------------------------------------------------+ enum ENUM_WINDOW_TYPE { W_MAIN =0, W_DIALOG =1 };
O fundo e o cabeçalho serão criados a partir do objeto primitivo "Rótulo Retangular" (Rectangle Label). Para isso, nós temos a classe CRectLabel. Para a legenda, nós vamos usar a classe CLabel, com o qual o objeto "Rótulo de texto" (Text Label) poderá ser criado. Para o ícone da janela e os botões listados acima, é necessário o objeto "Rótulo Gráfico" (Bitmap Label), e para isso nós já temos a classe CBmpLabel.
Se o arquivo Element.mqh já está incluído, então, todas estas classes, que foram definidas no arquivo Object.mqh estarão disponíveis para uso. Vamos criar as respectivas instâncias para cada controle da janela no corpo da classe CWindow e todos os métodos necessários para a criação de uma janela:
class CWindow : public CElement { private: //--- Objetos para a criação de um formulário CRectLabel m_bg; CRectLabel m_caption_bg; CBmpLabel m_icon; CLabel m_label; CBmpLabel m_button_tooltip; CBmpLabel m_button_unroll; CBmpLabel m_button_rollup; CBmpLabel m_button_close; //--- public: //--- Métodos para a criação de uma janela bool CreateWindow(const long chart_id,const int window,const string caption_text,const int x,const int y); //--- private: bool CreateBackground(void); bool CreateCaption(void); bool CreateIcon(void); bool CreateLabel(void); bool CreateButtonClose(void); bool CreateButtonRollUp(void); bool CreateButtonUnroll(void); bool CreateButtonTooltip(void); };
Antes de começarmos a preencher esses métodos, nós temos que preparar algumas funcionalidades na classe CWndContainer onde os arrays armazenarão os ponteiros para todos os controles de interface e suas componentes. Nós também precisamos fazer o seguinte passo no desenvolvimento da estrutura da biblioteca e preparar o programa para testes.
Incluir o arquivo Window.mqh com a classe CWindow no arquivo WndContainer.mqh com a classe CWndContainer. Nós vamos incluir aqui também todas as outras classes com os controles.
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Window.mqh" // ... // Nós vamos incluir aqui também todas as outras classes com os controles // ...
Desta forma, elas estarão disponíveis para uso na classe CProgram, que está na classe onde a interface do usuário da aplicação MQL será criada. Será possível criar e utilizar os membros da classe com estes tipos de dados nas classes CWndContainer e CWndEvents. Por exemplo, se depois da inclusão do arquivo Window.mqh, um array dinâmico de ponteiros do tipo CWindow for criado (ele não é necessário para o trabalho), não haverá quaisquer problemas e nenhuma mensagem de erro será exibida.
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Window.mqh" //+------------------------------------------------------------------+ //| Classe para armazenar todos os objetos da interface | //+------------------------------------------------------------------+ class CWndContainer { protected: //--- Array da janela CWindow *m_windows[]; };
No entanto, se o arquivo Window.mqh não foi incluído, então, você receberia uma mensagem de erro durante a criação de um membro da classe com o tipo CWindow, como na imagem abaixo quando você tentar compilar o arquivo:
Fig. 2. Mensagem na compilação notificando que o tipo especificado está ausente
Junto com o array de janela dinâmica, nós vamos precisar de uma estrutura de arrays dinâmicos de ponteiros para todos os controles (CElement) e arrays de todos os componentes dos objetos (CChartObject). Como um exemplo dessa estrutura, vamos criar um array dinâmico de tamanho igual ao do array de janela (m_window[]). Como resultado, nós vamos receber um array de arrays de ponteiro para os controles para cada forma (veja o código abaixo).
Por favor, note que o array m_objects[] é declarado com o tipo CChartObject. Na compilação não haverá quaisquer erros já que este tipo de objeto já está presente na estrutura da biblioteca em desenvolvimento e está incluída no arquivo Objects.mqh.
class CWndContainer { protected: //--- Estrutura de arrays de controle struct WindowElements { //--- Array comum de todos os objetos CChartObject *m_objects[]; //--- Array comum de todos os controles CElement *m_elements[]; }; //--- Array dos arrays de controles para cada janela WindowElements m_wnd[]; };
Esta não é uma lista completa de arrays dinâmicos da estrutura WindowElements. Esta lista será atualizada assim que outros controles forem criados.
Para obter o tamanho dos arrays, é necessário utilizar os métodos adequados. O número de objetos de todos os controles e o número de controles para cada janela pode ser obtido passando o índice da janela (window_index) como um parâmetro.
class CWndContainer { public: //--- Número de janelas na interface int WindowsTotal(void) { return(::ArraySize(m_windows)); } //--- Número de objetos de todos os controles int ObjectsElementsTotal(const int window_index); //--- Número de controles int ElementsTotal(const int window_index); };
Devemos verificar se o tamanho do array não foi excedido. Muitas vezes, uma frase repetida pode ser escrito como uma substituição de macro, adicionando no início uma substituição de macro predefinida que contém o nome da função (__FUNCTION__). Vamos escrever tal substituição de macro (PREVENTING_OUT_OF_RANGE) e adicionar isto para o arquivo Defines.mqh:
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ //--- Prevenção de exceder o tamanho do array #define PREVENTING_OUT_OF_RANGE __FUNCTION__," > Prevenção de exceder o tamanho do array."
Agora, será conveniente usá-lo em todas as funções onde há a verificação se o tamanho do array não foi excedido:
//+------------------------------------------------------------------+ //| Retorna o número de objetos pelo índice da janela específica | //+------------------------------------------------------------------+ int CWndContainer::ObjectsElementsTotal(const int window_index) { if(window_index>=::ArraySize(m_wnd)) { ::Print(PREVENTING_OUT_OF_RANGE); return(WRONG_VALUE); } //--- return(::ArraySize(m_wnd[window_index].m_objects)); } //+------------------------------------------------------------------+ //| Retorna o número de controles pelo índice da janela específica | //+------------------------------------------------------------------+ int CWndContainer::ElementsTotal(const int window_index) { if(window_index>=::ArraySize(m_wnd)) { ::Print(PREVENTING_OUT_OF_RANGE); return(WRONG_VALUE); } //--- return(::ArraySize(m_wnd[window_index].m_elements)); }
Agora, imagine que você é um usuário desta biblioteca e vai para a classe CProgram do EA criado anteriormente para testes. Vamos supor que nós precisamos criar um painel de negociação. Para isso, nós criamos o método CreateTradePanel() na classe CProgram. Não se esqueça que, para salvar espaço no artigo, nós só desenvolvemos em cima do que já foi feito antes e, à medida que avançamos, nós discutimos apenas o que foi adicionado nas diferentes classes.
class CProgram : public CWndEvents { public: //--- Cria um painel de negociação bool CreateTradePanel(void); }; //+------------------------------------------------------------------+ //| Cria um painel de negociação | //+------------------------------------------------------------------+ bool CProgram::CreateTradePanel(void) { //--- Criação de um formulário para os controles // ... //--- Criando os controles // ... //--- ::ChartRedraw(); return(true); }
O corpo deste método está vazio, mas em breve ele será preenchido com a funcionalidade necessária. A partir dos comentários no corpo do método, fica evidente que a janela para os controles será a primeira a ser criada. Nós precisamos de um método em que as propriedades da janela criada podem ser especificadas. Como nós já incluímos o arquivo Window.mqh no arquivo WndContainer.mqh, agora a classe CWindow estará disponível na classe CProgram. Portanto, nós vamos criar uma instância dessa classe e também um método para criar uma janela para os controles CreateWindow():
class CProgram : public CWndEvents { private: //--- Formulário para os controles CWindow m_window; //--- private: //--- Criação de um formulário bool CreateWindow(const string caption_text); };
Esta é hora para responder as seguintes perguntas:
- Como os ponteiros para os controles e seus objetos encontram o seu caminho para os arrays, que nós havíamos criado anteriormente na classe CWndContainer?
- Como será definido o identificador de cada controle de interface?
Já que a criação de uma interface leva uma sequência determinada (estrita), o processo de desenvolvimento da interface deve ser organizado de tal forma para que o desenvolvedor não se encontre na situação em que ele não sabe o que fazer caso a sequência não seja seguida exatamente. É por isso que a cada ação da biblioteca do usuário será controlada pelo motor da biblioteca. Vamos organizá-la de modo que é impossível criar um formulário para os controles enquanto o ponteiro para ele não se encontra na base dos ponteiros na classe CWndContainer. Um controle não será criado até que um ponteiro para o formulário seja anexado a ele, que não está armazenada em sua classe.
Vamos criar o método AddWindow() para a adição de um ponteiro de janela para a base dos controles de interface na classe CWndContainer. Aqui também será realizado (1) o armazenamento de um ponteiro no array de controles, (2) o armazenamento dos ponteiros de objetos do controle no array comum de ponteiros de objeto, para o qual vamos precisar do método AddToObjectsArray(), e também (3) definir o identificador. Além disso, é necessário (4) armazenar o último identificador na janela de propriedades, uma vez que será necessário definir os identificadores para cada controle dentro de suas classes. Isso será possível porque, como mencionado no parágrafo anterior, cada controle terá um ponteiro para a janela que ele está anexado.
Vamos começar com a criação do método LastId() na classe CWindow para armazenar e obter o identificador do último controle de interface criado:
class CWindow : public CElement { private: //--- Identificador do último controle int m_last_id; //--- public: //--- Métodos para armazenar e obter o ID do último controle criado void LastId(const int id) { m_last_id=id; } int LastId(void) const { return(m_last_id); } };
Vamos criar o resto dos métodos na classe CWndContainer, que será utilizado no método AddWindow(). Cada controle terá um nome de classe único, e, portanto, o método AddToObjectsArray() será um modelo já que os objetos de diferentes controles serão passados para ele. Neste método, nós vamos iterar sobre o array de objetos de controle adicionando em turno um ponteiro para cada um deles no array de base. Para isso, nós vamos precisar de mais um método. Vamos nomeá-lo de AddToArray(). Como apenas os objetos de um tipo (CChartObject) serão passados para este método, não há necessidade de fazer um modelo.
class CWndContainer { protected: //--- Adicionando ponteiros de objetos de controle para o array comum template<typename T> void AddToObjectsArray(const int window_index,T &object); //--- Adiciona um ponteiro de objeto para um array void AddToArray(const int window_index,CChartObject &object); }; //+------------------------------------------------------------------+ //| Adiciona ponteiros de objetos de controle para um array comum | //+------------------------------------------------------------------+ template<typename T> void CWndContainer::AddToObjectsArray(const int window_index,T &object) { int total=object.ObjectsElementTotal(); for(int i=0; i<total; i++) AddToArray(window_index,object.Object(i)); } //+------------------------------------------------------------------+ //| Adiciona um ponteiro de objeto para um array | //+------------------------------------------------------------------+ void CWndContainer::AddToArray(const int window_index,CChartObject &object) { int size=::ArraySize(m_wnd[window_index].m_objects); ::ArrayResize(m_wnd[window_index].m_objects,size+1); m_wnd[window_index].m_objects[size]=::GetPointer(object); }
Agora, o método AddWindow() pode ser criado. Aqui, nós também precisaremos de um contador de controle (m_counter_element_id). O valor dessa variável deve ser aumentado cada vez, depois de adicionar outro controle para a base.
class CWndContainer { private: //--- Contador de Controle int m_counter_element_id; //--- protected: //--- Adiciona um ponteiro da janela para a base de controles de interface void AddWindow(CWindow &object); }; //+------------------------------------------------------------------+ //| Adiciona um ponteiro de janela para a base de interface dos controles | //+------------------------------------------------------------------+ void CWndContainer::AddWindow(CWindow &object) { int windows_total=::ArraySize(m_windows); //--- Se não há quaisquer janelas, zere o contador de controle if(windows_total<1) m_counter_element_id=0; //--- Adiciona o ponteiro para o array da janela ::ArrayResize(m_wnd,windows_total+1); ::ArrayResize(m_windows,windows_total+1); m_windows[windows_total]=::GetPointer(object); //--- Adiciona o ponteiro para o array comum de controles int elements_total=::ArraySize(m_wnd[windows_total].m_elements); ::ArrayResize(m_wnd[windows_total].m_elements,elements_total+1); m_wnd[windows_total].m_elements[elements_total]=::GetPointer(object); //--- Adiciona objetos de controle para o array comum de objetos AddToObjectsArray(windows_total,object); //--- Define o identificador e armazena o id do último controle m_windows[windows_total].Id(m_counter_element_id); m_windows[windows_total].LastId(m_counter_element_id); //--- Aumenta o contador de identificadores de controle m_counter_element_id++; }
A partir de agora, cada vez que uma nova janela de classe personalizada for criada na aplicação MQL (no artigo é CProgram), é necessário adicioná-lo à base usando o método AddWindow().
Em seguida, os métodos para a criação da janela declarados anteriormente têm que ser implementados na classe CWindow. Para isso, nós precisaremos de variáveis adicionais e métodos relacionados com o tipo, aparência da janela e também os modos que da janela, que poderá ser definido pelo usuário ou dependendo do tipo da aplicação em MQL. Eles estão listados abaixo, acompanhados por uma breve descrição:
- Métodos para definir e obter o estado da janela (minimizado/maximizado).
- Métodos para definir e obter o tipo de janela (principal/caixa de diálogo).
- Os métodos para definir o modo de minimizar a janela, considerando o tipo de aplicação MQL quando ele está sendo criado (EA, indicador). Para isso, nós precisamos de métodos que nos permitam gerenciar o tamanho da janela, se este é um indicador não localizado na janela principal do gráfico.
- Os métodos para definir a cor de cada objeto da janela pelo utilizador.
- Método para a definição do rótulo janela.
- Método para definir o rótulo janela por padrão, caso o usuário não tenha feita isso.
- Identificação dos limites da área de captura no cabeçalho da janela.
- Constantes para o recuo da borda do botão da direita da janela.
- Mostrando o botão para o modo de exibição de dicas de ferramentas.
- Variáveis de prioridades para o clique esquerdo do mouse para cada objeto de janela.
Adicionar tudo o que foi listado acima para a classe CWindow. Constantes para os recuos da borda do botão da direita do formulário será adicionado no início do arquivo:
//+------------------------------------------------------------------+ //| Window.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" //--- Reuso do botão da borda direita da janela #define CLOSE_BUTTON_OFFSET (20) #define ROLL_BUTTON_OFFSET (36) #define TOOLTIP_BUTTON_OFFSET (53)
Cria variáveis mencionadas e métodos no corpo da classe:
//+------------------------------------------------------------------+ //| Classe para a criação do formulário para os controles | //+------------------------------------------------------------------+ class CWindow : public CElement { private: //--- Identificador do último controle int m_last_id; //--- Estado de uma janela minimizada bool m_is_minimized; // --- Tipo de janela ENUM_WINDOW_TYPE m_window_type; //--- Modo de de definição da altura da sub-janela (para os indicadores) bool m_height_subwindow_mode; //--- Modo de minimizar o formulário na sub-janela do indicador bool m_rollup_subwindow_mode; //--- Altura da sub-janela do indicador int m_subwindow_height; //--- Propriedades do fundo int m_bg_zorder; color m_bg_color; int m_bg_full_height; //--- Propriedades do cabeçalho int m_caption_zorder; string m_caption_text; int m_caption_height; color m_caption_bg_color; color m_caption_bg_color_hover; color m_caption_bg_color_off; color m_caption_color_bg_array[]; //--- Propriedades dos botões int m_button_zorder; //--- Cor do quadro do formulário (fundo, cabeçalho) color m_border_color; //--- Ícone do formulário string m_icon_file; //--- Presença do botão para o modo de exibição das dicas bool m_tooltips_button; //--- Para identificar os limites da área de captura no cabeçalho da janela int m_right_limit; //--- public: // --- Tipo de janela ENUM_WINDOW_TYPE WindowType(void) const { return(m_window_type); } void WindowType(const ENUM_WINDOW_TYPE flag) { m_window_type=flag; } //--- Ícone Padrão string DefaultIcon(void); //--- (1) ícone personalizado da janela, (2) Botão utilizar como referência, (3) limitação da área de captura do cabeçalho void IconFile(const string file_path) { m_icon_file=file_path; } void UseTooltipsButton(void) { m_tooltips_button=true; } void RightLimit(const int value) { m_right_limit=value; } //--- Estado de uma janela minimizada bool IsMinimized(void) const { return(m_is_minimized); } void IsMinimized(const bool flag) { m_is_minimized=flag; } //--- Propriedades do cabeçalho void CaptionText(const string text); string CaptionText(void) const { return(m_caption_text); } void CaptionHeight(const int height) { m_caption_height=height; } int CaptionHeight(void) const { return(m_caption_height); } void CaptionBgColor(const color clr) { m_caption_bg_color=clr; } color CaptionBgColor(void) const { return(m_caption_bg_color); } void CaptionBgColorHover(const color clr) { m_caption_bg_color_hover=clr; } color CaptionBgColorHover(void) const { return(m_caption_bg_color_hover); } void CaptionBgColorOff(const color clr) { m_caption_bg_color_off=clr; } //--- Propriedades de janela void WindowBgColor(const color clr) { m_bg_color=clr; } color WindowBgColor(void) { return(m_bg_color); } void WindowBorderColor(const color clr) { m_border_color=clr; } color WindowBorderColor(void) { return(m_border_color); } //--- Modos das sub-janela do indicador void RollUpSubwindowMode(const bool flag,const bool height_mode); //--- Mudando a altura da sub-janela do indicador void ChangeSubwindowHeight(const int height); };
Inicialização de variáveis no construtor da classe pelos valores padrão. No corpo do construtor nós também armazenamos o nome da classe do controle de interface e definimos uma sequência estrita de prioridades para a esquerda clicando no mouse.
//+------------------------------------------------------------------+ //| Construtor | //+------------------------------------------------------------------+ CWindow::CWindow(void) : m_last_id(0), m_subwindow_height(0), m_rollup_subwindow_mode(false), m_height_subwindow_mode(false), m_is_minimized(false), m_tooltips_button(false), m_window_type(W_MAIN), m_icon_file(""), m_right_limit(20), m_caption_height(20), m_caption_bg_color(C'88,157,255'), m_caption_bg_color_off(clrSilver), m_caption_bg_color_hover(C'118,177,255'), m_bg_color(C'15,15,15'), m_border_color(clrLightGray) { //--- Armazena o nome da classe de controle na classe base CElement::ClassName(CLASS_NAME); //--- Define uma sequência rigorosa de prioridades m_bg_zorder =0; m_caption_zorder =1; m_button_zorder =2; }
A implementação dos métodos destacados em amarelo no código acima é apresentada abaixo. Se as variáveis e métodos da classe base são necessários, então, como boas práticas de programação, é melhor prefixar eles com o nome da classe e dois pontos duplos (NomeDaClasse::). Desta forma, o código se torna mais fácil para estudar e mais rápido para se lembrar caso você não tenha visto ele por um tempo. O nome da variável m_subwindow_height será definida automaticamente quando a janela for criada.
//+------------------------------------------------------------------+ //| Modo de minimizar a sub-janela do indicador | //+------------------------------------------------------------------+ void CWindow::RollUpSubwindowMode(const bool rollup_mode=false,const bool height_mode=false) { if(CElement::m_program_type!=PROGRAM_INDICATOR) return; //--- m_rollup_subwindow_mode =rollup_mode; m_height_subwindow_mode =height_mode; //--- if(m_height_subwindow_mode) ChangeSubwindowHeight(m_subwindow_height); }
Nós devemos voltar para o método CWindow::RollUpSubwindowMode() em um dos capítulos seguintes, quando estaremos a considerar os exemplos práticos sobre o gráfico no terminal. Desta forma, ficará mais claro o que cada um dos modos sugeridos representa.
O método CWindow::ChangeSubwindowHeight() permite alterar a altura da sub-janela do indicador, se o programa é de tal modo:
//+------------------------------------------------------------------+ //| Altera a altura da sub-janela do indicador | //+------------------------------------------------------------------+ void CWindow::ChangeSubwindowHeight(const int height) { if(CElement::m_subwin<1 || CElement::m_program_type!=PROGRAM_INDICATOR) return; //--- if(height>0) ::IndicatorSetInteger(INDICATOR_HEIGHT,height); }
Métodos para a Criação de um Formulário
Para todas as partes componentes da janela serem visíveis após a criação, elas devem ser definidas em uma sequência estrita (camada após camada):
- Fundo.
- Cabeçalho.
- Controles de cabeçalho.
No início do método CWindow::CreateWindow() onde todas as partes componentes da janela serão criados, organizaremos uma verificação se o ponteiro para esta janela está armazenado na base dos controles. Se isto não for feito, então, o identificador da janela terá o valor de WRONG_VALUE. Nesse caso, a janela não será criada e a função retornará false, imprimindo uma mensagem na aba Diário sobre qual ação deve ser tomada. Se tudo estiver em ordem, então as variáveis da classe atual e base serão inicializadas pelos valores passados por parâmetro. Entre essas variáveis nós temos:
- Identificador do gráfico.
- Número da janela do gráfico.
- Nome da janela (texto no cabeçalho).
- Coordenadas para definir a janela (x, y).
- Variável duplicada da altura do formulário (m_bg_full_height). Será usado para mudar os tamanhos dos formulários. A altura do formulário é definida antes do método de criação de um formulário ser aplicado (teremos exemplos mais tarde).
O identificador gráfico e o número da janela do gráfico são definidos no construtor da classe CWndEvents. Ao criar os controles na classe personalizada (CWndEvents::CProgram), esses mesmos valores são passados para os métodos de sua criação.
Em seguida, seguem os métodos para a criação de todos os objetos do formulário. Se qualquer um dos objetos não forem criados, então, a função retornará false. Depois de instanciar todos os objetos, dependendo do tipo do programa e do modo definido, uma verificação deve ser efetuada caso o tamanho da janela tiver que ser ajustado (se este for um indicador numa janela separada gráfico). Anteriormente, eu mencionei a variável m_subwindow_height, que é inicializada quando a janela é criada. É precisamente aqui que essa inicialização acontece. Ele está destacado em amarelo no código abaixo.
E, por fim, se foi estabelecido que esta janela será uma janela de diálogo, isso deve ser ocultado. Alguma explicação é necessária aqui. Se a interface de programa contém mais do que uma janela, então, estas janelas serão anexadas ao gráfico durante o carregamento do programa. Assim, ao chamar uma ou outra janela de diálogo, o usuário não precisará ficar criando e removendo as janelas de diálogo. Elas estarão todas presentes no gráfico, elas estarão apenas no modo oculto. Quando uma janela de diálogo for chamada, ela será visível e quando ela for fechada, ela será oculta, mas não removida.
Tudo o que eu disse acima está refletido no código abaixo:
//+------------------------------------------------------------------+ //| Cria o formulário para os controles | //+------------------------------------------------------------------+ bool CWindow::CreateWindow(const long chart_id,const int subwin,const string caption_text,const int x,const int y) { if(CElement::Id()==WRONG_VALUE) { ::Print(__FUNCTION__," > Antes de criar uma janela, o ponteiro é para ser passado " "para o array de janela usando o método CWndContainer::AddWindow(CWindow &object)."); return(false); } //--- Inicialização das variáveis m_chart_id =chart_id; m_subwin =subwin; m_caption_text =caption_text; m_x =x; m_y =y; m_bg_full_height =m_y_size; //--- Criação de todos os objetos da janela if(!CreateBackground()) return(false); if(!CreateCaption()) return(false); if(!CreateLabel()) return(false); if(!CreateIcon()) return(false); if(!CreateButtonClose()) return(false); if(!CreateButtonRollUp()) return(false); if(!CreateButtonUnroll()) return(false); if(!CreateButtonTooltip()) return(false); //--- Se este programa é um indicador if(CElement::ProgramType()==PROGRAM_INDICATOR) { //--- Se foi definido o modo de altura da sub-janela if(m_height_subwindow_mode) { m_subwindow_height=m_bg_full_height+3; ChangeSubwindowHeight(m_subwindow_height); } } //--- Oculta a janela, se for uma janela de diálogo if(m_window_type==W_DIALOG) Hide(); //--- return(true); }
A função virtual Hide() é usada para ocultar os objetos de controle. No código acima, ele está destacado em verde. Antes, na classe CWindow, nós consideramos apenas a sua declaração. No seu código, todos os objetos da janela estão ocultos em todos os tempos gráficos e a variável m_is_visible da classe base recebe o estado false.
//+------------------------------------------------------------------+ //| Ocultar a janela | //+------------------------------------------------------------------+ void CWindow::Hide(void) { //--- Oculta todos os objetos for(int i=0; i<ObjectsElementTotal(); i++) Object(i).Timeframes(OBJ_NO_PERIODS); //--- Estado de visibilidade CElement::IsVisible(false); }
Se você tiver introduzido todas as alterações acima, não se apresse para compilar o arquivo com a classe CWindow já que você receberá uma mensagem de erro (veja a imagem abaixo). Isso acontecerá, pois, todos os métodos de criação de partes da janela, no momento, não possuem conteúdo. Agora, vamos dedicar algum tempo para a sua implementação.
Fig. 3. Mensagem sobre a ausência de implementação dos métodos
Vamos começar com o plano de fundo e usar o método CWindow::CreateBackground(). No início de cada método relacionado com a criação do objeto, nós devemos formar o nome para este objeto. Ele será composto por várias partes:
- Nome do programa.
- Pertencente ao controle ("window").
- Pertencente a uma parte do controle ("bg").
- Identificador do controle.
As partes serão separadas com o sinal de sublinhado «_». O nome do programa é salvo na classe base CElement na lista de inicialização do construtor. O identificador do controle é obtido no momento da adição da janela para a base de controle.
Então, vamos realizar a verificação do tamanho do fundo da janela e seu ajuste, que vai depender se a janela está minimizada ou maximizada. Vamos olhar para frente. Quando o tempo gráfico ou o símbolo do gráfico for alterado, todos os objetos serão movidos para as funções de desinicialização do programa. Na função do programa de inicialização, eles serão restaurados de acordo com os valores atuais dos campos da classe (variáveis).
Após o ajustamento do tamanho do fundo, a configuração do objeto é realizada, seguido pela definição das propriedades. No final do método, o ponteiro de objeto é salvo no array da classe base. O código da função CWindow::CreateBackground() é apresentada a seguir:
//+------------------------------------------------------------------+ //| Cria o fundo da janela | //+------------------------------------------------------------------+ bool CWindow::CreateBackground(void) { //--- Formação do nome da janela string name=CElement::ProgramName()+"_window_bg_"+(string)CElement::Id(); //--- O tamanho da janela depende do seu estado (minimizado/maximizado) int y_size=0; if(m_is_minimized) { y_size=m_caption_height; CElement::YSize(m_caption_height); } else { y_size=m_bg_full_height; CElement::YSize(m_bg_full_height); } //--- Ajusta o fundo da janela if(!m_bg.Create(m_chart_id,name,m_subwin,m_x,m_y,m_x_size,y_size)) return(false); //--- Define as propriedades m_bg.BackColor(m_bg_color); m_bg.Color(m_border_color); m_bg.BorderType(BORDER_FLAT); m_bg.Corner(m_corner); m_bg.Selectable(false); m_bg.Z_Order(m_bg_zorder); m_bg.Tooltip("\n"); //--- Armazena o ponteiro de objeto CElement::AddToArray(m_bg); return(true); }
No método de criação do cabeçalho da janela, além das ações básicas semelhantes à forma como ele foi implementado no método de criação do fundo da janela, é necessário salvar as coordenadas e tamanhos do objeto primitivo da classe CRectLabel, que está localizada no arquivo Objects.mqh. Os valores desses campos serão atualizados quando a janela for deslocada (e, portanto, as suas coordenadas mudam) e quando seus tamanhos mudarem. Se estes valores são estáticos, será impossível controlar o cursor sobre o cabeçalho. Esta regra será aplicável a todos os outros objetos em todos os outros controles.
Uma outra diferença reside na capacidade do cabeçalho de reagir com o cursor do mouse, quando ele está na área da janela ou quando o cursor sair da área da janela. Para isso, o método InitColorArray() foi criado na classe CElement.
Código do método CElement::createCaption():
//+------------------------------------------------------------------+ //| Cria o cabeçalho da janela | //+------------------------------------------------------------------+ bool CWindow::CreateCaption(void) { string name=CElement::ProgramName()+"_window_caption_"+(string)CElement::Id(); //--- Define o cabeçalho da janela if(!m_caption_bg.Create(m_chart_id,name,m_subwin,m_x,m_y,m_x_size,m_caption_height)) return(false); //--- Define as propriedades m_caption_bg.BackColor(m_caption_bg_color); m_caption_bg.Color(m_border_color); m_caption_bg.BorderType(BORDER_FLAT); m_caption_bg.Corner(m_corner); m_caption_bg.Selectable(false); m_caption_bg.Z_Order(m_caption_zorder); m_caption_bg.Tooltip("\n"); //--- Armazena as coordenadas m_caption_bg.X(m_x); m_caption_bg.Y(m_y); //--- Armazena os tamanhos (em objeto) m_caption_bg.XSize(m_caption_bg.X_Size()); m_caption_bg.YSize(m_caption_bg.Y_Size()); //--- Inicializar o array do gradiente CElement::InitColorArray(m_caption_bg_color,m_caption_bg_color_hover,m_caption_color_bg_array); //--- Armazena o ponteiro de objeto CElement::AddToArray(m_caption_bg); return(true); }
O rótulo do programa na janela principal da interface será representada por padrão pelos ícones previamente preparados. Qual delas que será refletida dependerá do tipo de programa a ser desenvolvido (ENUM_PROGRAM_TYPE). Todos os ícones do rótulo podem ser baixados no final do artigo. Para usar em programas, os ícones dos rótulos que não são controles clicáveis devem ser colocados na pasta <data folder>\MQLX\Images\EasyAndFastGUI\Icons\bmp16. O nome bmp16 significa que a pasta contém os ícones de tamanho 16X16 píxeis. Se a sua coleção de ícones da biblioteca for constituída por aqueles que são 24X24 em tamanho, então, a pasta será chamada de bmp24 etc.
Ícones padrões que são usados por padrão para a criação de controles, devem ser colocados na pasta <data folder>\MQLX\Images\EasyAndFastGUI\Controls.
Para a identificação automática do rótulo da janela principal do programa, o método DefaultIcon() foi declarado na classe CWindow. Abaixo está o código desse método:
//+------------------------------------------------------------------+ //| Identificando o rótulo padrão | //+------------------------------------------------------------------+ string CWindow::DefaultIcon(void) { string path="Images\\EasyAndFastGUI\\Icons\\bmp16\\advisor.bmp"; //--- switch(CElement::ProgramType()) { case PROGRAM_SCRIPT: { path="Images\\EasyAndFastGUI\\Icons\\bmp16\\script.bmp"; break; } case PROGRAM_EXPERT: { path="Images\\EasyAndFastGUI\\Icons\\bmp16\\advisor.bmp"; break; } case PROGRAM_INDICATOR: { path="Images\\EasyAndFastGUI\\Icons\\bmp16\\indicator.bmp"; break; } } //--- return(path); }
Deve-se notar que somente os comandos de interface relacionados com a visualização de dados podem ser utilizados em scripts. A explicação aqui é que os scripts não fornecem as funcionalidades necessárias para utilizar as funções para rastrear eventos.
Se o ícone da janela deve ser redefinido, o método CWindow::IconFile() poderá ser utilizado. Os exemplos deste e de outros métodos serão considerados quando nós testarmos as interfaces com base nesta biblioteca no gráfico do terminal.
Os recursos com ícones para diferentes controles serão incluídos (#resource) ao lado do método (fora do corpo do método), onde eles serão usados (destaque em amarelo). O rótulo terá sempre um estado (um ícone) e, portanto, um caminho para uma única versão será especificado nos métodos dos objetos primitivos CChartObjectBmpLabel::BmpFileOn() e CChartObjectBmpLabel::BmpFileOff().
Todas as coordenadas dos objetos (exceto o cabeçalho da janela) de todos os controles de cada janela são calculadas em relação às coordenadas (canto superior esquerdo) dessa janela nas variáveis locais deste método. Consequentemente, todos os recortes do objeto em relação às coordenadas da janela são armazenados nos campos correspondentes da classe do objeto primitivo no arquivo Objects.mqh. Eles são obrigados a não calcular as coordenadas novamente quando a janela for realocada. Isso significa que é necessário apenas uma operação para a atualização das coordenadas de cada objeto de todos os controlos, assim como no momento de ajuste do objeto. No código abaixo, a ausência de recortes do ponto da borda da janela está destacada em verde.
//+------------------------------------------------------------------+ //| Cria o rótulo do programa | //+------------------------------------------------------------------+ //--- Ícones (por padrão) que simboliza o tipo do programa #resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\advisor.bmp" #resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\indicator.bmp" #resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\script.bmp" //--- bool CWindow::CreateIcon(void) { string name=CElement::ProgramName()+"_window_icon_"+(string)CElement::Id(); //--- Coordenadas do objeto int x=m_x+5; int y=m_y+2; //--- Define o ícone da janela if(!m_icon.Create(m_chart_id,name,m_subwin,x,y)) return(false); //--- Ícone padrão, se não foi especificado pelo usuário if(m_icon_file=="") m_icon_file=DefaultIcon(); //--- Define as propriedades m_icon.BmpFileOn("::"+m_icon_file); m_icon.BmpFileOff("::"+m_icon_file); m_icon.Corner(m_corner); m_icon.Selectable(false); m_icon.Z_Order(m_button_zorder); m_icon.Tooltip("\n"); //--- Armazena as coordenadas m_icon.X(x); m_icon.Y(y); //--- Recortes do ponto da borda m_icon.XGap(x-m_x); m_icon.YGap(y-m_y); //--- Armazena os tamanhos (em objeto) m_icon.XSize(m_icon.X_Size()); m_icon.YSize(m_icon.Y_Size()); //--- Adiciona os objetos para o array do grupo CElement::AddToArray(m_icon); return(true); }
Como o código do método para a criação de um rótulo de texto do cabeçalho CWindow::createLabel() não possui peculiaridades diferentes do que foi descrito no método para a criação do rótulo do programa, nós não vamos discutir isso aqui para economizar espaço no artigo. Você pode encontrar o seu código no arquivo Window.mqh no final deste artigo.
Agora, nós vamos discutir os botões que serão localizados no cabeçalho da janela. Todos esses botões terão regras que os deixarão diferente dos métodos descritos acima:
- Alterando a aparência quando o cursor está pairando sobre ele. É por isso que dois ícones serão carregados para esses botões. Uma será usado para o estado ON quando o cursor está sobre o botão. O segundo será utilizado para o estado OFF quando o cursor está fora da área do botão.
- Os botões não serão definidos para scripts.
- Para as janelas de diálogo, estará disponível um único botão, que é para fechar a janela.
- A área de captura no cabeçalho para mover a janela será calculada dependendo da quantidade de botões que foram definidos.
Para a funcionalidade de minimizar e maximizar a janela, é necessário usar dois botões. Eles estarão localizados um por cima do outro e a visibilidade de cada um deles vai depender de qual estado a janela se encontra. Quando a janela é maximizada, então, o botão para minimizar a janela será visível e vice-versa.
Em todo o resto, estes botões não são diferentes de outros métodos para a criação de partes de um formulário de controle. Maiores detalhes do código dos métodos CreateButtonClose(), CreateButtonRollUp(), CreateButtonUnroll() e CreateButtonTooltip() podem ser encontrados no arquivo Window.mqh anexado no final do artigo.
Os ícones para todos os botões que serão utilizados nos exemplos abaixo, também podem ser baixados no final do artigo. Você pode usar os seus próprios ícones. Nesse caso, tenha em mente que em caso dos nomes de seus arquivos serem diferentes dos sugeridos neste artigo, os mesmos nomes dos arquivos BMP como no código em anexo devem ser armazenados ou o caminho para eles devem ser alterados no código.
Anexar um Formulário no Gráfico
Finalmente, estamos na fase em que podemos testar uma parte do que tem sido feito e ver o resultado. Vamos dar uma olhada como o controle de formulário se parece no gráfico. A funcionalidade para criação deste formulário foi criada acima.
Nós vamos continuar a trabalhar no arquivo de testes, que foi criado anteriormente. Lá nós paramos a criação de um método comum para a criação da interface CProgram::CreateTradePanel() e o método para a criação do formulário para os controles CProgram::CreateWindow(). Abaixo está o código para a criação de um formulário. Por favor, note como a cor de fundo e o cabeçalho do formulário pode ser redefinido.
//+------------------------------------------------------------------+ //| Cria o formulário para os controles | //+------------------------------------------------------------------+ bool CProgram::CreateWindow(const string caption_text) { //--- Adiciona um ponteiro de janela para o array da janela CWndContainer::AddWindow(m_window); //--- Propriedades m_window.XSize(200); m_window.YSize(200); m_window.WindowBgColor(clrWhiteSmoke); m_window.WindowBorderColor(clrLightSteelBlue); m_window.CaptionBgColor(clrLightSteelBlue); m_window.CaptionBgColorHover(C'200,210,225'); //--- Criação de um formulário if(!m_window.CreateWindow(m_chart_id,m_subwin,caption_text,1,1)) return(false); //--- return(true); }
No método comum CProgram::CreateTradePanel() chame o método CProgram::CreateWindow() , tendo passado um texto para o cabeçalho da janela como único parâmetro:
//+------------------------------------------------------------------+ //| Cria um painel de negociação | //+------------------------------------------------------------------+ bool CProgram::CreateTradePanel(void) { //--- Criação de um formulário para os controles if(!CreateWindow("EXPERT PANEL")) return(false); //--- Criando os controles // ... //--- ::ChartRedraw(); return(true); }
Agora, nós só temos que adicionar uma chamada do método CProgram::CreateTradePanel() no arquivo principal do programa na função OnInit(). No caso de haver um erro ao criar a interface do programa, imprima uma mensagem correspondente na aba Expert e termine o trabalho:
//+------------------------------------------------------------------+ //| Função de inicialização do Expert | //+------------------------------------------------------------------+ int OnInit(void) { program.OnInitEvent(); //--- Define um painel de negociação if(!program.CreateTradePanel()) { ::Print(__FUNCTION__," > Falha na criação de uma interface gráfica!"); return(INIT_FAILED); } //--- Inicialização bem sucedida return(INIT_SUCCEEDED); }
Compile o código e carregue o programa para o gráfico. Se tudo estiver correto, você verá um formulário no gráfico como na imagem abaixo:
Fig. 4. O primeiro teste para anexar o formulário ao gráfico
Se você esquecer de adicionar um ponteiro de janela para a base do ponteiro ao desenvolver uma aplicação MQL, você ainda será capaz de compilar o código. No entanto, quando você tentar carregar o programa para o gráfico, você verá que o programa será imediatamente apagado e a seguinte mensagem será impressa no registro da aba dos "Expert Advisors" (painel "Caixa de Ferramentas"):
2015.09.23 19:26:10.398 TestLibrary (EURUSD,D1) OnInit > Falha ao criar a interface gráfica! 2015.09.23 19:26:10.398 TestLibrary (EURUSD,D1) CWindow::CreateWindow > Antes de criar uma janela, o ponteiro deve ser guardado na base: CWndContainer::AddWindow(CWindow &object).
Exemplos mais detalhados sobre a utilização dos métodos da classe CWindow em um indicador e script, serão mostrados nos capítulos seguintes (artigos) da série.
Conclusão
A estrutura atual da biblioteca que está sendo desenvolvida está ilustrada no diagrama abaixo:
Fig. 5. Adicionando o controle principal da interface e um formulário para os controles do projeto
Todas as classes relativas aos controles de interface, derivados de sua classe base CElement estão localizados no retângulo grande com uma seta espessa de cor azul. Todos os elementos da interface serão incluídos no arquivo com a classe CWndContainer.
Você pode baixar para o seu computador todo o material da primeira parte da série para você testá-lo. 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:
- Interfaces Gráficas I: Preparação da Estrutura da Biblioteca (Capítulo 1)
- Interfaces Gráficas I: Formulário para os Controles (Capítulo 2)
- Interfaces Gráficas I: Animação na Interface Gráfica (Capítulo 3)
- Interfaces gráficas I: Funções para os Botões do Formulário e Remoção dos Elementos da Interface (Capítulo 4)
- Interfaces gráficas I: Biblioteca de Testes em Programas de Diferentes Tipos e no Terminal MetaTrader 4 (Capítulo 5)
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2126
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso