Interfaces Gráficas III: Grupos de Botões Simples e Multifuncionais (Capítulo 2)
Conteúdo
- Introdução
- Desenvolvimento da Classe para Criar os Grupos de Botões Simples
- Desenvolvimento da Classe para Criar os Grupos de Botões de Radio
- Desenvolvimento da Classe para Criar os Grupos de Botões com Ícone
- 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.
O primeiro capítulo desta terceira parte se tratava dos botões simples e multifuncionais. O segundo artigo se dedicará aos grupos de botões interconectados entre si. Eles são usados para criar os elementos nas aplicações, que permitem ao usuário selecionar uma das opções a partir de um determinado conjunto (grupo).
Desenvolvimento da Classe para Criar os Grupos de Botões Simples
Um grupo de botões simples é em sua essência um array de objetos gráficos do tipo OBJ_BUTTON. A característica diferencial de tais controles é que apenas um botão do grupo pode ser pressionado de cada vez. Nesta fase, a classe deste controle pode ser criada de duas maneiras:
- através da criação de um grupo de controle já implementado do tipo CSimpleButton;
- através da criação de um grupo de objetos primitivos do tipo CButton.
A segunda opção é mais simples, uma vez que ela não exige a criação de um método adicional para cada controle do tipo CSimpleButton para chegar à base de ponteiros. Por isso nós vamos utilizar esta função.
Crie o arquivo ButtonsGroup.mqh com a classe CButtonsGroup na pasta Controls que contém todos os controles e inclua-o no arquivo WndContainer.mqh. A classe deve ter os métodos virtuais e o ponteiro do formulário como foi demonstrado para todos os outros controles previamente desenvolvidos. Nós não vamos discuti-los aqui, indo direto para a descrição das propriedades e dos métodos para a sua construção.
Algumas propriedades serão comuns para cada botão dentro de um grupo, mas haverá algumas que serão únicas também. Abaixo encontramos dois grupos de propriedades relacionadas com a aparência de botões.
Propriedades comuns dos botões:
- alturas;
- cor dos botões bloqueados;
- cor do quadro nos modo disponível e bloqueado;
- cor do texto em diferentes estados;
- prioridade para o clique esquerdo do mouse.
Propriedades únicas dos botões:
- estado do botão (pressionado/solto);
- margens a partir do ponto da borda do formulário;
- texto;
- largura;
- cores dos botões em diferentes estados;
- gradientes para os botões.
As margens para cada botão permitirão organizar os botões em qualquer sequência.
Fig. 1. Exemplos dos botões organizados em um grupo.
Declare os arrays dinâmicos para os objetos do tipo CButton e as propriedades únicas dos botões, como é mostrado no código abaixo.
//+------------------------------------------------------------------+ //| Classe para criar um grupo de botões simples | //+------------------------------------------------------------------+ class CButtonsGroup : public CElement { private: //--- Objetos para a criação de um botão CButton m_buttons[]; //--- Gradientes do botão struct ButtonsGradients { color m_buttons_color_array[]; }; ButtonsGradients m_buttons_total[]; //--- Propriedades do botão: // Arrays de propriedades únicas dos botões bool m_buttons_state[]; int m_buttons_x_gap[]; int m_buttons_y_gap[]; string m_buttons_text[]; int m_buttons_width[]; color m_buttons_color[]; color m_buttons_color_hover[]; color m_buttons_color_pressed[]; //--- Altura dos botões int m_button_y_size; //--- Cor dos botões bloqueados color m_back_color_off; //--- Cor do quadro no modo ativo e bloqueado color m_border_color; color m_border_color_off; //--- Cor do texto color m_text_color; color m_text_color_off; color m_text_color_pressed; //--- Prioridade do botão esquerdo do mouse int m_buttons_zorder; //--- public: //--- Número de botões int ButtonsTotal(void) const { return(::ArraySize(m_buttons)); } //--- (1) Altura dos botões void ButtonYSize(const int y_size) { m_button_y_size=y_size; } //--- (1) Cores de fundo de um botão bloqueado e um quadro ((2) disponível/(3) bloqueado) void BackColorOff(const color clr) { m_back_color_off=clr; } void BorderColor(const color clr) { m_border_color=clr; } void BorderColorOff(const color clr) { m_border_color_off=clr; } //--- Cor do texto void TextColor(const color clr) { m_text_color=clr; } void TextColorOff(const color clr) { m_text_color_off=clr; } void TextColorPressed(const color clr) { m_text_color_pressed=clr; } };
O tamanho dos arrays dinâmicos serão definidos no momento da formação de um grupo de botões antes de sua criação (anexando ao gráfico). Toda vez que o método CButtonsGroup::AddButton() for chamado, os arrays serão incrementados de um elemento, sendo preenchidos com os parâmetros passados.
class CButtonsGroup : public CElement { public: //--- Adiciona um botão com as propriedades especificadas antes de sua criação void AddButton(const int x_gap,const int y_gap,const string text,const int width, const color button_color,const color button_color_hover,const color button_color_pressed); }; //+------------------------------------------------------------------+ //| Adiciona um botão | //+------------------------------------------------------------------+ void CButtonsGroup::AddButton(const int x_gap,const int y_gap,const string text,const int width, const color button_color,const color button_color_hover,const color pressed_button_color) { //--- Aumenta o tamanho do array por um elemento int array_size=::ArraySize(m_buttons); ::ArrayResize(m_buttons,array_size+1); ::ArrayResize(m_buttons_total,array_size+1); ::ArrayResize(m_buttons_state,array_size+1); ::ArrayResize(m_buttons_x_gap,array_size+1); ::ArrayResize(m_buttons_y_gap,array_size+1); ::ArrayResize(m_buttons_text,array_size+1); ::ArrayResize(m_buttons_width,array_size+1); ::ArrayResize(m_buttons_color,array_size+1); ::ArrayResize(m_buttons_color_hover,array_size+1); ::ArrayResize(m_buttons_color_pressed,array_size+1); //--- Armazena os valores dos parâmetros passados m_buttons_x_gap[array_size] =x_gap; m_buttons_y_gap[array_size] =y_gap; m_buttons_text[array_size] =text; m_buttons_width[array_size] =width; m_buttons_color[array_size] =button_color; m_buttons_color_hover[array_size] =button_color_hover; m_buttons_color_pressed[array_size] =pressed_button_color; m_buttons_state[array_size] =false; }
Ao criar um grupo de botões, o processo de criação da interface gráfica será interrompido e uma mensagem relevante serão impressa no registro, se antes disso não houver botões que foram adicionados usando o método CButtonsGroup::AddButton(). Os botões serão criados em um loop como é mostrado na versão reduzida do método CButtonsGroup:ÇCreateButtons() no código abaixo.
class CButtonsGroup : public CElement { public: //--- Métodos para a criação de um botão bool CreateButtonsGroup(const long chart_id,const int subwin,const int x,const int y); //--- private: bool CreateButtons(void); }; //+------------------------------------------------------------------+ //| Cria os botões | //+------------------------------------------------------------------+ bool CButtonsGroup::CreateButtons(void) { //--- Coordenadas int l_x =m_x; int l_y =m_y; //--- Obtém o número de botões int buttons_total=ButtonsTotal(); //--- Se não existir um botão em um grupo, reporta if(buttons_total<1) { ::Print(__FUNCTION__," > Este método era para ser chamado, " "se um grupo conter pelo menos um botão! Use the CButtonsGroup::AddButton() method"); return(false); } //--- Cria o número especificado de botões for(int i=0; i<buttons_total; i++) { //--- Elaborando o nome do objeto //--- Calcula as coordenadas //--- Configura um botão //--- Define as propriedades //--- Armazena as margens da borda do painel, coordenadas e o tamanho //--- Inicializa o array de gradiente //--- Armazena o ponteiro de objeto } //--- return(true); }
Vamos criar dois modos para este tipo de grupo de botão. Para configurar isso, adicione um campo especial e o método para a classe, como é mostrado no código abaixo. O valor padrão será false, significando que o modo que permite ter todos os botões soltos num grupo está ativado. O valor padrão true significa que um botão em um grupo será sempre pressionado.
class CButtonsGroup : public CElement { public: //--- O modo do botão de radio bool m_radio_buttons_mode; //--- public: //--- Estabelecendo o modo de botão de radio void RadioButtonsMode(const bool flag) { m_radio_buttons_mode=flag; } };
Igual como em qualquer outro controle, esta classe deve conter um método para bloquear o controle. Para este tipo de grupo de botão, é importante garantir que, ao desbloquear um botão previamente pressionado ele restaura a aparência de seu estado.
class CButtonsGroup : public CElement { private: //--- Disponível/bloqueado bool m_buttons_group_state; //--- public: //--- Estado geral do grupo de botões (disponível/bloqueado) bool ButtonsGroupState(void) const { return(m_buttons_group_state); } void ButtonsGroupState(const bool state); }; //+------------------------------------------------------------------+ //| Altera o estado dos botões | //+------------------------------------------------------------------+ void CButtonsGroup::ButtonsGroupState(const bool state) { m_buttons_group_state=state; //--- int buttons_total=ButtonsTotal(); for(int i=0; i<buttons_total; i++) { m_buttons[i].State(false); m_buttons[i].Color((state)? m_text_color : m_text_color_off); m_buttons[i].BackColor((state)? m_buttons_color[i]: m_back_color_off); m_buttons[i].BorderColor((state)? m_border_color : m_border_color_off); } //--- Pressiona o botão se ele foi pressionado antes do bloqueio if(m_buttons_group_state) { if(m_selected_button_index!=WRONG_VALUE) { m_buttons_state[m_selected_button_index]=true; m_buttons[m_selected_button_index].Color(m_text_color_pressed); m_buttons[m_selected_button_index].BackColor(m_buttons_color_pressed[m_selected_button_index]); } } }
Nós precisamos de um método para alternar o estado do botão quando pressionado. Nós também vamos exigir os campos e métodos para armazenar e obter o texto e o índice do botão em destaque.
No início do método para alternar o estado do botão no código abaixo, há uma verificação da quantidade de botões em um grupo. Se for verificado que não há botões, uma mensagem relevante será impressa no registro. O programa não vai sair do método. O programa irá continuar e em algum momento ele encontrará um erro que irá exceder o tamamho do array m_buttons_state[]. Isto significa que o desenvolvedor do aplicativo deve adicionar pelo menos um botão ao grupo para evitar tal erro. Depois da verificação, se existir pelo menos um botão, o programa irá ajustar o índice passado no caso dele exceder o tamanho do array. Em seguida, o estado do botão é alterado pelo índice especificado. Depois disso, todos os botões, exceto o pressionado, tornam-se soltos (a cor relevante está definida) em um loop, considerando o modo do grupo. Em outras palavras, se o modo do botão de radio está ativado, pelo menos um botão deve estar pressionado sempre, então, durante cada iteração é realizado uma verificação de uma condição de igualdade do índice passado para o índice atual do loop. Se o modo está ativado quando todos os botões podem ser soltos, então, a parte da verificação do índice, é verificado o estado atual do botão. Se a condição for atendida, então a sinalização do botão pressionado é definida, independentemente do modo. No final do método, na classe onde o texto e o índice do botão destacado serão guardados, os seus campos terão valores correspondentes a este sinalizador. Se não houver um botão pressionado, os valores são definidos como vazios ("" e WRONG_VALUE).
class CButtonsGroup : public CElement { private: //--- (1) Texto e (2) o índice do botão em destaque string m_selected_button_text; int m_selected_button_index; //--- public: //--- Retorna (1) o texto e (2) o índice do botão em destaque string SelectedButtonText(void) const { return(m_selected_button_text); } int SelectedButtonIndex(void) const { return(m_selected_button_index); } //--- Alterna o estado do botão pelo índice especificado void SelectionButton(const int index); }; //+------------------------------------------------------------------+ //| Alterna o estado do botão pelo índice especificado | //+------------------------------------------------------------------+ void CButtonsGroup::SelectionButton(const int index) { //--- Para verificar por um botão pressionado em um grupo bool check_pressed_button=false; //--- Obtém o número de botões int buttons_total=ButtonsTotal(); //--- Se não existir um botão em um grupo, reporta if(buttons_total<1) { ::Print(__FUNCTION__," > Este método era para ser chamado, " "se um grupo conter pelo menos um botão! Use the CButtonsGroup::AddButton() method"); } //--- Ajusta o valor do índice, se o intervalo do array for excedido int correct_index=(index>=buttons_total)? buttons_total-1 : (index<0)? 0 : index; //--- Altera o estado do botão para o seu oposto m_buttons_state[correct_index]=(m_buttons_state[correct_index])? false : true; //--- Iteração sobre um grupo de botões for(int i=0; i<buttons_total; i++) { //--- É realizado uma verificação de acordo com o modo bool condition=(m_radio_buttons_mode)? (i==correct_index) : (i==correct_index && m_buttons_state[i]); //--- Se a condição for atendida, faça o botão ser pressionado if(condition) { if(m_radio_buttons_mode) m_buttons_state[i]=true; //--- Há um botão pressionado check_pressed_button=true; //--- Define uma cor m_buttons[i].Color(m_text_color_pressed); m_buttons[i].BackColor(m_buttons_color_pressed[i]); CElement::InitColorArray(m_buttons_color_pressed[i],m_buttons_color_pressed[i],m_buttons_total[i].m_buttons_color_array); } //--- Se a condição não for atendida, faça o botão ser solto else { //--- Define as cores e o estado desativado m_buttons_state[i]=false; m_buttons[i].Color(m_text_color); m_buttons[i].BackColor(m_buttons_color[i]); CElement::InitColorArray(m_buttons_color[i],m_buttons_color_hover[i],m_buttons_total[i].m_buttons_color_array); } //--- Zera o estado normal do botão m_buttons[i].State(false); } //--- Se houver um botão pressionado, armazena o seu texto e índice m_selected_button_text =(check_pressed_button) ? m_buttons[correct_index].Description() : ""; m_selected_button_index =(check_pressed_button) ? correct_index : WRONG_VALUE; }
Para lidar com o pressionamento de um grupo de botão, crie o método CButtonsGroup::OnClickButton(). Semelhante a muitos outros métodos homônimos que foram considerados anteriormente, são realizadas as seguintes verificações:
- para o nome;
- para o identificador. Para extrair o identificador a partir do nome do objeto utilize método CButtonsGroup::IdFromObjectName(). O código do método é semelhante aos métodos homônimos considerados anteriormente em outras classes de controles, assim, nós não vamos discutir isso aqui;
- para o estado atual (disponível/bloqueado).
Se todas as verificações foram passadas e o programa não tiver deixado o método, será realizado a alternância do estado do botão no grupo pelo método CButtonsGroup::SelectionButton() considerado anteriormente. No final deste método, é enviado uma mensagem que contém os dados sobre o botão pressionado para o fluxo de eventos. Esta mensagem pode ser recebida no manipulador da classe personalizada.
class CButtonsGroup : public CElement { private: //--- Manipulando o pressionamento do botão bool OnClickButton(const string clicked_object); //--- Obtém o identificador do nome do botão int IdFromObjectName(const string object_name); }; //+------------------------------------------------------------------+ //| Manipulador de eventos do gráfico | //+------------------------------------------------------------------+ void CButtonsGroup::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) { if(OnClickButton(sparam)) return; } } //+------------------------------------------------------------------+ //| Pressionando o botão em um grupo | //+------------------------------------------------------------------+ bool CButtonsGroup::OnClickButton(const string pressed_object) { //--- Sai, se o pressionamento não foi no elemento de menu if(::StringFind(pressed_object,CElement::ProgramName()+"_buttons_",0)<0) return(false); //--- Obtém o identificador do nome do objeto int id=IdFromObjectName(pressed_object); //--- Sai, se os identificadores não corresponderem if(id!=CElement::Id()) return(false); //--- Para verificar o índice int check_index=WRONG_VALUE; //--- Verifica, se o botão pressionado estava em um dos botões deste grupo int buttons_total=ButtonsTotal(); //--- Sai, se os botões estão bloqueados if(!m_buttons_group_state) { for(int i=0; i<buttons_total; i++) m_buttons[i].State(false); //--- return(false); } //--- Se o pressionamento ocorreu, armazena o índice for(int i=0; i<buttons_total; i++) { if(m_buttons[i].Name()==pressed_object) { check_index=i; break; } } //--- Sai, se o botão deste grupo não foi pressionado if(check_index==WRONG_VALUE) return(false); //--- Alterna o estado do botão SelectionButton(check_index); //--- Envia um sinal sobre ele ::EventChartCustom(m_chart_id,ON_CLICK_BUTTON,CElement::Id(),m_selected_button_index,m_selected_button_text); return(true); }
Agora, nós precisamos configurar a reação do grupo de botões para quando o cursor do mouse estiver sobre eles e para o botão esquerdo do mouse. Para isso, nós vamos escrever o método CButtonsGroup::CheckPressedOverButton(), que será chamado no manipulador do controle durante o tratamento do evento CHARTEVENT_MOUSE_MOVE. Antes do método ser chamado, as seguintes verificações serão realizadas: (1) se o controle é visível, (2) se ele está disponível, (3) se o formulário está disponível e (4) se o botão esquerdo do mouse está pressionado.
class CButtonsGroup : public CElement { private: //--- Verifica o pressionamento do botão esquerdo do mouse sobre um grupo de botões void CheckPressedOverButton(void); }; //+------------------------------------------------------------------+ //| Manipulador de eventos do gráfico | //+------------------------------------------------------------------+ void CButtonsGroup::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 controle está oculto if(!CElement::IsVisible()) return; //--- Sai, se os botões estão bloqueados if(!m_buttons_group_state) return; //--- Define o foco int x=(int)lparam; int y=(int)dparam; int buttons_total=ButtonsTotal(); for(int i=0; i<buttons_total; i++) { m_buttons[i].MouseFocus(x>m_buttons[i].X() && x<m_buttons[i].X2() && y>m_buttons[i].Y() && y<m_buttons[i].Y2()); } //--- Sai, se o formulário for bloqueado if(m_wnd.IsLocked()) return; //--- Sai, se o botão do mouse não for pressionado if(sparam!="1") return; //--- Verifica o pressionamento do botão esquerdo do mouse sobre um grupo de botões CheckPressedOverButton(); return; } } //+------------------------------------------------------------------+ // Verifica o pressionamento do botão esquerdo do mouse sobre um grupo de botões | //+------------------------------------------------------------------+ void CButtonsGroup::CheckPressedOverButton(void) { int buttons_total=ButtonsTotal(); //--- Define a cor, dependendo da localização do pressionamento do botão esquerdo do mouse for(int i=0; i<buttons_total; i++) { //--- Se houver um foco, então, a cor do botão pressionado if(m_buttons[i].MouseFocus()) m_buttons[i].BackColor(m_buttons_color_pressed[i]); //--- Se não houver um foco, então ... else { //--- ...se um grupo de botões não estiver pressionado, atribuí a cor de fundo if(!m_buttons_state[i]) m_buttons[i].BackColor(m_buttons_color[i]); } } }
Tudo está pronto para testar um grupo de botões simples na aplicação de teste. Faça uma cópia do EA que nós fizemos os testes anteriormente. Remova todos os controles, exceto o menu principal com os seus menus de contexto. Nós vamos usar este programa para testar vários grupos de botões neste artigo.
Crie uma instância da classe CButtonsGroup do grupo de botões na classe personalizada. Declare o método CProgram::CreateButtonsGroup1() para a sua criação e a margem da borda do formulário.
class CProgram : public CWndEvents { private: //--- Grupo de botões simples CButtonsGroup m_buttons_group1; //--- private: //--- Grupo de botões simples #define BUTTONS_GROUP1_GAP_X (7) #define BUTTONS_GROUP1_GAP_Y (50) bool CreateButtonsGroup1(void); };
Crie um grupo de quatro botões. Organize-os horizontalmente. Para este exemplo, nós vamos fazer dois botões vermelhos e dois azuis. A implementação do método CProgram::CreateButtonsGroup1() é mostrada no código abaixo. Se você precisa de um botão para ser destacado logo após o grupo de botões ser criado, use o método público CButtonsGroup::SelectionButton().
//+------------------------------------------------------------------+ //| Cria um grupo de botões simples | //+------------------------------------------------------------------+ bool CProgram::CreateButtonsGroup1(void) { //--- Armazena o ponteiro da janela m_buttons_group1.WindowPointer(m_window); //--- Coordenadas int x =m_window.X()+BUTTONS_GROUP1_GAP_X; int y =m_window.Y()+BUTTONS_GROUP1_GAP_Y; //--- Propriedades int buttons_x_gap[] ={0,72,144,216}; string buttons_text[] ={"BUTTON 1","BUTTON 2","BUTTON 3","BUTTON 4"}; int buttons_width[] ={70,70,70,70}; color buttons_color[] ={C'195,0,0',C'195,0,0',clrRoyalBlue,clrRoyalBlue}; color buttons_color_hover[] ={C'255,51,51',C'255,51,51',C'85,170,255',C'85,170,255'}; color buttons_color_pressed[] ={C'135,0,0',C'135,0,0',C'50,100,135',C'50,100,135'}; //--- Define as propriedades m_buttons_group1.TextColor(clrWhite); m_buttons_group1.TextColorPressed(clrGold); //--- Adiciona quatro botões para um grupo for(int i=0; i<4; i++) m_buttons_group1.AddButton(buttons_x_gap[i],0,buttons_text[i],buttons_width[i], buttons_color[i],buttons_color_hover[i],buttons_color_pressed[i]); //--- Criar um grupo de botões if(!m_buttons_group1.CreateButtonsGroup(m_chart_id,m_subwin,x,y)) return(false); //--- Realce o segundo botão no grupo m_buttons_group1.SelectionButton(1); //--- Adiciona um objeto ao array comum de grupos de objetos CWndContainer::AddToElementsArray(0,m_buttons_group1); return(true); }
Após compilar os arquivos e carregar o programa no gráfico, você deve obter o resultado como é mostrado na imagem abaixo.
Fig. 2. Teste do grupo de botões de controle simples.
Nós terminamos o desenvolvimento do primeiro grupo de botões. A versão completa da classe CButtonsGroup pode ser baixada no final deste artigo. O próximo controle que vamos considerar é um grupo de botões de radio.
Desenvolvimento da Classe para Criar os Grupos de Botões de Radio
Crie o arquivo RadioButtons.mqh com a classe CRadioButtons que deve conter os métodos virtuais padrões e os membros da classe para armazenar e obter o ponteiro do formulário. Você pode ver os exemplos nas classes de outros controles acima. Inclua o arquivo RadioButtons.mqh na biblioteca (WndContainer.mqh).
Cada elemento de rádio será composto por três objetos primitivos:
- fundo;
- ícone;
- rótulo de texto.
Fig. 3. Partes integrantes dos botões de radio.
Ao contrário de um grupo de botões simples, um grupo de botões de radio terá apenas um modo. Isso ocorre pois neste controle todos os botões não podem ser soltos simultaneamente. Um botão neste grupo será sempre pressionado. A cor de fundo do grupo é geralmente a mesma que o plano de fundo do formulário em que o grupo está ligado. Essencialmente, ele é usado para identificar o foco e monitorar o pressionamento de um botão de radio (prioridade mais elevada). Nesta versão, nós podemos configurar a alteração da cor apenas para o rótulo de texto quando o cursor entra na área do fundo do botão e sai de lá. As listas de propriedades únicas e comuns de um botão de radio são diferentes das listas de propriedades de um botão simples, como é mostrado abaixo.
Propriedades comuns:
- Cor de fundo e do quadro.
- Cor do texto.
- Altura.
- Botão com ícones para o estado (1) ativo, (2) desativado e (3) bloqueado.
- Prioridade do botão esquerdo do mouse.
Propriedades únicas:
- Texto.
- Largura.
- Estado. Apenas um botão no grupo pode ser pressionado.
- Margens. Semelhante aos botões simples, os botões de radio podem ser organizadas em qualquer ordem (mosaico, horizontal, vertical, etc.).
- Gradientes para os rótulos de texto.
Você pode ver como ele é no código da classe CRadioButtons abaixo. O redimensionamento das propriedades do array e o seu preenchimento com os valores são realizados com o método público CRadioButtons::AddButton() antes de criar o controle na classe personalizada. Se não há botões adicionados, então, a criação da interface gráfica será terminada. Isso foi demonstrado no desenvolvimento da classe para a criação de um grupo de botões simples, portanto, nós não vamos mais gastar tempo com ele.
//+------------------------------------------------------------------+ //| Classe para a criação dos grupos de botões de radio | //+------------------------------------------------------------------+ class CRadioButtons : public CElement { private: //--- Gradientes dos rótulos de texto struct LabelsGradients { color m_labels_color_array[]; }; LabelsGradients m_labels_total[]; //--- Propriedades do botão: // (1) Cor e (2) a prioridade do plano de fundo do botão esquerdo do mouse color m_area_color; int m_area_zorder; //--- Arrays para as propriedades únicas dos botões bool m_buttons_state[]; int m_buttons_x_gap[]; int m_buttons_y_gap[]; int m_buttons_width[]; string m_buttons_text[]; //--- Altura dos botões int m_button_y_size; //--- Ícones para os botões no estado ativo, desativado e bloqueado string m_icon_file_on; string m_icon_file_off; string m_icon_file_on_locked; string m_icon_file_off_locked; //--- Cor do texto color m_text_color; color m_text_color_off; color m_text_color_hover; //--- Prioridade do botão esquerdo do mouse int m_buttons_zorder; //--- public: //--- Definindo os ícones para o botão no estado ativo, desativado e bloqueado void IconFileOn(const string file_path) { m_icon_file_on=file_path; } void IconFileOff(const string file_path) { m_icon_file_off=file_path; } void IconFileOnLocked(const string file_path) { m_icon_file_on_locked=file_path; } void IconFileOffLocked(const string file_path) { m_icon_file_off_locked=file_path; } //--- (1) A cor do fundo, (2) a cor do texto void AreaColor(const color clr) { m_area_color=clr; } void TextColor(const color clr) { m_text_color=clr; } void TextColorOff(const color clr) { m_text_color_off=clr; } void TextColorHover(const color clr) { m_text_color_hover=clr; } //--- Adiciona um botão com as propriedades especificadas antes de sua criação void AddButton(const int x_gap,const int y_gap,const string text,const int width); };
Em seguida, nós precisamos criar os arrays de instâncias de classes de objetos primitivos e os métodos para a sua criação. Semelhante ao grupo de botões simples, os botões de radio serão criados em um loop. No entanto, aqui o loop será localizado no método principal e o índice, que participará na formação do nome de cada objeto, será passado para os métodos de criação desses objetos.
class CRadioButtons : public CElement { private: //--- Objetos para a criação de um botão CRectLabel m_area[]; CBmpLabel m_icon[]; CLabel m_label[]; //--- public: //--- Métodos para a criação de um botão bool CreateRadioButtons(const long chart_id,const int window,const int x,const int y); //--- private: bool CreateArea(const int index); bool CreateRadio(const int index); bool CreateLabel(const int index); //--- public: //--- Número de botões int RadioButtonsTotal(void) const { return(::ArraySize(m_icon)); } }; //+------------------------------------------------------------------+ //| Cria um grupo de objetos de botões | //+------------------------------------------------------------------+ bool CRadioButtons::CreateRadioButtons(const long chart_id,const int window,const int x,const int y) { //--- Retorna se não há nenhum ponteiro do formulário if(::CheckPointer(m_wnd)==POINTER_INVALID) { ::Print(__FUNCTION__," > Antes de criar um grupo de botões de radio, a classe deve ser passada " "para o ponteiro do formulário: CButtonsGroup::WindowPointer(CWindow &object)"); return(false); } //--- Inicializa as variáveis m_id =m_wnd.LastId()+1; m_chart_id =chart_id; m_subwin =window; m_x =x; m_y =y; //--- Obtém o número de botões no grupo int radio_buttons_total=RadioButtonsTotal(); //--- Se não existir um botão em um grupo, reporta if(radio_buttons_total<1) { ::Print(__FUNCTION__," > Este método era para ser chamado, " "se um grupo conter pelo menos um botão! Utiliza o método CRadioButtons::AddButton()"); return(false); } //--- Configura um grupo de botões for(int i=0; i<radio_buttons_total; i++) { CreateArea(i); CreateRadio(i); CreateLabel(i); //--- Zera o foco m_area[i].MouseFocus(false); } //--- Oculta o elemento se ela for uma janela de diálogo ou está minimizada if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized()) Hide(); //--- return(true); }
Essa classe também deve conter os métodos para alterar o estado de um controle (disponível/bloqueado) e alternar o estado do botão pelo índice especificado. Aqui, estes métodos são um pouco mais simples do que na classe CButtonsGroup, pois um grupo de elementos de radio possui um único modo. Você pode estudar estes métodos nos arquivos anexados deste artigo. Além disso, esta classe requer os métodos para obter o texto e o índice do botão destacado como na classe dos botões simples.
class CButtonsGroup : public CElement { private: //--- (1) Texto e (2) o índice do botão em destaque string m_selected_button_text; int m_selected_button_index; //--- Disponível/bloqueado bool m_buttons_group_state; //--- public: //--- Estado geral do grupo de botões (disponível/bloqueado) bool ButtonsGroupState(void) const { return(m_buttons_group_state); } void ButtonsGroupState(const bool state); //--- Retorna (1) o texto e (2) o índice do botão em destaque string SelectedButtonText(void) const { return(m_selected_button_text); } int SelectedButtonIndex(void) const { return(m_selected_button_index); } //--- Alterna o estado do botão pelo índice especificado void SelectionButton(const int index); };
Nós vamos usar o novo identificador ON_CLICK_LABEL para enviar uma mensagem sobre o pressionamento dos controles que se parecem com um rótulo de texto no formulário. Adicione isso ao arquivo Defines.mqh:
#define ON_CLICK_LABEL (10) // Pressionando o rótulo de texto
A implementação do método de tratamento do pressionamento do grupo de botões é mostrada abaixo. Depois de passar pelas seguintes verificações: (1) o tipo de controle que ele pertence, (2) o identificador e (3) sua disponibilidade, o índice do botão pressionado será identificado pelo exato nome num loop. Se um botão deste grupo for pressionado e este botão não está em destaque atualmente, será realizado a alternância e uma mensagem será enviada, podendo ser recebida na classe personalizada.
class CButtonsGroup : public CElement { private: //--- Manipulando o pressionamento do botão bool OnClickButton(const string pressed_object); //--- Obtendo o identificador do nome do botão de radio int IdFromObjectName(const string object_name); }; //+------------------------------------------------------------------+ //| Manipulador de eventos do gráfico | //+------------------------------------------------------------------+ void CRadioButtons::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) { //--- Alterna o estado do botão if(OnClickButton(sparam)) return; } } //+------------------------------------------------------------------+ //| Pressionando o botão de radio | //+------------------------------------------------------------------+ bool CRadioButtons::OnClickButton(const string pressed_object) { //--- Sai, se o pressionamento não foi no elemento de menu if(::StringFind(pressed_object,CElement::ProgramName()+"_radio_area_",0)<0) return(false); //--- Obtém o identificador e o índice a partir do nome do objeto int id=IdFromObjectName(pressed_object); //--- Sai, se o pressionamento não foi para o elemento ao qual este menu de contexto está ligado if(id!=CElement::Id()) return(false); //--- Para verificar o índice int check_index=WRONG_VALUE; //--- Sai, se os botões estão bloqueados if(!m_radio_buttons_state) return(false); //--- Se o pressionamento ocorreu, armazena o índice int radio_buttons_total=RadioButtonsTotal(); for(int i=0; i<radio_buttons_total; i++) { if(m_area[i].Name()==pressed_object) { check_index=i; break; } } //--- Sai, se não houver o pressionamento de um botão neste grupo ou // se este já for um botão de radio em destaque if(check_index==WRONG_VALUE || check_index==m_selected_button_index) return(false); //--- Alterna o estado do botão SelectionRadioButton(check_index); //--- Envia um sinal sobre ele ::EventChartCustom(m_chart_id,ON_CLICK_LABEL,CElement::Id(),check_index,m_selected_button_text); return(true); }
Agora, tudo está pronto para o teste. Adicione quatro grupos de quatro botões de radio (CRadioButtons) e um grupo de botões simples (CButtonsGroup) ao EA, que foi utilizado para o teste de um grupo de botões simples.
class CProgram : public CWndEvents { private: //--- Grupo de botões de radio 1 CRadioButtons m_radio_buttons1; //--- Grupo de botões simples 2 CButtonsGroup m_buttons_group2; //--- Grupos de botões de radio 2,3,4 CRadioButtons m_radio_buttons2; CRadioButtons m_radio_buttons3; CRadioButtons m_radio_buttons4; //--- private: //--- Grupo de botões de radio 1 #define RADIO_BUTTONS1_GAP_X (7) #define RADIO_BUTTONS1_GAP_Y (75) bool CreateRadioButtons1(); //--- Grupo de botões simples 2 #define BUTTONS_GROUP2_GAP_X (7) #define BUTTONS_GROUP2_GAP_Y (100) bool CreateButtonsGroup2(void); //--- Grupos de botões de radio 2,3,4 #define RADIO_BUTTONS2_GAP_X (7) #define RADIO_BUTTONS2_GAP_Y (125) bool CreateRadioButtons2(); #define RADIO_BUTTONS3_GAP_X (105) #define RADIO_BUTTONS3_GAP_Y (125) bool CreateRadioButtons3(); #define RADIO_BUTTONS4_GAP_X (203) #define RADIO_BUTTONS4_GAP_Y (125) bool CreateRadioButtons4(); }; //+------------------------------------------------------------------+ //| Cria o painel de negociação | //+------------------------------------------------------------------+ bool CProgram::CreateTradePanel(void) { //--- Criação de um formulário para os controles //--- Criação dos controles: // Menu principal //--- Menus de contexto //--- Grupo de botões simples 1 //--- Grupo de botões de radio 1 if(!CreateRadioButtons1()) return(false); //--- Grupo de botões simples 2 if(!CreateButtonsGroup2()) return(false); //--- Grupos de botões de radio 2,3,4 if(!CreateRadioButtons2()) return(false); if(!CreateRadioButtons3()) return(false); if(!CreateRadioButtons4()) return(false); //--- Redesenho do gráfico m_chart.Redraw(); return(true); }
Nós vamos utilizar a implementação do método de um deles como exemplo. O resto irá diferir apenas nos valores de parâmetros.
//+------------------------------------------------------------------+ //| Cria um grupo de botões de radio 1 | //+------------------------------------------------------------------+ bool CProgram::CreateRadioButtons1(void) { //--- Passa o objeto do painel m_radio_buttons1.WindowPointer(m_window); //--- Coordenadas int x =m_window.X()+RADIO_BUTTONS1_GAP_X; int y =m_window.Y()+RADIO_BUTTONS1_GAP_Y; //--- Propriedades int buttons_x_offset[] ={0,98,196}; int buttons_y_offset[] ={0,0,0}; string buttons_text[] ={"Radio Button 1","Radio Button 2","Radio Button 3"}; int buttons_width[] ={92,92,92}; //--- for(int i=0; i<3; i++) m_radio_buttons1.AddButton(buttons_x_offset[i],buttons_y_offset[i],buttons_text[i],buttons_width[i]); //--- Criar um grupo de botões if(!m_radio_buttons1.CreateRadioButtons(m_chart_id,m_subwin,x,y)) return(false); //--- Realce o segundo botão no grupo m_radio_buttons1.SelectedRadioButton(1); //--- Bloqueia os botões de radio m_radio_buttons1.RadioButtonsState(false); //--- Adiciona um objeto ao array comum de grupos de objetos CWndContainer::AddToElementsArray(0,m_radio_buttons1); return(true); }
Vamos criar dois botões no grupo adicional de botões (segundo) do tipo CButtonsGroup. Para fins de ilustração, vamos organizar isso de modo que o segundo botão deste grupo irá bloquear os primeiros grupos de botões simples e de radio e o primeiro irá torná-los disponíveis.
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Pressionando o botão de evento if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { ::Print("id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); //--- Se o identificador do segundo grupo ou os botões simples e // o texto do botão em destaque neste grupo corresponde ao parâmetro de string da mensagem if(lparam==m_buttons_group2.Id() && sparam==m_buttons_group2.SelectedButtonText()) { //--- Se este é o índice do primeiro botão, desbloqueie os controles especificados if((int)dparam==0) { m_buttons_group1.ButtonsGroupState(true); m_radio_buttons1.RadioButtonsState(true); } //--- Se este é o índice do segundo botão, bloqueia os controles especificado else { m_buttons_group1.ButtonsGroupState(false); m_radio_buttons1.RadioButtonsState(false); } } return; } }
Compile os arquivos e carregue o programa ao gráfico. Nesta fase de desenvolvimento, você deve obter o resultado como é mostrado na imagem abaixo.
Fig. 4. Teste do controle botão de radio.
Nós terminamos o desenvolvimento da classe para a criação do grupo de controle botões de radio. Você pode baixar a versão completa nos arquivos anexados deste artigo.
Desenvolvimento da Classe para Criar os Grupos de Botões com Ícone
Nós já criamos anteriormente a classe CIconButton para criar o controle botão com ícone. Agora, nós vamos implementar um controle que nos permitirá criar um grupo de tais botões. Crie o arquivo IconButtonsGroup.mqh com a classe CIconButtonsGroup onde você poderá declarar e implementar os métodos virtuais padrões na pasta Controls que contém os arquivos criados anteriormente com as classes de controles. Inclua este arquivo WndContainer.mqh na biblioteca.
Não vamos descrever isto em detalhes já que a classe CIconButtonsGroup é essencialmente uma composição dos métodos que já foram considerados na classe CButtonsGroup e CRadioButtons. Semelhante a classe CRadioButtons de botões de radio, os objetos do grupo de botões serão criados com métodos privados em um loop do método principal para a criação do controle. O único parâmetro desses métodos é o índice que será utilizado para formar os nomes dos objetos gráficos. Nós podemos organizar com que os botões deste grupo alterem a sua cor de fundo e o texto quando o cursor do mouse estiver sobre eles.
Os métodos relativos à manipulação de eventos já foram discutidos neste artigo. Eles são os mesmos neste tipo de grupo de botões, por isso, nós vamos mostrar apenas o conteúdo da classe CIconButtonsGroup no código abaixo. A implementação dos métodos que estão fora do corpo da classe podem ser encontrados nos arquivos anexados neste artigo.
//+------------------------------------------------------------------+ //| Classe para criar um grupo de botões com ícones | //+------------------------------------------------------------------+ class CIconButtonsGroup : public CElement { private: //--- Ponteiro para o formulário ao qual o controle será anexado CWindow *m_wnd; //--- Objetos para a criação de um botão CButton m_buttons[]; CBmpLabel m_icons[]; CLabel m_labels[]; //--- Gradientes dos rótulos de texto struct IconButtonsGradients { color m_back_color_array[]; color m_label_color_array[]; }; IconButtonsGradients m_icon_buttons_total[]; //--- Propriedades do botão: // Arrays de propriedades únicas dos botões bool m_buttons_state[]; int m_buttons_x_gap[]; int m_buttons_y_gap[]; string m_buttons_text[]; int m_buttons_width[]; string m_icon_file_on[]; string m_icon_file_off[]; //--- Altura dos botões int m_buttons_y_size; //--- A cor do fundo em diferentes modos color m_back_color; color m_back_color_off; color m_back_color_hover; color m_back_color_pressed; //--- Cor do quadro color m_border_color; color m_border_color_off; //--- Margens do ícone int m_icon_x_gap; int m_icon_y_gap; //--- Margens do texto e do rótulo de texto int m_label_x_gap; int m_label_y_gap; //--- Cor do rótulo de texto em diferentes modos color m_label_color; color m_label_color_off; color m_label_color_hover; color m_label_color_pressed; //--- (1) Texto e (2) o índice do botão em destaque string m_selected_button_text; int m_selected_button_index; //--- Prioridade Geral dos objetos não clicáveis int m_zorder; //--- Prioridade do botão esquerdo do mouse int m_buttons_zorder; //--- Disponível/bloqueado bool m_icon_buttons_state; //--- public: CIconButtonsGroup(void); ~CIconButtonsGroup(void); //--- Métodos para a criação de um botão bool CreateIconButtonsGroup(const long chart_id,const int window,const int x,const int y); //--- private: bool CreateButton(const int index); bool CreateIcon(const int index); bool CreateLabel(const int index); //--- public: //--- (1) Armazena o ponteiro do formulário, (2) a altura do botão, (3) o número de botões, // (4) o estado geral do botão (disponível/bloqueado) void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } void ButtonsYSize(const int y_size) { m_buttons_y_size=y_size; } int IconButtonsTotal(void) const { return(::ArraySize(m_icons)); } bool IconButtonsState(void) const { return(m_icon_buttons_state); } void IconButtonsState(const bool state); //--- Cores de fundo do botão void BackColor(const color clr) { m_back_color=clr; } void BackColorOff(const color clr) { m_back_color_off=clr; } void BackColorHover(const color clr) { m_back_color_hover=clr; } void BackColorPressed(const color clr) { m_back_color_pressed=clr; } //--- Margens do ícone void IconXGap(const int x_gap) { m_icon_x_gap=x_gap; } void IconYGap(const int y_gap) { m_icon_y_gap=y_gap; } //--- 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; } //--- Retorna (1) o texto e (2) o índice do botão em destaque string SelectedButtonText(void) const { return(m_selected_button_text); } int SelectedButtonIndex(void) const { return(m_selected_button_index); } //--- Alterna o estado de um botão de opção pelo índice especificado void SelectedRadioButton(const int index); //--- Adiciona um botão com as propriedades especificadas antes de sua criação void AddButton(const int x_gap,const int y_gap,const string text, const int width,const string icon_file_on,const string icon_file_off); //--- Altera a cor void ChangeObjectsColor(void); //--- 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); //--- Mover o controle 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); //--- private: //--- Manipulando o pressionamento do botão bool OnClickButton(const string pressed_object); //--- Verifica o pressionamento do botão esquerdo do mouse sobre um grupo de botões void CheckPressedOverButton(void); //--- Obtendo o identificador do nome do botão de radio int IdFromObjectName(const string object_name); };
Vamos testar este controle. Como um exemplo, crie um grupo de botões com ícones no formulário do mesmo EA onde nós testamos anteriormente os grupos de botões do tipo CButtonsGroup e CRadioButtons. Para isso, adicione as seguintes linhas de código para a classe personalizada como é mostrado no código abaixo.
class CProgram : public CWndEvents { private: //--- Grupo de botões com ícones 1 CIconButtonsGroup m_icon_buttons_group1; //--- private: //--- Grupo de botões com ícones 1 #define IBUTTONS_GROUP1_GAP_X (7) #define IBUTTONS_GROUP1_GAP_Y (190) bool CreateIconButtonsGroup1(void); }; //+------------------------------------------------------------------+ //| Cria o painel de negociação | //+------------------------------------------------------------------+ bool CProgram::CreateTradePanel(void) { //--- Criação de um formulário para os controles //--- Criação dos controles: // Menu principal //--- Menus de contexto //--- Grupo de botões simples 1 //--- Grupo de botões de radio 1 //--- Grupo de botões simples 2 //--- Grupos de botões de radio 2,3,4 //--- Grupo de botões com ícones 1 if(!CreateIconButtonsGroup1()) return(false); //--- Redesenho do gráfico m_chart.Redraw(); return(true); }
Compile os arquivos e carregue o programa ao gráfico. Você deve obter o resultado como é mostrado na imagem abaixo.
Fig. 5. Teste do grupo de botões com ícones.
O desenvolvimento da classe para criar o controle grupo de botões com ícone foi concluído. Você pode baixar a versão completa nos arquivos anexados deste artigo.
Conclusão
Este é o fim da terceira parte da série sobre o desenvolvimento da biblioteca para a criação das interfaces gráficas nos terminais de negociação MetaTrader. Atualmente, a estrutura da biblioteca possui o seguinte aspecto:
Fig. 6. Estrutura da biblioteca no atual estágio de desenvolvimento.
Na próxima (quarta) parte da série, nós vamos continuar com o desenvolvimento da biblioteca. Nós vamos considerar os seguintes tópicos:
- Modo multi-janela.
- Sistema de gestão de prioridades do clique esquerdo do mouse sobre os objetos gráficos.
- Elementos de interface informativos como a barra de estado e as dicas de ferramentas.
Os arquivos abaixo com os arquivos da biblioteca no atual estágio de desenvolvimento, imagens e os arquivos dos programas considerados neste artigo podem ser baixados para a realização de testes nos terminais MetaTrader. 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 terceira parte:
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2298
- 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