Interfaces Gráficas XI: Controles renderizados (build 14.2)

Anatoli Kazharski | 23 agosto, 2017


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 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. 

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.

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.

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).

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.

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

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.