English Русский 中文 Español Deutsch 日本語
Interfaces Gráficas II: Os Elementos Linha de Separação e o Menu de Contexto (Capítulo 2)

Interfaces Gráficas II: Os Elementos Linha de Separação e o Menu de Contexto (Capítulo 2)

MetaTrader 5Exemplos | 10 agosto 2016, 10:49
974 0
Anatoli Kazharski
Anatoli Kazharski

Conteúdo  

 

Introdução

O primeiro artigo Interfaces gráficas I: Preparação da Estrutura da Biblioteca (Capítulo 1) considera em detalhes a finalidade desta biblioteca. A lista completa dos links para os artigos se encontra no final de cada capítulo da série. Lá, você também pode encontrar e baixar a versão completa da biblioteca, no estágio de desenvolvimento atual. Os arquivos devem estar localizados nas mesmas pastas que o arquivo baixado.  

No capítulo anterior, nós escrevemos uma classe para a criação de um elemento de menu. Isto é usado tanto como um controle independente e como parte de um menu principal e de contexto. Neste artigo, nós vamos descrever a criação do elemento linha de separação, que também pode ser usado não apenas como um elemento de interface independente, mas como parte de muitos outros elementos também. Depois disso, nós teremos todos os recursos necessários para o desenvolvimento da classe do menu de contexto, que também serão considerados neste artigo em detalhe. Acrescentando, nós vamos apresentar todos os incrementos necessários à classe, que é a base para armazenar os ponteiros para todos os elementos da interface gráfica da aplicação.



Desenvolvimento da Classe para Criar uma Linha de Separação

Em um menu de contexto, além de diferentes tipos de elementos de menu, muitas vezes nós podemos visualizar mais de um elemento de interface - a linha de separação. Esse elemento pode ser encontrado não só nos menus de contexto. Por exemplo, a barra de status do terminal de negociação MetaTrader e o editor de código MetaEditor possuem linhas de separação vertical. É por isso que nós vamos criar uma classe separada para este objeto de modo que ele pode ser utilizado em qualquer outro controle ou como um elemento separado da interface gráfica.

Para dar uma ilusão de volume, a linha de separação deve consistir de pelo menos duas partes. Se uma linha for mais clara do que o fundo e a outra mais escura, isto irá criar um efeito visual de uma canaleta na superfície. Existem duas maneiras de criar uma linha de separação: (1) a partir de dois objetos primitivos do tipo CRectLabel, que já estão presentes no arquivo Objects.mqh ou (2) criar um objeto do tipo OBJ_BITMAP_LABEL e usá-lo como uma tela preparada para desenho. Vamos usar a segunda opção. A biblioteca padrão sugere a classe CCanvas para o desenho. Esta classe já contém todos os métodos necessários para desenhar figuras geométricas simples, simplificando significativamente a implementação do projeto pretendido e poupando bastante o nosso tempo. 

A classe CCanvas deve ser incorporada de um modo que ela possa ser usada de uma maneira similar a esses objetos primitivos, que já estão presentes no arquivo Objects.mqh. Podemos conseguir isso facilmente, fazendo com que a classe CCanvas derive da classe CChartObjectBmpLabel. Uma pequena mudança tem de ser introduzida no código da classe CCanvas para que não haja posteriores erros ou avisos quando o programa for compilado. Isto é devido ambas as classes CCanvas e CChartObject, que é a classe base para a CChartObjectBmpLabel, possuirem o campo m_chart_id (variável). Como resultado, o compilador dará um aviso de que uma variável com esse nome já existe:

Fig. 1. Aviso do compilador dizendo que uma variável com esse nome já existe.

Fig. 1. Aviso do compilador

 

Na prática, tal advertência não irá causar quaisquer erros críticos e a compilação ocorrerá mesmo assim. Recomenda-se, no entanto, evitar tais situações, porque seu potencial impacto sobre o funcionamento do programa é desconhecido. Vamos assumir isso como uma regra e segui-la. Além disso, as alterações no arquivo Canvas.mqh terá de ser implementada de qualquer maneira, porque a classe CCanvastem que ser derivada a partir da CChartObjectBmpLabel. Desta forma, nós podemos facilmente se livrar desse aviso persistente. Nós iremos simplesmente remover a variável m_chart_id da classe CCanvas. Ao implementar alterações nas classes da biblioteca padrão, deve-se notar que na próxima atualização do terminal os arquivos de biblioteca padrão também poderão ser atualizados, fazendo com que as mudanças se anulem. Portanto, como não podemos alcançar nosso objetivo sem fazer alterações na classe CCanvas, crie a sua cópia e coloque-a na pasta onde os arquivos de nossa biblioteca estarão localizados.

Crie a pasta da tela (Canvas) na pasta #Include. Crie uma cópia do arquivo que contém a classe СCanvas e renomeie ela para CustomCanvas.mqh e o nomeie a classe para CCustomCanvas. Faça a inclusão do arquivo ChartObjectsBmpControls.mqh da biblioteca padrão no arquivo CustomCanvas.mqh e faça uma classe CCustomCanvas derivada da classe CChartObjectBmpLabel. Em seguida, remova a variável m_chart_id do corpo da classe CCustomCanvas e do construtor.

//+------------------------------------------------------------------+
//|                                                 CustomCanvas.mqh |
//|                   Copyright 2009-2013, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include <Files\FileBin.mqh>
#include <Controls\Rect.mqh>
#include <ChartObjects\ChartObjectsBmpControls.mqh>

//...

//+------------------------------------------------------------------+
//| Classe CCustomCanvas                                              |
//| Uso: classe para trabalhar com um recurso dinâmico               |
//+------------------------------------------------------------------+
class CCustomCanvas : public CChartObjectBmpLabel
  {
//...

Agora, faça a inclusão do arquivo CustomCanvas.mqh em Objects.mqh:

//+------------------------------------------------------------------+
//|                                                      Objects.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Enums.mqh"
#include "Defines.mqh"
#include "..\Canvas\CustomCanvas.mqh"
#include <ChartObjects\ChartObjectsBmpControls.mqh>
#include <ChartObjects\ChartObjectsTxtControls.mqh>

Em seguida, vamos criar a classe CRectCanvas, que tem de ser derivada da classe CCustomCanvas. A classe CRectCanvas será semelhante a todas as outras classes, que estão no arquivo Objects.mqh. O conteúdo delas foi examinado no artigo anterior. Agora, ela pode ser utilizada para desenhar quaisquer elementos de interface, que será uma parte da biblioteca em desenvolvimento. 

Nós já temos tudo pronto para o desenvolvimento da classe CSeparateLine, destinando-se a criação de uma linha de separação. Crie o arquivo SeparateLine.mqh na pasta Controls. Faça a inclusão dos arquivos Element.mqh e Window.mqh nela. Em seguida, siga a seguinte sequência de passos:

1) crie a classe CSeparateLine;

2) nesta classe, declare um ponteiro para a formulário que o elemento será ligado e crie um método para armazenar um ponteiro nele;

3) crie uma instância da classe CRectCanvas;

4) declare e implemente os métodos visuais padrão para todos os elementos que podem ser usados ​​para gerenciar o elemento.

//+------------------------------------------------------------------+
//|                                                 SeparateLine.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
//+------------------------------------------------------------------+
//| Classe para a criar a linha de separação                         |
//+------------------------------------------------------------------+
class CSeparateLine : public CElement
  {
private:
   //--- Ponteiro para o formulário ao qual o elemento está anexado
   CWindow          *m_wnd;
   //--- Objeto para a criação de uma linha de separação
   CRectCanvas       m_canvas;
   //---
public:
                     CSeparateLine(void);
                    ~CSeparateLine(void);
   //--- Armazena o ponteiro para o formulário passado
   void              WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); }
   //---
public:
   //--- Manipulador de eventos do gráfico
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Move o elemento
   virtual void      Moving(const int x,const int y);
   //--- (1) Exibe, (2) oculta, (3) reseta, (4) remove
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CSeparateLine::CSeparateLine(void)
  {
//--- Armazena o nome da classe do elemento na classe base  
   CElement::ClassName(CLASS_NAME);
  }
//+------------------------------------------------------------------+
//| Destrutor                                                        |
//+------------------------------------------------------------------+
CSeparateLine::~CSeparateLine(void)
  {
  }
//+------------------------------------------------------------------+

Para configurar a aparência da linha de separação, nós vamos criar três métodos. Estes métodos serão utilizados para definir:

  • O tipo da linha de separação: (1) horizontal, (2) vertical.
  • A cor da parte escura.
  • A cor da parte clara.

