Interfaces gráficas X: Atualizações para a Biblioteca Easy And Fast(Build 3)
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. 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.
Apresentamos neste artigo a próxima versão da biblioteca Easy And Fast(build 3). Foi corrigido certas falhas e adicionado novos recursos. Para maiores informações leia a continuação do artigo.
A partir desta versão, a biblioteca só será desenvolvida para a plataforma MetaTrader 5. Isto está relacionado com algumas diferenças fundamentais de arquitetura e limitações no MetaTrader 4. No entanto, se houver uma necessidade urgente de suporte da biblioteca para uma versão anterior a da plataforma, é possível emitir um pedido no serviço Freelance dirigindo-se ao autor ou qualquer outro desenvolvedor, dispostos e capazes de fazer o trabalho.
Atualizações
1. Até agora, as aplicações em MQL nos artigos anteriores demonstraram como implementar uma interface gráfica na sub-janela do indicador. Naturalmente, esta abordagem seria suficiente para algumas aplicações simples. Mas também seria interessante ter a possibilidade de criar uma interface gráfica em uma sub-janela para um programa do tipo "Expert". Dessa forma, seria possível criar os painéis de negociação completamente funcionais e que estariam separados da janela principal do gráfico. Trabalhando neste modo será mais conveniente: o gráfico de preços e quaisquer dados importantes sobre o gráfico estaria sempre aberto e organizado pela interface gráfica da aplicação. A implementação desta ideia com a biblioteca Easy And Fast será descrita posteriormente.
A tarefa era fazer com que o modo "Expert na sub-janela" seja ativada pela inclusão do recurso com o indicador vazio (SubWindow.ex5) no arquivo principal da aplicação MQL. O espaço reservado é apenas uma sub-janela sem a parte de cálculos das séries. Mas já que não há atualmente nenhuma maneira de saber se o indicador está incluído como um recurso usando os meios MQL, adicione temporariamente a diretiva com o identificador da constante EXPERT_IN_SUBWINDOW ao arquivo Defines.mqh. Ela é necessária para eliminar o erro do registro que ocorre quando se tenta obter o identificador de um indicador que não está disponível no diretório especificado.
O valor padrão é false, significando que o modo está desativado e a interface gráfica será criada na janela do gráfico principal (veja o código abaixo).
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ //--- Modo "Expert na sub-janela" #define EXPERT_IN_SUBWINDOW false ...
Se for necessário criar a interface gráfica em uma sub-janela, defina o valor para true e inclua o recurso com o indicador vazio no arquivo principal da aplicação MQL.
//+------------------------------------------------------------------+ //| TestLibrary01.mq5 | //| Copyright 2016, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2016, MetaQuotes Software Corp." #property link "http://www.mql5.com" //--- Incluindo o indicador no modo "Expert na sub-janela" #resource \\Indicators\\SubWindow.ex5 ...
Em seguida, considere o indicador SubWindow.ex5 e como ele está ligado ao EA. O código completo do indicador SubWindow.ex5 foi fornecido abaixo. As propriedades do programa especificam que o indicador é uma sub-janela do gráfico, o número de buffers e séries é zero, escala vertical máxima e o mínima também são zero.
O indicador e o expert trocarão mensagens um com o outro. Acontece que o indicador "não sabe" quais os modos para traçar a interface gráfica que foi selecionada no expert. É preciso ser passado uma mensagem, se o desenvolvedor decidiu fazer com que a sub-janela tenha uma altura fixa, bem como os valores da altura em pixels. Será necessário de outro identificador do evento ON_SUBWINDOW_CHANGE_HEIGHT para criar a mensagem. Este identificador também deve ser colocado no arquivo Defines.mqh, para que ele esteja disponível nas aplicações que usam esta biblioteca para a criação das interfaces gráficas.
Para compreender quando o EA necessita gerar o evento para alterar a altura da subjanela: após uma inicialização bem sucedida do indicador, durante a primeira chamada automática da função ::OnCalculate() (quando o argumento prev_calculated é zero), será passado uma mensagem para o EA. Para determinar de forma inequívoca de que a mensagem foi proveniente do indicador SubWindow.ex5, além do identificador de evento ON_SUBWINDOW_CHANGE_HEIGHT, o nome do programa será enviado como um parâmetro string (sparam). A mensagem será monitorada no manipulador da classe CWindow. Se todas as condições forem atendidas lá, o indicador enviará uma resposta com o mesmo identificador (1) (ON_SUBWINDOW_CHANGE_HEIGHT), (2) o valor da altura como parâmetro long do evento (lparam) e (3) o nome do expert. Uma vez que esta mensagem é recebida, ela será processada na função ::OnChartEvent(). Após verificar o nome, a altura da sub-janela é definida de acordo com o valor passado.
//+------------------------------------------------------------------+ //| SubWindow.mq5 | //| Copyright 2016, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2016, MetaQuotes Software Corp." #property link "http://www.mql5.com" #property indicator_separate_window #property indicator_plots 0 #property indicator_buffers 0 #property indicator_minimum 0.0 #property indicator_maximum 0.0 //--- Nome do programa #define PROGRAM_NAME ::MQLInfoString(MQL_PROGRAM_NAME) //--- Identificador do evento para alterar a altura da sub-janela do expert #define ON_SUBWINDOW_CHANGE_HEIGHT (25) //+------------------------------------------------------------------+ //| Função de inicialização do indicador personalizado | //+------------------------------------------------------------------+ int OnInit(void) { //--- Nome abreviado do indicador ::IndicatorSetString(INDICATOR_SHORTNAME,PROGRAM_NAME); //--- Inicialização bem sucedida return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Desinicialização | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { } //+------------------------------------------------------------------+ //| Função de iteração do indicador personalizado | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { //--- Se a inicialização teve êxito if(prev_calculated<1) //--- Envia a mensagem para o expert para obter o tamanho da sub-janela a partir dele ::EventChartCustom(0,ON_SUBWINDOW_CHANGE_HEIGHT,0,0.0,PROGRAM_NAME); //--- return(rates_total); } //+------------------------------------------------------------------+ //| Função ChartEvent | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Manipulação do evento para alterar a altura da sub-janela do expert if(id==CHARTEVENT_CUSTOM+ON_SUBWINDOW_CHANGE_HEIGHT) { //--- Aceita as mensagens apenas do nome do expert if(sparam==PROGRAM_NAME) return; //--- Alterar a altura da sub-janela ::IndicatorSetInteger(INDICATOR_HEIGHT,(int)lparam); } } //+------------------------------------------------------------------+
Além disso no manipulador de eventos da classe CWindow deve-se adicionar um bloco de código como é exibido abaixo. Quando chega um evento com o identificador ON_SUBWINDOW_CHANGE_HEIGHT, será necessário realizar várias verificações. O programa deixa o método se:
- a mensagem foi enviada do expert para o indicador;
- este programa não é um expert;
- o modo da altura fixa da sub-janela do expert não foi definida.
//+------------------------------------------------------------------+ //| Manipulador de eventos do gráfico | //+------------------------------------------------------------------+ void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { ... //--- Manipulação do evento para alterar a altura da sub-janela do expert if(id==CHARTEVENT_CUSTOM+ON_SUBWINDOW_CHANGE_HEIGHT) { //--- Sai, se a mensagem era do expert if(sparam==PROGRAM_NAME) return; //--- Sai, se este programa não é do expert if(CElement::ProgramType()!=PROGRAM_EXPERT) return; //--- Sai, se o modo da altura fixa da sub-janela não está definida if(!m_height_subwindow_mode) return; //--- Calcula e altera a altura da sub-janela m_subwindow_height=(m_is_minimized)? m_caption_height+3 : m_bg_full_height+3; ChangeSubwindowHeight(m_subwindow_height); return; } }
Se todas as verificações são passados, então a altura para a sub-janela é calculada levando em consideração o estado atual da janela (minimizada/maximizada). Após isso, o método CWindow::ChangeSubwindowHeight() é chamado, que também sofreu uma ligeira modificação (veja o código abaixo). A sua finalidade é que se o tipo do programa for um «Expert», então, uma mensagem é gerada pelo indicador SubWindow.ex5.
//+------------------------------------------------------------------+ //| Altera a altura da sub-janela do indicador | //+------------------------------------------------------------------+ void CWindow::ChangeSubwindowHeight(const int height) { //--- Se a interface gráfica não está na sub-janela ou o programa é do tipo "Script" if(CElement::m_subwin<=0 || CElement::m_program_type==PROGRAM_SCRIPT) return; //--- Se a altura da sub-janela precisar ser alterada if(height>0) { //--- Se o programa é do tipo indicador if(CElement::m_program_type==PROGRAM_INDICATOR) { if(!::IndicatorSetInteger(INDICATOR_HEIGHT,height)) ::Print(__FUNCTION__," > Falha ao alterar a altura do indicador! Código do erro: ",::GetLastError()); } //--- Se o programa é do tipo "Expert" else { //--- Envia a mensagem para o indicador SubWindow.ex5, informando que o tamanho da janela deve ser alterado ::EventChartCustom(m_chart_id,ON_SUBWINDOW_CHANGE_HEIGHT,(long)height,0,PROGRAM_NAME); } } }
O motor da biblioteca (classe CWndEvents) também exige certos complementos a fim de determinar, verificar e ajustar o número da sub-janela, onde a interface gráfica da aplicação MQL do tipo "Expert" deve ser colocada. O código para o modo "Expert na sub-janela" deve ser adicionado ao método CWndEvents::DetermineSubwindow(). O código a seguir mostra a versão resumida deste método. A entrada para o bloco é feita na condição de que o tipo do programa seja um "Expert". Em seguida, vem a verificação se o modo "Expert na sub-janela" está ativada. Se ativado, obtém o identificador do indicador SubWindow.ex5. E se não houver problemas com isso, então, é determinado primeiro o número atual de sub-janelas na janela do gráfico. O valor obtido define o número de sub-janelas do indicador SubWindow.ex5, que é definido pela função ::ChartIndicatorAdd(). Então, se não houver erros ao definir a sub-janela, ele armazena (1) o número de sub-janelas do expert, (2) o número atual de sub-janelas e (3) o nome abreviado do indicador SubWindow.ex5.
//+------------------------------------------------------------------+ //| Classe para a manipulação de eventos | //+------------------------------------------------------------------+ class CWndEvents : public CWndContainer { protected: //--- Manipula a sub-janela do expert int m_subwindow_handle; //--- Nome da sub-janela do expert string m_subwindow_shortname; //--- O número de sub-janelas no gráfico depois de definir a sub-janela do expert int m_subwindows_total; }; //+------------------------------------------------------------------+ //| Identificando o número da sub-janela | //+------------------------------------------------------------------+ void CWndEvents::DetermineSubwindow(void) { //--- Sai, se o tipo do programa é um "Script" //--- Reseta o último erro //--- Se o tipo do programa é um "Expert" if(PROGRAM_TYPE==PROGRAM_EXPERT) { //--- Sai, se é preciso ter a interface gráfica do expert na janela principal if(!EXPERT_IN_SUBWINDOW) return; //--- Obtém o identificador do indicador vazio (sub-janela vazia) m_subwindow_handle=iCustom(Symbol(),Period(),"::Indicators\\SubWindow.ex5"); //--- Se não existe tal indicador, relata o erro ao registro if(m_subwindow_handle==INVALID_HANDLE) ::Print(__FUNCTION__," > Erro em obter o manipulador do indicador no diretório ::Indicators\\SubWindow.ex5 !"); //--- Se o identificador é obtido, então o indicador existe, inclua-o na aplicação como um recurso, // e isso significa que a interface gráfica da aplicação deve ser colocada na sub-janela. else { //--- Obtém o número de sub-janelas no gráfico int subwindows_total=(int)::ChartGetInteger(m_chart_id,CHART_WINDOWS_TOTAL); //--- Define a sub-janela para a interface gráfica do expert if(::ChartIndicatorAdd(m_chart_id,subwindows_total,m_subwindow_handle)) { //--- Armazena o número da sub-janela e o número atual de subjanelas no gráfico m_subwin =subwindows_total; m_subwindows_total =subwindows_total+1; //--- Obtém e armazena o nome abreviado da sub-janela do expert m_subwindow_shortname=::ChartIndicatorName(m_chart_id,m_subwin,0); } //--- Se a sub-janla não foi definida else ::Print(__FUNCTION__," > Erro ao definir a sub-janela do expert! Código do erro: ",::GetLastError()); } //--- return; } //--- Identifica a janela do indicador //--- Sai, se não conseguiu identificar o número //--- Se esta não é a janela principal do gráfico ... }
Também é necessário ter certeza de que o número da sub-janela do expert seja ajustada para o funcionamento correto da interface gráfica quando se adiciona e remover outros indicadores nas sub-janelas do gráfico. Além disso, vamos fazê-lo de modo que se o usuário excluir a sub-janela do expert, o expert é removido também, deixando uma mensagem de registro na guia "Experts" da janela "caixa de ferramentas", indicando a razão da remoção do gráfico. O método CWndEvents::CheckExpertSubwindowNumber() é aplicado para esta finalidade. A admissão a este método é realizada sob a condição de que o programa deve ser do tipo "Expert". Se a verificação foi satisfeita, será calculado o número de sub-janelas na janela do gráfico. Se acontecer do número de sub-janelas não serem alteradas, o programa sairá do método.
Em seguida, é necessário encontrar a sub-janela do expert em um loop, e se ela existe - verifica se o número da sua sub-janela foi alterada. O número da sub-janela poderia ter sido alterada devido à adição ou remoção de um indicador que também foi localizado em uma sub-janela separada. Se o número da sub-janela tiver sido alterada, é necessário armazenar seu número e atualizar o valor em todos os controles da janela principal.
Se a sub-janela não foi encontrada, pode haver apenas uma razão: ela foi excluída. Nesse caso, o expert também será removido do gráfico.
class CWndEvents : public CWndContainer { private: //--- Verifica e atualiza o número de sub-janelas do expert void CheckExpertSubwindowNumber(void); }; //+------------------------------------------------------------------+ //|Verifica e atualiza o número de sub-janelas do expert | //+------------------------------------------------------------------+ void CWndEvents::CheckExpertSubwindowNumber(void) { //--- Sai, se isso não for um expert if(PROGRAM_TYPE!=PROGRAM_EXPERT) return; //--- Obtém o número de sub-janelas no gráfico int subwindows_total=(int)::ChartGetInteger(m_chart_id,CHART_WINDOWS_TOTAL); //--- Sai, se o número de sub-janelas e o número de indicadores não foram alterados if(subwindows_total==m_subwindows_total) return; //--- Armazena o número atual de sub-janelas m_subwindows_total=subwindows_total; //--- Para verificar se a sub-janela do expert está presente bool is_subwindow=false; //--- Encontra o expert da sub-janela for(int sw=0; sw<subwindows_total; sw++) { //--- Parar o ciclo, se a sub-janela do expert foi encontrada if(is_subwindow) break; //--- O número de indicadores nesta janela/sub-janela int indicators_total=::ChartIndicatorsTotal(m_chart_id,sw); //--- Iterar sobre todos os indicadores na janela for(int i=0; i<indicators_total; i++) { //--- Obtém o nome abreviado do indicador string indicator_name=::ChartIndicatorName(m_chart_id,sw,i); //--- Se este não é a sub-janela do expert, vá para a próxima if(indicator_name!=m_subwindow_shortname) continue; //--- Marca que o expert possui uma sub-janela is_subwindow=true; //--- Se o número o número de sub-janelas foi alterado, então // é necessário armazenar o novo número em todos os controles do formulário principal if(sw!=m_subwin) { //--- Armazena o número da sub-janela m_subwin=sw; //--- Guarde-o em todos os controles do formulário principal da interface int elements_total=CWndContainer::ElementsTotal(0); for(int e=0; e<elements_total; e++) m_wnd[0].m_elements[e].SubwindowNumber(m_subwin); } //--- break; } } //--- Se a sub-janela do expert não foi encontrada, remova o expert if(!is_subwindow) { ::Print(__FUNCTION__," > Removendo a sub-janela do expert causa a remoção do mesmo!"); //--- Removendo o EA a partir do gráfico ::ExpertRemove(); } }
2. A versão anterior da biblioteca introduziu a possibilidade de alternar a alteração automática da largura do formulário. Vamos adicionar um recurso semelhante para a altura. O identificador ON_WINDOW_CHANGE_SIZE para o evento de alterar o tamanho do formulário não é adequado para resolver esta tarefa, já que a alteração da largura e da altura será tratada com eventos separados. Portanto, o arquivo Defines.mqh terá dois identificadores separados, em vez do ON_WINDOW_CHANGE_SIZE, como é exibido no código abaixo. A substituição correspondente do identificador tem sido realizada em outros arquivos da biblioteca.
#define ON_WINDOW_CHANGE_XSIZE (3) // Altera o tamanho da janela ao longo do eixo X #define ON_WINDOW_CHANGE_YSIZE (4) // Altera o tamanho da janela ao longo do eixo y
O método CWindow::ChangeWindowHeight() foi adicionado para alterar a altura do formulário. Ao chamar o método após a alteração do tamanho do formulário, a mensagem sobre a ação realizada é gerada no final.
//+------------------------------------------------------------------+ //| Classe para criar um formulário de controles | //+------------------------------------------------------------------+ class CWindow : public CElement { public: //--- Gerenciamento do tamanho void ChangeWindowHeight(const int height); }; //+------------------------------------------------------------------+ //| Altera a altura da janela | //+------------------------------------------------------------------+ void CWindow::ChangeWindowHeight(const int height) { //--- Se a altura não mudar, sai if(height==m_bg.YSize()) return; //--- Sai se a janela é minimizada if(m_is_minimized) return; //--- Atualiza a altura do fundo CElement::YSize(height); m_bg.YSize(height); m_bg.Y_Size(height); m_bg_full_height=height; //--- Uma mensagem de que os tamanhos da janela foram alterados ::EventChartCustom(m_chart_id,ON_WINDOW_CHANGE_YSIZE,(long)CElement::Id(),0,""); }
Para a altura do formulário ser alterada automaticamente, o usuário desenvolvedor da aplicação MQL deve definir o modo correspondente, utilizando o método CElement::AutoYResizeMode():
//+------------------------------------------------------------------+ //| Classe base do controle | //+------------------------------------------------------------------+ class CElement { protected: //--- Modo de redimensionamento automático do controle bool m_auto_yresize_mode; //--- public: //--- (1) Modo da alteração automática do controle de altura bool AutoYResizeMode(void) const { return(m_auto_yresize_mode); } void AutoYResizeMode(const bool flag) { m_auto_yresize_mode=flag; } };
Agora, se os modos de redimensionamento automático do formulário está habilitado, então, se o tamanho da janela do gráfico ser alterada, o manipulador de eventos do formulário ajusta os tamanhos do formulário com base no evento CHARTEVENT_CHART_CHANGE. Isto irá funcionar tanto na sub-janela do indicador quanto na janela do gráfico. É possível habilitar qualquer um dos modos, ou ambos ao mesmo tempo.
//--- O evento de alteração das propriedades do gráfico if(id==CHARTEVENT_CHART_CHANGE) { //--- Se o botão é solto if(m_clamping_area_mouse==NOT_PRESSED) { //--- Obter o tamanho da janela do gráfico SetWindowProperties(); //--- Ajuste das coordenadas UpdateWindowXY(m_x,m_y); } //--- Altera a largura se o modo está habilitado if(CElement::AutoXResizeMode()) ChangeWindowWidth(m_chart.WidthInPixels()-2); //--- Altera a altura, se o modo está habilitado if(CElement::AutoYResizeMode()) ChangeWindowHeight(m_chart.HeightInPixels(m_subwin)-3); //--- return; }
3. O modo de alteração automática da altura também se aplica a todos os controles da biblioteca. Mas, na versão atual, isso irá funcionar apenas nos controles relacionados abaixo:
- CTabs – guias simples.
- CIconTabs – guias com imagens.
- CCanvasTable – tabela renderizada.
- CLineGraph – gráfico de linha.
Semelhante à adição anterior do método virtual CElement::ChangeWidthByRightWindowSide() para alterar a largura do controle para a classe CElement, vamos adicionar o método para a alteração da altura automática. Além disso, nós vamos criar os métodos para definir o deslocamento a partir da borda inferior do formulário, semelhante aos deslocamentos adicionados anteriormente da borda direita do formulário ao mudar a sua largura.
class CElement { protected: //--- Deslocamento da margem direita/inferior do formulário no modo de controle automático da alteração da largura/altura int m_auto_xresize_right_offset; int m_auto_yresize_bottom_offset; //--- public: //--- Obtém/define o deslocamento a partir da borda inferior do formulário int AutoYResizeBottomOffset(void) const { return(m_auto_yresize_bottom_offset); } void AutoYResizeBottomOffset(const int offset) { m_auto_yresize_bottom_offset=offset; } //--- public: //--- Altera a altura na borda inferior da janela virtual void ChangeHeightByBottomWindowSide(void) {} };
Cada controle terá a sua própria implementação do método ChangeWidthByRightWindowSide(), que considera as características únicas e modos. Como um exemplo, o código abaixo mostra o método CCanvasTable na classe:
//+------------------------------------------------------------------+ //| Altera a altura da borda inferior da janela | //+------------------------------------------------------------------+ void CCanvasTable::ChangeHeightByBottomWindowSide(void) { //--- Sai, se o modo de ancoragem para a parte inferior do formulário está habilitada if(m_anchor_bottom_window_side) return; //--- Coordenadas int y=0; //--- Tamanho int x_size=(m_auto_xresize_mode)? m_wnd.X2()-m_area.X()-m_auto_xresize_right_offset : m_x_size; int y_size=m_wnd.Y2()-m_area.Y()-m_auto_yresize_bottom_offset; //--- Sai se o tamanho é menor do que o especificado if(y_size<60) return; //--- Define o novo tamanho do fundo da tabela ChangeMainSize(x_size,y_size); //--- Calcula o tamanho da tabela CalculateTableSize(); //--- Verifica a presença de uma barra de rolagem bool is_scrollh=!(m_table_visible_x_size>=m_table_x_size); bool is_scrollv=!(m_table_visible_y_size>=m_table_y_size); //--- Deslocamento relativo à presença da barra de rolagem int offset=(is_scrollh || (!is_scrollh && !is_scrollv))? 0 : 2; //--- Calcula e define a nova coordenada para a barra de rolagem horizontal y=m_area.Y2()-m_scrollh.ScrollWidth(); m_scrollh.YDistance(y); //--- Inicializa a barra de rolagem horizontal para o novo tamanho m_scrollh.Reinit(m_table_x_size,m_table_visible_x_size); //--- Calcula e altera a altura da barra de rolagem vertical m_scrollv.Reinit(m_table_y_size,m_table_visible_y_size-offset); m_scrollv.ChangeYSize((is_scrollh)? m_table_visible_y_size+2 : m_table_visible_y_size); //--- Se a barra de rolagem vertical não é necessária if(!is_scrollv) { //--- Ocultar a barra de rolagem vertical m_scrollv.Hide(); //--- Altera a largura da barra de rolagem horizontal m_scrollh.ChangeXSize(m_area.XSize()); } else { //--- Mostra a barra de rolagem vertical if(CElement::IsVisible()) m_scrollv.Show(); //--- Calcula e altera a largura da barra de rolagem horizontal m_scrollh.ChangeXSize(m_area.XSize()-m_scrollv.ScrollWidth()+1); } //--- Gerencia a visibilidade da barra de rolagem horizontal if(CElement::IsVisible()) { if(!is_scrollh) m_scrollh.Hide(); else m_scrollh.Show(); } //--- Redimensiona a tabela ChangeTableSize(m_table_x_size,m_table_y_size,m_table_visible_x_size,m_table_visible_y_size-offset); / --- Desenhar a tabela DrawTable(); //--- Atualiza a posição dos objetos Moving(m_wnd.X(),m_wnd.Y()); }
Para que tudo isso funcione, algumas adições e mudanças devem ser feitas para na classe CWndEvents. Já que os eventos de redimensionamento (largura e altura) são gerados agora com os identificadores únicos, dois métodos separados são necessários para o seu tratamento.
//+------------------------------------------------------------------+ //| Classe para a manipulação de eventos | //+------------------------------------------------------------------+ class CWndEvents : public CWndContainer { private: //--- Manipula a alteração do tamanho da janela bool OnWindowChangeXSize(void); bool OnWindowChangeYSize(void); }; //+------------------------------------------------------------------+ //| Evento ON_WINDOW_CHANGE_XSIZE | //+------------------------------------------------------------------+ bool CWndEvents::OnWindowChangeXSize(void) { //--- Se o sinal for para "redimensionar os controles" if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_CHANGE_XSIZE) return(false); //--- Índice da janela ativa int awi=m_active_window_index; //--- Se a janela de identificadores correspondem entre si if(m_lparam!=m_windows[awi].Id()) return(true); //--- Altera a largura de todos os controles, exceto do formulário int elements_total=CWndContainer::ElementsTotal(awi); for(int e=0; e<elements_total; e++) { //--- Se for uma janela, vai para o próximo if(m_wnd[awi].m_elements[e].ClassName()=="CWindow") continue; //--- Se o modo estiver habilitado, ajusta a largura if(m_wnd[awi].m_elements[e].AutoXResizeMode()) m_wnd[awi].m_elements[e].ChangeWidthByRightWindowSide(); } //--- return(true); } //+------------------------------------------------------------------+ //| Evento ON_WINDOW_CHANGE_YSIZE | //+------------------------------------------------------------------+ bool CWndEvents::OnWindowChangeYSize(void) { //--- Se o sinal for para "redimensionar os controles" if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_CHANGE_YSIZE) return(false); //--- Índice da janela ativa int awi=m_active_window_index; //--- Se a janela de identificadores correspondem entre si if(m_lparam!=m_windows[awi].Id()) return(true); //--- Altera a largura de todos os controles, exceto do formulário int elements_total=CWndContainer::ElementsTotal(awi); for(int e=0; e<elements_total; e++) { //--- Se for uma janela, vai para o próximo if(m_wnd[awi].m_elements[e].ClassName()=="CWindow") continue; //--- Se o modo estiver habilitado, ajusta a altura if(m_wnd[awi].m_elements[e].AutoYResizeMode()) m_wnd[awi].m_elements[e].ChangeHeightByBottomWindowSide(); } //--- return(true); }
Os métodos CWndEvents::OnWindowChangeXSize() e CWndEvents::OnWindowChangeYSize() são chamados no método comum CWndEvents::ChartEventCustom() para lidar com os eventos personalizados. O código a seguir mostra a versão resumida deste método.
//+------------------------------------------------------------------+ //| 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 for para redimensionar os controles ao longo do eixo X if(OnWindowChangeXSize()) return; //--- Se o sinal for para redimensionar os controles ao longo do eixo Y if(OnWindowChangeYSize()) return; //--- 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 //--- Se o sinal for para restaurar as prioridades do clique do botão esquerdo do mouse }
Agora, ao redimensionar a janela do gráfico, se o modo de redimensionamento do formulário e os controles especificados em que ele está habilitado, eles também serão automaticamente redimensionados.
As imagens abaixo mostram um exemplo de uma interface gráfica em um aplicativo MQL, que foi criado na janela do gráfico principal. Para a janela do aplicativo (CWindow), os modos da largura e altura automática ajustam a altura da janela do gráfico. Para os controles «menu principal» (CMenuBar) e a «barra de estado» (CStatusBar), é definido o modo da largura automática e o ajuste de altura na janela da aplicação MQL. Para os controles «Guias» (CTabs) e a «tabela renderizada» (CCanvasTable), é definido o modo da largura automática e o ajuste de altura do tamanho do formulário e especificado os deslocamentos a partir da borda inferior da aplicação MQL.
Fig. 1. Tamanho mínimo da janela do terminal. Interface gráfica de uma aplicação MQL com os modos de redimensionamento automático ativado.
Se o tamanho da janela do terminal foi alterado, quando os modos acima mencionados são habilitados, a interface gráfica da aplicação MQL também será redimensionada.
Fig. 2. Quando os tamanhos da janela do terminal forem alterados, a interface gráfica da aplicação MQL também será redimensionada.
4. Muitas vezes, quando a concepção da interface gráfica em que os tamanhos dos formulários podem se alterar, pode ser necessário que controles sejam posicionados na parte direita ou na parte inferior da janela da aplicação. Anteriormente, havia uma opção para especificar de forma simples as coordenadas para o controle, relativo ao ponto superior esquerdo do formulário que ele está ligado. Agora, existem quatro opções para posicionar o controle em relação ao formulário:
- Canto superior esquerdo.
- Canto superior direito.
- Canto inferior direito.
- Canto inferior esquerdo.
Para implementar esta ideia, vamos adicionar dois métodos para a classe base de controles (CElement) para configurar/obter os modos de posicionamento do controle na direita e inferior ao formulário:
class CElement { protected: //--- Os pontos de ancoragem do controle a direita e inferior a janela bool m_anchor_right_window_side; bool m_anchor_bottom_window_side; //--- public: //--- Modo (obter/definir) do ponto de ancoragem do controle para a borda (1) direita e (2) inferior da janela bool AnchorRightWindowSide(void) const { return(m_anchor_right_window_side); } void AnchorRightWindowSide(const bool flag) { m_anchor_right_window_side=flag; } bool AnchorBottomWindowSide(void) const { return(m_anchor_bottom_window_side); } void AnchorBottomWindowSide(const bool flag) { m_anchor_bottom_window_side=flag; } };
Para que tudo funcione de acordo com estes modos, as mudanças foram feitas para todas as classes dos controles da biblioteca. Como exemplo, segue o código do método para a criação de um rótulo de texto para o controle «caixa de seleção» (CCheckBox). Preste atenção nas linhas que calculam as coordenadas e os deslocamentos para o objeto gráfico.
//+------------------------------------------------------------------+ //| Cria o rótulo de texto da caixa de seleção | //+------------------------------------------------------------------+ bool CCheckBox::CreateLabel(void) { //--- Elaborando o nome do objeto string name=CElement::ProgramName()+"_checkbox_lable_"+(string)CElement::Id(); //--- Coordenadas int x =(m_anchor_right_window_side)? m_x-m_label_x_gap : m_x+m_label_x_gap; int y =(m_anchor_bottom_window_side)? m_y-m_label_y_gap : m_y+m_label_y_gap; //--- Cor do texto de acordo com o estado color label_color=(m_check_button_state) ? m_label_color : m_label_color_off; //--- Define o objeto if(!m_label.Create(m_chart_id,name,m_subwin,x,y)) return(false); //--- Define as propriedades m_label.Description(m_label_text); m_label.Font(FONT); m_label.FontSize(FONT_SIZE); m_label.Color(label_color); m_label.Corner(m_corner); m_label.Anchor(m_anchor); m_label.Selectable(false); m_label.Z_Order(m_zorder); m_label.Tooltip("\n"); //--- Margens da borda m_label.XGap((m_anchor_right_window_side)? x : x-m_wnd.X()); m_label.YGap((m_anchor_bottom_window_side)? y : y-m_wnd.Y()); //--- Inicialização do array de gradiente CElement::InitColorArray(label_color,m_label_color_hover,m_label_color_array); //--- Armazena o ponteiro de objeto CElement::AddToArray(m_label); return(true); }
Os métodos Moving() de todos os controles na biblioteca consideram agora os modos de posicionamento do controle. Como um exemplo, o código abaixo mostra o método CCheckBox::Moving():
//+------------------------------------------------------------------+ //| Deslocamento dos controles | //+------------------------------------------------------------------+ void CCheckBox::Moving(const int x,const int y) { //--- Sai se o elemento está oculto if(!CElement::IsVisible()) return; //--- Se está ancorado à direita if(m_anchor_right_window_side) { //--- Armazena as coordenadas nos campos do elemento CElement::X(m_wnd.X2()-XGap()); //--- Armazena as coordenadas nos campos dos objetos m_area.X(m_wnd.X2()-m_area.XGap()); m_check.X(m_wnd.X2()-m_check.XGap()); m_label.X(m_wnd.X2()-m_label.XGap()); } else { //--- Armazena as coordenadas nos campos dos objetos CElement::X(x+XGap()); //--- Armazena as coordenadas nos campos dos objetos m_area.X(x+m_area.XGap()); m_check.X(x+m_check.XGap()); m_label.X(x+m_label.XGap()); } //--- Se está ancorado no canto inferior if(m_anchor_bottom_window_side) { //--- Armazena as coordenadas nos campos do elemento CElement::Y(m_wnd.Y2()-YGap()); //--- Armazena as coordenadas nos campos dos objetos m_area.Y(m_wnd.Y2()-m_area.YGap()); m_check.Y(m_wnd.Y2()-m_check.YGap()); m_label.Y(m_wnd.Y2()-m_label.YGap()); } else { //--- Armazena as coordenadas nos campos dos objetos CElement::Y(y+YGap()); //--- Armazena as coordenadas nos campos dos objetos m_area.Y(y+m_area.YGap()); m_check.Y(y+m_check.YGap()); m_label.Y(y+m_label.YGap()); } //--- Atualizando as coordenadas dos objetos gráficos m_area.X_Distance(m_area.X()); m_area.Y_Distance(m_area.Y()); m_check.X_Distance(m_check.X()); m_check.Y_Distance(m_check.Y()); m_label.X_Distance(m_label.X()); m_label.Y_Distance(m_label.Y()); }
Para uma maior clareza, a figura abaixo mostra esquematicamente todas as combinações possíveis dos modos de posicionamento e redimensionamento automático dos controles. Ele é um exemplo abstrato, na qual o formulário (ver a nona coluna «Result») é representado por um retângulo branco com um quadro preto em negrito e o tamanho de 400 x 400 pixels, e o controle é representado por um retângulo cinzento com um tamanho de 200 x 200 pixels. As coordenadas relativas e o tamanho do controle também são exibidos para cada combinação. Os traços indicam os casos que não é obrigatório definir o tamanho (se o modo automático de redimensionamento está habilitado).
Fig. 3. Tabela listando as diferentes opções em combinação com o posicionamento e o redimensionamento automático do controle.
5. Foi adicionado o identificador de evento ON_CLICK_TAB para gerar o evento das alternâncias das guias nas classes CTabs e CIconTabs.
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ ... #define ON_CLICK_TAB (27) // Alternância das guias
Agora o evento com o identificador ON_CLICK_TAB pode ser monitorado no manipulador de uma classe personalizada, oferecendo mais possibilidades para gerir a aparência da interface gráfica.
6. Foi adicionado a atualização forçada das coordenadas pelos métodos Show() de todos os controles. Como um exemplo, o código abaixo mostra o método da classe CSimpleButton (linha destacada em amarelo):
//+------------------------------------------------------------------+ //| Exibe o botão | //+------------------------------------------------------------------+ void CSimpleButton::Show(void) { //--- Retorna, se o elemento já for visível if(CElement::IsVisible()) return; //--- Faz com que todos os objetos sejam visíveis m_button.Timeframes(OBJ_ALL_PERIODS); //--- Estado de visibilidade CElement::IsVisible(true); //--- Atualiza a posição dos objetos Moving(m_wnd.X(),m_wnd.Y()); }
Em certos casos, o uso ativo da interface gráfica da aplicação MQL pode acontecer de alguns de seus controles ficarem posicionados de forma incorreta. Agora, este problema foi resolvido.
7. Os métodos para obter os ponteiros para os botões foram adicionados a classe CWindow:
//+------------------------------------------------------------------+ //| Classe para criar um formulário de controles | //+------------------------------------------------------------------+ class CWindow : public CElement { public: //--- Retorna os ponteiros para os botões do formulário CBmpLabel *GetCloseButtonPointer(void) { return(::GetPointer(m_button_close)); } CBmpLabel *GetRollUpButtonPointer(void) { return(::GetPointer(m_button_unroll)); } CBmpLabel *GetUnrollButtonPointer(void) { return(::GetPointer(m_button_rollup)); } CBmpLabel *GetTooltipButtonPointer(void) { return(::GetPointer(m_button_tooltip)); } };
Agora os usuários da biblioteca possuem a possibilidade de alterar as propriedades destes objetos gráficos em qualquer momento do ciclo de vida da aplicação MQL após a interface gráfica ter sido criada. Por exemplo, se antes as descrições para os botões estavam representados com valores predefinidos, agora o usuário pode definir de forma independente. Isso pode ser útil na criação de aplicações MQL em vários idiomas.
8. Foi corrigido a classe CTable. Adicionado a verificação para o método principal da criação de um controle (Veja o código abaixo). A parte visível não precisa ser mais comum. Agora, se o usuário comete um erro ao configurar as propriedades da tabela, os valores serão corrigidos automaticamente.
//+------------------------------------------------------------------+ //| Cria tabela com a caixa de edição | //+------------------------------------------------------------------+ bool CTable::CreateTable(const long chart_id,const int subwin,const int x,const int y) { //--- Retorna se não há nenhum ponteiro do formulário if(!CElement::CheckWindowPointer(::CheckPointer(m_wnd))) return(false); //--- A parte visível não precisa ser mais comum m_visible_rows_total =(m_visible_rows_total>m_rows_total)? m_rows_total : m_visible_rows_total; m_visible_columns_total =(m_visible_columns_total>m_columns_total)? m_columns_total : m_visible_columns_total; //--- Inicializa as variáveis m_id =m_wnd.LastId()+1; m_chart_id =chart_id; m_subwin =subwin; m_x =x; m_y =y; m_x_size =(m_x_size<1 || m_auto_xresize_mode)? (m_anchor_right_window_side)? m_wnd.X2()-m_x+m_x_size-(m_wnd.X2()-m_x)+1-m_auto_xresize_right_offset : m_wnd.X2()-m_x-m_auto_xresize_right_offset : m_x_size; m_y_size =m_row_y_size*m_visible_rows_total-(m_visible_rows_total-1)+2; //--- Margens da borda CElement::XGap((m_anchor_right_window_side)? m_x : m_x-m_wnd.X()); CElement::YGap((m_anchor_bottom_window_side)? m_y : m_y-m_wnd.Y()); //--- Cria a tabela if(!CreateArea()) return(false); if(!CreateCells()) return(false); if(!CreateScrollV()) return(false); if(!CreateScrollH()) return(false); //--- Oculta o elemento se ele for uma janela de diálogo ou ela está minimizada if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized()) Hide(); //--- return(true); }
Aplicação para Testar as Atualizações
Para o teste, vamos alterar ligeiramente a aplicação do artigo anterior para tornar possível demonstrar tudo o que foi apresentado neste artigo. Crie o EA em uma sub-janela do indicador "SubWindow". O tamanho da janela principal da interface gráfica irá ajustar automaticamente para o tamanho da sub-janela. A altura da sub-janela poderá ser alterada manualmente. Para fazer isso, o argumento "false" deve ser passado (destacado em verde no código abaixo) ao chamar o método CWindow::RollUpSubwindowMode().
O código a seguir também demonstra como obter acesso para gerenciar as propriedades dos botões na janela principal da interface gráfica da aplicação. Neste caso, o exemplo mostra a definição das dicas de ferramentas.
//+------------------------------------------------------------------+ //| Cria um formulário para os controles | //+------------------------------------------------------------------+ bool CProgram::CreateWindow(const string caption_text) { //--- Adiciona o ponteiro da janela para o array de janela CWndContainer::AddWindow(m_window); //--- Coordenadas int x=1; int y=1; //--- Propriedades m_window.Movable(false); m_window.UseRollButton(); m_window.AutoXResizeMode(true); m_window.AutoYResizeMode(true); m_window.RollUpSubwindowMode(false,false); //--- Criando o formulário if(!m_window.CreateWindow(m_chart_id,m_subwin,caption_text,x,y)) return(false); //--- Defina as dicas de ferramentas m_window.GetCloseButtonPointer().Tooltip("Close program"); m_window.GetUnrollButtonPointer().Tooltip("Unroll"); m_window.GetRollUpButtonPointer().Tooltip("Roll up"); return(true); }
Na primeira guia, todos os controles serão ancorados ao lado direito do formulário (veja a imagem abaixo). Se a largura do formulário for alterada, eles permanecerão na mesma distância da sua margem direita.
Fig. 4. Os controles da primeira guia estão ancorados ao lado direito do formulário.
Como exemplo, segue o código para criar o controle "Simple button" (CSimpleButton). A fim de ancorar o controle ao lado direito, é suficiente chamar o método AnchorRightWindowSide(), e passá-lo o valor true.
//+------------------------------------------------------------------+ //| Cria um botão simples 1 | //+------------------------------------------------------------------+ bool CProgram::CreateSimpleButton1(const int x_gap,const int y_gap,string button_text) { //--- Armazena o ponteiro da janela m_simple_button1.WindowPointer(m_window); //--- Anexa à primeira guia m_tabs.AddToElementsArray(0,m_simple_button1); //--- Coordenadas int x=m_window.X()+x_gap; int y=m_window.Y()+y_gap; //--- Define as propriedades antes da criação m_simple_button1.ButtonXSize(140); m_simple_button1.BackColor(C'255,140,140'); m_simple_button1.BackColorHover(C'255,180,180'); m_simple_button1.BackColorPressed(C'255,120,120'); m_simple_button1.AnchorRightWindowSide(true); //--- Cria um botão if(!m_simple_button1.CreateSimpleButton(m_chart_id,m_subwin,button_text,x,y)) return(false); //--- Adiciona o ponteiro do elemento para a base CWndContainer::AddToElementsArray(0,m_simple_button1); return(true); }
A segunda guia será atribuído apenas uma tabela renderizada (CCanvasTable), que irá ajustar-se aos tamanhos do formulário quando a largura e a altura da sub-janela se alterar.
Fig. 5. Tabela renderizada que se ajusta conforme os tamanhos do formulário.
Para que tudo funcione como pretendido, é necessário usar os métodos AutoXResizeMode() e AutoYResizeMode() e permitir os modos para redimensionamento horizontal e vertical automática. Com os métodos AutoXResizeRightOffset() e AutoYResizeBottomOffset(), é possível ajustar os deslocamentos da bordas direita e inferior do controle para a extremidade inferior ou da direita do formulário. Nesse caso, o deslocamento da borda direita está definido para 1 pixel, e 25 pixels da parte inferior (Veja o código abaixo).
//+------------------------------------------------------------------+ //| Cria uma tabela renderizada | //+------------------------------------------------------------------+ bool CProgram::CreateCanvasTable(const int x_gap,const int y_gap) { #define COLUMNS3_TOTAL 15 #define ROWS3_TOTAL 30 //--- Armazena o ponteiro ao formulário m_canvas_table.WindowPointer(m_window); //--- Anexa para a segunda guia m_tabs.AddToElementsArray(1,m_canvas_table); //--- Coordenadas int x=m_window.X()+x_gap; int y=m_window.Y()+y_gap; //--- Array de largura das colunas int width[COLUMNS3_TOTAL]; ::ArrayInitialize(width,70); width[0]=100; width[1]=90; //--- Array do alinhamento de texto em colunas ENUM_ALIGN_MODE align[COLUMNS3_TOTAL]; ::ArrayInitialize(align,ALIGN_CENTER); align[0]=ALIGN_RIGHT; align[1]=ALIGN_RIGHT; align[2]=ALIGN_LEFT; //--- Define as propriedades antes da criação m_canvas_table.XSize(400); m_canvas_table.YSize(200); m_canvas_table.TableSize(COLUMNS3_TOTAL,ROWS3_TOTAL); m_canvas_table.TextAlign(align); m_canvas_table.ColumnsWidth(width); m_canvas_table.GridColor(clrLightGray); m_canvas_table.AutoXResizeMode(true); m_canvas_table.AutoYResizeMode(true); m_canvas_table.AutoXResizeRightOffset(1); m_canvas_table.AutoYResizeBottomOffset(25); //--- Preenche a tabela com os dados for(int c=0; c<COLUMNS3_TOTAL; c++) for(int r=0; r<ROWS3_TOTAL; r++) m_canvas_table.SetValue(c,r,string(c)+":"+string(r)); //--- Cria o controle if(!m_canvas_table.CreateTable(m_chart_id,m_subwin,x,y)) return(false); //--- Adiciona o objeto ao array comum dos grupos de objetos CWndContainer::AddToElementsArray(0,m_canvas_table); return(true); }
O gráfico de linha (CLineGraph) será colocado sobre a terceira guia, com o ajuste automático dos tamanhos do formulário:
Fig. 6. O controle «gráfico de linha» que se ajusta aos tamanhos do formulário.
A quarta e quinta guia demonstram a ancoragem da borda direita de muitos outros controles da biblioteca:
Fig. 7. Controles na quarta guia.
Fig. 8. Controles na quinta guia.
O aplicativo de teste apresentado no artigo pode ser baixado usando o link abaixo para estudá-lo ainda mais.
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. 9. Estrutura da biblioteca no atual estágio de desenvolvimento.
Na próxima versão, a biblioteca será expandido com os controles adicionais, que podem ser necessários ao desenvolvimento das aplicações MQL. Os controles existentes sofrerão melhorias e será adicionado novas funcionalidades.
Abaixo, você pode baixar a terceira versão da biblioteca Easy And Fast. Se você tiver dúvidas sobre a utilização de material a partir desses arquivos, você poderá consultar a descrição detalhada desta série de artigos ou realizar uma pergunta nos comentários deste artigo.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2723
- 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