Interfaces Gráficas XI: Controles renderizados (build 14.2)
Anatoli Kazharski | 23 agosto, 2017
Conteúdo
- Introdução
- Métodos para desenhar os controles
- Novo design da interface gráfica
- Dicas de contexto
- Novos identificadores de eventos
- Otimização do núcleo da biblioteca
- Aplicação para testar os controles
- Conclusão
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 versão completa da biblioteca no estágio atual de desenvolvimento está disponível no final de cada artigo da série. Os arquivos devem estar localizados nas mesmas pastas que o arquivo baixado.
Na versão atualizada da biblioteca, todos os seus controles serão desenhados em objetos gráficos separados do tipo OBJ_BITMAP_LABEL. Além disso, continuaremos a descrever a otimização generalizada do código da biblioteca. Esta descrição foi iniciada no artigo anterior. Agora, vamos considerar as mudanças nas classes principais da biblioteca. A nova versão da biblioteca está ainda mais orientada a objetos. O código está ainda mais compreensível. Isso ajuda os usuários a desenvolver a biblioteca de forma independente de acordo com suas próprias tarefas.
Métodos para desenhar os controles
A instância da classe da tela foi declarada na classe CElement. Seus métodos permitem criar um objeto para desenhar e excluí-lo. Se necessário, seu ponteiro poderá ser obtido.
class CElement : public CElementBase { protected: //--- Tela para desenhar um controle CRectCanvas m_canvas; //--- public: //--- Retorna o ponteiro para a tela do controle CRectCanvas *CanvasPointer(void) { return(::GetPointer(m_canvas)); } };
Agora, existe um método geral para criar um objeto (tela) para desenhar a aparência de um controle. Ela está localizada na classe base CElement e pode ser acessada a partir de todas as classes dos controles da biblioteca. O método CElement::CreateCanvas() é usado para criar um objeto gráfico desse tipo. Como argumentos, deve ser passado (1) o nome, (2) coordenadas, (3) dimensões e o (4) formato da cor. O formato padrão é COLOR_FORMAT_ARGB_NORMALIZE, o que permite deixar os controles transparentes. Caso sejam passadas dimensões inválidas, elas serão corrigidas no início do método. Uma vez que o objeto é criado e anexado a um gráfico com a aplicação MQL, as propriedades da base são definidas, que foram repetidas anteriormente em todas as classes de controles.
class CElement : public CElementBase { public: //--- Criação da tela bool CreateCanvas(const string name,const int x,const int y, const int x_size,const int y_size,ENUM_COLOR_FORMAT clr_format=COLOR_FORMAT_ARGB_NORMALIZE); }; //+------------------------------------------------------------------+ //| Criação da tela para desenhar um controle | //+------------------------------------------------------------------+ bool CElement::CreateCanvas(const string name,const int x,const int y, const int x_size,const int y_size,ENUM_COLOR_FORMAT clr_format=COLOR_FORMAT_ARGB_NORMALIZE) { //--- Ajustar os tamanhos int xsize =(x_size<1)? 50 : x_size; int ysize =(y_size<1)? 20 : y_size; //--- Reseta o último erro ::ResetLastError(); //--- Cria um objeto if(!m_canvas.CreateBitmapLabel(m_chart_id,m_subwin,name,x,y,xsize,ysize,clr_format)) { ::Print(__FUNCTION__," > Falha ao criar a tela para desenhar o controle ("+m_class_name+"): ",::GetLastError()); return(false); } //--- Reseta o último erro ::ResetLastError(); //--- Obtém o ponteiro para a classe base CChartObject *chart=::GetPointer(m_canvas); //--- Anexa ao gráfico if(!chart.Attach(m_chart_id,name,(int)m_subwin,(int)1)) { ::Print(__FUNCTION__," > Falha ao anexar a tela para desenhar no gráfico: ",::GetLastError()); return(false); } //--- Propriedades m_canvas.Tooltip("\n"); m_canvas.Corner(m_corner); m_canvas.Selectable(false); //--- Todos os controles, exceto o formulário, têm maior prioridade do que o controle principal Z_Order((dynamic_cast<CWindow*>(&this)!=NULL)? 0 : m_main.Z_Order()+1); //--- Coordenadas m_canvas.X(x); m_canvas.Y(y); //--- Tamanho m_canvas.XSize(x_size); m_canvas.YSize(y_size); //--- Deslocamentos do ponto extremo m_canvas.XGap(CalculateXGap(x)); m_canvas.YGap(CalculateYGap(y)); return(true); }
Vamos seguir com os métodos básicos para desenhar os controles. Eles estão todos localizados na classe CElement e declarado como virtual.
Em primeiro lugar, ele desenha o plano de fundo. Na versão básica, ele é um preenchimento de core simples, que usa o método CElement::DrawBackground(). Se necessário, a transparência pode ser ativada. Para fazer isso, use o método CElement::Alpha(), com o valor do canal alfa de 0 para 255, passado como argumento. O valor zero significa transparência total. Na versão atual, a transparência se aplica apenas ao preenchimento do fundo e da borda. O Texto e as imagens permanecerão totalmente opacos e claros em qualquer valor do canal alfa.
class CElement : public CElementBase { protected: //--- Valor do canal alfa (transparência do controle) uchar m_alpha; //--- public: //--- Valor do canal alfa (transparência do controle) void Alpha(const uchar value) { m_alpha=value; } uchar Alpha(void) const { return(m_alpha); } //--- protected: //--- Desenha o fundo virtual void DrawBackground(void); }; //+------------------------------------------------------------------+ //| Desenha o fundo | //+------------------------------------------------------------------+ void CElement::DrawBackground(void) { m_canvas.Erase(::ColorToARGB(m_back_color,m_alpha)); }
Muitas vezes é necessário desenhar uma borda para um controle particular. O método CElement::DrawBorder() desenha uma borda ao redor das bordas do objeto na tela. O método Rectangle() também pode ser usado para este propósito. Ele desenha um retângulo sem preenchimento.
class CElement : public CElementBase { protected: //--- Desenha a estrutura virtual void DrawBorder(void); }; //+------------------------------------------------------------------+ //| Desenha a borda | //+------------------------------------------------------------------+ void CElement::DrawBorder(void) { //--- Coordenadas int x1=0,y1=0; int x2=m_canvas.X_Size()-1; int y2=m_canvas.Y_Size()-1; //--- Desenha um retângulo sem preenchimento m_canvas.Rectangle(x1,y1,x2,y2,::ColorToARGB(m_border_color,m_alpha)); }
O artigo anterior já mencionou que qualquer número de grupos de imagens pode ser atribuído a qualquer controle. Portanto, o método para desenhar um controle deve ser capaz de exibir todas as imagens definidas pelo usuário. O método CElement::DrawImage() é usado aqui para esta finalidade. O programa itera sequencialmente sobre todos os grupos e imagens neles, enviando-os para a tela pixel por pixel. Antes do início do loop produzir a saída da imagem, a imagem atualmente selecionada no grupo é determinada. Veja o código deste método:
class CElement : public CElementBase { protected: //--- Desenha a imagem virtual void DrawImage(void); }; //+------------------------------------------------------------------+ //| Desenha a imagem | //+------------------------------------------------------------------+ void CElement::DrawImage(void) { //--- O número de grupos uint group_total=ImagesGroupTotal(); //--- Desenha a imagem for(uint g=0; g<group_total; g++) { //--- Índice da imagem selecionada int i=SelectedImage(g); //--- Se não há imagens if(i==WRONG_VALUE) continue; //--- Coordenadas int x =m_images_group[g].m_x_gap; int y =m_images_group[g].m_y_gap; //--- Tamanho uint height =m_images_group[g].m_image[i].Height(); uint width =m_images_group[g].m_image[i].Width(); //--- Desenha for(uint ly=0,p=0; ly<height; ly++) { for(uint lx=0; lx<width; lx++,p++) { //--- Se não há cor, vai para o próximo pixel if(m_images_group[g].m_image[i].Data(p)<1) continue; //--- Obtém a cor da camada inferior (fundo da célula) e a cor do pixel especificado do ícone uint background =::ColorToARGB(m_canvas.PixelGet(x+lx,y+ly)); uint pixel_color =m_images_group[g].m_image[i].Data(p); //--- Mistura as cores uint foreground=::ColorToARGB(m_clr.BlendColors(background,pixel_color)); //--- Desenha o pixel da sobreposição do ícone m_canvas.PixelSet(x+lx,y+ly,foreground); } } } }
Muitos controles possuem uma descrição de texto. Ele pode ser exibido usando o método CElement::DrawText(). Vários campos neste método permitem personalizar a exibição de texto dependendo do estado do controle. Estão disponíveis três estados do controle:
- bloqueado;
- pressionado;
- focado (embaixo do cursor do mouse).
Além disso, o método considera se o modo de alinhamento de texto para o centro está habilitado. Aqui está o seu código:
class CElement : public CElementBase { protected: //--- Desenha o texto virtual void DrawText(void); }; //+------------------------------------------------------------------+ //| Desenha o texto | //+------------------------------------------------------------------+ void CElement::DrawText(void) { //--- Coordenadas int x =m_label_x_gap; int y =m_label_y_gap; //--- Define a cor para o rótulo de texto color clr=clrBlack; //--- Se o controle estiver bloqueado if(m_is_locked) clr=m_label_color_locked; else { //--- Se o controle for pressionado if(!m_is_pressed) clr=(m_mouse_focus)? m_label_color_hover : m_label_color; else { if(m_class_name=="CButton") clr=m_label_color_pressed; else clr=(m_mouse_focus)? m_label_color_hover : m_label_color_pressed; } } //--- Propriedades da fonte m_canvas.FontSet(m_font,-m_font_size*10,FW_NORMAL); //--- Desenha o texto levando em consideração o modo de alinhamento central if(m_is_center_text) { x =m_x_size>>1; y =m_y_size>>1; m_canvas.TextOut(x,y,m_label_text,::ColorToARGB(clr),TA_CENTER|TA_VCENTER); } else m_canvas.TextOut(x,y,m_label_text,::ColorToARGB(clr),TA_LEFT); }
Todos os métodos acima serão chamados no método virtual público comum CElement::Draw(). Ele não possui um código base, pois o conjunto de métodos chamado para desenho será exclusivo de cada controle.
class CElement : public CElementBase { public: //--- Desenha o controle virtual void Draw(void) {} };
Considere o método CElement::Update(). Ele é chamado sempre que uma alteração no programa é realizada a um controle da interface gráfica. Estão disponíveis duas opções de chamada: (1) redesenhando completamente o controle ou (2) aplicando as mudanças realizadas antes (veja o código abaixo). Esse método também é declarado como virtual, uma vez que certas classes de controles podem ter suas versões únicas, que consideram suas peculiaridades nos métodos e sequências de renderização.
class CElement : public CElementBase { public: //--- Atualiza o controle para exibir as últimas alterações virtual void Update(const bool redraw=false); }; //+------------------------------------------------------------------+ //| Atualizando o controle | //+------------------------------------------------------------------+ void CElement::Update(const bool redraw=false) { //--- Com o redesenho do controle if(redraw) { Draw(); m_canvas.Update(); return; } //--- Aplicar m_canvas.Update(); }
Novo design da interface gráfica
Uma vez que todos os controles da biblioteca são agora renderizados, se tornou possível implementar um novo design para a interface gráfica. Não há necessidade de inventar nada de especial, uma solução pronta poderá ser usada. A estética lacônica do SO Windows 10 foi usado como base.
As imagens dos ícones para tais elementos como os botões de formulário para controles, botões de radio, caixas de seleção, caixas combinadas, elementos de menu, elementos da lista e outros foram feitos de maneira semelhante ao Windows 10.
Foi mencionado anteriormente que agora a transparência pode ser definida para qualquer controle. A imagem abaixo mostra um exemplo de uma janela translúcida - (CWindow). O valor do canal alfa nesta imagem é igual a 200.
Fig. 8. Demonstração da transparência do formulário para os controles.
Para tornar a área inteira do formulário transparente, use o método CWindow::TransparentOnlyCaption(). O modo padrão aplica a transparência apenas ao cabeçalho.
class CWindow : public CElement { private: //--- Permite transparência apenas para o cabeçalho bool m_transparent_only_caption; //--- public: //--- Permite o modo de transparência apenas para o cabeçalho void TransparentOnlyCaption(const bool state) { m_transparent_only_caption=state; } }; //+------------------------------------------------------------------+ //| Construtor | //+------------------------------------------------------------------+ CWindow::CWindow(void) : m_transparent_only_caption(true) { ... }
Abaixo está a aparência dos diferentes tipos de botões:
Fig. 9. Demonstração da aparência de vários tipos de botão.
A próxima imagem mostra a aparência atual das caixas de seleção, caixas de edição numérica, caixa combinada com uma lista suspensa e uma barra de rolagem, bem como os controles deslizantes numéricos. Observe que agora é possível fazer ícones animados. O terceiro elemento da barra de estado imita uma desconexão do servidor. Sua aparência é uma cópia exata de um elemento semelhante ao da barra de status do MetaTrader 5.
Fig. 10. Demonstração da aparência de caixas de seleção, caixas combinadas, controles deslizantes e outros controles.
A aparência de outros controles da biblioteca de interface gráfica podem ser vistos na aplicação MQL de teste anexada a este artigo.
Dicas de contexto
Os métodos adicionais para gerenciar a exibição das dicas de contexto nos controles foram adicionados a classe CElement. Agora é possível configurar uma dica de contexto padrão para qualquer controle, se seu texto não exceder 63 caracteres. Use o método CElement::Tooltip() para configurar e obter o texto da dica de contexto.
class CElement : public CElementBase { protected: //--- Texto da dica de contexto string m_tooltip_text; //--- public: //--- Dica de contexto void Tooltip(const string text) { m_tooltip_text=text; } string Tooltip(void) const { return(m_tooltip_text); } };
Use o método CElement::ShowTooltip() para ativar ou desativar a exibição da dica de contexto.
class CElement : public CElementBase { public: //--- Modo de exibição da dica de contexto void ShowTooltip(const bool state); }; //+------------------------------------------------------------------+ //| Definindo a exibição da dica de contexto | //+------------------------------------------------------------------+ void CElement::ShowTooltip(const bool state) { if(state) m_canvas.Tooltip(m_tooltip_text); else m_canvas.Tooltip("\n"); }
Cada classe de controles possui métodos para obter os ponteiros para os controles aninhados. Por exemplo, se for necessário criar dicas de ferramentas para os botões do formulário, as seguintes linhas de código devem ser adicionadas ao método de criação do formulário na classe personalizada:
... //--- Defina as dicas de contexto m_window.GetCloseButtonPointer().Tooltip("Close"); m_window.GetCollapseButtonPointer().Tooltip("Collapse/Expand"); m_window.GetTooltipButtonPointer().Tooltip("Tooltips"); ...
Veja como funciona na figura abaixo. As dicas de contexto padrão foram ativadas para os botões de formulário. Nos controles que talvez precisem de uma descrição mais longa do que 63 caracteres, use o controle CTooltip.
Fig. 11. Demonstração de dois tipos de dicas de contexto (padrão e personalizado).
Novos identificadores de eventos
Novos identificadores de eventos foram adicionados. Isso reduziu significativamente o consumo de recursos da CPU. Como isso foi alcançado?
Ao criar uma grande aplicação MQL com uma interface gráfica e muitos controles, é importante minimizar o consumo da CPU.
Se você estiver com o mouse sobre um controle, ele é destacado. Isso indica que o controle está disponível para interação. No entanto, nem todos os controles estão disponíveis e visíveis ao mesmo tempo.
- A lista suspensa, os calendários e os menus de contexto estão ocultos a maior parte do tempo. Eles são abertos ocasionalmente, apenas para permitir que o usuário selecione a opção necessária, podendo ser a data ou o modo.
- Grupos de controles podem ser atribuídos a guias diferentes, mas apenas uma guia pode ser aberta por vez.
- Se o formulário for minimizado, todos os seus controles também serão ocultos.
- Se uma caixa de diálogo estiver aberta, somente este formulário terá o processamento de eventos.
Logicamente, não há nenhum ponto em processar constantemente toda a lista de controles na interface gráfica quando somente alguns deles estão disponíveis para uso. É necessário gerar um array de gerenciamento de eventos apenas para a lista de controles abertos.
Também há controles com interações que só afetam os próprios controles. Portanto, esse controle é o único que deve ser deixado disponível para processamento. Vamos listar esses controles e os casos:
- Mover o deslizador da barra de rolagem (CScroll). É necessário habilitar o processamento apenas da própria barra de rolagem e do controle de que ele faz parte (lista, tabela, caixa de texto multilinha, etc.).
- Mover deslizador do slider (CSlider). Para isso, basta que o controle deslizante e a caixa de edição numérica estejam disponíveis, onde as mudanças nos valores serão refletidas.
- Alterar a largura das colunas na tabela (CTable). Somente a tabela deve estar disponível para processamento.
- Alterar a largura das listas no controle da lista hierárquica(CTreeView). Somente esse controle deve ser processado ao arrastar a borda adjacente das listas.
- Mover o formulário (CWindow). Todos os controles estão excluídos do processamento, exceto o formulário enquanto ele é movido.
Em todos os casos listados, os controles precisam enviar mensagens, que devem ser recebidas e processadas no núcleo da biblioteca. O núcleo processará dois identificadores de eventos para determinar a disponibilidade dos controles (ON_SET_AVAILABLE) e gerando um array de controles (ON_CHANGE_GUI). Todos os identificadores de eventos estão localizados no arquivo Define.mqh:
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ ... #define ON_CHANGE_GUI (28) // A interface gráfica foi alterada #define ON_SET_AVAILABLE (39) // Define os elementos disponíveis ...
Oculta e mostra os controles usando os métodos Show() e Hide(). Uma nova propriedade foi adicionada a classe CElement para configurar a disponibilidade. Seu valor é definido usando o método público virtual CElement::IsAvailable(). Aqui, semelhante aos outros métodos que definem o estado dos controles, o valor passado está definido para os controles aninhados também. As prioridades do clique do botão esquerdo do mouse são definidas em relação ao estado passado. Se o controle estiver indisponível, as prioridades serão redefinidas.
class CElement : public CElementBase { protected: bool m_is_available; // disponibilidade //--- public: //--- Sinal de disponibilidade do controle virtual void IsAvailable(const bool state) { m_is_available=state; } bool IsAvailable(void) const { return(m_is_available); } }; //+------------------------------------------------------------------+ //| Disponibilidade do controle | //+------------------------------------------------------------------+ void CElement::IsAvailable(const bool state) { //--- Retorna, se já definido if(state==CElementBase::IsAvailable()) return; //--- Define CElementBase::IsAvailable(state); //--- Outros controles int elements_total=ElementsTotal(); for(int i=0; i<elements_total; i++) m_elements[i].IsAvailable(state); //--- Define as prioridades do botão esquerdo do mouse if(state) SetZorders(); else ResetZorders(); }
Como exemplo, aqui está o código do método CComboBox::ChangeComboBoxListState(), que determina a visibilidade da lista suspensa no controle da caixa combinada.
Se um botão da caixa de combinação for pressionado e for necessário exibir a lista, então um evento com o identificador ON_SET_AVAILABLE é enviado imediatamente após a lista ser exibida. Como parâmetros adicionais, o (1) identificador do controle e (2) o sinalizador da ação necessária para o manipulador de eventos são passados: restaurar todos os controles visíveis ou disponibiliza apenas o controle que possui o identificador especificado no evento. Uma flag com o valor 1 é usado para restaurar, enquanto o valor 0 define a disponibilidade do controle especificado.
A mensagem com o identificador ON_SET_AVAILABLE é seguido por uma mensagem com o identificador de evento ON_CHANGE_GUI. Manipular isso envolverá a geração de um array com os controles atualmente disponíveis.
//+------------------------------------------------------------------+ //| Altera o estado atual da caixa de combinação para o contrário | //+------------------------------------------------------------------+ void CComboBox::ChangeComboBoxListState(void) { //--- Se o botão foi pressionado if(m_button.IsPressed()) { //--- Mostra a lista m_listview.Show(); //--- Envia uma mensagem para determinar os controles disponíveis ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),0,""); //--- Envia uma mensagem sobre a alteração na interface gráfica ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,""); } else { //--- Oculta a lista m_listview.Hide(); //--- Envia uma mensagem para restaurar os controles ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),1,""); //--- Envia uma mensagem sobre a alteração na interface gráfica ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,""); } }
Mas para o controle Guias, por exemplo, é suficiente enviar apenas um dos eventos descritos para processamento, aquele com o identificador ON_CHANGE_GUI. Não há necessidade de garantir determinados controles. Ao alternar as guias, o estado da visibilidade é definido para os controles, que são atribuídos ao grupo de guias. Na classe CTabs, a visibilidade dos grupos de controles é gerenciada pelo método CTabs::ShowTabElements(), que foi modificado na nova versão da biblioteca. Às vezes, pode ser necessário colocar um grupo de guias dentro de uma guia. Assim sendo, mesmo se exibir os controles da guia selecionada revela que um deles tem um tipo de CTabs, então o método CTabs::ShowTabElements() será imediatamente chamado nesse controle também. Esta abordagem permite a colocação de guias em qualquer nível de aninhamento.
//+------------------------------------------------------------------+ //| Mostra somente os controles da guia selecionada | //+------------------------------------------------------------------+ void CTabs::ShowTabElements(void) { //--- Sai, se as abas estão ocultas if(!CElementBase::IsVisible()) return; //--- Verificação do índice da guia selecionada CheckTabIndex(); //--- uint tabs_total=TabsTotal(); for(uint i=0; i<tabs_total; i++) { //--- Obtém o número de controles ligado à guia int tab_elements_total=::ArraySize(m_tab[i].elements); //--- Se esta guia é selecionada if(i==m_selected_tab) { //--- Exibe os controles da guia for(int j=0; j<tab_elements_total; j++) { //--- Exibe os controles CElement *el=m_tab[i].elements[j]; el.Reset(); //--- Se este é o controle Guias, mostre os controles abertos CTabs *tb=dynamic_cast<CTabs*>(el); if(tb!=NULL) tb.ShowTabElements(); } } //--- Ocultar os controles das guias inativas else { for(int j=0; j<tab_elements_total; j++) m_tab[i].elements[j].Hide(); } } //--- Envia uma mensagem sobre ele ::EventChartCustom(m_chart_id,ON_CLICK_TAB,CElementBase::Id(),m_selected_tab,""); }
Uma vez que os controles da guia selecionada são exibidos, o método envia uma mensagem informando que a interface gráfica foi alterada e é necessário gerar um array de controles disponíveis para processamento.
//+------------------------------------------------------------------+ //| Pressionando uma guia em um grupo | //+------------------------------------------------------------------+ bool CTabs::OnClickTab(const int id,const int index) { //--- Retorna, se (1) os identificadores não coincidem ou (2) o controle está bloqueado if(id!=CElementBase::Id() || CElementBase::IsLocked()) return(false); //--- Retorna, se o índice não corresponder if(index!=m_tabs.SelectedButtonIndex()) return(true); //--- Armazena o índice da guia selecionada SelectedTab(index); //--- Redesenha o controle Reset(); Update(true); //--- Mostra somente os controles da guia selecionada ShowTabElements(); //--- Envia uma mensagem sobre a alteração na interface gráfica ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0.0,""); return(true); }
Dois novos identificadores para geração de eventos foram adicionados ao arquivo Define.mqh.
- ON_MOUSE_FOCUS — o cursor do mouse entrou na área do controle;
- ON_MOUSE_BLUR — o cursor do mouse deixou a área do controle.
... #define ON_MOUSE_BLUR (34) // o cursor do mouse deixou a área do controle #define ON_MOUSE_FOCUS (35) // o cursor do mouse entrou na área do controle ...
Esses eventos são gerados somente quando os limites dos controles são cruzados. A classe base dos controles (CElementBase) contém o método CElementBase::CheckCrossingBorder(), que determina o momento em que o cursor do mouse cruza os limites das áreas de controle. Vamos complementá-lo com a geração dos eventos descritos acima:
//+------------------------------------------------------------------+ //| Verificando o cruzamento dos limites do controle | //+------------------------------------------------------------------+ bool CElementBase::CheckCrossingBorder(void) { //--- Se este é o momento de cruzar os limites do controle if((MouseFocus() && !IsMouseFocus()) || (!MouseFocus() && IsMouseFocus())) { IsMouseFocus(MouseFocus()); //--- Mensagem sobre o cruzamento no controle if(MouseFocus()) ::EventChartCustom(m_chart_id,ON_MOUSE_FOCUS,m_id,m_index,m_class_name); //--- Mensagem sobre o cruzamento fora do controle else ::EventChartCustom(m_chart_id,ON_MOUSE_BLUR,m_id,m_index,m_class_name); //--- return(true); } //--- return(false); }
Na versão atual da biblioteca, esses eventos são tratados apenas no menu principal (CMenuBar). Vamos ver como funciona.
Uma vez que o menu principal é criado e armazenado, seus elementos (CMENUItem) vão para a lista de armazenamento como controles separados. A classe CMENUItem é derivada do CButton (o controle Botão). Portanto, uma chamada para o manipulador de eventos do elemento de menu começa com uma chamada para o manipulador de eventos da classe base CButton.
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CMenuItem::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Manipula o evento na classe base CButton::OnEvent(id,lparam,dparam,sparam); ... }
O manipulador de eventos base já contém o monitoramento do cruzamento do botão, não precisa ser duplicado na classe derivada CMENUItem.
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CButton::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Manipulação do evento de movimento do mouse if(id==CHARTEVENT_MOUSE_MOVE) { //--- Redesenha o controle se os limites foram cruzados if(CheckCrossingBorder()) Update(true); //--- return; } ... }
Se o cursor atravessar a borda dentro da área do botão, um evento com o identificador ON_MOUSE_FOCUS é gerado. Agora, o manipulador de eventos da classe CMenuBar usa esse mesmo evento para alternar os menus de contexto, quando o controle do menu principal está ativado.
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CMenuBar::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Manipulando o evento de alteração do foco nos elementos do menu if(id==CHARTEVENT_CUSTOM+ON_MOUSE_FOCUS) { //--- Retorna, se (2) o menu principal não tiver sido ativado ou (2) os identificadores não coincidirem if(!m_menubar_state || lparam!=CElementBase::Id()) return; //--- Desliga o menu de contexto pelo elemento ativado do menu principal SwitchContextMenuByFocus(); return; } ... }
Otimização do núcleo da biblioteca
Consideremos as mudanças, correções e adições feitas nas classes CWndContainer e CWndEvents, que podem ser chamadas como núcleo da biblioteca. Afinal, elas organizam o acesso a todos os seus controles e controlam os fluxos de eventos que são gerados pelos controles da interface gráfica.
Um método modelo CWndContainer::ResizeArray() foi adicionado à classe CWndContainer para trabalhar com arrays. Um array de qualquer tipo passado para este método será aumentado por um elemento e o método retornará o índice do último elemento.
//+------------------------------------------------------------------+ //| Classe para armazenar todos os objetos da interface | //+------------------------------------------------------------------+ class CWndContainer { private: //--- Aumenta o array por um elemento e retorna o último índice template<typename T> int ResizeArray(T &array[]); }; //+------------------------------------------------------------------+ //| Aumenta o array por um elemento e retorna o último índice | //+------------------------------------------------------------------+ template<typename T> int CWndContainer::ResizeArray(T &array[]) { int size=::ArraySize(array); ::ArrayResize(array,size+1,RESERVE_SIZE_ARRAY); return(size); }
Deixe-me lembrá-lo de que os arrays privados foram declarados na estrutura WindowElements para muitos controles da classe CWndContainer armazenamento de ponteiros para todos os controles da interface gráfica). Para obter o número de controles de um determinado tipo desta lista, foi implementado o método universal CWndContainer::ElementsTotal(). Passe-o no índice da janela e no tipo de controle para obter seu número na interface gráfica da aplicação MQL. Uma nova enumeração ENUM_ELEMENT_TYPE foi adicionada ao arquivo Enums.mqh para especificar o tipo de controle:
//+------------------------------------------------------------------+ //| Enumeração dos tipos de controle | //+------------------------------------------------------------------+ enum ENUM_ELEMENT_TYPE { E_CONTEXT_MENU =0, E_COMBO_BOX =1, E_SPLIT_BUTTON =2, E_MENU_BAR =3, E_MENU_ITEM =4, E_DROP_LIST =5, E_SCROLL =6, E_TABLE =7, E_TABS =8, E_SLIDER =9, E_CALENDAR =10, E_DROP_CALENDAR =11, E_SUB_CHART =12, E_PICTURES_SLIDER =13, E_TIME_EDIT =14, E_TEXT_BOX =15, E_TREE_VIEW =16, E_FILE_NAVIGATOR =17, E_TOOLTIP =18 };
O código do método CWndContainer::ElementsTotal() é apresentado na seguinte lista:
//+------------------------------------------------------------------+ //| Nº de controles do tipo especificado no índice da janela especificada| //+------------------------------------------------------------------+ int CWndContainer::ElementsTotal(const int window_index,const ENUM_ELEMENT_TYPE type) { //--- Verifica se o tamanho do array não excedeu int index=CheckOutOfRange(window_index); if(index==WRONG_VALUE) return(WRONG_VALUE); //--- int elements_total=0; //--- switch(type) { case E_CONTEXT_MENU : elements_total=::ArraySize(m_wnd[index].m_context_menus); break; case E_COMBO_BOX : elements_total=::ArraySize(m_wnd[index].m_combo_boxes); break; case E_SPLIT_BUTTON : elements_total=::ArraySize(m_wnd[index].m_split_buttons); break; case E_MENU_BAR : elements_total=::ArraySize(m_wnd[index].m_menu_bars); break; case E_MENU_ITEM : elements_total=::ArraySize(m_wnd[index].m_menu_items); break; case E_DROP_LIST : elements_total=::ArraySize(m_wnd[index].m_drop_lists); break; case E_SCROLL : elements_total=::ArraySize(m_wnd[index].m_scrolls); break; case E_TABLE : elements_total=::ArraySize(m_wnd[index].m_tables); break; case E_TABS : elements_total=::ArraySize(m_wnd[index].m_tabs); break; case E_SLIDER : elements_total=::ArraySize(m_wnd[index].m_sliders); break; case E_CALENDAR : elements_total=::ArraySize(m_wnd[index].m_calendars); break; case E_DROP_CALENDAR : elements_total=::ArraySize(m_wnd[index].m_drop_calendars); break; case E_SUB_CHART : elements_total=::ArraySize(m_wnd[index].m_sub_charts); break; case E_PICTURES_SLIDER : elements_total=::ArraySize(m_wnd[index].m_pictures_slider); break; case E_TIME_EDIT : elements_total=::ArraySize(m_wnd[index].m_time_edits); break; case E_TEXT_BOX : elements_total=::ArraySize(m_wnd[index].m_text_boxes); break; case E_TREE_VIEW : elements_total=::ArraySize(m_wnd[index].m_treeview_lists); break; case E_FILE_NAVIGATOR : elements_total=::ArraySize(m_wnd[index].m_file_navigators); break; case E_TOOLTIP : elements_total=::ArraySize(m_wnd[index].m_tooltips); break; } //--- Retorna o número de controles do tipo especificado return(elements_total); }
Para reduzir a carga da CPU, foi necessário adicionar mais alguns arrays a estrutura WindowElements, que irá armazenar os ponteiros para controles das seguintes categorias.
- Array de controles principais
- Array de controles com timers
- Array de controles visíveis e disponíveis para processamento
- Array de controles com redimensionamento automático habilitado ao longo do eixo X.
- Array de controles com redimensionamento automático habilitado ao longo do eixo Y
class CWndContainer { protected: ... //--- Estrutura de arrays de controle struct WindowElements { ... //--- Array dos controles principais CElement *m_main_elements[]; //--- Controles do timer CElement *m_timer_elements[]; //--- Controles que estão atualmente visíveis e disponíveis CElement *m_available_elements[]; //--- Controles com redimensionamento automático ao longo do eixo X CElement *m_auto_x_resize_elements[]; //--- Controles com redimensionamento automático ao longo do eixo Y CElement *m_auto_y_resize_elements[]; ... }; //--- Array de arrays de controles para cada janela WindowElements m_wnd[]; ... };
Os tamanhos desses arrays são obtidos pelos métodos apropriados:
class CWndContainer { public: //--- O número de controles principais int MainElementsTotal(const int window_index); //--- O número de controles com timers int TimerElementsTotal(const int window_index); //--- O número de controles com redimensionamento automático ao longo do eixo X int AutoXResizeElementsTotal(const int window_index); //--- O número de controles com redimensionamento automático ao longo do eixo Y int AutoYResizeElementsTotal(const int window_index); //--- O número de controles atualmente disponíveis int AvailableElementsTotal(const int window_index); };
O método CWndContainer::AddToElementsArray() adiciona ponteiros ao array de controles principais. Segue a versão resumida do método:
//+------------------------------------------------------------------+ //| Adiciona o ponteiro ao array de controles | //+------------------------------------------------------------------+ void CWndContainer::AddToElementsArray(const int window_index,CElementBase &object) { ... //--- Adiciona ao array dos controles principais last_index=ResizeArray(m_wnd[window_index].m_main_elements); m_wnd[window_index].m_main_elements[last_index]=::GetPointer(object); ... }
Arrays de outras categorias são gerados na classe CWndEvents (veja abaixo). Métodos separados são usados neles para a adição de ponteiros.
class CWndContainer { protected: //--- Adiciona o ponteiro ao array de controles com timers void AddTimerElement(const int window_index,CElement &object); //--- Adiciona o ponteiro ao array de controles com redimensionamento automático ao longo do eixo X. void AddAutoXResizeElement(const int window_index,CElement &object); //--- Adiciona o ponteiro ao array de controles com redimensionamento automático ao longo do eixo Y void AddAutoYResizeElement(const int window_index,CElement &object); //--- Adiciona o ponteiro ao array de controles atualmente disponíveis void AddAvailableElement(const int window_index,CElement &object); }; //+------------------------------------------------------------------+ //| Adiciona o ponteiro ao array de controles com timers | //+------------------------------------------------------------------+ void CWndContainer::AddTimerElement(const int window_index,CElement &object) { int last_index=ResizeArray(m_wnd[window_index].m_timer_elements); m_wnd[window_index].m_timer_elements[last_index]=::GetPointer(object); } //+------------------------------------------------------------------+ //| Adiciona o ponteiro ao array de controles com redimensionamento automático (X)| //+------------------------------------------------------------------+ void CWndContainer::AddAutoXResizeElement(const int window_index,CElement &object) { int last_index=ResizeArray(m_wnd[window_index].m_auto_x_resize_elements); m_wnd[window_index].m_auto_x_resize_elements[last_index]=::GetPointer(object); } //+------------------------------------------------------------------+ //| Adiciona o ponteiro ao array de controles com redimensionamento auto (Y) | //+------------------------------------------------------------------+ void CWndContainer::AddAutoYResizeElement(const int window_index,CElement &object) { int last_index=ResizeArray(m_wnd[window_index].m_auto_y_resize_elements); m_wnd[window_index].m_auto_y_resize_elements[last_index]=::GetPointer(object); } //+------------------------------------------------------------------+ //| Adiciona o ponteiro ao array de controles disponíveis | //+------------------------------------------------------------------+ void CWndContainer::AddAvailableElement(const int window_index,CElement &object) { int last_index=ResizeArray(m_wnd[window_index].m_available_elements); m_wnd[window_index].m_available_elements[last_index]=::GetPointer(object); }
Existem também novos métodos para uso interno na classe CWndEvents também. Assim, o método CWndEvents::Hide() é necessário para ocultar todos os controles da interface gráfica. Usa um loop duplo: primeiro o os formulários estão ocultados, e então o segundo loop oculta os controles anexados ao formulário. Por favor, note que, nesse método, o segundo loop itera sobre o array de controles, consistindo de ponteiros para os controles principais. Os métodos Hide() e Show() dos controles estão agora dispostos de maneira que eles afetam toda a cadeia desses métodos de controles aninhados para toda a profundidade do aninhamento.
//+------------------------------------------------------------------+ //| Classe para a manipulação de eventos | //+------------------------------------------------------------------+ class CWndEvents : public CWndContainer { protected: //--- Oculta todos os controles void Hide(); }; //+------------------------------------------------------------------+ //| Oculta os controles | //+------------------------------------------------------------------+ void CWndEvents::Hide(void) { int windows_total=CWndContainer::WindowsTotal(); for(int w=0; w<windows_total; w++) { m_windows[w].Hide(); int main_total=MainElementsTotal(w); for(int e=0; e<main_total; e++) { CElement *el=m_wnd[w].m_main_elements[e]; el.Hide(); } } }
Isso também tem um novo método CWndEvents::Show() para mostrar os controles na forma especificada. A janela especificada no argumento é exibida primeiro. Então, se a janela não for minimizada, todos os controles anexados a este formulário serão visíveis. Este ciclo ignora apenas os controles que são (1) suspensos ou (2) aqueles com um controle de Guias designado como seu controle principal. Os controles nas guias são exibidos mais tarde usando o método CWndEvents::ShowTabElements() fora do loop.
class CWndEvents : public CWndContainer { protected: //--- Exibe os controles da janela especificada void Show(const uint window_index); }; //+------------------------------------------------------------------+ //| Exibe os controles da janela especificada | //+------------------------------------------------------------------+ void CWndEvents::Show(const uint window_index) { //--- Exibe os controles da janela especificada m_windows[window_index].Show(); //--- Se a janela não for minimizada if(!m_windows[window_index].IsMinimized()) { int main_total=MainElementsTotal(window_index); for(int e=0; e<main_total; e++) { CElement *el=m_wnd[window_index].m_main_elements[e]; //--- Exibe o controle, se ele não for (1) suspenso e (2) o seu controle principal não for uma guia if(!el.IsDropdown() && dynamic_cast<CTabs*>(el.MainPointer())==NULL) el.Show(); } //--- Exibe somente os controles das guias selecionadas ShowTabElements(window_index); } }
Será necessário que o método CWndEvents::Update() redesenhe todos os controles da interface gráfica da aplicação MQL. Este método pode funcionar de dois modos: (1) redesenhar completamente todos os controles ou (2) aplicar as alterações feitas anteriormente. Para redesenhar e atualizar completamente a interface gráfica, é necessário passar o valor true.
class CWndEvents : public CWndContainer { protected: //--- Redesenha os controles void Update(const bool redraw=false); }; //+------------------------------------------------------------------+ //| Redesenha os controles | //+------------------------------------------------------------------+ void CWndEvents::Update(const bool redraw=false) { int windows_total=CWndContainer::WindowsTotal(); for(int w=0; w<windows_total; w++) { //--- Redesenha os controles int elements_total=CWndContainer::ElementsTotal(w); for(int e=0; e<elements_total; e++) { CElement *el=m_wnd[w].m_elements[e]; el.Update(redraw); } } }
Nós vamos discutir esses métodos um pouco mais tarde. Por enquanto, considere uma série de métodos projetados para gerar arrays para as categorias acima mencionadas.
A versão anterior apresentava uma alteração gradual da cor dos controles em função do tempo quando o cursor do mouse estava situado sobre eles. Para reduzir o volume e diminuir o consumo de recursos, esse recurso supérfluo foi removido. Portanto, o timer não é usado em todos os controles da versão atual da biblioteca. Ele está presente apenas na rolagem rápida do (1) do deslizador da barra de rolagem, (2) os valores nas caixas de edição numérica e (3) as datas no calendário. Portanto, somente os controles apropriados são adicionados ao array correspondente no método CWndEvents::FormTimerElementsArray() (veja a lista de códigos abaixo).
Como os ponteiros para controles são armazenados em arrays do tipo base de controles (CElement), a conversão tipo dinâmico (Dynamic_cast) é usada aqui e em muitos outros métodos das classes para determinar o tipo derivado dos controles.
class CWndEvents : public CWndContainer { protected: //--- Gera o conjunto de controles com timers void FormTimerElementsArray(void); }; //+------------------------------------------------------------------+ //| Gera o array de controles com timers | //+------------------------------------------------------------------+ void CWndEvents::FormTimerElementsArray(void) { int windows_total=CWndContainer::WindowsTotal(); for(int w=0; w<windows_total; w++) { int elements_total=CWndContainer::ElementsTotal(w); for(int e=0; e<elements_total; e++) { CElement *el=m_wnd[w].m_elements[e]; //--- if(dynamic_cast<CCalendar *>(el)!=NULL || dynamic_cast<CColorPicker *>(el)!=NULL || dynamic_cast<CListView *>(el)!=NULL || dynamic_cast<CTable *>(el)!=NULL || dynamic_cast<CTextBox *>(el)!=NULL || dynamic_cast<CTextEdit *>(el)!=NULL || dynamic_cast<CTreeView *>(el)!=NULL) { CWndContainer::AddTimerElement(w,el); } } } }
Agora, o timer tornou-se muito mais simples: ele não precisa mais verificar a lista completa de controles, mas apenas aqueles que incluem esta função:
//+------------------------------------------------------------------+ //| Verificação dos eventos de todos os controles pelo timer | //+------------------------------------------------------------------+ void CWndEvents::CheckElementsEventsTimer(void) { int awi=m_active_window_index; int timer_elements_total=CWndContainer::TimerElementsTotal(awi); for(int e=0; e<timer_elements_total; e++) { CElement *el=m_wnd[awi].m_timer_elements[e]; if(el.IsVisible()) el.OnEventTimer(); } }
O manipulamento do evento do mouse é se faz necessário também apenas para determinados controles da interface gráfica. O array de controles disponíveis para lidar com esse evento agora exclui:
- CButtonsGroup — grupo de botões;
- CFileNavigator — navegador de arquivos;
- CLineGraph — gráfico de linha;
- CPicture — figura;
- CPicturesSlider — slider de imagem;
- CProgressBar — barra de progresso;
- CSeparateLine — linha de separação;
- CStatusBar — barra de status;
- CTabs — guias;
- CTextLabel — rótulo de texto.
Todos esses controles não são realçados quando o mouse estiver acima deles. No entanto, alguns deles possuem controles aninhados que são destacados. Mas, como o array comum é usado no loop para formar o array de controles disponíveis, os controles aninhados também participarão da seleção. O array escolherá todos os controles que são visíveis, disponíveis e desbloqueados.
class CWndEvents : public CWndContainer { protected: //--- Gera o array de controles disponíveis void FormAvailableElementsArray(void); }; //+------------------------------------------------------------------+ //| Gera o array de controles disponíveis | //+------------------------------------------------------------------+ void CWndEvents::FormAvailableElementsArray(void) { //--- Índice de janelas int awi=m_active_window_index; //--- O número total de controles int elements_total=CWndContainer::ElementsTotal(awi); //--- Limpe o array ::ArrayFree(m_wnd[awi].m_available_elements); //--- for(int e=0; e<elements_total; e++) { CElement *el=m_wnd[awi].m_elements[e]; //--- Adiciona apenas os controles visíveis e disponíveis para processamento if(!el.IsVisible() || !el.IsAvailable() || el.IsLocked()) continue; //--- Exclui os controles que não exigem o tratamento dos eventos do mouse if(dynamic_cast<CButtonsGroup *>(el)==NULL && dynamic_cast<CFileNavigator *>(el)==NULL && dynamic_cast<CLineGraph *>(el)==NULL && dynamic_cast<CPicture *>(el)==NULL && dynamic_cast<CPicturesSlider *>(el)==NULL && dynamic_cast<CProgressBar *>(el)==NULL && dynamic_cast<CSeparateLine *>(el)==NULL && dynamic_cast<CStatusBar *>(el)==NULL && dynamic_cast<CTabs *>(el)==NULL && dynamic_cast<CTextLabel *>(el)==NULL) { AddAvailableElement(awi,el); } } }
Resta considerar os métodos CWndEvents::FormAutoXResizeElementsArray() e CWndEvents::FormAutoYResizeElementsArray(), que geram arrays com ponteiros para controles que possuem os modos de redimensionamento automático habilitados. Esses controles seguem os tamanhos dos controles principais aos quais estão ligados. Nem todos os controles possuem um método para redimensionamento automático. Aqui estão os que o possuem ele:
Controles que possuem o código definido no método virtual CElement::ChangeWidthByRightWindowSide() para redimensionar automaticamente a largura:
- CButton — botão.
- CFileNavigator — navegador de arquivos.
- CLineGraph — gráfico de linha.
- CListView — lista.
- CMenuBar — menu principal.
- CProgressBar — barra de progresso.
- CStandardChart — gráfico padrão.
- CStatusBar — Barra de status.
- CTable — tabela.
- CTabs — guias.
- CTextBox — caixa de edição de texto.
- CTextEdit — caixa de edição.
- CTreeView — lista hierárquica.
Controles que possuem o código definido no método virtual CElement::ChangeHeightByBottomWindowSide() para redimensionar automaticamente a altura:
- CLineGraph — gráfico de linha.
- CListView — lista.
- CStandardChart — gráfico padrão.
- CTable — tabela.
- CTabs — guias.
- CTextBox — caixa de edição de texto.
Ao criar os arrays para essas categorias, ele verifica se os modos de redimensionamento automático estão habilitados nesses controles; se ativado, os controles são adicionados ao array. O código destes não serão considerados: métodos semelhantes foram discutidos acima.
Agora, vamos descobrir quando são gerados os arrays das categorias listadas acima. No método principal para criar a interface gráfica (esse usuário cria por conta própria), uma vez que todos os controles especificados são criados com sucesso, agora é necessário chamar apenas um método CWndEvents::CompletedGUI() para exibi-los todos no gráfico. Isso indica que o programa de criação da interface gráfica da aplicação MQL está concluída.
Consideremos o método CWndEvents::CompletedGUI() em detalhes. Ele chama todos os métodos descritos anteriormente nesta seção. Primeiro, todos os controles da interface gráfica estão ocultos. Nenhum deles foi renderizado ainda. Portanto, para evitar sua aparência gradual e sequencial, eles precisam estar escondidos antes da renderização. Em seguida, a renderização ocorre, e as mudanças mais recentes são aplicadas a cada controle. Depois disso, é necessário exibir apenas os controles da janela principal. Em seguida, os arrays de ponteiros para os controles por categorias são gerados. No final do método, o gráfico é atualizado.
class CWndEvents : public CWndContainer { protected: //--- Finalizando a criação da GUI void CompletedGUI(void); }; //+------------------------------------------------------------------+ //| Finalizando a criação da GUI | //+------------------------------------------------------------------+ void CWndEvents::CompletedGUI(void) { //--- Retorna, se ainda não há janela int windows_total=CWndContainer::WindowsTotal(); if(windows_total<1) return; //--- Exibe o comentário informando o usuário ::Comment("Atualizando. Por favor aguarde..."); //--- Oculta os controles Hide(); //--- Desenha os controles Update(true); //--- Exibe os controles da janela ativada Show(m_active_window_index); //--- Gerar o array de controles com timers FormTimerElementsArray(); //--- Gera o array de controles visíveis e ao mesmo tempo disponíveis FormAvailableElementsArray(); //--- Gera os arrays de controles com redimensionamento automático FormAutoXResizeElementsArray(); FormAutoYResizeElementsArray(); //--- Redesenha o gráfico m_chart.Redraw(); //--- Limpa o comentário ::Comment(""); }
O método CWndEvents::CheckElementsEvents() para verificar e manipular os eventos dos controles foi significativamente alterado. Permitam-nos analisar isso com mais detalhes.
Este método agora possui dois blocos de manipulação de eventos. Um bloco foi projetado exclusivamente para manipular o movimento do cursor do mouse (CHARTEVENT_MOUSE_MOVE). Em vez de percorrer a lista de todos os controles da janela ativa, como era antes, o loop agora itera apenas sobre os controles disponíveis para processamento. Esta é a razão pela qual o array com os ponteiros para os controles disponíveis foi gerado primeiro. A interface gráfica de uma grande aplicação MQL pode ter centenas e até milhares de controles, e apenas alguns da lista podem ser visíveis e disponíveis em um determinado momento. Esta abordagem economiza muito os recursos da CPU.
Outra alteração é que agora é verificado em qual (1) sub-janela do formulário ela está localizada e (2) o foco sobre o controle agora são executados em um loop externo, e não nos manipuladores de cada classe de controles. Assim, as verificações relevantes para cada controle agora estão localizadas no mesmo local. Isso será conveniente no futuro, caso seja necessário fazer alterações no algoritmo de gerenciamento de eventos.
Todos os outros tipos de eventos são tratados em um bloco separado. A versão atual itera sobre toda a lista de controles da interface gráfica. Todas as verificações anteriormente localizadas nas classes de controles também foram movidas para um loop externo.
No final do método, o evento é enviado para a classe personalizada da aplicação MQL.
//+------------------------------------------------------------------+ //| Verificação dos eventos de controle | //+------------------------------------------------------------------+ void CWndEvents::CheckElementsEvents(void) { //--- Manipulação do evento de mover o cursor do mouse if(m_id==CHARTEVENT_MOUSE_MOVE) { //--- Retorna, se o formulário estiver em outra sub-janela do gráfico if(!m_windows[m_active_window_index].CheckSubwindowNumber()) return; //--- Verifica apenas os controles disponíveis int available_elements_total=CWndContainer::AvailableElementsTotal(m_active_window_index); for(int e=0; e<available_elements_total; e++) { CElement *el=m_wnd[m_active_window_index].m_available_elements[e]; //--- Verifica o foco sobre os controles el.CheckMouseFocus(); //--- Manipulação do evento el.OnEvent(m_id,m_lparam,m_dparam,m_sparam); } } //--- Todos os eventos, exceto o movimento do cursor do mouse else { int elements_total=CWndContainer::ElementsTotal(m_active_window_index); for(int e=0; e<elements_total; e++) { //--- Verifica apenas os controles disponíveis CElement *el=m_wnd[m_active_window_index].m_elements[e]; if(!el.IsVisible() || !el.IsAvailable() || el.IsLocked()) continue; //--- Manipulação do evento no manipulador de eventos do controle el.OnEvent(m_id,m_lparam,m_dparam,m_sparam); } } //--- Enviando o evento para o arquivo do aplicativo OnEvent(m_id,m_lparam,m_dparam,m_sparam); }
O método CWndEvents::FormAvailableElementsArray() para gerar o array de controles, que são visíveis e ao mesmo tempo disponíveis para processamento, é chamado nos seguintes casos:
- Abrindo uma caixa de diálogo. Uma vez que uma caixa de diálogo é aberta, o evento ON_OPEN_DIALOG_BOX é gerado, na qual é tratado no método CWndEvents::OnOpenDialogBox(). Depois de processar este evento, é necessário gerar o evento de controles disponíveis para a janela aberta.
- Alterações na interface gráfica. Qualquer alteração na interface gráfica causada pela interação gera o evento ON_CHANGE_GUI. Ele é administrado por um novo método privado CWndEvents::OnChangeGUI(). Aqui, quando o evento ON_CHANGE_GUI chega, o array de controles disponíveis é gerado primeiro. Então todas as dicas de contexto são movidas para a camada superior. No final do método, o gráfico é redesenhado para exibir as últimas mudanças.
class CWndEvents : public CWndContainer { private: //--- Alterações na interface gráfica bool OnChangeGUI(void); }; //+------------------------------------------------------------------+ //| Evento de alterações na interface gráfica | //+------------------------------------------------------------------+ bool CWndEvents::OnChangeGUI(void) { //--- Se o sinal é sobre a alteração na interface gráfica if(m_id!=CHARTEVENT_CUSTOM+ON_CHANGE_GUI) return(false); //--- Gera o array de controles visíveis e ao mesmo tempo disponíveis FormAvailableElementsArray(); //--- Mover as dicas de contexto para a camada superior ResetTooltips(); //--- Redesenha o gráfico m_chart.Redraw(); return(true); }
Em seguida, considere o tratamento de um evento com o identificador ON_SET_AVAILABLE para determinar os controles que estarão disponíveis para processamento.
O método CWndEvents::OnSetAvailable() foi implementado para manipular o evento ON_SET_AVAILABLE. Mas antes de seguir a descrição de seu código, é necessário considerar uma série de métodos auxiliares. Há 10 controles de interface gráfica que geram eventos com esse identificador. Todos eles têm os meios para determinar seu estado ativo. Vamos nomeá-los:
- Menu principal — CMenuBar::State().
- Elemento de menu — CMenuItem::GetContextMenuPointer().IsVisible().
- Botão de divisão — CSplitButton::GetContextMenuPointer().IsVisible().
- Caixa combinada — CComboBox::GetListViewPointer().IsVisible().
- Calendário suspenso — DropCalendar::GetCalendarPointer().IsVisible().
- Bara de rolagem — CScroll::State().
- Tabela — CTable::ColumnResizeControl().
- Slider numérico— CSlider::State().
- Lista hierárquica — CTreeView::GetMousePointer().State().
- Gráfico padrão — CStandartChart::GetMousePointer().IsVisible().
Cada um desses controles possui arrays privados na classe CWndContainer. A classe CWndEvents implementa métodos para determinar qual dos controles que estão ativados atualmente. Todos esses métodos retornam o índice do controle ativado em array privado.
class CWndEvents : public CWndContainer { private: //--- Retorna o índice do menu principal ativado int ActivatedMenuBarIndex(void); //--- Retorna o índice do elemento de menu ativado int ActivatedMenuItemIndex(void); //--- Retorna o índice do botão de divisão ativado int ActivatedSplitButtonIndex(void); //--- Retorna o índice da caixa combinada ativada int ActivatedComboBoxIndex(void); //--- Retorna o índice do calendário suspenso ativado int ActivatedDropCalendarIndex(void); //--- Retorna o índice da barra de rolagem ativada int ActivatedScrollIndex(void); //--- Retorna o índice da tabela ativada int ActivatedTableIndex(void); //--- Retorna o índice do controle deslizante ativado int ActivatedSliderIndex(void); //--- Retorna o índice lista hierárquica ativada int ActivatedTreeViewIndex(void); //--- Retorna o índice do sub-gráfico ativado int ActivatedSubChartIndex(void); };
Como a única diferença na maioria desses métodos reside nas condições para determinar os estados dos controles, somente o código para um deles será considerado. Segue abaixo o código do método CWndEvents::ActivatedTreeViewIndex(), que retorna o índice da lista hierárquica ativada. Se esse tipo de controle tiver ativado o modo de elementos da guia, a verificação é rejeitada.
//+------------------------------------------------------------------+ //| Retorna o índice da lista hierárquica ativada | //+------------------------------------------------------------------+ int CWndEvents::ActivatedTreeViewIndex(void) { int index=WRONG_VALUE; //--- int total=ElementsTotal(m_active_window_index,E_TREE_VIEW); for(int i=0; i<total; i++) { CTreeView *el=m_wnd[m_active_window_index].m_treeview_lists[i]; //--- Vai para a próxima, se o modo de guias estiver ativado if(el.TabItemsMode()) continue; //--- Se no processo de alteração da largura das listas if(el.GetMousePointer().State()) { index=i; break; } } return(index); }
O método CWndEvents::SetAvailable() é projetado para configurar os estados de disponibilidade dos controles. Como argumentos, é necessário passar (1) o índice de formulário exigido e (2) o estado a ser definido para os controles.
Se for necessário tornar todos os controles indisponíveis, basta iterar sobre eles em um loop e definir o valor false.
Se for necessário disponibilizar os controles, então o método de sobrecarga CTreeView::IsAvailable() de mesmo nome é chamado para as listas hierárquicas, que contém dois modos para configurar o estado: (1) apenas para o controle principal e (2) para todos os controles que se encontram na profundidade de aninhamento. Portanto, a conversão do tipo dinâmico é usado aqui para obter o ponteiro para controlar a classe de controle derivada.
class CWndEvents : public CWndContainer { protected: //--- Define os estados de disponibilidade dos controles void SetAvailable(const uint window_index,const bool state); }; //+------------------------------------------------------------------+ //| Define os estados de disponibilidade dos controles | //+------------------------------------------------------------------+ void CWndEvents::SetAvailable(const uint window_index,const bool state) { //--- Obtém o número dos controles principais int main_total=MainElementsTotal(window_index); //--- Se for necessário tornar os controles indisponíveis if(!state) { m_windows[window_index].IsAvailable(state); for(int e=0; e<main_total; e++) { CElement *el=m_wnd[window_index].m_main_elements[e]; el.IsAvailable(state); } } else { m_windows[window_index].IsAvailable(state); for(int e=0; e<main_total; e++) { CElement *el=m_wnd[window_index].m_main_elements[e]; //--- Se é uma lista hierárquica if(dynamic_cast<CTreeView*>(el)!=NULL) { CTreeView *tv=dynamic_cast<CTreeView*>(el); tv.IsAvailable(true); continue; } //--- Se é um navegador de arquivos if(dynamic_cast<CFileNavigator*>(el)!=NULL) { CFileNavigator *fn =dynamic_cast<CFileNavigator*>(el); CTreeView *tv =fn.GetTreeViewPointer(); fn.IsAvailable(state); tv.IsAvailable(state); continue; } //--- Torna o controle disponível el.IsAvailable(state); } } }
Os elementos de menu com menus de contexto anexados requeriam um método, o que permitiria o loop de toda a profundidade dos menus de contexto abertos, acessando-os. Neste caso, será necessário disponibilizar os menus de contexto para processamento. Isso será implementado de forma recursiva.
Abaixo está o código do método CWndEvents::CheckContextMenu(). Primeiro, passe um objeto do tipo elemento de menu e tente obter o ponteiro para o menu de contexto. Se o ponteiro estiver correto, verifique se este menu de contexto foi aberto. Se sim, defina a flag de disponibilidade para ele. Em seguida, defina o status de disponibilidade para todos os itens deste menu em um loop. Ao mesmo tempo, verifique cada elemento se tiver um menu de contexto usando o método CWndEvents::CheckContextMenu().
class CWndEvents : public CWndContainer { private: //--- Verifica e disponibiliza o menu de contexto void CheckContextMenu(CMenuItem &object); }; //+------------------------------------------------------------------+ //| Verifica recursivamente e disponibiliza os menus de contexto | //+------------------------------------------------------------------+ void CWndEvents::CheckContextMenu(CMenuItem &object) { //--- Obtém o ponteiro do menu de contexto CContextMenu *cm=object.GetContextMenuPointer(); //--- Retorna, se não houver nenhum menu de contexto no elemento if(::CheckPointer(cm)==POINTER_INVALID) return; //--- Retorna, se houver um menu de contexto, mas oculto if(!cm.IsVisible()) return; //--- Define os sinais de controle disponíveis cm.IsAvailable(true); //--- int items_total=cm.ItemsTotal(); for(int i=0; i<items_total; i++) { //--- Define os sinais de controle disponíveis CMenuItem *mi=cm.GetItemPointer(i); mi.IsAvailable(true); //--- Verifica se esse elemento possui um menu de contexto CheckContextMenu(mi); } }
Agora, vamos considerar o método CWndEvents::OnSetAvailable(), que lida com o evento para determinar os controles disponíveis.
Se foi recebido um evento personalizado com o identificador ON_SET_AVAILABLE, primeiro é necessário determinar se existem controles atualmente ativados. As variáveis locais armazenam os índices dos controles ativados para acesso rápido aos seus arrays privados.
Se for recebido um sinal para determinar os controles disponíveis, primeiro desative o acesso em toda a lista. Se o sinal for para restaurar, então, depois de verificar a ausência de controles ativados, o acesso é restaurado na lista inteira e o programa deixa o método.
Se o programa atingir o próximo bloco de código neste método, isso significa que ele é (1) um sinal para determinar os controles disponíveis, ou (2) para restaurar, mas há um calendário suspenso ativado. A segunda situação pode ocorrer ao abrir um calendário suspenso com uma caixa combinada ativada, onde a lista suspensa foi fechada.
Se uma das condições descritas for atendida, tente obter o ponteiro para o controle ativado. Se não recebeu o ponteiro, o programa deixa o método.
Se o ponteiro foi obtido, o controle é disponibilizado. Para alguns controles, existem nuances de como isso é feito. Aqui estão todos esses casos:
- Menu principal (CMenuBar). Se for ativado, é necessário disponibilizar todos os menus de contexto abertos relacionados a ele. Para esse fim, o método recursivo CWndEvents::CheckContextMenu() foi considerado na lista de códigos acima.
- Elemento de menu (CMenuItem). Os elementos de menu podem ser controles independentes, aos quais os menus de contexto podem ser anexados. Portanto, se esse controle for ativado, o próprio controle (elemento de menu) também está disponível aqui, bem como todos os menus de contexto abertos a partir dele.
- Barra de rolagem (CScroll). Se uma barra de rolagem estiver ativada (no processo de movimentação), então disponibilize todos os controles, a partir do primeiro. Por exemplo, se a barra de rolagem estiver anexada a uma exibição de lista, a visão da lista e todos os seus controles ficarão disponíveis, para a profundidade total de aninhamento.
- Lista hierárquica (CTreeView). Este controle pode ser ativado quando a largura de suas listas está sendo alterada. É necessário excluir o processamento das visualizações das listas quando o mouse está pairado e tornar a lista hierárquica disponível para processamento.
Abaixo está o código do método CWndEvents::OnSetAvailable().
class CWndEvents : public CWndContainer { private: //--- Determina os controles disponíveis bool OnSetAvailable(void); }; //+------------------------------------------------------------------+ //| Evento para determinar os controles disponíveis | //+------------------------------------------------------------------+ bool CWndEvents::OnSetAvailable(void) { //--- Se o sinal é sobre mudar a disponibilidade dos controles if(m_id!=CHARTEVENT_CUSTOM+ON_SET_AVAILABLE) return(false); //--- Sinal para definir/restaurar bool is_restore=(bool)m_dparam; //--- Determina os controles ativos int mb_index =ActivatedMenuBarIndex(); int mi_index =ActivatedMenuItemIndex(); int sb_index =ActivatedSplitButtonIndex(); int cb_index =ActivatedComboBoxIndex(); int dc_index =ActivatedDropCalendarIndex(); int sc_index =ActivatedScrollIndex(); int tl_index =ActivatedTableIndex(); int sd_index =ActivatedSliderIndex(); int tv_index =ActivatedTreeViewIndex(); int ch_index =ActivatedSubChartIndex(); //--- Se o sinal for determinar os controles disponíveis, desative o acesso primeiro if(!is_restore) SetAvailable(m_active_window_index,false); //--- Restaure somente se não houver itens ativados else { if(mb_index==WRONG_VALUE && mi_index==WRONG_VALUE && sb_index==WRONG_VALUE && dc_index==WRONG_VALUE && cb_index==WRONG_VALUE && sc_index==WRONG_VALUE && tl_index==WRONG_VALUE && sd_index==WRONG_VALUE && tv_index==WRONG_VALUE && ch_index==WRONG_VALUE) { SetAvailable(m_active_window_index,true); return(true); } } //--- Se (1) o sinal é para desativar o acesso ou (2) para restaurar o calendário suspenso if(!is_restore || (is_restore && dc_index!=WRONG_VALUE)) { CElement *el=NULL; //--- Menu principal if(mb_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_menu_bars[mb_index]; } //--- Elemento de menu else if(mi_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_menu_items[mi_index]; } //--- Botão de divisão else if(sb_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_split_buttons[sb_index]; } //--- Calendário suspenso sem uma lista suspensa else if(dc_index!=WRONG_VALUE && cb_index==WRONG_VALUE) { el=m_wnd[m_active_window_index].m_drop_calendars[dc_index]; } //--- Lista suspensa else if(cb_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_combo_boxes[cb_index]; } //--- Barra de rolagem else if(sc_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_scrolls[sc_index]; } //--- Tabela else if(tl_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_tables[tl_index]; } //--- Slider else if(sd_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_sliders[sd_index]; } //--- Lista hierárquica else if(tv_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_treeview_lists[tv_index]; } //--- Sub-gráfico else if(ch_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_sub_charts[ch_index]; } //--- Retorna, se o ponteiro de controle não for recebido if(::CheckPointer(el)==POINTER_INVALID) return(true); //--- Bloqueia o menu principal if(mb_index!=WRONG_VALUE) { //--- Torna o menu principal e os menus de contexto visíveis disponíveis el.IsAvailable(true); //--- CMenuBar *mb=dynamic_cast<CMenuBar*>(el); int items_total=mb.ItemsTotal(); for(int i=0; i<items_total; i++) { CMenuItem *mi=mb.GetItemPointer(i); mi.IsAvailable(true); //--- Verifica e disponibiliza o menu de contexto CheckContextMenu(mi); } } //--- Bloqueia o elemento de menu if(mi_index!=WRONG_VALUE) { CMenuItem *mi=dynamic_cast<CMenuItem*>(el); mi.IsAvailable(true); //--- Verifica e disponibiliza o menu de contexto CheckContextMenu(mi); } //--- Bloqueia para a barra de rolagem else if(sc_index!=WRONG_VALUE) { //--- Disponibiliza a partir do nó principal el.MainPointer().IsAvailable(true); } //--- Bloqueia para a lista hierárquica else if(tv_index!=WRONG_VALUE) { //--- Bloqueia todos os controles, exceto o controle principal CTreeView *tv=dynamic_cast<CTreeView*>(el); tv.IsAvailable(true,true); int total=tv.ElementsTotal(); for(int i=0; i<total; i++) tv.Element(i).IsAvailable(false); } else { //--- Torna o controle disponível el.IsAvailable(true); } } //--- return(true); }
Aplicação para testar os controles
Um aplicação MQL foi implementada para fins de teste. Sua interface gráfica contém todos os controles da biblioteca. Esta é sua aparência:
Fig. 12. Interface gráfica da aplicação MQL de teste.
Você pode baixá-lo no final do artigo para estudar de forma mais detalhada.
Conclusão
Esta versão da biblioteca tem diferenças significativas com a apresentada no artigo Interfaces gráficas X: Seleção de texto na caixa de texto multilinha (build 13). Muito trabalho foi feito, o que afetou quase todos os arquivos da biblioteca. Agora todos os controles da biblioteca são desenhados em objetos separados. A legibilidade do código melhorou, o volume do código diminuiu aproximadamente 30% e seus recursos foram expandidos. Foram corrigidos vários outros erros e falhas relatadas pelos usuários.
Se você já começou a criar suas aplicações MQL usando a versão anterior da biblioteca, é recomendável que você primeiro faça o download da nova versão para uma cópia da instalação da plataforma MetaTrader 5 separada para estudar e testar completamente a biblioteca.
A biblioteca para a criação de interfaces gráficas no atual estágio de desenvolvimento se parece com o esquema abaixo. Esta não é a versão final. A biblioteca irá melhorar e evoluir no futuro.
Fig. 13. Estrutura da biblioteca, no atual estágio de desenvolvimento
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.