A enumeração, que será utilizada para especificar o tipo, deve ser adicionada ao arquivo Enums.mqh:

//+------------------------------------------------------------------+
//| Enumeração dos tipos de linha de separação                       |
//+------------------------------------------------------------------+
enum ENUM_TYPE_SEP_LINE
  {
   H_SEP_LINE =0,
   V_SEP_LINE =1
  };

Agora, nós podemos adicionar as variáveis ​​correspondentes e os métodos para a classe CSeparateLine e realizar a inicialização dos valores padrões no construtor:

class CSeparateLine : public CElement
  {
private:
   //--- Propriedades
   ENUM_TYPE_SEP_LINE m_type_sep_line;   
   color             m_dark_color;
   color             m_light_color;
   //---
public:
   //--- (1) tipo da linha, (2) cores da linha
   void              TypeSepLine(const ENUM_TYPE_SEP_LINE type) { m_type_sep_line=type; }
   void              DarkColor(const color clr)                 { m_dark_color=clr;     }
   void              LightColor(const color clr)                { m_light_color=clr;    }
   //---
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CSeparateLine::CSeparateLine(void) : m_type_sep_line(H_SEP_LINE),
                                     m_dark_color(clrBlack),
                                     m_light_color(clrDimGray)
  {
  }

Nós só temos que adicionar os métodos para criar o elemento e o método, em que a linha de separação será desenhada na tela. Os seguintes parâmetros devem ser passados ​​para o método public para criar o elemento CreateSeparateLine(), que será chamado no aplicativo personalizado:

  • identificador do gráfico;
  • número da janela do gráfico;
  • número do índice da linha. Isso é necessário para os casos em que várias linhas de separação devem ser criadas em um loop, em um menu de contexto ou outros elementos da interface. Neste caso, o único elemento para identificação não é suficiente para a criação de um nome único do objecto gráfico;
  • coordenadas;
  • tamanho.
class CSeparateLine : public CElement
  {
public:
   //--- Criação de uma linha de separação
   bool              CreateSeparateLine(const long chart_id,const int subwin,const int index,
                                        const int x,const int y,const int x_size,const int y_size);
   //---
private:
   //--- Cria a tela canvas para desenhar uma linha de separação
   bool              CreateSepLine(void);
   //--- Desenhando uma linha de separação
   void              DrawSeparateLine(void);
   //---
  };

O código do método CreateSeparateLine(), a princípio, não difere de outros métodos semelhantes, (por exemplo, na classe CMenuItem), é por isso que nós vamos analisar em seguida o código do método CreateSepLine(). 

Semelhante a todos os outros métodos deste tipo, o nome do objeto gráfico é definido no início. Em seguida, o objeto gráfico (canvas) é criado onde nós vamos desenhar. Deve-se notar que, para a criação do objeto do tipo OBJ_BITMAP_LABEL o método CreateBitmapLabel() é utilizado, pertencendo a classe CCustomCanvas. Nesta classe, quando os objetos são criados, a ligação do objeto ao gráfico não é fornecida como no classe CChartObjectBmpLabel, onde é utilizado o método CChartObject::Attach() da classe base logo após a criação do objeto. Nós mesmos que devemos gerir isto. À medida que a classe CCustomCanvas foi derivada a partir da classe CChartObjectBmpLabel, nós podemos acessar o método CChartObject::Attach() de sua classe base. Se o objeto não está ligado ao gráfico, será impossível de geri-lo.

Após o objeto ter sido criado, anexado ao gráfico e as propriedades foram criadas, uma linha de separação pode ser desenhada em nossa tela personalizada usando o método DrawSeparateLine(). Isto é mostrado no código abaixo. Em seguida, o ponteiro do objeto é armazenado no array da classe base CElement.

//+------------------------------------------------------------------+
//| Cria a tela canvas para desenhar uma linha de separação          |
//+------------------------------------------------------------------+
bool CSeparateLine::CreateSepLine(void)
  {
//--- Elaborando o nome do objeto  
   string name=CElement::ProgramName()+"_separate_line_"+(string)CElement::Index()+"__"+(string)CElement::Id();
//--- Cria um objeto
   if(!m_canvas.CreateBitmapLabel(m_chart_id,m_subwin,name,m_x,m_y,m_x_size,m_y_size,COLOR_FORMAT_ARGB_NORMALIZE))
      return(false);
//--- Anexa ao gráfico
   if(!m_canvas.Attach(m_chart_id,name,m_subwin,1))
      return(false);
//--- Propriedades
   m_canvas.Background(false);
//--- Margens do ponto extremo
   m_canvas.XGap(m_x-m_wnd.X());
   m_canvas.YGap(m_y-m_wnd.Y());
//--- Desenha uma linha de separação
   DrawSeparateLine();
//--- Adiciona ao array
   CElement::AddToArray(m_canvas);
   return(true);
  }

O código do método DrawSeparateLine() é simples. Primeiramente, obtenha o tamanho da tela. Em seguida, limpe a tela usando o método CCustomCanvas::Erase(). Em seguida, o programa irá mover para o bloco de código correspondente a linha que deverá ser feita, ou seja, uma linha horizontal ou vertical. Como exemplo, nós vamos descrever a construção de uma linha horizontal. Em primeiro lugar, as coordenadas de dois pontos da linha são definidas e, em seguida, a primeira linha é traçada na parte superior da tela. As coordenadas para a segunda linha são definidas na parte inferior da tela. Se a altura da tela é ajustada para ser de dois pixels, então, as linhas serão localizadas muito próximas umas das outras. Você pode organizar uma margem entre a parte superior e as linhas inferiores, definindo a altura da tela sendo maior que dois pixels. Para exibir as alterações, a tela deve ser atualizada no final do método usando o método CCustomCanvas::Update().

//+------------------------------------------------------------------+
//|  Desenha uma linha de separação                                  |
//+------------------------------------------------------------------+
void CSeparateLine::DrawSeparateLine(void)
  {
//--- Coordenadas para as linhas
   int x1=0,x2=0,y1=0,y2=0;
//--- Tamanho da tela
   int   x_size =m_canvas.X_Size()-1;
   int   y_size =m_canvas.Y_Size()-1;
//--- Limpa a tela
   m_canvas.Erase(::ColorToARGB(clrNONE,0));
//--- Se a linha é horizontal
   if(m_type_sep_line==H_SEP_LINE)
     {
      //--- A linha escura acima
      x1=0;
      y1=0;
      x2=x_size;
      y2=0;
      //---
      m_canvas.Line(x1,y1,x2,y2,::ColorToARGB(m_dark_color));
      //--- A linha clara abaixo
      x1=0;
      x2=x_size;
      y1=y_size;
      y2=y_size;
      //---
      m_canvas.Line(x1,y1,x2,y2,::ColorToARGB(m_light_color));
     }
//--- Se a linha é vertical
   else
     {
      //--- A linha escura na esquerda
      x1=0;
      x2=0;
      y1=0;
      y2=y_size;
      //---
      m_canvas.Line(x1,y1,x2,y2,::ColorToARGB(m_dark_color));
      //--- A linha clara à direita
      x1=x_size;
      y1=0;
      x2=x_size;
      y2=y_size;
      //---
      m_canvas.Line(x1,y1,x2,y2,::ColorToARGB(m_light_color));
     }
//--- Atualização da tela
   m_canvas.Update();
  }

 



Teste da Colocação da Linha de Separação

Agora, nós podemos testar como isso funciona. No artigo anterior, eu anexei um elemento de menu ao formulário. Seguindo o mesmo princípio, uma linha de separação pode ser anexada como um elemento de interface separado. 

Vou lembrá-lo brevemente sobre o processo de anexar um elemento ao formulário.

  • Se o elemento da classe não está na base ainda, o seu arquivo deve ser incluído no arquivo WndContainer.mqh.
  • Já que a classe personalizada da aplicação (em nosso caso, esta é a CProgram) deve ser derivada a partir da CWndContainer -> CWndEvents, então, a criação da instância do elemento da classe e o seu método se tornará disponível após a inclusão do arquivo do elemento.
  • Em seguida, chame o método de criação do elemento no método em que a interface gráfica do programa foi criada.

Se tudo for feito corretamente, então, após a compilação do programa e do seu carregamento ao gráfico, o resultado deve ser o seguinte:

Fig. 2. Teste do elemento linha de separação.

Fig. 2. Teste do elemento linha de separação.


O desenvolvimento da classe CSeparateLine está concluído, e nós temos tudo pronto para começar a implementar a classe para criar um menu de contexto.

 


Desenvolvimento da Classe para Criar um Menu de Contexto

Anteriormente, no processo de desenvolvimento da biblioteca, três elementos de interface foram criados: (1) um formulário para os controles (CWindow), (2) o controle do elemento de menu (CMenuItem) e (3) o elemento linha de separação (CSeparateLine). Cada um deles pertence a um elemento do tipo simples, uma vez que eles são compostos apenas de objetos primitivos. Um menu de contexto é classificado como um tipo complexo de controle (composto). Ele será composto não só de objetos primitivos, mas também de outros elementos. A classe base para esses elementos é a CElement

Crie o arquivo ContextMenu.mqh na pasta Controls na pasta de nossa biblioteca. Neste arquivo faça a inclusão dos arquivos que serão utilizados para a criação do menu de contexto:

//+------------------------------------------------------------------+
//|                                                  ContextMenu.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
#include "MenuItem.mqh"
#include "SeparateLine.mqh"

Em seguida, crie a classe CContextMenu com o conjunto padrão de métodos virtuais para todos os elementos da biblioteca, o ponteiro do formulário e o método para armazená-lo. Nós precisamos de um objeto do tipo OBJ_RECTANGLE_LABEL como fundo do elemento. É por isso que para criar esse objeto, nós vamos usar a classe CRectLabel do arquivo Object.mqh. A classe CMenuItem para os elementos de menu foi criada no artigo anterior. Como um menu de contexto normalmente contém mais do que uma classe CMenuItem e o número destas classes não é conhecido inicialmente, então, um array dinâmico das instâncias desta classe tem de ser declarado. O mesmo se aplica para as linhas de separação (CSeparateLine) de um menu de contexto.

//+------------------------------------------------------------------+
//| Classe para criar um menu de contexto                            |
//+------------------------------------------------------------------+
class CContextMenu : public CElement
  {
private:
   //--- Ponteiro para o formulário ao qual o elemento está anexado
   CWindow          *m_wnd;
   //--- Objetos para a criação de um elemento de menu
   CRectLabel        m_area;
   CMenuItem         m_items[];
   CSeparateLine     m_sep_line[];
   //---
public:
                     CContextMenu(void);
                    ~CContextMenu(void);
   //--- Armazena o ponteiro para o formulário passado
   void              WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); }

   //--- Manipulador de eventos do gráfico
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Timer
   virtual void      OnEventTimer(void);
   //--- Move o elemento
   virtual void      Moving(const int x,const int y);
   //--- (1) Exibe, (2) oculta, (3) reseta, (4) remove
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- (1) Definir (2), resetar as prioridades para o clique esquerdo do mouse
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CContextMenu::CContextMenu(void)
  {
//--- Armazena o nome da classe do elemento na classe base
   CElement::ClassName(CLASS_NAME);
//--- Menu de contexto é um elemento suspenso
   CElement::m_is_dropdown=true;
  }
