![Interfaces Gráficas II: Os Elementos Linha de Separação e o Menu de Contexto (Capítulo 2)](https://c.mql5.com/i/articles/overlay.png)
Interfaces Gráficas II: Os Elementos Linha de Separação e o Menu de Contexto (Capítulo 2)
Conteúdo
- Introdução
- Desenvolvimento da Classe para Criar uma Linha de Separação
- Teste da Colocação da Linha de Separação
- Desenvolvimento da Classe para Criar um Menu de Contexto
- Teste da Colocação de um Menu de Contexto
- Continuação do Desenvolvimento da classe para Armazenar os Ponteiros para Todos os Elementos
- Conclusão
Introdução
O primeiro artigo Interfaces gráficas I: Preparação da Estrutura da Biblioteca (Capítulo 1) considera em detalhes a finalidade desta biblioteca. A 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
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.
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.
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.
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.
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:
- Interfaces Gráficas II: O Elemento de Menu (Capítulo 1)
- Interfaces Gráficas II: Os Elementos Linha de Separação e o Menu de Contexto (Capítulo 2)
- Interfaces Gráficas II: Configuração dos manipuladores de eventos da Biblioteca (Capítulo 3)
- Interfaces Gráficas II: O Elemento Menu Principal (Capítulo 4)
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2202
![Que testes deve passar o robô de negociação antes da publicação no Mercado](https://c.mql5.com/2/23/ava_market.png)
![Interfaces Gráficas II: O Elemento de Menu (Capítulo 1)](https://c.mql5.com/2/22/Graphic-interface-part2.png)
![Como criar um indicador de gráfico não padronizado no Mercado MetaTrader](https://c.mql5.com/2/23/ava3_.png)
![Como criar bots para Telegram em MQL5](https://c.mql5.com/2/22/telegram-avatar.png)
![MQL5 - Linguagem para estratégias de negociação inseridas no terminal do cliente MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso