Interfaces Gráficas V: O Controle Combobox (Capítulo 3)
Conteúdo
- Introdução
- O controle ComboBox
- Escrevendo uma Classe para Criar o Controle Combobox
- Métodos para Manipular os Eventos do Controle
- Conexão da Classe de Controle com o Motor da Biblioteca
- Testando o Controle ComboBox na Interface Gráfica do Aplicativo Personalizado
- 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. Você irã encontrar uma lista de artigos com os links no final de cada capítulo. Lá, você também pode encontrar e baixar a versão completa da biblioteca, no estágio de desenvolvimento atual. Os arquivos devem estar localizados nas mesmas pastas que o arquivo baixado.
Nos dois primeiros capítulos da quinta parte da série, nós desenvolvemos as classes para criar uma barra de rolagem e uma lista. Lá, nós não demonstramos como anexar uma barra de rolagem ao elemento se os dados não se encaixam na área designada. Neste capítulo, nós falaremos sobre a criação de uma classe para o controle combobox. Este é também um controle composto que contém, entre outros, os elementos considerados nos capítulos anteriores desta quinta parte.
O controle ComboBox
Um combobox é um controle composto, as partes principais dele são: (1) um botão e (2) uma lista. A lista, neste caso, é um elemento suspenso e é chamado quando o botão é pressionado. Depois de selecionar uma lista de elementos, o texto é exibido no botão e a lista é oculta. Quando um programa contém muitos parâmetros multi-opcionais, utilizar um combobox permitirá criar uma interface gráfica compacta.
A seguir estão os objetos primitivos para compor o controle combobox.
- O elemento de fundo
- Rótulo (descrição do elemento)
- Botão
- Indicação de uma lista suspensa
Fig. 1. Partes integrantes do controle combobox
Na próxima parte do artigo, nós vamos escrever uma classe para criar esse controle.
Escrevendo uma Classe para Criar o Controle Combobox
Nós vamos considerar todas as fases do desenvolvimento do controle combobox de modo que, no futuro, este artigo poderá ser utilizado como um exemplo para escrever classes semelhantes. No início, crie um arquivo mqh chamado (ComboBox.mqh) e inclua nele todos os arquivos necessários com as classes que exigidas para criar uma combobox. Em nosso caso, estes serão três arquivos com as seguintes classes:
- CElement — classe base para criar o controle.
- CWindow — classe do formulário que este controle será anexado.
- CListView — classe da lista cuja visibilidade será gerenciada pela combobox.
//+------------------------------------------------------------------+ //| ComboBox.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" #include "ListView.mqh"
Em seguida, crie a classe CComboBox e os métodos padrão para cada elemento da biblioteca no arquivo ComboBox.mqh:
//+------------------------------------------------------------------+ //| Classe para criar um menu de contexto | //+------------------------------------------------------------------+ class CComboBox : public CElement { private: //--- Ponteiro para o formulário que este elemento está anexado CWindow *m_wnd; //--- public: CComboBox(void); ~CComboBox(void); //--- Armazena o ponteiro do formulário 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); //--- 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); //--- Reseta a cor virtual void ResetColors(void); }; //+------------------------------------------------------------------+ //| Construtor | //+------------------------------------------------------------------+ CComboBox::CComboBox(void) { //--- Armazena o nome da classe do elemento na classe base CElement::ClassName(CLASS_NAME); } //+------------------------------------------------------------------+ //| Destrutor | //+------------------------------------------------------------------+ CComboBox::~CComboBox(void) { }
O usuário deve ter a oportunidade de escolher o seu próprio esquema de cores para a interface gráfica. Para isso, ele deve ter acesso às configurações das propriedades dos objetos que se compõem o controle. A seguir estão as propriedades que se podem ajustar antes do controle ser criado:
- Cor de fundo do controle
- A descrição exibida no combobox (rótulo de texto)
- Margens para o rótulo de texto ao longo do eixo X e Y
- Cores do rótulo de texto em diferentes estados
- Texto do botão (texto do elemento selecionado da lista)
- Tamanho do botão
- Cores do botão em diferentes estados
- Cores do quadro do botão em diferentes estados
- Cores do texto do botão em diferentes estados
- Ícones para a seta - a indicação de uma lista suspensa para o modo ativo e bloqueado
- Margens para os ícones da seta ao longo do eixo X e Y
O código a seguir contém os campos e métodos para instalar as propriedades listadas acima:
class CComboBox : public CElement { private: //--- Propriedades do combobox: // Cor do fundo geral color m_area_color; //--- Texto e margens do rótulo de texto string m_label_text; int m_label_x_gap; int m_label_y_gap; //--- Cores do rótulo de texto em diferentes estados color m_label_color; color m_label_color_off; color m_label_color_hover; color m_label_color_array[]; //--- (1) Texto do botão e (2) o seu tamanho string m_button_text; int m_button_x_size; int m_button_y_size; //--- Cores do botão em diferentes estados color m_button_color; color m_button_color_off; color m_button_color_hover; color m_button_color_pressed; color m_button_color_array[]; //--- Cores do quadro de botão em diferentes estados color m_button_border_color; color m_button_border_color_off; //--- Cor do botão de texto em diferentes estados color m_button_text_color; color m_button_text_color_off; //--- Margens do rótulo int m_drop_arrow_x_gap; int m_drop_arrow_y_gap; //--- Rótulos dos botões com um menu suspenso nos estado ativo e bloqueado string m_drop_arrow_file_on; string m_drop_arrow_file_off; //--- Prioridades do clique do botão esquerdo do mouse int m_area_zorder; int m_button_zorder; int m_zorder; //--- public: //--- (1) A cor de fundo, (2) define e (3) retorna o valor do rótulo de texto void AreaColor(const color clr) { m_area_color=clr; } void LabelText(const string label_text) { m_label_text=label_text; } string LabelText(void) const { return(m_label_text); } //--- Margens do rótulo de texto void LabelXGap(const int x_gap) { m_label_x_gap=x_gap; } void LabelYGap(const int y_gap) { m_label_y_gap=y_gap; } //--- (1) Retorna o texto do botão (2), definindo o tamanho do botão string ButtonText(void) const { return(m_button_text); } void ButtonXSize(const int x_size) { m_button_x_size=x_size; } void ButtonYSize(const int y_size) { m_button_y_size=y_size; } //--- (1) cor de fundo, (2) cores do rótulo de texto void LabelColor(const color clr) { m_label_color=clr; } void LabelColorOff(const color clr) { m_label_color_off=clr; } void LabelColorHover(const color clr) { m_label_color_hover=clr; } //--- Cores dos botões void ButtonBackColor(const color clr) { m_button_color=clr; } void ButtonBackColorOff(const color clr) { m_button_color_off=clr; } void ButtonBackColorHover(const color clr) { m_button_color_hover=clr; } void ButtonBackColorPressed(const color clr) { m_button_color_pressed=clr; } //--- Cores do quadro de botão void ButtonBorderColor(const color clr) { m_button_border_color=clr; } void ButtonBorderColorOff(const color clr) { m_button_border_color_off=clr; } //--- Cores do texto do botão void ButtonTextColor(const color clr) { m_button_text_color=clr; } void ButtonTextColorOff(const color clr) { m_button_text_color_off=clr; } //--- Ícones configuração para o botão com um menu suspenso no estado ativo e bloqueado void DropArrowFileOn(const string file_path) { m_drop_arrow_file_on=file_path; } void DropArrowFileOff(const string file_path) { m_drop_arrow_file_off=file_path; } //--- Margens do rótulo void DropArrowXGap(const int x_gap) { m_drop_arrow_x_gap=x_gap; } void DropArrowYGap(const int y_gap) { m_drop_arrow_y_gap=y_gap; } };
A inicialização de todas as propriedades listadas acima por valores padrão estão no construtor da classe:
//+------------------------------------------------------------------+ //| Construtor | //+------------------------------------------------------------------+ CComboBox::CComboBox(void) : m_area_color(C'15,15,15'), m_label_text("combobox: "), m_label_x_gap(0), m_label_y_gap(2), m_label_color(clrWhite), m_label_color_off(clrGray), m_label_color_hover(C'85,170,255'), m_button_text(""), m_button_y_size(18), m_button_text_color(clrBlack), m_button_text_color_off(clrDarkGray), m_button_color(clrGainsboro), m_button_color_off(clrLightGray), m_button_color_hover(C'193,218,255'), m_button_color_pressed(C'153,178,215'), m_button_border_color(clrWhite), m_button_border_color_off(clrWhite), m_drop_arrow_x_gap(16), m_drop_arrow_y_gap(1), m_drop_arrow_file_on(""), m_drop_arrow_file_off("") { //--- Define as prioridades do botão esquerdo do mouse m_zorder =0; m_area_zorder =1; m_button_zorder =2; }
O combobox será criado com cinco métodos privados, que serão chamados no método público e principal CComboBox::CreateComboBox(). Para obter acesso à configuração da lista e as propriedades da barra de rolagem, crie os métodos para obter os ponteiros destes elementos:
class CComboBox : public CElement { private: //--- Objetos para criar um combobox CRectLabel m_area; CLabel m_label; CEdit m_button; CBmpLabel m_drop_arrow; CListView m_listview; //--- public: //--- Métodos para criar um combobox bool CreateComboBox(const long chart_id,const int subwin,const int x,const int y); //--- private: bool CreateArea(void); bool CreateLabel(void); bool CreateButton(void); bool CreateDropArrow(void); bool CreateList(void); //--- public: //--- Retorna os ponteiros para (1) a lista e (2) a barra de rolagem CListView *GetListViewPointer(void) { return(::GetPointer(m_listview)); } CScrollV *GetScrollVPointer(void) { return(m_listview.GetScrollVPointer()); } };
Fora dos métodos apresentados no código acima, nós vamos considerar apenas o método CComboBox::CreateList() para criar uma lista em detalhes. Outros métodos não possuem nada que nós já não estudamos nos artigos anteriores desta série. Antes disso, no entanto, nós devemos introduzir algumas alterações à classe da lista CListView.
A lista suspensa é sempre uma parte componente de um outro elemento. Isso significa que seu manipulador de eventos pode exigir o monitoramento do foco sobre o elemento que está anexado. No nosso caso, é o combobox. Adicione um campo e um método para a classe CListView para armazenar o ponteiro para o combobox para o qual a lista será anexada.
class CListView : public CElement { private: //--- Ponteiro para o elemento que gerencia a visibilidade da lista CElement *m_combobox; //--- public: //--- Armazena o ponteiro combobox void ComboBoxPointer(CElement &object) { m_combobox=::GetPointer(object); } };
Se a lista é suspensa, adicione uma verificação ao ponteiro para o método principal (público) para a criação de uma lista. Se durante a criação da lista acontecer de não haver nenhum ponteiro, então, a criação da interface gráfica será encerrada e uma mensagem relevante será impressa no registro.
Abaixo está uma versão resumida do método CListView::CreateListView():
//+------------------------------------------------------------------+ //| Cria uma lista | //+------------------------------------------------------------------+ bool CListView::CreateListView(const long chart_id,const int window,const int x,const int y) { //--- Retorna se não há nenhum ponteiro do formulário //--- Se a lista é suspensa, será necessário haver um ponteiro para o combobox para o qual ele será anexado if(CElement::IsDropdown()) { //--- Sai, se não houver nenhum ponteiro para a combobox if(::CheckPointer(m_combobox)==POINTER_INVALID) { ::Print(__FUNCTION__," > Antes de criar a lista suspensa, a classe deve ser passada " "para o ponteiro do combobox: CListView::ComboBoxPointer(CElement &object)"); return(false); } } //--- Inicializa as variáveis //--- Margens da borda //--- Cria um botão //--- Oculta o elemento se ele for uma janela de diálogo ou ela está minimizada //--- return(true); }
Agora, nós vamos voltar para o desenvolvimento da classe combobox (CComboBox). A propriedade indicando que a lista será suspensa terá que ser definida em uma fase muito precoce, que está no construtor da classe:
CComboBox::CComboBox(void) { //--- Modo da lista suspensa m_listview.IsDropdown(true); }
Ao criar uma lista, os ponteiros para o formulário e o combobox em que a lista será ligada tem de ser armazenada no início do método. Por favor note que a lista e o combobox devem ter o identificador em comum como neste caso, eles compõem um controle. Após a lista ser criada, ela deve ser oculta.
//+------------------------------------------------------------------+ //| Cria uma lista | //+------------------------------------------------------------------+ bool CComboBox::CreateList(void) { //--- Armazena os ponteiros para o formulário e o combobox m_listview.WindowPointer(m_wnd); m_listview.ComboBoxPointer(this); //--- Coordenadas int x=CElement::X2()-m_button_x_size; int y=CElement::Y()+m_button_y_size; //--- Define as propriedades m_listview.Id(CElement::Id()); m_listview.XSize(m_button_x_size); //--- Cria o controle if(!m_listview.CreateListView(m_chart_id,m_subwin,x,y)) return(false); //--- Oculta a lista m_listview.Hide(); return(true); }
Para definir o número de elementos da lista e preencher a lista com os valores, adicione os métodos relevantes para a classe CComboBox :
class CListView : public CElement { public: //--- Define (1) o tamanho da lista (número de elementos) e (2) a sua parte visível void ItemsTotal(const int items_total) { m_listview.ListSize(items_total); } void VisibleItemsTotal(const int visible_items_total) { m_listview.VisibleListSize(visible_items_total); } //--- Armazena o valor passado na lista pelo índice especificado void ValueToList(const int item_index,const string item_text); }; //+------------------------------------------------------------------+ //| Armazena o valor passado da lista pelo índice especificado | //+------------------------------------------------------------------+ void CComboBox::ValueToList(const int item_index,const string item_text) { m_listview.ValueToList(item_index,item_text); }
Para selecionar (destacar) o elemento da lista, crie o método CComboBox::SelectedItemByIndex(). O índice do elemento que deve ser destacado é o único argumento que tem de ser passado para este método. Então, realiza-se o destaque do elemento no método de mesmo nome da lista (CListView), onde o índice é ajustado, se o intervalo for excedido. Depois disso, o texto do elemento é armazenado e definido no botão combobox.
class CListView : public CElement { public: //--- Destacando o elemento pelo índice especificado void SelectedItemByIndex(const int index); }; //+------------------------------------------------------------------+ //| Destacando o elemento pelo índice especificado | //+------------------------------------------------------------------+ void CComboBox::SelectedItemByIndex(const int index) { //--- Destaca o elemento na lista m_listview.SelectedItemByIndex(index); //--- Armazena e definir o texto do botão m_button_text=m_listview.SelectedItemText(); m_button.Description(m_listview.SelectedItemText()); }
Nós também vamos precisar de um método para bloquear e desbloquear o elemento e também de um para obter o seu estado atual. Dependendo do novo estado do elemento, as cores correspondentes a este estado são definidas para seus objetos. Alguns exemplos serão mostrados mais adiante no artigo.
class CListView : public CElement { public: //--- Obter e definir o estado do elemento bool ComboBoxState(void) const { return(m_combobox_state); } void ComboBoxState(const bool state); }; //+------------------------------------------------------------------+ //| Alterando o estado do combobox | //+------------------------------------------------------------------+ void CComboBox::ComboBoxState(const bool state) { m_combobox_state=state; //--- Define as cores correspondentes ao estado atual para o objeto m_label.Color((state)? m_label_color : m_label_color_off); m_button.Color((state)? m_button_text_color : m_button_text_color_off); m_button.BackColor((state)? m_button_color : m_button_color_off); m_button.BorderColor((state)? m_button_border_color : m_button_border_color_off); m_drop_arrow.State(state); }
Quando o cursor do mouse está sobre os objetos do elemento, será possível mudar sua cor usando o método CComboBox::ChangeObjectsColor() quando este elemento estiver disponível:
class CListView : public CElement { public: //--- Mudando a cor do objeto quando o cursor estiver sobre ele void ChangeObjectsColor(void); }; //+------------------------------------------------------------------+ //| Alterar a cor do objeto quando o cursor estiver pairando sobre ele| //+------------------------------------------------------------------+ void CComboBox::ChangeObjectsColor(void) { //--- Sai, se o elemento está bloqueado if(!m_combobox_state) return; //--- Muda a cor do objeto CElement::ChangeObjectColor(m_label.Name(),CElement::MouseFocus(),OBJPROP_COLOR,m_label_color,m_label_color_hover,m_label_color_array); CElement::ChangeObjectColor(m_button.Name(),CElement::MouseFocus(),OBJPROP_BGCOLOR,m_button_color,m_button_color_hover,m_button_color_array); }
O método CComboBox::ChangeObjectsColor() deve ser chamado no timer do controle CComboBox::OnEventTimer(). Seguindo em frente, deve-se mencionar que um combobox pode ser parte de um elemento composto mais complexo, que também é suspenso. Em um dos artigos futuros, nós vamos falar de um calendário suspenso - um exemplo de tal elemento. A parte composto dela é um combobox. As condições para alterar a cor do timer para tal elemento será formado como é mostrado no código abaixo:
- Se este for um elemento suspenso e a lista estiver oculta.
- Se a primeira condição não for cumprida, verifique a disponibilidade do formulário e do próprio elemento.
//+------------------------------------------------------------------+ //| Timer | //+------------------------------------------------------------------+ void CComboBox::OnEventTimer(void) { //--- Se este for um elemento suspenso e a lista estiver oculta. if(CElement::IsDropdown() && !m_listview.IsVisible()) ChangeObjectsColor(); else { //--- Se o formulário e o elemento não estiverem bloqueados if(!m_wnd.IsLocked() && m_combobox_state) ChangeObjectsColor(); } }
Vamos criar o método CComboBox::ChangeComboboxListState() para gerenciar a visibilidade da lista do combobox. Este método irá alterar o estado atual da combobox para a uma oposta. No início do método, haverá uma verificação para a disponibilidade do elemento. Se a combobox for bloqueada, o programa irá sair do método. Em seguida, o código será dividido em dois ramos:
- Se a visualização da lista já for visível, ela será ocultada aqui e as cores correspondentes serão definidas para o botão da combobox. Depois disso, se este não for um elemento suspenso, o formulário deve ser desbloqueado e o identificador do elemento ativo deve ser redefinido.
- Se a lista está oculta, ela deve voltar a ser visível e as cores correspondentes a este estado devem ser definidas para o botão do combobox. No final do ramo, a formulário deve ser bloqueado e o identificador do elemento de ativação deve ser armazenado.
class CListView : public CElement { public: //--- Altera o estado atual da combobox para o oposto void ChangeComboBoxListState(void); }; //+------------------------------------------------------------------+ //| Altera o estado atual da combobox para o oposto | //+------------------------------------------------------------------+ void CComboBox::ChangeComboBoxListState(void) { //--- Sai, se o elemento está bloqueado if(!m_combobox_state) return; //--- Se a lista é visível if(m_listview.IsVisible()) { //--- Oculta a lista m_listview.Hide(); //--- Define as cores m_label.Color(m_label_color_hover); m_button.BackColor(m_button_color_hover); //--- Se este não for um elemento suspenso if(!CElement::IsDropdown()) { //--- Desbloqueia o formulário m_wnd.IsLocked(false); m_wnd.IdActivatedElement(WRONG_VALUE); } } //--- Se a lista está oculta else { //--- Mostra a lista m_listview.Show(); //--- Define as cores m_label.Color(m_label_color_hover); m_button.BackColor(m_button_color_pressed); //--- Bloqueia o formulário m_wnd.IsLocked(true); m_wnd.IdActivatedElement(CElement::Id()); } }
Métodos para Manipular os Eventos do Controle
Nós podemos seguir definindo o controle de manipulação de eventos CComboBox::OnEvent(). Para isso, nós precisaremos de mais dois métodos privados auxiliares:
- CComboBox::OnClickButton() - o evento de pressionar o botão do combobox será monitorado neste método.
- CComboBox::CheckPressedOverButton() - o estado do botão esquerdo do mouse sobre o botão combobox será rastreado aqui.
class CListView : public CElement { private: //--- Manipulando o pressionamento de um botão bool OnClickButton(const string clicked_object); //--- Verifica o pressionamento do botão esquerdo do mouse sobre o botão combobox void CheckPressedOverButton(void); };
O código do método CComboBox::OnClickButton() é muito simples. Ele irá conter apenas uma verificação do nome do objeto em que ocorreu o pressionamento e uma chamada do método CComboBox::ChangeComboBoxListState() discutido anteriormente, na qual irá gerir a visibilidade da lista combobox, como é mostrado no código abaixo.
//+------------------------------------------------------------------+ //| Pressionando o botão combobox | //+------------------------------------------------------------------+ bool CComboBox::OnClickButton(const string clicked_object) { //--- Sai, se o nome do objeto for diferente if(clicked_object!=m_button.Name()) return(false); //--- Altera o estado de exibição da lista ChangeComboboxListState(); return(true); }
O código do método CComboBox::CheckPressedOverButton() é apresentado abaixo. Uma verificação para a disponibilidade do formulário e o identificador do elemento ativo é realizado no início do método. O programa irá deixar o método se o formulário for bloqueado e os identificadores não corresponderem.
Então, se não há foco sobre o elemento, nós verificamos a presença do foco sobre a lista e o estado da barra de rolagem. Se o foco acontecer de não ser sobre a lista ou a barra de rolagem está no modo do movimento do deslizador, então o programa irá deixar o método. Já é de seu conhecimento que o deslizador da barra de rolagem, se estiver no modo de movimento, ele pode ser movido mesmo se o cursor sair da área do mesmo. Se nenhuma dessas condições forem atendidas, então:
(1) a lista está oculta,
(2) as cores dos objetos do elemento foram restaurados,
e no final deste bloco de código, se os identificadores não forem iguais e o elemento não for suspenso, (3) o formulário terá de ser desbloqueado. Deixe-me lembrá-lo que um formulário pode ser desbloqueado apenas pelo elemento que o bloqueou.
No caso, quando há um foco sobre o elemento, em primeiro lugar, é realizado a verificação da lista. Se a lista é visível, então não há nenhum ponto em continuar e o programa deixará o método. Se a lista está oculta, então, as cores correspondentes são definidas de acordo com o foco sobre o botão combobox.
//+------------------------------------------------------------------+ //| Verifica o pressionamento do botão esquerdo do mouse sobre um botão | //+------------------------------------------------------------------+ void CComboBox::CheckPressedOverButton(void) { //--- Sai, se o formulário for bloqueado e seus identificadores não corresponderem if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id()) return; //--- Se não houver foco if(!CElement::MouseFocus()) { //--- Sai, se o foco não estiver sobre a lista ou a barra de rolagem estiver ativada if(m_listview.MouseFocus() || m_listview.ScrollState()) return; //--- Oculta a lista m_listview.Hide(); //--- Restaura as cores ResetColors(); //--- Se os identificadores corresponderem e o elemento não é suspenso if(m_wnd.IdActivatedElement()==CElement::Id() && !CElement::IsDropdown()) //--- Desbloqueia o formulário m_wnd.IsLocked(false); } //--- Se houver foco else { //--- Sai, se a lista é visível if(m_listview.IsVisible()) return; //--- Define a cor considerando o foco if(m_button.MouseFocus()) m_button.BackColor(m_button_color_pressed); else m_button.BackColor(m_button_color_hover); } }
A chamada do método CComboBox::OnClickButton() deve ser passado no bloco de manipulação do evento de pressionar o objecto gráfico, que pode ser identificado pelo identificador CHARTEVENT_OBJECT_CLICK:
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CComboBox::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Manipula o evento do clique do botão esquerdo do mouse sobre o objeto if(id==CHARTEVENT_OBJECT_CLICK) { //--- Pressionando o botão combobox if(OnClickButton(sparam)) return; } }
Antes de chamar o método CComboBox::CheckPressedOverButton(), as seguintes verificações devem ser passadas no bloco de manipulação de evento do movimento do cursor do mouse CHARTEVENT_MOUSE_MOVE:
- a visibilidade do elemento;
- a disponibilidade do elemento;
- estado do botão esquerdo do mouse.
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CComboBox::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Manipulação do evento do movimento do cursor if(id==CHARTEVENT_MOUSE_MOVE) { //--- Sai se o elemento está oculto if(!CElement::IsVisible()) return; //--- Coordenadas int x=(int)lparam; int y=(int)dparam; //--- Verificando o foco sobre os elementos CElement::MouseFocus(x>CElement::X() && x<CElement::X2() && y>CElement::Y() && y<CElement::Y2()); m_button.MouseFocus(x>m_button.X() && x<m_button.X2() && y>m_button.Y() && y<m_button.Y2()); //--- Sai, se o elemento está bloqueado if(!m_combobox_state) return; //--- Sai, se o botão esquerdo do mouse for liberado if(sparam=="0") return; //--- Verifica o pressionamento do botão esquerdo do mouse sobre um botão de divisão CheckPressedOverButton(); return; } }
No momento do pressionamento sobre um dos elementos da lista, o evento personalizado ON_CLICK_LIST_ITEM é gerado, como é mostrado no código abaixo. Esta mensagem deve ser recebido no manipulador de eventos combobox. Se os identificadores do elemento corresponderem, devemos receber a mensagem que veio da lista anexado ao combobox, em seguida, armazenar o texto do elemento destacado da lista e ocultar a lista usando o método CComboBox::ChangeComboBoxListState().
Para garantir a conexão com o aplicativo em desenvolvimento, esta mensagem com o identificador ON_CLICK_LIST_ITEM pode ser recebida na classe personalizada CProgram. A mensagem também poderá ser enviada a partir da combobox com o seu (1) identificador de evento exclusivo, (2) o identificador de elemento (3) e a descrição combobox. Para ampliar a capacidade de identificação do evento dos controles, nós vamos usar esta opção também. Adicione um identificador exclusivo para o controle combobox ao arquivo Defines.mqh.
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #define ON_CLICK_COMBOBOX_ITEM (17) // Selecionando um elemento na lista combobox
Neste caso, adicione uma linha destacada no código abaixo em azul para o manipulador de evento do controle:
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CComboBox::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Manipulando o evento de pressionar um elemento da lista if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM) { //--- Se os identificadores forem iguais if(lparam==CElement::Id()) { //--- Armazena e definir o texto do botão m_button_text=m_listview.SelectedItemText(); m_button.Description(m_listview.SelectedItemText()); //--- Altera o estado de exibição da lista ChangeComboBoxListState(); //--- Envia uma mensagem sobre ele ::EventChartCustom(m_chart_id,ON_CLICK_COMBOBOX_ITEM,CElement::Id(),0,m_label_text); } //--- return; } }
Nós também podemos definir para ocultar a lista quando as propriedades do gráfico são alteradas. Para isso, um evento com o identificador CHARTEVENT_CHART_CHANGE deve ser tratado como é mostrado no código abaixo:
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CComboBox::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Manipulando o evento de alteração das propriedades do gráfico if(id==CHARTEVENT_CHART_CHANGE) { //--- Sai, se o elemento está bloqueado if(!m_combobox_state) return; //--- Oculta a lista m_listview.Hide(); //--- Restaura as cores ResetColors(); //--- Desbloqueia o formulário m_wnd.IsLocked(false); m_wnd.IdActivatedElement(WRONG_VALUE); return; } }
A classe para criar o controle combobox está pronta para o teste, mas antes disso ela deve ser conectada com o motor da biblioteca para o seu funcionamento correto.
Conexão da Classe de Controle com o Motor da Biblioteca
A conexão de um controle com o motor de biblioteca é realizada apenas com algumas ações simples:
1.Inclua o arquivo com a classe de controle no arquivos raiz da biblioteca WndContainer.mqh.
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "ComboBox.mqh"
2. Se necessário, crie um array privado para o elemento e um método para criar o tamanho deste array. No nosso caso, será necessário um array privado para as listas suspensas.
class CWndContainer { protected: //--- Estrutura dos arrays de elementos struct WindowElements { //--- Array comum de todos os objetos //--- Array comum de todos os elementos //---Arrays privados de Elementos // Array dos menus de contexto //--- Array dos menus principais //--- Dicas de contexto //--- Array da lista suspensa de diferentes tipos CElement *m_drop_lists[]; }; //--- Array dos arrays de elemento para cada janela WindowElements m_wnd[]; //--- public: //--- Número da lista suspensa int DropListsTotal(const int window_index); }; //+------------------------------------------------------------------+ //| Retorna o número da lista suspensa pelo índice da janela especificada | //+------------------------------------------------------------------+ int CWndContainer::DropListsTotal(const int window_index) { if(window_index>=::ArraySize(m_wnd)) { ::Print(PREVENTING_OUT_OF_RANGE); return(WRONG_VALUE); } //--- return(::ArraySize(m_wnd[window_index].m_drop_lists)); }
3.Cria um método privado para adicionar os ponteiros ao array privado. Neste caso, nós precisamos obter um ponteiro da lista do controle combobox e adicioná-lo ao array privado. Além disso, os ponteiros do objeto da lista e a barra de rolagem da lista devem ser adicionados ao array comum de objetos. O código do método CWndContainer::AddComboBoxElements() é apresentado abaixo.
class CWndContainer { private: //--- Armazena os ponteiros para os elementos do menu de contexto na base //--- Armazena os ponteiros para os elementos do menu principal na base //--- Armazena os ponteiros aos elementos do botão de divisão na base //--- Armazena os ponteiros aos elementos dicas de contexto na base //--- Armazena os ponteiros para a lista de objetos na base //--- Armazena os ponteiros para os elementos da lista suspensa na base bool AddComboBoxElements(const int window_index,CElement &object); }; //+------------------------------------------------------------------+ //| Armazena os ponteiros para os elementos da lista suspensa no array privado | //+------------------------------------------------------------------+ bool CWndContainer::AddComboBoxElements(const int window_index,CElement &object) { //--- Retorna, se este não for uma dica de contexto if(object.ClassName()!="CComboBox") return(false); //--- Obtém o ponteiro combobox CComboBox *cb=::GetPointer(object); //--- for(int i=0; i<2; i++) { //--- Incrementa o array de elementos int size=::ArraySize(m_wnd[window_index].m_elements); ::ArrayResize(m_wnd[window_index].m_elements,size+1); //--- Adiciona a lista para a base if(i==0) { CListView *lv=cb.GetListViewPointer(); m_wnd[window_index].m_elements[size]=lv; AddToObjectsArray(window_index,lv); //--- Adiciona o ponteiro para o array privado AddToRefArray(lv,m_wnd[window_index].m_drop_lists); } //--- Adiciona a barra de rolagem para a base else if(i==1) { CScrollV *sv=cb.GetScrollVPointer(); m_wnd[window_index].m_elements[size]=sv; AddToObjectsArray(window_index,sv); } } //--- return(true); }
4.Se forem necessárias as ações do item 3, não se esqueça de chamar o método CWndContainer::AddComboBoxElements() no método principal CWndContainer::AddToElementsArray() onde as chamadas de tais métodos devem ser localizadas.
//+------------------------------------------------------------------+ //| 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 //--- Armazena os ponteiros para os objetos do menu principal na base //--- Armazena os ponteiros aos elementos do botão de divisão na base //--- Armazena os ponteiros aos elementos dicas de contexto na base //--- Armazena os ponteiros para a lista de objetos na base //--- Armazena os ponteiros para os objetos de controle combobox na base if(AddComboBoxElements(window_index,object)) return; }
Quando um elemento é suspenso, às vezes ele pode exceder os limites do formulário, dependendo da localização deste elemento no formulário. A localização do cursor do mouse deve ser controlada em todos os momentos e a rolagem do gráfico deve ser desativada se o gráfico for mais de um desses elementos. Isso permitirá evitar rolar o gráfico quando o botão esquerdo do mouse for pressionado sobre o elemento suspenso. Para isso, nós já escrevemos o método CWndEvents::SetChartState() na classe CWndEvents. Agora, ele tem de ser enriquecido com a verificação da lista suspensa. No código abaixo, esta parte é destacada em amarelo:
//+------------------------------------------------------------------+ //| Define o estado do gráfico | //+------------------------------------------------------------------+ void CWndEvents::SetChartState(void) { int awi=m_active_window_index; //--- Para identificar o evento quando a gestão deve ser desativada bool condition=false; //--- Verifica a janela int windows_total=CWndContainer::WindowsTotal(); for(int i=0; i<windows_total; i++) { //--- Move para o próximo, se este formulário está oculto if(!m_windows[i].IsVisible()) continue; //--- Verifica as condições no manipulador interno do formulário m_windows[i].OnEvent(m_id,m_lparam,m_dparam,m_sparam); //--- Se houver um foco, marque-o if(m_windows[i].MouseFocus()) { condition=true; break; } } //--- Verifica a lista suspensa if(!condition) { //--- Obtém o total das listas suspensas int drop_lists_total=CWndContainer::DropListsTotal(awi); for(int i=0; i<drop_lists_total; i++) { //--- Obtém o ponteiro para a exibição da lista suspensa CListView *lv=m_wnd[awi].m_drop_lists[i]; //--- Se a lista é ativada (visível) if(lv.IsVisible()) { //--- Verifica a focagem através da lista e o estado da sua barra de rolagem if(m_wnd[awi].m_drop_lists[i].MouseFocus() || lv.ScrollState()) { condition=true; break; } } } } //--- Verifica o foco dos menus de contexto if(!condition) { //--- Verifique o total de menus de contexto suspensos int context_menus_total=CWndContainer::ContextMenusTotal(awi); for(int i=0; i<context_menus_total; i++) { //--- Se o foco está sobre o menu de contexto if(m_wnd[awi].m_context_menus[i].MouseFocus()) { condition=true; break; } } } //--- Define o estado do gráfico em todas os formulários for(int i=0; i<windows_total; i++) m_windows[i].CustomEventChartState(condition); }
Nós temos tudo pronto para testar o controle combobox.
Testando o Controle ComboBox na Interface Gráfica do Aplicativo Personalizado
Vamos testar tudo o que foi implementado na interface gráfica do aplicativo personalizado na quinta parte da série. O teste no artigo anterior foi finalizado com três modos da lista. Vamos manter a lista e adicionar quatro combobox para a interface gráfica da aplicação. Coloque duas combobox de forma que nos permitirá testar o impacto da lista suspensa sobre a lista estática que será localizada abaixo dela. Além disso, nós temos que testar o funcionamento do método CWndEvents::SetChartState(). Para isso, nós vamos colocar as combobox para que quando as listas suspensa serem visíveis, elas ultrapassem os limites do formulário que eles estão ligados.
Na classe personalizada CProgram da aplicação do teste, a classe da combobox já está disponível através das classes base. Crie quatro instâncias da classe do tipo CComboBox e declare quatro métodos para cada um deles especificando as margens a partir do ponto superior esquerdo do formulário como é mostrado no código abaixo.
//+------------------------------------------------------------------+ //| Classe para a criação de um aplicativo | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Combobox CComboBox m_combobox1; CComboBox m_combobox2; CComboBox m_combobox3; CComboBox m_combobox4; //--- private: //--- Combobox 1 #define COMBOBOX1_GAP_X (7) #define COMBOBOX1_GAP_Y (50) bool CreateComboBox1(const string text); //--- Combobox 2 #define COMBOBOX2_GAP_X (160) #define COMBOBOX2_GAP_Y (50) bool CreateComboBox2(const string text); //--- Combobox 3 #define COMBOBOX3_GAP_X (7) #define COMBOBOX3_GAP_Y (202) bool CreateComboBox3(const string text); //--- Combobox 4 #define COMBOBOX4_GAP_X (160) #define COMBOBOX4_GAP_Y (202) bool CreateComboBox4(const string text); };
Consideremos um destes métodos já que todos eles são idênticos com exceção das propriedades definidas pelo utilizador. Por exemplo, nós vamos bloquear a quarta combobox logo após a sua criação. Abaixo está a sequência de ações para criar o controle combobox.
- Armazene o ponteiro do formulário na classe de controle.
- Calcule as coordenadas.
- Declare e inicialize imediatamente o array de texto para os elementos da lista.
- Defina as propriedades do controle. A maioria deles são inicializados por valores padrão. Os valores podem ser redefinidos antes de criar o controle, se for necessário.
- Armazene os valores dos elementos na lista combobox.
- Se necessário, defina as propriedades para a exibição da lista e a barra de rolagem.
- Destaque o elemento na lista. O primeiro elemento (0) será destacado por padrão.
- Cria o controle.
- O controlo pode ser bloqueado, se necessário. Nós vamos bloquear a quarta combobox em nosso aplicativo de teste, como um exemplo.
- No final do método, passe o objeto para a classe base para armazenar o ponteiro.
A sequência de ações pode ser diferente. O importante é manter a sequência das três ações principais:
- Adicionando o ponteiro do formulário para a classe do controle. Caso contrário, a criação da interface gráfica será encerrada. A razão da falha pode ser encontrada a partir das mensagens no registro.
- Criando o controle.
- Armazenando o ponteiro de controle na base do objeto.
//+------------------------------------------------------------------+ //| Cria o combobox 1 | //+------------------------------------------------------------------+ bool CProgram::CreateComboBox1(const string text) { //--- Número total de elementos de exibição da lista #define ITEMS_TOTAL1 8 //--- Passa o objeto do formulário m_combobox1.WindowPointer(m_window1); //--- Coordenadas int x=m_window1.X()+COMBOBOX1_GAP_X; int y=m_window1.Y()+COMBOBOX1_GAP_Y; //--- Array dos valores de elementos da lista string items_text[ITEMS_TOTAL1]={"FALSE","item 1","item 2","item 3","item 4","item 5","item 6","item 7"}; //--- Define as propriedades antes da criação m_combobox1.XSize(140); m_combobox1.YSize(18); m_combobox1.LabelText(text); m_combobox1.ButtonXSize(70); m_combobox1.AreaColor(clrWhiteSmoke); m_combobox1.LabelColor(clrBlack); m_combobox1.LabelColorHover(clrCornflowerBlue); m_combobox1.ButtonBackColor(C'206,206,206'); m_combobox1.ButtonBackColorHover(C'193,218,255'); m_combobox1.ButtonBorderColor(C'150,170,180'); m_combobox1.ButtonBorderColorOff(C'178,195,207'); m_combobox1.ItemsTotal(ITEMS_TOTAL1); m_combobox1.VisibleItemsTotal(5); //--- Armazena os valores dos elementos na lista combobox. for(int i=0; i<ITEMS_TOTAL1; i++) m_combobox1.ValueToList(i,items_text[i]); //--- Obtém o ponteiro de exibição da lista CListView *lv=m_combobox1.GetListViewPointer(); //--- Define as propriedades da lista lv.LightsHover(true); lv.SelectedItemByIndex(lv.SelectedItemIndex()==WRONG_VALUE ? 2 : lv.SelectedItemIndex()); //--- Cria o controle if(!m_combobox1.CreateComboBox(m_chart_id,m_subwin,x,y)) return(false); //--- Adiciona o objeto para o array comum dos grupos de objetos CWndContainer::AddToElementsArray(0,m_combobox1); return(true); }
Para criar a interface gráfica, a chamada dos métodos para a criação dos controles deve ser colocada no método principal. No nosso caso, este é o CProgram::CreateTradePanel(). Abaixo está uma versão resumida do método:
//+------------------------------------------------------------------+ //| Cria o painel de negociação | //+------------------------------------------------------------------+ bool CProgram::CreateTradePanel(void) { //--- Criação do formulário 1 para os controles //--- Criação dos controles: // Menu principal //--- Menus de contexto //--- Criando a barra de status //--- Combobox if(!CreateComboBox1("Combobox 1:")) return(false); if(!CreateComboBox2("Combobox 2:")) return(false); if(!CreateComboBox3("Combobox 3:")) return(false); if(!CreateComboBox4("Combobox 4:")) return(false); //--- Lista //--- Redesenho do gráfico m_chart.Redraw(); return(true); }
Adicione o bloco de identificação de mensagens a partir das combobox com o identificador ON_CLICK_COMBOBOX_ITEM para o manipulador de eventos da classe personalizada CProgram. Vamos garantir que, se uma mensagem a partir do terceiro combobox for recebida, então, dependendo de qual lista foi selecionada, o estado do quarto combobox irá mudar. No nosso caso, a seleção de qualquer elemento da lista, exceto o primeiro (0) fará com que a quarta combobox esteja disponível. Selecionando o primeiro elemento irá bloqueá-lo.
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Seleção do elemento no evento combobox if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM) { if(sparam==m_combobox1.LabelText()) ::Print(__FUNCTION__," > Esta mensagem é do combobox 1 > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); else if(sparam==m_combobox2.LabelText()) ::Print(__FUNCTION__," > Esta mensagem é do combobox 2 > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); //--- Manipula a mensagem a partir do terceiro combobox else if(sparam==m_combobox3.LabelText()) { ::Print(__FUNCTION__," > Esta mensagem é do combobox 3 > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); //--- Se o valor especificado for selecionado, desativa o combobox 4 if(m_combobox3.ButtonText()=="FALSE") m_combobox4.ComboBoxState(false); //--- Se outro valor tiver sido selecionado, ativa o combobox 4 else m_combobox4.ComboBoxState(true); } else if(sparam==m_combobox4.LabelText()) ::Print(__FUNCTION__," > Esta mensagem é do combobox 4 > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); } }
Se compilarmos o programa e carregarmos ao gráfico, o resultado será como na imagem abaixo:
Fig. 2. Testando o controle combobox.
Nós completamos o desenvolvimento da classe CComboBox para a criação do combobox.
Conclusão
Neste artigo nós consideramos o controle composto combobox. A esquemática da biblioteca para a criação das interfaces gráficas no atual estágio de desenvolvimento é parecido com a imagem abaixo:
Fig. 3. Estrutura da biblioteca no atual estágio de desenvolvimento.
O próximo artigo daremos início a sexta parte da série dedicada ao desenvolvimento da biblioteca para a criação das interfaces gráficas. Lá, nós vamos escrever as classes para criar os controles da caixa de seleção, de edição e seus tipos mistos.
Você pode baixar o material da parte V e testar o seu funcionamento. 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 da quinta parte:
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2381
- 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