//+------------------------------------------------------------------+
//| Destrutor                                                        |
//+------------------------------------------------------------------+
CContextMenu::~CContextMenu(void)
  {
  }
//+------------------------------------------------------------------+

Para configurar a aparência do menu de contexto, os campos correspondentes e os métodos são necessários:

class CContextMenu : public CElement
  {
private:
   //--- Propriedades do fundo
   int               m_area_zorder;
   color             m_area_color;
   color             m_area_border_color;
   color             m_area_color_hover;
   color             m_area_color_array[];
   //--- Propriedades dos elementos de menu
   int               m_item_y_size;
   color             m_item_back_color;
   color             m_item_border_color;
   color             m_item_back_color_hover;
   color             m_item_back_color_hover_off;
   color             m_label_color;
   color             m_label_color_hover;
   string            m_right_arrow_file_on;
   string            m_right_arrow_file_off;
   //--- Propriedades da linha de separação
   color             m_sepline_dark_color;
   color             m_sepline_light_color;
   //---
public:
   //--- Número dos elementos de menu
   int               ItemsTotal(void)                         const { return(::ArraySize(m_items));         }
   //--- Métodos para definir a aparência do menu de contexto:
   //    A cor de fundo do menu de contexto
   void              MenuBackColor(const color clr)                 { m_area_color=clr;                     }
   void              MenuBorderColor(const color clr)               { m_area_border_color=clr;              }
   //--- (1) Altura, (2) cor de fundo e (3) cor do quadro do elemento de menu 
   void              ItemYSize(const int y_size)                    { m_item_y_size=y_size;                 }
   void              ItemBackColor(const color clr)                 { m_item_back_color=clr;                }
   void              ItemBorderColor(const color clr)               { m_item_border_color=clr;              }
   //--- Cor de fundo do elemento de menu (1) disponível e (2) bloqueado ao passar o cursor do mouse sobre ele
   void              ItemBackColorHover(const color clr)            { m_item_back_color_hover=clr;          }
   void              ItemBackColorHoverOff(const color clr)         { m_item_back_color_hover_off=clr;      }
   //--- (1) Padrão e (2) cor do texto em foco 
   void              LabelColor(const color clr)                    { m_label_color=clr;                    }
   void              LabelColorHover(const color clr)               { m_label_color_hover=clr;              }
   //--- Definindo um ícone para indicar a presença de um menu de contexto no elemento
   void              RightArrowFileOn(const string file_path)       { m_right_arrow_file_on=file_path;      }
   void              RightArrowFileOff(const string file_path)      { m_right_arrow_file_off=file_path;     }
   //--- Linha de separação na cor (1) escura e (2) clara
   void              SeparateLineDarkColor(const color clr)         { m_sepline_dark_color=clr;             }
   void              SeparateLineLightColor(const color clr)        { m_sepline_light_color=clr;            }
   //---
  };

O elemento de menu e seu menu de contexto devem estar conectados, caso contrário, será impossível de gerir esses elementos corretamente. Para ser preciso, o menu de contexto e seus elementos devem ter acesso ao elemento de menu ao qual ele está ligado, ou seja, o nó anterior. Isto significa que tanto a classe CContextMenu e a classe CMenuItem devem ter um ponteiro do tipo CMenuItem e os métodos para armazenar e obter este ponteiro. Este ponteiro também será utilizado para verificar a sequência de criação da interface gráfica do programa está correta. Isso será mostrado mais tarde, quando os métodos para criar um menu de contexto forem estudados. 

Adicionando o ponteiro e os métodos para armazenar e colocá-lo na classe CContextMenu:

class CContextMenu : public CElement
  {
private:
   //--- Ponteiro para o nó anterior
   CMenuItem        *m_prev_node;
   //---
public:
   //--- Obter e armazenar o ponteiro para o nó anterior
   CMenuItem        *PrevNodePointer(void)                    const { return(m_prev_node);                  }
   void              PrevNodePointer(CMenuItem &object)             { m_prev_node=::GetPointer(object);     }
   //---
  };


O mesma deve ser adicionada à classe CMenuItem:

class CMenuItem : public CElement
  {
private:
   //--- Ponteiro para o nó anterior
   CMenuItem        *m_prev_node;
   //---
public:
   //--- Obter e armazenar o ponteiro para o nó anterior
   CMenuItem        *PrevNodePointer(void)                    const { return(m_prev_node);                  }
   void              PrevNodePointer(CMenuItem &object)             { m_prev_node=::GetPointer(object);     }
   //---
  };

A interface gráfica será construída na classe personalizada da aplicação (CProgram). Ao criar um menu de contexto, nós vamos exigir um método para especificar o número previsto de elementos em um menu de contexto e os valores únicos de alguns parâmetros, que não são comuns para todos os itens. Vamos escrever o método CContextMenu::AddItem(), que irá aceitar como parâmetro: (1) o texto do elemento de menu, (2) o caminho do ícone para o rótulo do elemento disponível, (3) o caminho do ícone para o elemento bloqueado e (4) o tipo do elemento de menu. Os arrays para armazenar os valores passados também serão necessários. O tamanho destes arrays será aumentado por um elemento de cada vez quando o método CContextMenu::AddItem() for chamado.

class CContextMenu : public CElement
  {
private:
   //--- Arrays of the menu item properties:
   //    (1) Texto, (2) rótulo do elemento disponível, (3) rótulo do elemento bloqueado
   string            m_label_text[];
   string            m_path_bmp_on[];
   string            m_path_bmp_off[];
   //---
public:
   //--- Adiciona um elemento de menu com as propriedades especificadas antes da criação de um menu de contexto
   void              AddItem(const string text,const string path_bmp_on,const string path_bmp_off,const ENUM_TYPE_MENU_ITEM type);
   //---
  };
//+------------------------------------------------------------------+
//| Adiciona um elemento de menu                                     |
//+------------------------------------------------------------------+
void CContextMenu::AddItem(const string text,const string path_bmp_on,const string path_bmp_off,const ENUM_TYPE_MENU_ITEM type)
  {
//--- Incrementa o tamanho dos arrays por um elemento
   int array_size=::ArraySize(m_items);
   ::ArrayResize(m_items,array_size+1);
   ::ArrayResize(m_label_text,array_size+1);
   ::ArrayResize(m_path_bmp_on,array_size+1);
   ::ArrayResize(m_path_bmp_off,array_size+1);
//--- Armazena os valores dos parâmetros passados
   m_label_text[array_size]   =text;
   m_path_bmp_on[array_size]  =path_bmp_on;
   m_path_bmp_off[array_size] =path_bmp_off;
//--- Define o tipo do elemento de menu
   m_items[array_size].TypeMenuItem(type);
  }


Para adicionar uma linha de separação em um menu de contexto, nós vamos precisar de um array para armazenar o número do índice do elemento de menu após esta linha ser definida. O número do índice do elemento de menu será passado para o método CContextMenu::AddSeparateLine(). O código é mostrado abaixo.

class CContextMenu : public CElement
  {
private:
   //--- Array dos números do índice dos elementos de menu após a linha de separação ser definida
   int               m_sep_line_index[];
   //---
public:
   //--- Adiciona uma linha de separação após o elemento especificado antes da criação do menu de contexto
   void              AddSeparateLine(const int item_index);
   //---
  };
//+------------------------------------------------------------------+
//| Adiciona uma linha de separação                                  |
//+------------------------------------------------------------------+
void CContextMenu::AddSeparateLine(const int item_index)
  {
//--- Incrementa o tamanho dos arrays por um elemento
   int array_size=::ArraySize(m_sep_line);
   ::ArrayResize(m_sep_line,array_size+1);
   ::ArrayResize(m_sep_line_index,array_size+1);
//--- Armazena o número do índice
   m_sep_line_index[array_size]=item_index;
  }


Nós vamos precisar de métodos que nos permitam obter o seguinte, tendo especificado o índice do elemento de menu: (1) o ponteiro para o elemento de menu, (2) descrição (texto exibido) e (3) o tipo. Em todos os métodos, antes de retornar o valor da propriedade, primeiramente, ele verifica se o tamanho do array não foi excedido e então, o índice é ajustado. Ele é implementado de modo que, se o índice passado for maior do que o tamanho do array, então, o último elemento será chamado e se o índice for menor do que zero, então, o primeiro elemento será chamado. 

class CContextMenu : public CElement
  {
public:
   //--- Retorna o ponteiro do elemento menu de contexto
   CMenuItem        *ItemPointerByIndex(const int index);
   //--- Retorna a descrição (texto exibido)
   string            DescriptionByIndex(const int index);
   //--- Retorna o tipo do elemento de menu
   ENUM_TYPE_MENU_ITEM TypeMenuItemByIndex(const int index);
   //---
  };
//+------------------------------------------------------------------+
//| Retorna o ponteiro do elemento de menu pelo índice               |
//+------------------------------------------------------------------+
CMenuItem *CContextMenu::ItemPointerByIndex(const int index)
  {
   int array_size=::ArraySize(m_items);
//--- Se não houver um elemento no menu de contexto, registra
   if(array_size<1)
     {
      ::Print(__FUNCTION__," > Este método era para ser chamado, "
              "caso o menu de contexto tenha no mínimo um elemento!");
     }
//--- Ajuste no caso do tamanho exceder
   int i=(index>=array_size)? array_size-1 :(index<0)? 0 : index;
//--- Retorna o ponteiro
   return(::GetPointer(m_items[i]));
  }
//+------------------------------------------------------------------+
//| Retorna o nome do elemento pelo índice                           |
//+------------------------------------------------------------------+
string CContextMenu::DescriptionByIndex(const int index)
  {
   int array_size=::ArraySize(m_items);
//--- Se não houver um elemento no menu de contexto, registra
   if(array_size<1)
     {
      ::Print(__FUNCTION__," > Este método era para ser chamado, "
              "caso o menu de contexto tenha no mínimo um elemento!");
     }
//--- Ajuste no caso do tamanho exceder
   int i=(index>=array_size)? array_size-1 :(index<0)? 0 : index;
//--- Retorna a descrição do elemento
   return(m_items[i].LabelText());
  }
//+------------------------------------------------------------------+
//| Retorna o tipo de elemento pelo índice                           |
//+------------------------------------------------------------------+
ENUM_TYPE_MENU_ITEM CContextMenu::TypeMenuItemByIndex(const int index)
  {
   int array_size=::ArraySize(m_items);
//--- Se não houver um elemento no menu de contexto, registra
   if(array_size<1)
     {
      ::Print(__FUNCTION__," > Este método era para ser chamado, "
              "caso o menu de contexto tenha no mínimo um elemento!");
     }
//--- Ajuste no caso do tamanho exceder
   int i=(index>=array_size)? array_size-1 :(index<0)? 0 : index;
//--- Retorna o tipo de elemento
   return(m_items[i].TypeMenuItem());
  }

Um menu de contexto pode ter vários grupos de elementos de botão de radio. Para evitar confusão ao identificar qual elemento foi clicado, cada botão de radio deve ter seu próprio identificador de grupo e o índice em relação à lista deste grupo. O esquema abaixo mostra que além dos índices gerais e o identificador do elemento do menu de contexto, o botão de rádio possui suas próprias características distintivas. 

Fig. 3. Esquemática dos identificadores e índices de diferentes grupos em um menu de contexto.

Fig. 3. Esquemática dos identificadores e índices de diferentes grupos em um menu de contexto.

 

Ao formar um menu de contexto, o tipo do elemento de menu tem de ser identificado antes que o menu seja anexado ao gráfico. No caso deste ser um botão de radio, é necessário especificar o grupo que ele pertence. Em outras palavras, precisamos de um método que nos permitirá determinar o identificador de cada elemento do botão de radio. Os identificadores padrões dos elementos do botão de radio serão iguais a zero. Se nós deixarmos desse modo, um menu de contexto terá apenas um grupo de elementos de botão de radio não importando quantos nós adicionarmos. Haverá situações em que será necessário estabelecer o identificador do elemento do botão radio e qual deles está em destaque. Também deve haver a possibilidade de alternar os elementos de radio. 

Além disso, os métodos para se trabalhar com as caixas de seleção serão exigidos. Eles vão permitir descobrir o estado de uma caixa de seleção e alterar o seu estado, se necessário. A declaração e implementação desses métodos está no código abaixo.

class CContextMenu : public CElement
  {
public:
   //--- (1) Obtendo e (2) definindo o estado da caixa de seleção
   bool              CheckBoxStateByIndex(const int index);
   void              CheckBoxStateByIndex(const int index,const bool state);
   //--- (1) Retorna e (2) define o ID do elemento do botão de radio pelo índice
   int               RadioItemIdByIndex(const int index);
   void              RadioItemIdByIndex(const int item_index,const int radio_id);
   //--- (1) Retorna o elemento do botão de rádio selecionado, (2) muda o elemento do botão de radio
   int               SelectedRadioItem(const int radio_id);
   void              SelectedRadioItem(const int radio_index,const int radio_id);
   //---
  };
//+------------------------------------------------------------------+
//| Retorna o estado da caixa de seleção pelo índice                 |
//+------------------------------------------------------------------+
bool CContextMenu::CheckBoxStateByIndex(const int index)
  {
   int array_size=::ArraySize(m_items);
//--- Se não houver um elemento no menu de contexto, registra
   if(array_size<1)
     {
      ::Print(__FUNCTION__," > Este método era para ser chamado, "
              "caso o menu de contexto tenha no mínimo um elemento!");
     }
//--- Ajuste no caso do tamanho exceder
   int i=(index>=array_size)? array_size-1 :(index<0)? 0 : index;
//--- Retorna o estado do elemento
   return(m_items[i].CheckBoxState());
  }
//+------------------------------------------------------------------+
//| Define o estado da caixa de seleção pelo índice                  |
//+------------------------------------------------------------------+
void CContextMenu::CheckBoxStateByIndex(const int index,const bool state)
  {
//--- Verifica se o tamanho não foi excedido
   int size=::ArraySize(m_items);
   if(size<1 || index<0 || index>=size)
      return;
//--- Define o estado
   m_items[index].CheckBoxState(state);
  }
//+------------------------------------------------------------------+
//| Retorna o ID do elemento do botão de rádio pelo índice           |
//+------------------------------------------------------------------+
int CContextMenu::RadioItemIdByIndex(const int index)
  {
   int array_size=::ArraySize(m_items);
//--- Se não houver um elemento no menu de contexto, registra
   if(array_size<1)
     {
      ::Print(__FUNCTION__," > Este método era para ser chamado, "
              "caso o menu de contexto tenha no mínimo um elemento!");
     }
//--- Ajuste no caso do tamanho exceder
   int i=(index>=array_size)? array_size-1 :(index<0)? 0 : index;
//--- Retorna o identificador
   return(m_items[i].RadioButtonID());
  }
//+------------------------------------------------------------------+
//| Define o ID do elemento do botão de radio pelo índice            |
//+------------------------------------------------------------------+
void CContextMenu::RadioItemIdByIndex(const int index,const int id)
  {
//--- Verifica se o tamanho não foi excedido
   int array_size=::ArraySize(m_items);
   if(array_size<1 || index<0 || index>=array_size)
      return;
//--- Define o identificador
   m_items[index].RadioButtonID(id);
  }
//+------------------------------------------------------------------+
//| Retorna o índice do elemento do botão de rádio pela ID           |
//+------------------------------------------------------------------+
int CContextMenu::SelectedRadioItem(const int radio_id)
  {
//--- Contador dos elementos botão de radio
   int count_radio_id=0;
//--- Itera sobre a lista de elementos do menu de contexto
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- Move para o próximo, caso este não seja um elemento do botão de radio
      if(m_items[i].TypeMenuItem()!=MI_RADIOBUTTON)
         continue;
      //--- Se os identificadores forem iguais
      if(m_items[i].RadioButtonID()==radio_id)
        {
         //--- Se este for um elemento do botão de rádio ativo, deixa o loop
         if(m_items[i].RadioButtonState())
            break;
         //--- Incrementa o contador dos elementos do botão de radio
         count_radio_id++;
        }
     }
//--- Retorna o índice
   return(count_radio_id);
  }
//+------------------------------------------------------------------+
//| Altera o elemento do botão de radio pelo índice e o ID           |
//+------------------------------------------------------------------+
void CContextMenu::SelectedRadioItem(const int radio_index,const int radio_id)
  {
//--- Contador dos elementos botão de radio
   int count_radio_id=0;
//--- Itera sobre a lista de elementos do menu de contexto
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- Move para o próximo, caso este não seja um elemento do botão de radio
      if(m_items[i].TypeMenuItem()!=MI_RADIOBUTTON)
         continue;
      //--- Se os identificadores forem iguais
      if(m_items[i].RadioButtonID()==radio_id)
        {
         //--- Muda o elemento do botão de radio
         if(count_radio_id==radio_index)
            m_items[i].RadioButtonState(true);
         else
            m_items[i].RadioButtonState(false);
         //--- Incrementa o contador dos elementos do botão de radio
         count_radio_id++;
        }
     }
  }

Tudo está pronto para a criação dos métodos para anexar o menu de contexto ao gráfico. A colocação no gráfico terá três fases:

  • Criação do fundo do menu de contexto.
  • Criação dos elementos de menu.
  • Criação de linhas de separação.

Cada etapa exige um método private. Mais tarde, eles serão chamados no método public comum. Declare eles no corpo da classe:

class CContextMenu : public CElement
  {
public:
   //--- Métodos para criar o menu de contexto
   bool              CreateContextMenu(const long chart_id,const int window,const int x=0,const int y=0);
   //---
private:
   bool              CreateArea(void);
   bool              CreateItems(void);
   bool              CreateSeparateLine(const int line_number,const int x,const int y);
   //---
  };

A altura do fundo do menu de contexto depende do número de elementos e linhas de separação. É por isso que não há nenhum ponto em definir este valor na classe da aplicação já que este valor será substituído no método CContextMenu::CreateArea() que foi projetado para definir o fundo do menu de contexto. A altura para a área da linha de separação será igual a nove pixels e, portanto, o número de linhas deve ser multiplicado por esse valor para calcular a área que eles ocupam.

//+------------------------------------------------------------------+
//| Cria a área comum do menu de contexto                            |
//+------------------------------------------------------------------+
bool CContextMenu::CreateArea(void)
  {
//--- Elaborando o nome do objeto
   string name=CElement::ProgramName()+"_contextmenu_bg_"+(string)CElement::Id();
//--- O cálculo da altura do menu de contexto depende do número de elementos de menu e das linhas de separação
   int items_total =ItemsTotal();
   int sep_y_size  =::ArraySize(m_sep_line)*9;
   m_y_size        =(m_item_y_size*items_total+2)+sep_y_size-(items_total-1);
//--- Define o fundo do menu de contexto
   if(!m_area.Create(m_chart_id,name,m_subwin,m_x,m_y,m_x_size,m_y_size))
      return(false);
//--- Define as propriedades
   m_area.BackColor(m_area_color);
   m_area.Color(m_area_border_color);
   m_area.BorderType(BORDER_FLAT);
   m_area.Corner(m_corner);
   m_area.Selectable(false);
   m_area.Z_Order(m_area_zorder);
   m_area.Tooltip("\n");
//--- Margens do ponto extremo
   m_area.XGap(m_x-m_wnd.X());
   m_area.YGap(m_y-m_wnd.Y());
//--- Tamanho do fundo
   m_area.XSize(m_x_size);
   m_area.YSize(m_y_size);
//--- Define o ponteiro do objeto
   CElement::AddToArray(m_area);
   return(true);
  }

As linhas de separação serão definidas pelo método CContextMenu::CreateSeparateLine(). O número da linha e das coordenadas serão passados para este método como parâmetros:

//+------------------------------------------------------------------+
//| Cria uma linha de separação                                      |
//+------------------------------------------------------------------+
bool CContextMenu::CreateSeparateLine(const int line_number,const int x,const int y)
  {
//--- Armazena o ponteiro do formulário
   m_sep_line[line_number].WindowPointer(m_wnd);
//--- Define as propriedades
   m_sep_line[line_number].TypeSepLine(H_SEP_LINE);
   m_sep_line[line_number].DarkColor(m_sepline_dark_color);
   m_sep_line[line_number].LightColor(m_sepline_light_color);
//--- Criação de uma linha de separação
   if(!m_sep_line[line_number].CreateSeparateLine(m_chart_id,m_subwin,line_number,x,y,m_x_size-10,2))
      return(false);
//--- Define o ponteiro do objeto
   CElement::AddToArray(m_sep_line[line_number].Object(0));
   return(true);
  }

O método CContextMenu::CreateSeparateLine() será chamado no método CContextMenu::CreateItems() de fixação de elementos de menu. As coordenadas e a sequência da definição desses elementos serão definidas em um loop. Mais cedo, nós consideramos o array m_sep_line_index[]. Os números do índice dos elementos de menu após a linha de separação ser definida serão armazenados nesse array quando um menu de contexto for formado. Comparando o número da iteração atual do loop e do índice dos elementos de menu no array m_sep_line_index[], podemos identificar qual a linha de separação que será definida. 

É necessário também armazenar o ponteiro para o nó anterior antes de definir cada elemento no menu de contexto. O código do método CContextMenu::CreateItems() com comentários detalhados é apresentado abaixo.

//+------------------------------------------------------------------+
//| Cria uma lista de elementos de menu                              |
//+------------------------------------------------------------------+
bool CContextMenu::CreateItems(void)
  {
   int s =0;     // Para a identificação da localização das linhas de separação
   int x =m_x+1; // coordenada X
   int y =m_y+1; // coordenada Y. Será calculado em um loop para cada item do menu.
//--- Número de linhas de separação
   int sep_lines_total=::ArraySize(m_sep_line_index);
//---
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- Cálculo da coordenada Y
      y=(i>0)? y+m_item_y_size-1 : y;
      //--- Armazena o ponteiro do formulário
      m_items[i].WindowPointer(m_wnd);
      //--- Adiciona o ponteiro para o nó anterior
      m_items[i].PrevNodePointer(m_prev_node);
      //--- Define as propriedades
      m_items[i].XSize(m_x_size-2);
      m_items[i].YSize(m_item_y_size);
      m_items[i].IconFileOn(m_path_bmp_on[i]);
      m_items[i].IconFileOff(m_path_bmp_off[i]);
      m_items[i].AreaBackColor(m_area_color);
      m_items[i].AreaBackColorOff(m_item_back_color_hover_off);
      m_items[i].AreaBorderColor(m_area_color);
      m_items[i].LabelColor(m_label_color);
      m_items[i].LabelColorHover(m_label_color_hover);
      m_items[i].RightArrowFileOn(m_right_arrow_file_on);
      m_items[i].RightArrowFileOff(m_right_arrow_file_off);
      m_items[i].IsDropdown(m_is_dropdown);
      //--- Margens do ponto a extremidade do painel
      m_items[i].XGap(x-m_wnd.X());
      m_items[i].YGap(y-m_wnd.Y());
      //--- Criando o elemento de menu
      if(!m_items[i].CreateMenuItem(m_chart_id,m_subwin,i,m_label_text[i],x,y))
         return(false);
      //--- Mover para o próximo se todas as linhas de separação são configuradas.
      if(s>=sep_lines_total)
         continue;
      //--- Se todos os índices correspondem, então, uma linha de separação poderá ser configurada após este elemento
      if(i==m_sep_line_index[s])
        {
         //--- Coordenadas
         int l_x=x+5;
         y=y+m_item_y_size+2;
         //--- Criação de uma linha de separação
         if(!CreateSeparateLine(s,l_x,y))
            return(false);
         //--- Ajuste da coordenada Y para o elemento seguinte
         y=y-m_item_y_size+7;
         //--- Aumenta o contador para as linhas de separação
         s++;
        }
     }
   return(true);
  }

Em seguida, o método CContextMenu::CreateContextMenu() para uso externo deve ser implementado. Nesta fase, vamos considerar uma opção onde um menu de contexto deve ser anexado a um elemento de menu externo ou a um elemento de menu sozinho e independente. Isso significa que, antes de criar um menu de contexto, ele tem que ser passado para o nó anterior do ponteiro, como foi dito anteriormente. Além disso, é necessário realizar a verificação se o ponteiro do formulário está presente e verificação da existência do nó anterior do ponteiro. Para a biblioteca do usuário, este será um ponto de referência adicional, eliminando a possibilidade da formação incorreta da interface gráfica. 

O menu de contexto, geralmente está oculto após sua criação, já que ele foi projetado para ser criado, clicando em outro controle ou clicando na área de trabalho. O método Hide() foi projetado para ocultar os objetos de cada elemento. Há um método semelhante, na classe CContextMenu. Nos primeiros objetos de um menu de contexto - o fundo e a linha de separação estão ocultos por ela. Então, todos os elementos de menu estão ocultos em um loop. Ao mesmo tempo, o seu próprio método CMenuItem::Hide() são chamados de elementos de menu. As linhas de separação também pode ser ocultas de um modo semelhante, pois este elemento tem o seu próprio método CSeparateLine::Hide(). No entanto, como este é apenas um elemento de design, que consiste em apenas um objeto gráfico e ele não foi projetado para interagir com o usuário, ele foi adicionado ao array comum de objetos do menu de contexto no momento da criação, sendo oculto no loop correspondente.

//+------------------------------------------------------------------+
//| Oculta o menu de contexto                                        |
//+------------------------------------------------------------------+
void CContextMenu::Hide(void)
  {
//--- Sai se o elemento está oculto
   if(!CElement::m_is_visible)
      return;
//--- Oculta os objetos do menu de contexto
   for(int i=0; i<ObjectsElementTotal(); i++)
      Object(i).Timeframes(OBJ_NO_PERIODS);
//--- Oculta os elementos de menu
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
      m_items[i].Hide();
//--- Zera o foco
   CElement::MouseFocus(false);
//--- Atribui o estado de um controle oculto
   CElement::m_is_visible=false;
  }

Todos os métodos para gerenciar um menu de contexto serão estruturados de forma semelhante, sendo que nós não iremos analisar o seu código aqui. Você pode ver o código nos arquivos anexados deste artigo. Nós só vamos discutir o código do método CContextMenu::Delete() para a remoção de um elemento. Aqui, além de remover todos os objetos gráficos, todos os arrays que foram utilizados para a formação do menu de contexto são esvaziados. Se isso não for feito, então, cada vez que um símbolo ou um tempo gráfico for alterado, a lista de elementos de menu aumentará. Na fase de testes, você pode tentar realizar esta experiência, basta comentar essas linhas.

//+------------------------------------------------------------------+
//| Remoção                                                          |
//+------------------------------------------------------------------+
void CContextMenu::Delete(void)
  {
//--- Remove Objetos  
   m_area.Delete();
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
      m_items[i].Delete();
//--- Remove as linhas de separação
   int sep_total=::ArraySize(m_sep_line);
   for(int i=0; i<sep_total; i++)
      m_sep_line[i].Delete();
//--- Esvazia os arrays de controle
   ::ArrayFree(m_items);
   ::ArrayFree(m_sep_line);
   ::ArrayFree(m_sep_line_index);
   ::ArrayFree(m_label_text);
   ::ArrayFree(m_path_bmp_on);
   ::ArrayFree(m_path_bmp_off);
//--- Esvazia o array dos objetos
   CElement::FreeObjectsArray();
  }

Voltando ao método para criar um menu de contexto, deve-se mencionar que as coordenadas serão definidas em relação ao nó anterior. Nós vamos providenciar algo para que o usuário da biblioteca possa definir as coordenadas, caso tal necessidade seja levantada futuramente. As coordenadas padrão serão definidas para zero no método CContextMenu::CreateContextMenu() para criar um menu de contexto. As coordenadas serão calculadas a partir do nó anterior automaticamente somente se pelo menos uma coordenada for especificada. Se ambas as coordenadas são especificadas, então, o cálculo automático será cancelado.

Para esses menus de contexto que são abertos a partir de outros menus de contexto, suas coordenadas serão calculadas automaticamente a partir da parte direita do elemento ao qual o menu está conectado. Para esses menus de contexto ligados aos elementos de menu do menu principal, o cálculo da coordenada será realizado a partir da parte inferior dos elementos. Para gerir este sistema, serão necessários adicionar mais um campo e método para a classe CContextMenu. Vamos adicionar uma nova enumeração para o arquivo Enums.mqh:

//+------------------------------------------------------------------+
//| Enumeração dos lados que o menu foi anexado                         |
//+------------------------------------------------------------------+
enum ENUM_FIX_CONTEXT_MENU
  {
   FIX_RIGHT  =0,
   FIX_BOTTOM =1
  };

Um campo correspondente e o método para definir o modo de cálculo das coordenadas devem ser adicionados à classe do menu de contexto. Por padrão, as coordenadas serão calculadas a partir da parte direita do elemento.

