Interfaces gráficas I: Funções para os Botões do Formulário e Remoção dos Elementos da Interface (Capítulo 4)
Conteúdo
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) explica 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 artigo anterior, a classe CWindow foi estendida com adições que permitiram o deslocamento do formulário sobre o gráfico. Agora, os controles do formulário reagem aos movimentos do cursor do mouse. Neste artigo, nós vamos continuar desenvolvendo a classe CWindow e enriquecê-la com os métodos que nos permitem gerir o formulário mediante os cliques em seus controles. Nós habilitaremos o fechamento do programa por um botão do formulário, bem como implementar a funcionalidade de minimização e maximização do mesmo.
Funções para os Botões do Formulário
O formulário na biblioteca em desenvolvimento contém dois botões principais, que são obrigatórios para um EA e um indicador, e um adicional que poderá ser omitido.
O primeiro botão à direita deve conter a função para fechar a janela. Ela deve ser especificada aqui como isso será exatamente implementado. Se o botão que foi pressionado está em uma janela de diálogo que foi chamada da janela principal ou outra janela de diálogo, então, esta janela de diálogo será fechada. Se o botão que foi pressionado estiver na janela principal, então, o programa será excluído do gráfico.
O segundo botão consiste de duas partes. Para ser mais preciso, de dois botões. Cada um deles será exibido dependendo do modo atual. Se a janela estiver maximizada, então, será mostrado o botão para minimizar a janela. Uma janela minimizada terá um botão para maximizar a janela.
O terceiro botão serve para ativar o modo de dica personalizada. É necessário haver algum esclarecimento quanto ao significado de "dicas personalizadas". A linguagem MQL permite exibir dicas de texto para cada objeto gráfico no foco do cursor do mouse.
Você deve ter notado que no código da classe CWindow o método CChartObject::Tooltip() foi utilizado para a criação de cada objeto do formulário quando as propriedades dele foram especificadas. Se for necessário o objeto ter uma dica, então, o texto que será mostrado quando o cursor é passado sobre o objeto deve ser passado para o método Tooltip(). Se a dica não for necessária, então deve ser passado o texto "\n".
Esta propriedade possui algumas limitações: (1) se o texto contiver mais de 128 símbolos, ele não poderá ser exibido completamente e as (2) propriedades do texto, como a espessura do traço, cor, fonte da letra não poderão ser gerenciados. Desse modo, elas não serão dicas personalizadas. Nós vamos classificá-las como dicas padrões em MQL. Essas dicas são boas para pequenos comentários. Eles não se adequam, no entanto, quando o texto for significativamente maior e tem que ser exibido em várias linhas com algumas palavras em destaque.
Eu tenho certeza de que todo mundo está familiarizado com o Excel. Como um exemplo, você pode visualizar as dicas para todas as opções neste programa (veja a imagem abaixo). Será criado na biblioteca em desenvolvimento uma classe que permitirá a exibição de dicas deste tipo. Essas dicas serão chamadas de personalizadas já que somos nós que criamos e estamos implementando a funcionalidade. Voltaremos a este assunto somente quando a classe de formulário para os controles estiver totalmente implementada e que há pelo menos um controle que possa ser anexado ao formulário.
Fig. 1. As dicas em Excel
Vamos começar com o botão para fechar a janela. Crie o método CloseWindow() na classe CWindow. Ela é para ser chamada dentro do manipulador de eventos do gráfico CWindow::OnEvent() ao manipular um evento com o identificador CHARTEVENT_OBJECT_CLICK causado por um clique em um objeto gráfico. Um evento com este identificador é gerado ao clicar em qualquer objeto gráfico. Naquele momento, o parâmetro de string dos eventos do gráfico (sparam) continha o nome do objeto que fosse clicado. Por essa razão, é realizado uma verificação com o nome do objeto para se certificar de que este é o objeto em questão.
O método CWindow::CloseWindow() aceita um parâmetro - o nome do objeto que foi clicado. No início do código há uma verificação se o clique foi no objeto, que por nossa concepção é um botão para fechar a janela. Em seguida, o código do método é dividido em dois ramos - para o principal e a janela de diálogo. Como o modo multi-janela não está completo ainda, o ramo para a janela de diálogo será deixado vazio. Mais tarde, nós vamos voltar para a sua implementação. Tudo está pronto para se trabalhar com a janela principal. É por isso que é exigido as verificações para o tipo do programa (seja este um EA ou um indicador) no corpo desta condição. Isto é porque a remoção dos diferentes tipos de programas requer diferentes funções da linguagem MQL.
O MQL tem sua própria função para chamar uma janela de diálogo para a confirmação de várias ações do usuário. Está é a função MessageBox(). Nós vamos usá-la temporariamente para a confirmação da exclusão de um EA do gráfico. Esta janela não pode ser usada para apagar um indicador já que isso pertence ao tipo modal. A razão é que os indicadores são executados no fluxo da interface e nós não devemos suspendê-lo. Quando o modo multi-janela e o controle "Button" são implementados, nós poderemos usar a nossa janela de diálogo, que é a janela criada com os meios de nossa biblioteca.
Antes do programa ser removido do gráfico, será impresso no registro da aba Experts do terminal uma mensagem dizendo que o programa foi removido devido a decisão do usuário.
Vamos apresentar as adições para a classe CWindow, como mostrado no código abaixo.
Declaração e implementação do método CWindow::CloseWindow() para fechar a janela:
class CWindow: public CElement { public: //--- Fechando a janela bool CloseWindow(const string pressed_object); }; //+------------------------------------------------------------------+ //| Fechando a janela de diálogo ou o programa | //+------------------------------------------------------------------+ bool CWindow::CloseWindow(const string pressed_object) { //--- Se o clique não foi no botão para fechar a janela if(pressed_object!=m_button_close.Name()) return(false); //--- Se está é a janela principal if(m_window_type==W_MAIN) { //--- Se o programa é do tipo "Expert Advisor" if(CElement::ProgramType()==PROGRAM_EXPERT) { string text="Do you want the program to be deleted from the chart?"; //--- Abre uma janela de diálogo int mb_res=::MessageBox(text,NULL,MB_YESNO|MB_ICONQUESTION); //--- Se o botão "Yes" é pressionado, remove o programa a partir do gráfico if(mb_res==IDYES) { ::Print(__FUNCTION__," > O programa foi removido do gráfico devido à sua decisão!"); //--- Removendo o Expert Advisor do gráfico ::ExpertRemove(); return(true); } } //--- Se o programa é do tipo "Indicador" else if(CElement::ProgramType()==PROGRAM_INDICATOR) { //--- Removendo o indicador do gráfico if(::ChartIndicatorDelete(m_chart_id,m_subwin,CElement::ProgramName())) { ::Print(__FUNCTION__," > O programa foi removido do gráfico devido à sua decisão!"); return(true); } } } //--- Se isto é uma janela de diálogo else if(m_window_type==W_DIALOG) { } //--- return(false); }
Chamando o método CWindow::CloseWindow() no manipulador de eventos do gráfico:
//+------------------------------------------------------------------+ //| Manipulador de eventos do gráfico | //+------------------------------------------------------------------+ void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Manipulando o evento de clique em um objeto if(id==CHARTEVENT_OBJECT_CLICK) { //--- Fechando a janela CloseWindow(sparam); return; } }
Compile os arquivos da biblioteca e o EA que foi usado antes dos testes. Carregue o EA para o gráfico. Pressionando o botão para fechar a janela no formulário irá abrir uma janela como é mostrado na imagem:
Fig. 2. Teste do fechamento do programa pressionando um botão no formulário
Agora, nós vamos criar métodos para o segundo botão, o que permitirá maximizar e minimizar o formulário:
- O método CWindow::RollUp() para minimizar o formulário.
- O método CWindow::Unroll() para maximizar o formulário.
Estes métodos são muito simples e semelhantes em seu conteúdo. No início de cada método, o ícone é alterado, então, o tamanho (altura) do fundo é definido e armazenado, em seguida, o foco é zerado e é definido o estado correspondente a escolha do usuário. Quando o programa é um indicador localizado em qualquer janela do gráfico diferente da principal, o modo de altura fixo da sub-janela do indicador é definido na criação do formulário e o modo permitindo a alteração do tamanho da sub-janela do indicador é definido, então o método CWindow::ChangeSubwindowHeight() é chamado. Isto foi criado anteriormente, devendo estar na classe CWindow.
O código dos métodos CWindow::RollUp() e CWindow::Unroll() é apresentado abaixo em detalhes:
//+------------------------------------------------------------------+ //| Minimiza a janela | //+------------------------------------------------------------------+ void CWindow::RollUp(void) { //--- Altera o botão m_button_rollup.Timeframes(OBJ_NO_PERIODS); m_button_unroll.Timeframes(OBJ_ALL_PERIODS); //--- Define e armazena o tamanho m_bg.Y_Size(m_caption_height); CElement::YSize(m_caption_height); //--- Desabilita o botão m_button_unroll.MouseFocus(false); m_button_unroll.State(false); //--- Estado do formulário "Minimizado" m_is_minimized=true; //--- Se este é um indicador com uma altura definida e com a sub-janela no modo minimizado, // define o tamanho do indicador da sub-janela if(m_height_subwindow_mode) if(m_rollup_subwindow_mode) ChangeSubwindowHeight(m_caption_height+3); } //+------------------------------------------------------------------+ //| Maximiza a janela | //+------------------------------------------------------------------+ void CWindow::Unroll(void) { //--- Altera o botão m_button_unroll.Timeframes(OBJ_NO_PERIODS); m_button_rollup.Timeframes(OBJ_ALL_PERIODS); //--- Define e armazena o tamanho m_bg.Y_Size(m_bg_full_height); CElement::YSize(m_bg_full_height); //--- Desabilita o botão m_button_rollup.MouseFocus(false); m_button_rollup.State(false); //--- Estado do formulário "Maximizado" m_is_minimized=false; //--- Se este é um indicador com uma altura definida e com a sub-janela no modo minimizado, // define o tamanho do indicador da sub-janela if(m_height_subwindow_mode) if(m_rollup_subwindow_mode) ChangeSubwindowHeight(m_subwindow_height); }
Agora, nós criamos um ou mais métodos aceitando o parâmetro de string nos eventos do gráfico. Será realizado a verificação do nome do objeto clicado parâmetro de string semelhante a maneira que foi implementado no método CWindow::CloseWindow(). Dependendo de qual botão foi pressionado, o método correspondente considerado no código acima será chamado. Vamos nomear este método de CWindow::ChangeWindowState(). Adicione esta declaração, implementação e a chamada para o manipulador de evento do gráfico na classe CWindow, como mostrado no código abaixo:
class CWindow: public CElement { public: //--- Alterando o estado da janela bool ChangeWindowState(const string pressed_object); }; //+------------------------------------------------------------------+ //| Manipulador de eventos do gráfico | //+------------------------------------------------------------------+ void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Manipulando o evento de clique em um objeto if(id==CHARTEVENT_OBJECT_CLICK) { //--- Minimiza/Maximiza a janela. ChangeWindowState(sparam); return; } } //+------------------------------------------------------------------+ //| Verifica o evento para a minimização/maximização da janela | //+------------------------------------------------------------------+ bool CWindow::ChangeWindowState(const string pressed_object) { //--- Se o botão "Minimize the window" foi pressionado if(pressed_object==m_button_rollup.Name()) { RollUp(); return(true); } //--- Se o botão "Maximize the window" foi pressionado if(pressed_object==m_button_unroll.Name()) { Unroll(); return(true); } //--- return(false); }
Compile os arquivos da biblioteca onde você introduziu as alterações e o arquivo de programa destinado aos testes. O resultado esperado do nosso trabalho é a possibilidade de minimizar/maximizar a formulário para os controles. A imagem abaixo mostra o formulário no modo minimizado:
Fig. 3. Teste da funcionalidade do formulário
Isso funciona. O formulário para os controles pode ser deslocado sobre o gráfico, cada objeto reage ao cursor do mouse e os botões funcionam de acordo com a sua funcionalidade projetada.
Remoção dos Elementos da Interface
Se você seguiu a sequência de ações sugeridas no artigo e fazendo isso até este ponto, você pode ver que quando o EA é removido do gráfico, todos os objetos da interface gráfica são removidos. Nós ainda não discutimos os métodos para apagar os objetos gráficos a partir do gráfico. Por que os objetos são apagados quando o EA é removido? Isto é fornecido na biblioteca padrão das classes, para ser mais preciso, no destrutor da classe CChartObject, a classe derivada que é utilizada em nossa biblioteca. Quando o programa é removido do gráfico, os destrutores de classes são chamados, incluindo este. Se um objeto é anexado neste gráfico, ele é removido:
//+------------------------------------------------------------------+ //| Destrutor | //+------------------------------------------------------------------+ CChartObject::~CChartObject(void) { if(m_chart_id!=-1) ::ObjectDelete(m_chart_id,m_name); }
Se o símbolo gráfico ou o seu tempo gráfico é alterado quando o EA está neste gráfico, então, os destrutores não são chamados e objetos gráficos não são removidos. Como a interface gráfica é criada na função de inicialização OnInit() no arquivo de programa principal, e é realizado a desinicialização e, em seguida, a inicialização na mudança do símbolo ou no tempo gráfico do EA, a interface gráfica é criada no topo da existente. Como resultado, a primeira instância de tal mudança lhe dará duas cópias dos objetos. Se você continuar a alterar o símbolo gráfico ou o tempo gráfico, você terá muitas cópias de objetos de interface.
Fig. 4. Teste do formulário quando se muda o símbolo gráfico e o período gráfico
Nós devemos levar isso na biblioteca que estamos desenvolvendo, ou seja, nós temos de assegurar que quando o programa é desinicializado, todos os objetos gráficos são removidos. Além disso, todos os arrays contendo ponteiros para estes objetos devem ser esvaziados e aceitar o tamanho de suas dimensões como zero. Depois que foi feito, a configuração dos objetos ficará correto no momento da inicialização, ou seja, sem a criação de clones. Agora, nós vamos implementar este mecanismo.
O método virtual Delete() é declarado na classe CElement. Cada classe derivada da classe CElement terá sua própria implementação deste método. O método Delete() foi declarado anteriormente na classe CWindow. Agora, nós só temos de implementar este método (ver o código abaixo). Neste método algumas variáveis são zeradas, todos os objetos do controle são excluídos e o array de ponteiros para os objetos da classe base também são esvaziados.
//+------------------------------------------------------------------+ //| Remoção | //+------------------------------------------------------------------+ void CWindow::Delete(void) { //--- Zerando as variáveis m_right_limit=0; //--- Removendo objetos m_bg.Delete(); m_caption_bg.Delete(); m_icon.Delete(); m_label.Delete(); m_button_close.Delete(); m_button_rollup.Delete(); m_button_unroll.Delete(); m_button_tooltip.Delete(); //--- Esvaziando o array de objetos CElement::FreeObjectsArray(); //--- Zerando o foco do controle CElement::MouseFocus(false); }
O acesso aos métodos Delete() de todos os controles de interface podem ser obtidos a partir da classe CWndEvents. Portanto, nós vamos criar o método CWndEvents::Destroy() para apagar a interface gráfica. Neste método é iterado sobre os controles de todas os formulários, chamando o método Delete() de cada controle. Antes de chamar o método Delete(), verifique a validade do ponteiro. Após a remoção dos objetos, os arrays de controle devem ser esvaziados. Após a saída do loop do formulário, seus arrays devem ser esvaziados também.
O código a seguir mostra a declaração e implementação do método CWndEvents::Destroy():
class CWndEvents : public CWndContainer { protected: //--- Removendo a interface void Destroy(void); }; //+------------------------------------------------------------------+ //| Removendo todos os objetos | //+------------------------------------------------------------------+ void CWndEvents::Destroy(void) { int window_total=CWndContainer::WindowsTotal(); 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 controle m_wnd[w].m_elements[e].Delete(); } //--- Esvazia os arrays de controle ::ArrayFree(m_wnd[w].m_objects); ::ArrayFree(m_wnd[w].m_elements); } //--- Esvazia os arrays de formulário ::ArrayFree(m_wnd); ::ArrayFree(m_windows); }
Agora, chame o método Destroy() dentro do método CProgram::OnDeinitEvent() ligado à função OnDeinit() no arquivo principal do programa. Adicione-o lá, como é mostrado abaixo:
//+------------------------------------------------------------------+ //| Desinicialização | //+------------------------------------------------------------------+ void CProgram::OnDeinitEvent(const int reason) { //--- Removendo a interface CWndEvents::Destroy(); }
Compile todos os arquivos da biblioteca, onde foram feitos as últimas alterações e o arquivo de programa principal. Carregue o EA para o gráfico e altere algumas vezes o símbolo e o período gráfico. Agora, tudo deve funcionar corretamente. Os clones de objetos não aparecem mais. Problema resolvido.
Conclusão
No capítulo seguinte, nós iremos realizar os testes para ver como o formulário funciona em outros tipos de programa, como indicadores e scripts. Nós também iremos realizar testes no terminal MetaTrader 4 já que o objetivo inicial era fazer uma biblioteca para se criar interfaces gráficas multi-plataforma.
Você pode encontrar o material da Parte I e baixá-la para testar como é seu funcionamento. Se você tiver dúvidas sobre a utilização do material a partir desses arquivos, você poderá consultar a descrição detalhada do desenvolvimento da biblioteca em um dos artigos da lista abaixo ou fazer sua pergunta nos comentários deste artigo.
Lista de artigos (capítulos) da primeira parte:
- 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/2128
- 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