class CContextMenu : public CElement
  {
private:
   //--- Lado da ligação do menu de contexto
   ENUM_FIX_CONTEXT_MENU m_fix_side;
   //---
public:
   //--- Define o modo da ligação do menu de contexto
   void              FixSide(const ENUM_FIX_CONTEXT_MENU side)      { m_fix_side=side;                      }
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CContextMenu::CContextMenu(void) : m_fix_side(FIX_RIGHT)
  {
  }

Abaixo está o código do método CContextMenu::CreateContextMenu(). A criação de um elemento é apenas possível se houver um ponteiro para ele. As propriedades deste nó estarão disponíveis somente após sua verificação correspondente, discutido anteriormente, permitindo que seja feito o cálculo das coordenadas relativas automaticamente. O código para ocultar um menu de contexto deve estar localizado depois de sua criação.

//+------------------------------------------------------------------+
//| Cria um menu de contexto                                         |
//+------------------------------------------------------------------+
bool CContextMenu::CreateContextMenu(const long chart_id,const int subwin,const int x=0,const int y=0)
  {
//--- Retorna se não há nenhum ponteiro do formulário
   if(::CheckPointer(m_wnd)==POINTER_INVALID)
     {
      ::Print(__FUNCTION__," > Antes de criar um menu de conxtexto, ele precisa ser passado  "
              "para o objeto da janela utilizando o WindowPointer(CWindow &object).");
      return(false);
     }
//--- Retorna se não houver nenhum ponteiro para o nó anterior 
   if(::CheckPointer(m_prev_node)==POINTER_INVALID)
     {
      ::Print(__FUNCTION__," > Antes de criar um menu de contexto, ele precisa ser passado "
              "para o ponteiro do nó anterior utilizando o método CContextMenu::PrevNodePointer(CMenuItem &object).");
      return(false);
     }
//--- Inicialização das variáveis
   m_id       =m_wnd.LastId()+1;
   m_chart_id =chart_id;
   m_subwin   =subwin;
//--- Se as coordenadas não foram especificadas
   if(x==0 || y==0)
     {
      m_x =(m_fix_side==FIX_RIGHT)? m_prev_node.X2()-3 : m_prev_node.X()+1;
      m_y =(m_fix_side==FIX_RIGHT)? m_prev_node.Y()-1  : m_prev_node.Y2()-1;
     }
//--- Se as coordenadas foram especificadas
   else
     {
      m_x =x;
      m_y =y;
     } 
//--- Margens do ponto extremo
   CElement::XGap(m_x-m_wnd.X());
   CElement::YGap(m_y-m_wnd.Y());
//--- Criando o menu de contexto
   if(!CreateArea())
      return(false);
   if(!CreateItems())
      return(false);
//--- Oculta o elemento
   Hide();
   return(true);
  }

A verificação da existência do ponteiro do nó anterior com uma condição tem que ser adicionada à classe CMenuItem do método CreateMenuItem(). A ausência de um ponteiro implica em um elemento de menu independente Isto significa que este item não faz parte de um menu de contexto. Tais elementos podem ser tanto os elementos de um tipo simples (MI_SIMPLE) ou elementos que contêm um menu de contexto (MI_HAS_CONTEXT_MENU). Pode ser difícil de entender isso agora, mas isto ficará claro quando olhamos para os exemplos no final deste artigo.

Coloque este código no método CMenuItem::CreateMenuItem() após verificar a presença do ponteiro do formulário, como mostrado abaixo.

//--- Se não houver nenhum ponteiro para o nó anterior, então
//    implica em um ​​elemento de menu independente, sendo que ele faz parte do menu de contexto
   if(::CheckPointer(m_prev_node)==POINTER_INVALID)
     {
      //--- Retorna, se o tipo definido não corresponder
      if(m_type_menu_item!=MI_SIMPLE && m_type_menu_item!=MI_HAS_CONTEXT_MENU)
        {
         ::Print(__FUNCTION__," > O tipo do elemento de menu independente pode ser apenas MI_SIMPLE ou MI_HAS_CONTEXT_MENU,",
                 "isto é apenas como o menu de contexto.\n",
                 __FUNCTION__," > O tipo do elemento de menu pode ser definido utilizando o método CMenuItem::TypeMenuItem()");
         return(false);
        }
     }



Teste da Colocação de um Menu de Contexto

O teste em anexar um menu de contexto ao gráfico pode ser feito agora. Faça a inclusão do arquivo ContextMenu.mqh junto da classe do menu CContextMenu na biblioteca, como mostrado abaixo.

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Window.mqh"
#include "MenuItem.mqh"
#include "ContextMenu.mqh"
#include "SeparateLine.mqh"

Crie uma instância da classe CContextMenu na classe personalizada da aplicação CProgram e declare o método para criar um menu de contexto. As margens do ponto da borda do formulário não precisam ser especificadas já que eles serão calculados em relação ao elemento de menu ao qual estão ligados.

class CProgram : public CWndEvents
  {
private:
   //--- Elemento de menu e do contexto
   CMenuItem         m_menu_item1;
   CContextMenu      m_mi1_contextmenu1;
   //---
private:
#define MENU_ITEM1_GAP_X (6)
#define MENU_ITEM1_GAP_Y (25)
   bool              CreateMenuItem1(const string item_text);
   bool              CreateMI1ContextMenu1(void);
  };

Agora, vamos formar o menu de contexto. Ele terá cinco elementos: três simples (MI_SIMPLE) e dois do tipo caixa de seleção (MI_CHECKBOX). Inclua os recursos com os ícones para os seus rótulos nos elementos simples fora do corpo do método. O ícone será colorido quando o elemento estiver disponível e incolor quando bloqueado. Você pode baixar eles nos links que se encontram no final deste artigo. Então, no início do método, armazene os ponteiros para o formulário ao nó anterior no menu de contexto. Sem estas ações, a interface gráfica não será criada e o programa será removido a partir do gráfico. Em seguida, siga os arrays com (1) a descrição do item (texto exibido), com os ícones dos estados (2) disponíveis e (3) bloqueados (4) e os tipos de elementos. Depois disso, nós precisamos especificar as propriedades comuns de todos os elementos e, em seguida, adicioná-los na classe do menu de contexto em um loop utilizando o método CContextMenu::AddItem(). Adicione uma linha de separação após o segundo elemento (índice 1). Depois que todas essas ações serem concluídas, o menu de contexto poderá ser anexado ao gráfico. No final do método, adicione o ponteiro do elemento para a base. Abaixo está o código do método.

//+------------------------------------------------------------------+
//| Cria um menu de contexto                                         |
//+------------------------------------------------------------------+
#resource "\\Images\\Controls\\coins.bmp"
#resource "\\Images\\Controls\\coins_colorless.bmp"
#resource "\\Images\\Controls\\line_chart.bmp"
#resource "\\Images\\Controls\\line_chart_colorless.bmp"
#resource "\\Images\\Controls\\safe.bmp"
#resource "\\Images\\Controls\\safe_colorless.bmp"
//---
bool CProgram::CreateMI1ContextMenu1(void)
  {
//--- Cinco elementos no menu de contexto
#define CONTEXTMENU_ITEMS1 5
//--- Armazena o ponteiro da janela
   m_mi1_contextmenu1.WindowPointer(m_window);
//--- Armazena o ponteiro para o nó anterior
   m_mi1_contextmenu1.PrevNodePointer(m_menu_item1);
//--- Array de nomes dos elementos
   string items_text[CONTEXTMENU_ITEMS1]=
     {
      "ContextMenu 1 Item 1",
      "ContextMenu 1 Item 2",
      "ContextMenu 1 Item 3",
      "ContextMenu 1 Item 4",
      "ContextMenu 1 Item 5"
     };
//--- Array dos rótulos para o modo disponível
   string items_bmp_on[CONTEXTMENU_ITEMS1]=
     {
      "Images\\Controls\\coins.bmp",
      "Images\\Controls\\line_chart.bmp",
      "Images\\Controls\\safe.bmp",
      "",""
     };
//--- Array do rótulo para o modo bloqueado
   string items_bmp_off[CONTEXTMENU_ITEMS1]=
     {
      "Images\\Controls\\coins_colorless.bmp",
      "Images\\Controls\\line_chart_colorless.bmp",
      "Images\\Controls\\safe_colorless.bmp",
      "",""
     };
//--- Array dos tipos de elementos
   ENUM_TYPE_MENU_ITEM items_type[CONTEXTMENU_ITEMS1]=
     {
      MI_SIMPLE,
      MI_SIMPLE,
      MI_SIMPLE,
      MI_CHECKBOX,
      MI_CHECKBOX
     };
//--- Configura as propriedades antes da criação
   m_mi1_contextmenu1.XSize(160);
   m_mi1_contextmenu1.ItemYSize(24);
   m_mi1_contextmenu1.AreaBackColor(C'240,240,240');
   m_mi1_contextmenu1.AreaBorderColor(clrSilver);
   m_mi1_contextmenu1.ItemBackColorHover(C'240,240,240');
   m_mi1_contextmenu1.ItemBackColorHoverOff(clrLightGray);
   m_mi1_contextmenu1.ItemBorderColor(C'240,240,240');
   m_mi1_contextmenu1.LabelColor(clrBlack);
   m_mi1_contextmenu1.LabelColorHover(clrWhite);
   m_mi1_contextmenu1.RightArrowFileOff("Images\\EasyAndFastGUI\\Controls\\RightTransp_black.bmp");
   m_mi1_contextmenu1.SeparateLineDarkColor(C'160,160,160');
   m_mi1_contextmenu1.SeparateLineLightColor(clrWhite);
//--- Adiciona os elementos ao menu de contexto
   for(int i=0; i<CONTEXTMENU_ITEMS1; i++)
      m_mi1_contextmenu1.AddItem(items_text[i],items_bmp_on[i],items_bmp_off[i],items_type[i]);
//--- Linha de separação após o segundo item
   m_mi1_contextmenu1.AddSeparateLine(1);
//--- Criar o menu de contexto
   if(!m_mi1_contextmenu1.CreateContextMenu(m_chart_id,m_subwin))
      return(false);
//--- Adiciona o ponteiro do controle para a base
   CWndContainer::AddToElementsArray(0,m_mi1_contextmenu1);
   return(true);
  }

Agora, adicione a chamada do método para criar um menu de contexto ao método principal da criação da interface gráfica. Embora o menu de contexto é para estar oculto na criação, ele será exibido neste teste. O código abaixo mostra quais linhas têm de ser adicionadas ao método CProgram::CreateTradePanel().

//+------------------------------------------------------------------+
//| Cria o painel de negociação                                      |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- Criação de um formulário para os controles
   if(!CreateWindow("EXPERT PANEL"))
      return(false);
//--- Criação dos controles:
//    Elemento de menu
   if(!CreateMenuItem1("Menu item"))
      return(false);
   if(!CreateMI1ContextMenu1())
      return(false);
//--- Linha de separação
   if(!CreateSepLine())
      return(false);
//--- Exibe o menu de contexto
   m_mi1_contextmenu1.Show();
//--- Redesenho do gráfico
   m_chart.Redraw();
   return(true);
  }

Compile os arquivos e carregue o programa ao gráfico. O resultado deve ser igual ao da imagem abaixo

Fig. 4. Teste do elemento menu de contexto.

Fig. 4. Teste do elemento menu de contexto.


Nesta fase, quando o cursor do mouse estiver pairando sobre os elementos do menu de contexto, eles não mudarão de cor. Esta funcionalidade pode ser criada na classe do menu de contexto CContextMenu ou você pode usar uma pronta na classe do elemento de menu CMenuItem. Após o menu de contexto ser anexado ao gráfico, seu ponteiro é adicionado à base. No entanto, os ponteiros para cada elemento de menu ficam indisponível para utilização na classe CWndEvents para manipulação de eventos na implementação atual de adicionar ponteiros ao array comum de elementos. Para cada controle complexo (composto), que consiste de vários elementos, nós vamos criar na classe CWndContainerum método no qual é possível obter os ponteiros para estes elementos. Para isso, nós implementamos o método ItemPointerByIndex() na classe CContextMenu, usando ele, nó podemos obter o ponteiro para o elemento de menu especificando o seu índice.

 


Continuação do Desenvolvimento da classe para Armazenar os Ponteiros para Todos os Elementos

Vamos implementar o método AddContextMenuElements() na classe CWndContainer para trabalhar com o elemento do menu de contexto. O índice do formulário e o objeto do elemento devem ser passados ​​para ele por parâmetros. No início do método, será realizado uma verificação, se o elemento passado é um menu de contexto. Se ele for, então, é necessário um ponteiro para o menu de contexto (CContextMenu) para ter acesso aos seus métodos. Como isso pode ser feito se o objeto que está sendo passado pertence à classe base (CElement)? Para isso, é suficiente declarar apenas um ponteiro com o tipo CContextMenu e atribuir o ponteiro ao objeto passado para ele. No código abaixo ele está destacado em amarelo. Dessa forma, há um acesso para os itens do menu de contexto. Em seguida, eles são adicionados em um loop para o array comum de elementos de seu formulário. No fim de cada iteração, os elementos de menu são passados ​​para o método CWndContainer::AddToObjectsArray() para armazenar os ponteiros para os objetos que compõem o array de objetos do tipo CChartObject.

//+------------------------------------------------------------------+
//| Classe para armazenar todos os objetos da interface              |
//+------------------------------------------------------------------+
class CWndContainer
  {
protected:
   //--- Armazena os ponteiros para os elementos do menu de contexto na base
   bool              AddContextMenuElements(const int window_index,CElement &object);
  };
//+------------------------------------------------------------------+
//| Armazena os ponteiros para os elementos do menu de contexto na base|
//+------------------------------------------------------------------+
bool CWndContainer::AddContextMenuElements(const int window_index,CElement &object)
  {
//--- Retorna, se este não for um menu de contexto
   if(object.ClassName()!="CContextMenu")
      return(false);
//--- Obtém os ponteiros do menu de contexto
   CContextMenu *cm=::GetPointer(object);
//--- Armazena os ponteiros para os seus objetos na base
   int items_total=cm.ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- Incrementa o array de elementos
      int size=::ArraySize(m_wnd[window_index].m_elements);
      ::ArrayResize(m_wnd[window_index].m_elements,size+1);
      //--- Obtém os ponteiros do elemento de menu
      CMenuItem *mi=cm.ItemPointerByIndex(i);
      //--- Armazena o ponteiro no array
      m_wnd[window_index].m_elements[size]=mi;
      //--- Adiciona os ponteiros de todos os objetos do elemento de menu para o array comum
      AddToObjectsArray(window_index,mi);
     }
//---
   return(true);
  }

Vamos chamar o método CWndContainer::AddToElementsArray() imediatamente após o incremento do contador de elemento. Para economizarmos espaço no artigo, nós vamos mostrar apenas a versão reduzida do código. A versão completa pode ser encontrada nos arquivos anexados a este artigo.

//+------------------------------------------------------------------+
//| Adiciona um ponteiro ao array de elemento                        |
//+------------------------------------------------------------------+
void CWndContainer::AddToElementsArray(const int window_index,CElement &object)
  {
//--- Se a base não contém formulários para os controles
/ --- Se a solicitação for para um formulário que não existe
//--- Adiciona ao array comum de elementos
//--- Adiciona os objetos do elemento para o array comum de objetos
//--- Armazena o ID do último elemento em todos os formulários
//--- Aumenta o contador de identificadores do elemento

//--- Armazena os ponteiros para os objetos do menu de contexto na base
   if(AddContextMenuElements(window_index,object))
      return;
  }

O código do método CWndContainer::AddToElementsArray() será enriquecido da mesma maneira que os métodos semelhantes dos elementos complexos (compostos) foram.

Se compilarmos todos os arquivos e carregar o programa ao gráfico, os elementos de menu do contexto irá mudar sua aparência quando o cursor do mouse estiver pairando sobre eles.

Fig. 5. Teste dos elementos do menu de contexto.

Fig. 5. Teste dos elementos do menu de contexto.


O desenvolvimento da classe para criar o menu de contexto está concluído. O próximo passo será a criação do manipulador de eventos dele e para os elementos de menu. Nós vamos lidar com isso no próximo artigo.

 


Conclusão

Nossa biblioteca já contém três classes para criar os seguintes elementos:

  • elemento de menu;
  • linha de separação;
  • menu de contexto.

No próximo artigo, nós vamos configurar os manipuladores de eventos da biblioteca na classe principal e nas classes de controles criadas anteriormente.

Você pode buscar e baixar os arquivos da biblioteca no atual estágio de desenvolvimento, as imagens e os arquivos dos programas discutidos no artigo (o EA, indicadores e o script) podem ser baixados para testes nos terminais MetaTrader 4 e MetaTrader 5. Se você tiver dúvidas sobre a utilização do material a partir desses arquivos, você poderá consultar a descrição detalhada do desenvolvimento da biblioteca em um dos artigos da lista abaixo ou fazer sua pergunta nos comentários deste artigo. 

Lista de artigos (capítulos) da segunda parte:

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2202

Arquivos anexados |
Que testes deve passar o robô de negociação antes da publicação no Mercado Que testes deve passar o robô de negociação antes da publicação no Mercado
Todos os produtos do Mercado, antes de serem publicados, passam uma revisão preliminar obrigatória para garantir um único padrão de qualidade. Neste artigo, vamos falar sobre os erros mais comuns que os desenvolvedores cometem ao trabalhar com os seus indicadores técnicos e robôs de negociação. Além disso, mostraremos como testar por si mesmo o seu produto antes de enviá-lo para o Mercado.
Interfaces Gráficas II: O Elemento de Menu (Capítulo 1) Interfaces Gráficas II: O Elemento de Menu (Capítulo 1)
Na segunda parte da série, nós vamos mostrar em detalhes o desenvolvimento de tais elementos de interface como o menu principal e o menu de contexto. Nós também vamos mencionar os elementos de desenho e criar uma classe especial para ele. Nós vamos discutir detalhadamente tais questões como a gestão de eventos do programa, incluindo aquelas que são personalizadas.
Como criar um indicador de gráfico não padronizado no Mercado MetaTrader Como criar um indicador de gráfico não padronizado no Mercado MetaTrader
Através de gráficos off-line, programação em MQL4 e uma disposição razoável, você pode obter uma variedade de tipos de gráficos: "Point & Figure", "Renko", "Kagi", "Range bars", gráficos equivolumes, etc. Neste artigo, vamos mostrar como isso pode ser alcançado sem o uso de DLL e como indicadores "dois-para-um" podem ser publicados e comprados no mercado.
Como criar bots para Telegram em MQL5 Como criar bots para Telegram em MQL5
Este artigo contém instruções passo-a-passo para a criação de bots para o Telegram em MQL5. Esta informação pode ser útil aos usuários que desejam sincronizar o seu robô de negociação a um dispositivo móvel. Existem exemplos de bots no artigo que fornecem sinais de negociação, busca de informações em sites, enviam informações sobre o balanço da conta, cotações e imagens de gráficos ao seu telefone celular.