
Interfaces Gráficas VI: Os Controles Deslizante e Deslizante Duplo (Capítulo 2)
Conteúdo
- Introdução
- O Controle Deslizante
- O Controle Deslizante Duplo
- Desenvolvimento de uma Classe para a Criação do Controle Deslizante Duplo
- Teste do Controle Deslizante Duplo
- Conclusão
Introdução
O primeiro artigo Interfaces gráficas I: Preparação da Estrutura da Biblioteca (Capítulo 1) explica 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.
No artigo anterior, nós enriquecemos a nossa biblioteca com quatro controles que são frequentemente usados nas interfaces gráficas: caixa de seleção, campo de edição, campo de edição com caixa de seleção e a lista combinada com a caixa de seleção. O segundo capítulo da sexta parte será dedicado aos controles deslizante e deslizante duplo.
O Controle Deslizante
O Controle deslizante é um tipo do controle campo de edição que contém um intervalo limitado por um valor mínimo e máximo. Ao contrário do controle campo de edição que nós discutimos em detalhes no artigo anterior, o controle deslizante não possui botões para alterar o valor no campo de entrada. Para esse propósito, existe uma linha ao longo da qual o cursor pode ser deslocado. Tal elemento de interface é adequado para os casos em que é necessário apenas um valor aproximado dentro de um certo intervalo, não sendo necessário seu valor preciso. No entanto, ainda há a possibilidade de introduzir um valor preciso.
Este elemento será composto por seis objetos gráficos. Eles são:
- Fundo
- Título (rótulo de texto)
- Campo de entrada
- Linha do controle deslizante.
- Corrediça do controle deslizante
- Indicador do controle deslizante
Fig. 1. Partes integrantes do controle deslizante.
Vamos estudar a classe desse controle em detalhes.
Desenvolvimento de uma Classe para a Criação do Controle Deslizante
Crie o arquivo Slider.mqh e inclua-o no arquivo WndContainer.mqh:
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Slider.mqh"
No arquivo Slider.mqh crie a classe CSlider com o conjunto padrão de métodos que devem estar presentes em cada controle da biblioteca em desenvolvimento. Ao lado dos arquivos de inclusão Element.mqh e Window.mqh, inclua o arquivo SeparateLine.mqh com a classe CSeparateLine para a criação de uma linha de separação. A classe CSeparateLine já foi discutida no artigo Interfaces Gráficas II: Os Elementos Linha de Separação e o Menu de Contexto (Capítulo 2)e, portanto, não vamos considerá-la novamente. A única coisa que eu gostaria de lembrá-lo é que se a altura definida for maior do que dois pixels, haverá um espaço vazio entre as duas linhas desenhadas. Visualmente, ela se parece como um buraco que é adequado para a criação de uma linha do controle deslizante (fenda ou ranhura), onde a corrediça do controle deslizante estará se movendo.
//+------------------------------------------------------------------+ //| Slider.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" #include "SeparateLine.mqh" //+------------------------------------------------------------------+ //| Classe para criar o controle deslizante com o campo de edição | //+------------------------------------------------------------------+ class CSlider : public CElement { private: //--- Ponteiro para o formulário ao qual o elemento está anexado CWindow *m_wnd; public: CSlider(void); ~CSlider(void); //--- public: //--- 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); };
A biblioteca do usuário deve ter a possibilidade de definir as propriedades de todos os objetos que o controle deslizante será composto. Estas propriedades são listadas abaixo.
- Cor de fundo do elemento
- Texto para descrever o controle deslizante
- Cores do rótulo de texto em diferentes estados
- Valor atual do campo de entrada
- Tamanho do campo de entrada
- Cores do campo de entrada em diferentes estados
- Cores do texto do campo de entrada em diferentes estados
- Cores do quadro do campo de entrada em diferentes estados
- Tamanho da fenda ao longo do eixo Y (altura)
- Cores das linhas da fenda
- Cores do indicador do controle deslizante em diferentes estados
- Tamanho da corrediça do controle deslizante
- Cores da corrediça do controle deslizante
- Prioridades do clique do botão esquerdo do mouse
Abaixo está o código dos campos e métodos da classe das propriedades dos objetos listados acima:
class CSlider : public CElement { private: //--- Cor de fundo do elemento color m_area_color; //--- Texto para descrever o controle deslizante string m_label_text; //--- Cores do rótulo de texto em diferentes estados color m_label_color; color m_label_color_hover; color m_label_color_locked; color m_label_color_array[]; //--- Tamanho do campo de entrada int m_edit_x_size; int m_edit_y_size; //--- Cores do campo de entrada em diferentes estados color m_edit_color; color m_edit_color_locked; //--- Cores do texto do campo de entrada em diferentes estados color m_edit_text_color; color m_edit_text_color_locked; //--- Cores da estrutura do campo de entrada em diferentes estados color m_edit_border_color; color m_edit_border_color_hover; color m_edit_border_color_locked; color m_edit_border_color_array[]; //--- Tamanho da fenda int m_slot_y_size; //--- Cores da fenda color m_slot_line_dark_color; color m_slot_line_light_color; //--- Cores do indicador em diferentes estados color m_slot_indicator_color; color m_slot_indicator_color_locked; //--- Tamanho da corrediça do controle deslizante int m_thumb_x_size; int m_thumb_y_size; //--- Cores da corrediça do controle deslizante color m_thumb_color; color m_thumb_color_hover; color m_thumb_color_locked; color m_thumb_color_pressed; //--- Prioridades do clique do botão esquerdo do mouse int m_zorder; int m_area_zorder; int m_edit_zorder; //--- public: //--- (1) cor de fundo, (2) cores do rótulo de texto void AreaColor(const color clr) { m_area_color=clr; } void LabelColor(const color clr) { m_label_color=clr; } void LabelColorHover(const color clr) { m_label_color_hover=clr; } void LabelColorLocked(const color clr) { m_label_color_locked=clr; } //--- Tamanho do (1) campo de entrada e (2) a fenda void EditXSize(const int x_size) { m_edit_x_size=x_size; } void EditYSize(const int y_size) { m_edit_y_size=y_size; } void SlotYSize(const int y_size) { m_slot_y_size=y_size; } //--- Cores do campo de entrada em diferentes estados void EditColor(const color clr) { m_edit_color=clr; } void EditColorLocked(const color clr) { m_edit_color_locked=clr; } //--- Cores do texto do campo de entrada em diferentes estados void EditTextColor(const color clr) { m_edit_text_color=clr; } void EditTextColorLocked(const color clr) { m_edit_text_color_locked=clr; } //--- Cores da estrutura do campo de entrada em diferentes estados void EditBorderColor(const color clr) { m_edit_border_color=clr; } void EditBorderColorHover(const color clr) { m_edit_border_color_hover=clr; } void EditBorderColorLocked(const color clr) { m_edit_border_color_locked=clr; } //--- Linha de separação na cor (1) escura e (2) clara void SlotLineDarkColor(const color clr) { m_slot_line_dark_color=clr; } void SlotLineLightColor(const color clr) { m_slot_line_light_color=clr; } //--- Cores do indicador deslizante em diferentes estados void SlotIndicatorColor(const color clr) { m_slot_indicator_color=clr; } void SlotIndicatorColorLocked(const color clr) { m_slot_indicator_color_locked=clr; } //--- Tamanho da corrediça do controle deslizante void ThumbXSize(const int x_size) { m_thumb_x_size=x_size; } void ThumbYSize(const int y_size) { m_thumb_y_size=y_size; } //--- Cores da corrediça do controle deslizante void ThumbColor(const color clr) { m_thumb_color=clr; } void ThumbColorHover(const color clr) { m_thumb_color_hover=clr; } void ThumbColorLocked(const color clr) { m_thumb_color_locked=clr; } void ThumbColorPressed(const color clr) { m_thumb_color_pressed=clr; } };
As propriedades da lista anterior se relacionam principalmente com a cor e o tamanho dos objetos do controle. As propriedades relacionadas com o alcance e o campo de entrada do cursor será mostrado em um grupo separado. Estas propriedades são:
- Valor mínimo
- Valor máximo
- Passo da variação dos valores no campo de entrada
- Modo de alinhamento de texto
- Número de casas decimais
class CSlider : public CElement { private: //--- (1) Valor mínimo (2) e máximo, (3) passo para alterar o valor double m_min_value; double m_max_value; double m_step_value; //--- Número de casas decimais int m_digits; //--- Modo de alinhamento de texto ENUM_ALIGN_MODE m_align_mode; //--- public: //--- Valor mínimo double MinValue(void) const { return(m_min_value); } void MinValue(const double value) { m_min_value=value; } //--- Valor máximo double MaxValue(void) const { return(m_max_value); } void MaxValue(const double value) { m_max_value=value; } //--- Passo de alteração do valor double StepValue(void) const { return(m_step_value); } void StepValue(const double value) { m_step_value=(value<=0)? 1 : value; } //--- (1) Número de casas decimais, (2) o modo de alinhamento do texto void SetDigits(const int digits) { m_digits=::fabs(digits); } void AlignMode(ENUM_ALIGN_MODE mode) { m_align_mode=mode; } };
Para obter o valor atual, bem como para ajustar e definir o novo valor no campo de entrada, nós vamos usar os métodos CSlider::GetValue(), CSlider::SetValue() e o CSlider::ChangeValue():
class CSlider : public CElement { private: //--- Valor atual do campo de entrada double m_edit_value; //--- public: //--- Retorna e define o valor do campo de entrada double GetValue(void) const { return(m_edit_value); } bool SetValue(const double value); //--- Valor atual do campo de entrada void ChangeValue(const double value); }; //+------------------------------------------------------------------+ //| Define o valor atual | //+------------------------------------------------------------------+ bool CSlider::SetValue(const double value) { //--- Para o ajuste double corrected_value=0.0; //--- Ajustar considerando o passo corrected_value=::MathRound(value/m_step_value)*m_step_value; //--- Verifica a mínima/máxima if(corrected_value<=m_min_value) corrected_value=m_min_value; if(corrected_value>=m_max_value) corrected_value=m_max_value; //--- Se o valor tiver sido alterado if(m_edit_value!=corrected_value) { m_edit_value=corrected_value; return(true); } //--- Valor inalterado return(false); } //+------------------------------------------------------------------+ //| Valor atual do campo de entrada | //+------------------------------------------------------------------+ void CSlider::ChangeValue(const double value) { //--- Verifica, ajusta e armazena o novo valor SetValue(value); //--- Define o novo valor no campo de entrada m_edit.Description(::DoubleToString(GetValue(),m_digits)); }
Quando a corrediça do controle deslizante se move, o valor no campo de entrada deve ser calculado em relação a coordenada X. Se o valor for inserido manualmente, então a coordenada X da corrediça do controle deslizante deve ser calculada em relação ao novo valor no campo de entrada. Em outras palavras, a possibilidade da transformação inversa deve ser permitida quando o elemento for desenvolvido.
Para o cálculo correto, nós precisaremos de campos auxiliares (variáveis) da classe que será utilizada no cálculo. Estas variáveis deverão ser calculadas (inicializadas) apenas uma vez, imediatamente após o elemento ter sido criado. Abaixo está a descrição dessas variáveis.
- Número de pixels na área de trabalho (m_pixels_total).
- Número de passos no intervalo de valores da área de trabalho (m_value_steps_total).
- Passo em relação à largura da área de trabalho (m_position_step).
Vamos escrever um método para calcular esses valores e chamá-lo de CSlider::CalculateCoefficients():
class CSlider : public CElement { private: //--- Número de pixels na área de trabalho int m_pixels_total; //--- Número de passos na área de trabalho int m_value_steps_total; //--- Passo da largura transformada da área de trabalho double m_position_step; //--- private: //--- Cálculo dos valores (passos e coeficientes) bool CalculateCoefficients(void); }; //+------------------------------------------------------------------+ //| Cálculo dos valores (passos e coeficientes) | //+------------------------------------------------------------------+ bool CSlider::CalculateCoefficients(void) { //--- Sai se a largura do elemento for menor do que a largura da corrediça do controle deslizante if(CElement::XSize()<m_thumb_x_size) return(false); //--- Número de pixels na área de trabalho m_pixels_total=CElement::XSize()-m_thumb_x_size; //--- Número de passos no valor do intervalo da área de trabalho m_value_steps_total=int((m_max_value-m_min_value)/m_step_value); //--- Passo em relação à largura da área de trabalho m_position_step=m_step_value*(double(m_value_steps_total)/double(m_pixels_total)); return(true); }
Agora, os valores das variáveis listadas acima podem ser utilizadas para o cálculo da coordenada X da corrediça do controle deslizante em relação ao valor no campo de entrada e vice-versa. Para isso, nós vamos escrever dois métodos distintos, o CSlider::CalculateThumbX() e o CSlider::CalculateThumbPos().
No início do método CSlider::CalculateThumbX(), nós iremos calcular os valores da variável local auxiliar (neg_range) para ajuste no caso do valor do limite mínimo ser negativo. Então, nós vamos calcular a coordenada X para a corrediça do controle deslizante. Depois disso, no caso da linha do controle deslizante exceder, o valor é ajustado. No final do método, é definido um novo valor da coordenada X para a corrediça do controle deslizante e a margem da borda do formulário que o elemento está anexado é calculada de novo.
No início do método CSlider::CalculateThumbPos(), nós obtemos a posição da corrediça do controle deslizante na faixa de valores. Em seguida, é realizado um ajuste se o valor do limite mínimo for negativo e o valor da variável m_current_pos_x estiver correta. Então, se a área de trabalho foi excedida, é feito uma alteração no valor.
class CSlider : public CElement { private: //--- Posição atual da corrediça do controle deslizante: (1) valor, (2) a coordenada X double m_current_pos; double m_current_pos_x; //--- private: //--- Cálculo da coordenada X da corrediça do controle deslizante void CalculateThumbX(void); //--- Altera a posição atual da corrediça do controle deslizante em relação ao valor atual void CalculateThumbPos(void); }; //+------------------------------------------------------------------+ //| Cálculo da coordenada X do deslizado do cursor | //+------------------------------------------------------------------+ void CSlider::CalculateThumbX(void) { //--- Ajuste considerando que o valor mínimo pode ser negativo double neg_range=(m_min_value<0)? ::fabs(m_min_value/m_position_step) : 0; //--- Calcula a coordenada X para a corrediça do controle deslizante m_current_pos_x=m_area.X()+(m_edit_value/m_position_step)+neg_range; //--- Se a área de trabalho exceder pelo lado esquerdo if(m_current_pos_x<m_area.X()) m_current_pos_x=m_area.X(); //--- Se a área de trabalho exceder pelo lado direito if(m_current_pos_x+m_thumb.XSize()>m_area.X2()) m_current_pos_x=m_area.X2()-m_thumb.XSize(); //--- Armazena e define a nova coordenada X m_thumb.X(int(m_current_pos_x)); m_thumb.X_Distance(int(m_current_pos_x)); m_thumb.XGap(m_thumb.X()-m_wnd.X()); } //+------------------------------------------------------------------+ //| Cálculo da posição da corrediça do controle deslizante na faixa de valores | //+------------------------------------------------------------------+ void CSlider::CalculateThumbPos(void) { //--- Obtém o número da posição da corrediça do controle deslizante m_current_pos=(m_thumb.X()-m_area.X())*m_position_step; //--- Ajuste considerando que o valor mínimo pode ser negativo if(m_min_value<0 && m_current_pos_x!=WRONG_VALUE) m_current_pos+=int(m_min_value); //--- Verifica se a área de trabalho excedeu pela direita/esquerda if(m_thumb.X2()>=m_area.X2()) m_current_pos=int(m_max_value); if(m_thumb.X()<=m_area.X()) m_current_pos=int(m_min_value); }
Quando os deslizador do cursor está se movendo, a largura do indicador do controle deslizante deve ser calculado e atualizado. O lado direito do indicador do controle deslizante deve ser vinculado a corrediça do controle deslizante Para isso, vamos escrever o método CSlider::UpdateIndicator():
class CSlider : public CElement { private: //--- Atualiza o indicador do controle deslizante void UpdateIndicator(void); }; //+------------------------------------------------------------------+ //| Atualiza o indicador do controle deslizante | //+------------------------------------------------------------------+ void CSlider::UpdateIndicator(void) { //--- Calcula o tamanho int x_size=m_thumb.X()-m_indicator.X(); //--- Ajuste no caso dos valores não serem permitidos if(x_size<=0) x_size=1; //--- Define um novo tamanho m_indicator.X_Size(x_size); }
Para criar o controle deslizante nós precisaremos de seis métodos privados e um público.
class CSlider : public CElement { public: //--- Métodos para criar o controle bool CreateSlider(const long chart_id,const int subwin,const string text,const int x,const int y); //--- private: bool CreateArea(void); bool CreateLabel(void); bool CreateEdit(void); bool CreateSlot(void); bool CreateIndicator(void); bool CreateThumb(void); };
Nós vamos discutir apenas o código do método CSlider::CreateThumb(), porque este é o lugar onde é chamado todos os métodos de cálculos considerados anteriormente. Outros métodos não possuem nada que nós já não estudamos nos artigos anteriores desta série.
//+------------------------------------------------------------------+ //| Cria a corrediça do controle deslizante | //+------------------------------------------------------------------+ bool CSlider::CreateThumb(void) { //--- Elaborando o nome do objeto string name=CElement::ProgramName()+"_slider_thumb_"+(string)CElement::Id(); //--- Coordenadas int x=CElement::X(); int y=m_slot.Y()-((m_thumb_y_size-m_slot_y_size)/2); //--- Define o objeto if(!m_thumb.Create(m_chart_id,name,m_subwin,x,y,m_thumb_x_size,m_thumb_y_size)) return(false); //--- Define as propriedades m_thumb.Color(m_thumb_color); m_thumb.BackColor(m_thumb_color); m_thumb.BorderType(BORDER_FLAT); m_thumb.Corner(m_corner); m_thumb.Selectable(false); m_thumb.Z_Order(m_zorder); m_thumb.Tooltip("\n"); //--- Armazenar o tamanho (no objecto) m_thumb.XSize(m_thumb.X_Size()); m_thumb.YSize(m_thumb.Y_Size()); //--- Armazena as coordenadas m_thumb.X(x); m_thumb.Y(y); //--- Margens da borda m_thumb.XGap(x-m_wnd.X()); m_thumb.YGap(y-m_wnd.Y()); //--- Calcula os valores das variáveis auxiliares CalculateCoefficients(); //--- Calcula as coordenadas X da corrediça do controle deslizante em relação ao valor atual no campo de entrada CalculateThumbX(); //--- Cálculo da posição da corrediça do controle deslizante na faixa do valor CalculateThumbPos(); //--- Atualiza o indicador do controle deslizante UpdateIndicator(); //--- Armazena o ponteiro de objeto CElement::AddToArray(m_thumb); return(true); }
Os métodos de mover a corrediça do controle deslizante são idênticos aos métodos com nomes semelhantes nas classes CScroll e CScrollH, que foram discutidos em detalhe no artigo Interfaces Gráficas V: A Barra de Rolagem Vertical e Horizontal (Capítulo 1). É por isso que nós não vamos considerar o seu código aqui. Nós só vamos declará-los no corpo da classe CSlider.
class CSlider : public CElement { private: //--- Estado do botão do mouse (pressionado/liberado) ENUM_THUMB_MOUSE_STATE m_clamping_area_mouse; //--- Para identificar o modo do movimento da corrediça do controle deslizante bool m_slider_thumb_state; //--- Variáveis relacionadas com o movimento da corrediça do controle deslizante int m_slider_size_fixing; int m_slider_point_fixing; //--- private: //--- Processo do movimento deslizante do cursor void OnDragThumb(const int x); //--- Atualização da localização da corrediça do controle deslizante void UpdateThumb(const int new_x_point); //--- Verifica o estado do botão do mouse void CheckMouseButtonState(void); //--- Zerando as variáveis relacionadas com o movimento deslizante do cursor void ZeroThumbVariables(void); };
Para lidar com os valores inseridos no campo de entrada, crie o método CSlider::OnEndEdit() que será chamado no manipulador de eventos CSlider::OnEvent().
No início do método CSlider::OnEndEdit(), há uma verificação com o nome do objeto se o valor foi inserido no campo deste cursor. Em seguida, obtenha o valor atual do campo de entrada. Então, siga uma verificação obrigatória para a entrada com valores não permitidos, ajuste, o cálculo da coordenada X da corrediça do controle deslizante e a posição na faixa do valor. Depois disso, o indicador do controle deslizante é atualizado. No final deste método, uma mensagem deve ser enviada com (1) o identificador de evento personalizado ON_END_EDIT, (2) o identificador do elemento, (3) o índice do elemento e (4) a descrição contida no rótulo de texto.
class CSlider : public CElement { private: //--- Lidando com a inserção do valor no campo de entrada bool OnEndEdit(const string object_name); }; //+------------------------------------------------------------------+ //| Lidando com a inserção do valor no campo de entrada | //+------------------------------------------------------------------+ bool CSlider::OnEndEdit(const string object_name) { //--- Sai, se o nome do objeto for diferente if(object_name!=m_edit.Name()) return(false); //--- Obter o valor inserido double entered_value=::StringToDouble(m_edit.Description()); //--- Verifica, ajusta e armazena o novo valor ChangeValue(entered_value); //--- Calcula a coordenada X da corrediça do controle deslizante CalculateThumbX(); //--- Calcula a posição no intervalo de valores CalculateThumbPos(); //--- Atualiza o indicador do controle deslizante UpdateIndicator(); //--- Envia uma mensagem sobre ele ::EventChartCustom(m_chart_id,ON_END_EDIT,CElement::Id(),CElement::Index(),m_label.Description()); return(true); }
A mesma mensagem personalizada deve ser enviada após a corrediça do controle deslizante parar de se mover para alterar o valor do campo de entrada. O método CSlider::ZeroThumbVariables() serve muito bem para este objetivo. Isso é chamado no método CSlider::CheckMouseButtonState(), onde a área do clique do botão esquerdo do mouse é monitorada. A chamada do método CSlider::ZeroThumbVariables() já implica que o botão esquerdo do mouse já foi liberado. Se ele é pressionado para baixo sobre a área da corrediça do controle deslizante, significa que o movimento dele foi finalizado e uma mensagem deverá ser enviada dizendo que o valor no campo de entrada foi alterado.
//+------------------------------------------------------------------+ //| Zerando as variáveis relacionadas ao movimento da barra de rolagem | //+------------------------------------------------------------------+ void CSlider::ZeroThumbVariables(void) { //--- Se você está aqui, isso significa que o botão esquerdo do mouse foi liberado. // Se o botão esquerdo do mouse foi pressionado sobre o segundo controle deslizante... if(m_clamping_area_mouse==THUMB_PRESSED_INSIDE) { //--- ...envia uma mensagem que a alteração do valor no campo de entrada com a corrediça do controle deslizante foi concluída ::EventChartCustom(m_chart_id,ON_END_EDIT,CElement::Id(),CElement::Index(),m_label.Description()); } //--- m_slider_size_fixing =0; m_clamping_area_mouse =THUMB_NOT_PRESSED; //--- Se o identificador do elemento coincide com o identificador de ativação, // desbloqueia o formulário e redefine o identificador do elemento ativado if(CElement::Id()==m_wnd.IdActivatedElement()) { m_wnd.IsLocked(false); m_wnd.IdActivatedElement(WRONG_VALUE); } }
O código completo do manipulador de evento CSlider::OnEvent(), neste caso, ficará da seguinte forma:
//+------------------------------------------------------------------+ //| Manipulador de eventos do gráfico | //+------------------------------------------------------------------+ void CSlider::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 e o estado do botão esquerdo do mouse int x=(int)lparam; int y=(int)dparam; m_mouse_state=(bool)int(sparam); //--- Verificando o foco sobre os elementos CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2()); m_thumb.MouseFocus(x>m_thumb.X() && x<m_thumb.X2() && y>m_thumb.Y() && y<m_thumb.Y2()); //--- Sai, se o elemento está bloqueado if(!m_slider_state) return; //--- Verifica e armazena o estado do botão do mouse CheckMouseButtonState(); //--- Altera a cor da corrediça do controle deslizante ChangeThumbColor(); //--- Se a gestão foi passada para a linha do controle deslizante, identifica a sua localização if(m_clamping_area_mouse==THUMB_PRESSED_INSIDE) { //--- Movendo a corrediça do controle deslizante OnDragThumb(x); //--- Cálculo da posição da corrediça do controle deslizante na faixa do valor CalculateThumbPos(); //--- Define o novo valor no campo de entrada ChangeValue(m_current_pos); //--- Atualiza o indicador do controle deslizante UpdateIndicator(); return; } } //--- Lidando com a alteração do valor no evento campo de entrada if(id==CHARTEVENT_OBJECT_ENDEDIT) { //--- Manipula o valor de entrada if(OnEndEdit(sparam)) return; } }
Nós implementamos os métodos para a criação e gerenciamento do controle deslizante. Vamos testá-lo no programa em MQL que nós utilizamos nos artigos anteriores.
Teste do Controle Deslizante
No artigo anterior, na aplicação de teste, nós criamos quatro caixas de seleção que gerenciavam a disponibilidade de outros elementos. Nós vamos adicionar o controle deslizante e a quinta caixa de seleção para gerir a sua disponibilidade na interface gráfica. Para isso, na classe personalizada CProgram, declare as instâncias da classe CCheckBox e CSlider, bem como os métodos com margens a partir da borda do formulário que estes controles serão vinculados.
//+------------------------------------------------------------------+ //| Classe para a criação de um aplicativo | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Caixas de seleção CCheckBox m_checkbox5; //--- Controles deslizantes CSlider m_slider1; //--- private: //--- Caixas de seleção #define CHECKBOX5_GAP_X (7) #define CHECKBOX5_GAP_Y (200) bool CreateCheckBox5(const string text); //--- Controles deslizantes #define SLIDER1_GAP_X (32) #define SLIDER1_GAP_Y (225) bool CreateSlider1(const string text); };
Nós consideramos o código dos métodos para a criação das caixas de seleção no artigo anterior, é por isso que nós estamos avançando com o método para a criação do elemento CProgram::CreateSlider1(). Usando os métodos CSlider::MinValue() e CSlider::MaxValue(), defina um intervalo no valor de -1 até 1. Defina o passo até a 8ª casa decimal (0.00000001). A disponibilidade irá depender do estado atual da quinta caixa de seleção.
//+------------------------------------------------------------------+ //| Cria o controle deslizante 1 | //+------------------------------------------------------------------+ bool CProgram::CreateSlider1(const string text) { //--- Armazena o ponteiro da janela m_slider1.WindowPointer(m_window1); //--- Coordenadas int x=m_window1.X()+SLIDER1_GAP_X; int y=m_window1.Y()+SLIDER1_GAP_Y; //--- Valor double v=(m_slider1.GetValue()==WRONG_VALUE) ? 0.84615385 : m_slider1.GetValue(); //--- Define as propriedades antes da criação m_slider1.XSize(264); m_slider1.YSize(40); m_slider1.EditXSize(87); m_slider1.MaxValue(1); m_slider1.StepValue(0.00000001); m_slider1.MinValue(-1); m_slider1.SetDigits(8); m_slider1.SetValue(v); m_slider1.AreaColor(clrWhiteSmoke); m_slider1.LabelColor(clrBlack); m_slider1.LabelColorLocked(clrSilver); m_slider1.EditColorLocked(clrWhiteSmoke); m_slider1.EditBorderColor(clrSilver); m_slider1.EditBorderColorLocked(clrSilver); m_slider1.EditTextColorLocked(clrSilver); m_slider1.SlotLineDarkColor(clrSilver); m_slider1.SlotLineLightColor(clrWhite); m_slider1.SlotYSize(4); m_slider1.ThumbColorLocked(clrLightGray); m_slider1.ThumbColorPressed(clrSilver); m_slider1.SlotIndicatorColor(C'85,170,255'); m_slider1.SlotIndicatorColorLocked(clrLightGray); //--- Cria o controle if(!m_slider1.CreateSlider(m_chart_id,m_subwin,text,x,y)) return(false); //--- A disponibilidade irá depender do estado atual da quinta caixa de seleção m_slider1.SliderState(m_checkbox5.CheckButtonState()); //--- Adiciona o objeto ao array comum dos grupos de objetos CWndContainer::AddToElementsArray(0,m_slider1); return(true); }
Os novos métodos para a criação dos elementos devem ser chamados no método principal para a criação da interface gráfica. Abaixo está o código de uma versão resumida deste 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 //--- Caixas de seleção if(!CreateCheckBox5("Checkbox 5")) return(false); //--- Controles deslizantes if(!CreateSlider1("Slider 1:")) return(false); //--- Redesenha o gráfico m_chart.Redraw(); return(true); }
Nós vamos controlar a mudança do estado da quinta caixa de seleção para controlar a disponibilidade do controle deslizante no manipulador de eventos CProgram::OnEvent() do aplicativo. O evento com o identificador personalizado ON_END_EDIT irá indicar quando o valor no campo de entrada irá mudar.
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- O evento clique no rótulo de texto if(id==CHARTEVENT_CUSTOM+ON_CLICK_LABEL) { ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); //--- Se a quinta caixa de seleção foi clicada if(lparam==m_checkbox5.Id()) { //--- Define o estado do primeiro controle deslizante m_slider1.SliderState(m_checkbox5.CheckButtonState()); } } //--- o fim do evento de introdução do valor no campo de entrada if(id==CHARTEVENT_CUSTOM+ON_END_EDIT) { ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); } }
Agora, o programa pode ser compilado e carregado ao gráfico. Tente interagir com os controles da interface gráfica da aplicação. Se tudo foi feito corretamente, você verá um resultado como na imagem abaixo:
Fig. 2. Testando o controle deslizante.
O Controle Deslizante Duplo
A diferença entre um controle deslizante duplo e um simples é que o primeiro permite a seleção na faixa de valor definida pelo usuário. Por esse motivo, existem duas corrediças na linha do controle deslizante. A corrediça esquerda do controle deslizante pode ser movido a partir do lado esquerdo da linha do controle deslizante para a corrediça direita do controle deslizante. A corrediça direita do controle deslizante da direita pode ser movida a partir do lado direito da linha do controle deslizante para a corrediça esquerda do controle deslizante. O controle deslizante duplo também tem dois campos de entrada que refletem o valor em relação à posição de suas corrediças linha do controle deslizante. Os valores podem ser inseridos manualmente nestes campos de entrada. Isso vai mudar a posição das corrediças do controle deslizante.
Este controle será composto por oito objetos gráficos primitivos. Eles são:
- Fundo.
- Título (rótulo de texto)
- Campo de entrada esquerdo.
- Campo de entrada direito.
- Linha do controle deslizante.
- Corrediça esquerda do controle deslizante
- Corrediça direita do controle deslizante
- Indicador do controle deslizante
Fig. 3. Partes integrantes do controle deslizante duplo.
Vamos dar uma olhada na classe do controle deslizante duplo e notar as diferenças do controle deslizante simples.
Desenvolvimento de uma Classe para a Criação do Controle Deslizante Duplo
Inclua o arquivo DualSlider.mqh com a classe do controle CDualSlider no arquivo WndContainer.mqh:
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "DualSlider.mqh"
Em termos das propriedades do controle, a classe CDualSlider é idêntica a classe CSlider. A única diferença aqui é que os campos de entrada da esquerda e direita e as corrediças do controle deslizante exigem campos e métodos distintos. As diferenças entre estes dois métodos são insignificantes e é por isso que nós não vamos discutir o seu código aqui. Você pode encontrá-lo nos arquivos anexados neste artigo.
class CDualSlider : public CElement { private: //--- Valores atuais nos campos de entrada (esquerda e direita) double m_left_edit_value; double m_right_edit_value; //--- Posição atual das corrediças do controle deslizante (esquerda e direita) double m_left_current_pos; double m_left_current_pos_x; double m_right_current_pos; double m_right_current_pos_x; //--- Estado do botão do mouse (pressionado/solto) para os corrediças do controle deslizante (esquerda e direita) ENUM_THUMB_MOUSE_STATE m_clamping_mouse_left_thumb; ENUM_THUMB_MOUSE_STATE m_clamping_mouse_right_thumb; //--- public: //--- Retorna e define o valor dos campos de entrada (esquerda e direita) double GetLeftValue(void) const { return(m_left_edit_value); } double GetRightValue(void) const { return(m_right_edit_value); } bool SetLeftValue(double value); bool SetRightValue(double value); //--- Altera os valores nos campos de entrada (esquerda e direita) void ChangeLeftValue(const double value); void ChangeRightValue(const double value); //--- private: //--- Processo de mover as corrediças do controle deslizante (esquerda e direita) void OnDragLeftThumb(const int x); void OnDragRightThumb(const int x); //--- Atualiza a posição das corrediças do controle deslizante (esquerda e direita) void UpdateLeftThumb(const int new_x_point); void UpdateRightThumb(const int new_x_point); //--- Verifica o estado do botão esquerdo do mouse sobre o segundo controle deslizante void CheckMouseOnLeftThumb(void); void CheckMouseOnRightThumb(void); //--- Calcula a coordenada X dos controles deslizantes (esquerda e direita) void CalculateLeftThumbX(void); void CalculateRightThumbX(void); //--- Altera a posição da corrediça esquerda do controle deslizante em relação ao valor (esquerda e direita) void CalculateLeftThumbPos(void); void CalculateRightThumbPos(void); };
Aqui, nós iremos apresentar apenas o código dos métodos onde foi considerado os dois campos de entrada e as corrediças do controle deslizante. Abaixo encontra-se a estrutura do método CDualSlider::OnEndEdit():
class CDualSlider : public CElement { private: //--- Lidando com a inserção do valor no campo de entrada bool OnEndEdit(const string object_name); }; //+------------------------------------------------------------------+ //| Fim da entrada dos valores | //+------------------------------------------------------------------+ bool CDualSlider::OnEndEdit(const string object_name) { //--- Se o valor é inserido no campo de entrada esquerdo if(object_name==m_left_edit.Name()) { //--- Obter o valor inserido double entered_value=::StringToDouble(m_left_edit.Description()); //--- Verifica, ajusta e armazena o novo valor ChangeLeftValue(entered_value); //--- Calcula a coordenada X da corrediça do controle deslizante CalculateLeftThumbX(); //--- Atualização da localização da corrediça do controle deslizante UpdateLeftThumb(m_left_thumb.X()); //--- Calcula a posição no intervalo de valores CalculateLeftThumbPos(); //--- Verifica, ajusta e armazena o novo valor ChangeLeftValue(m_left_current_pos); //--- Atualiza o indicador do controle deslizante UpdateIndicator(); //--- Envia uma mensagem sobre ele ::EventChartCustom(m_chart_id,ON_END_EDIT,CElement::Id(),CElement::Index(),m_label.Description()); return(true); } //--- Se o valor é inserido no campo de entrada à direita if(object_name==m_right_edit.Name()) { //--- Obter o valor inserido double entered_value=::StringToDouble(m_right_edit.Description()); //--- Verifica, ajusta e armazena o novo valor ChangeRightValue(entered_value); //--- Calcula a coordenada X da corrediça do controle deslizante CalculateRightThumbX(); //--- Atualização da localização da corrediça do controle deslizante UpdateRightThumb(m_right_thumb.X()); //--- Calcula a posição no intervalo de valores CalculateRightThumbPos(); //--- Verifica, ajusta e armazena o novo valor ChangeRightValue(m_right_current_pos); //--- Atualiza o indicador do controle deslizante UpdateIndicator(); //--- Envia uma mensagem sobre ele ::EventChartCustom(m_chart_id,ON_END_EDIT,CElement::Id(),CElement::Index(),m_label.Description()); return(true); } //--- return(false); }
O mesmo aplica-se ao movimento das corrediças esquerda e direita do controle deslizante. O manipulador de evento CDualSlider::OnEvent() contém as verificações e blocos de código separados para cada um deles:
//+------------------------------------------------------------------+ //| Manipulador de eventos do gráfico | //+------------------------------------------------------------------+ void CDualSlider::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 e o estado do botão esquerdo do mouse int x=(int)lparam; int y=(int)dparam; m_mouse_state=(bool)int(sparam); //--- Verificando o foco sobre os elementos CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2()); m_left_thumb.MouseFocus(x>m_left_thumb.X() && x<m_left_thumb.X2() && y>m_left_thumb.Y() && y<m_left_thumb.Y2()); m_right_thumb.MouseFocus(x>m_right_thumb.X() && x<m_right_thumb.X2() && y>m_right_thumb.Y() && y<m_right_thumb.Y2()); //--- Sai, se o elemento está bloqueado if(!m_slider_state) return; //--- Verifica e armazena o estado do botão do mouse CheckMouseOnLeftThumb(); CheckMouseOnRightThumb(); //--- Altera a cor da corrediça do controle deslizante ChangeThumbColor(); //--- Se a gestão foi passada para a linha do controle deslizante (corrediça esquerda do controle deslizante) if(m_clamping_mouse_left_thumb==THUMB_PRESSED_INSIDE) { //--- Movendo a corrediça do controle deslizante OnDragLeftThumb(x); //--- Cálculo da posição da corrediça do controle deslizante na faixa do valor CalculateLeftThumbPos(); //--- Define o novo valor no campo de entrada ChangeLeftValue(m_left_current_pos); //--- Atualiza o indicador do controle deslizante UpdateIndicator(); return; } //--- Se a gestão foi passada para a barra de rolagem (corrediça direita do controle deslizante) if(m_clamping_mouse_right_thumb==THUMB_PRESSED_INSIDE) { //--- Movendo a corrediça do controle deslizante OnDragRightThumb(x); //--- Cálculo da posição da corrediça do controle deslizante na faixa do valor CalculateRightThumbPos(); //--- Define o novo valor no campo de entrada ChangeRightValue(m_right_current_pos); //--- Atualiza o indicador do controle deslizante UpdateIndicator(); return; } } }
Você pode baixar os arquivos anexados neste artigo e estudar mais a fundo o código da classe CDualSlider.
Teste do Controle Deslizante Duplo
Adicione o controle deslizante duplo na interface gráfica da aplicação de teste. Declare uma instância da classe CDualSlider e um método com as margens da borda do formulário na classe personalizada CProgram do aplicativo:
//+------------------------------------------------------------------+ //| Classe para a criação de um aplicativo | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Controles deslizantes CDualSlider m_dual_slider1; //--- private: //--- Controles deslizantes #define DUALSLIDER1_GAP_X (32) #define DUALSLIDER1_GAP_Y (275) bool CreateDualSlider1(const string text); };
O código a seguir mostra o código do método CProgram::CreateDualSlider1(). Defina o intervalo do valor de -2000 até 1000. A disponibilidade deste controle dependerá do estado atual da quinta caixa de seleção semelhante ao controle deslizante simples que foi criado no início deste artigo.
//+------------------------------------------------------------------+ //| Cria o controle deslizante duplo 1 | //+------------------------------------------------------------------+ bool CProgram::CreateDualSlider1(const string text) { //--- Armazena o ponteiro da janela m_dual_slider1.WindowPointer(m_window1); //--- Coordenadas int x=m_window1.X()+DUALSLIDER1_GAP_X; int y=m_window1.Y()+DUALSLIDER1_GAP_Y; //--- Valores double v1=(m_dual_slider1.GetLeftValue()==WRONG_VALUE) ? 0 : m_dual_slider1.GetLeftValue(); double v2=(m_dual_slider1.GetRightValue()==WRONG_VALUE) ? 500 : m_dual_slider1.GetRightValue(); //--- Define as propriedades antes da criação m_dual_slider1.XSize(264); m_dual_slider1.YSize(40); m_dual_slider1.EditXSize(87); m_dual_slider1.MaxValue(1000); m_dual_slider1.StepValue(1); m_dual_slider1.MinValue(-2000); m_dual_slider1.SetDigits(0); m_dual_slider1.SetLeftValue(v1); m_dual_slider1.SetRightValue(v2); m_dual_slider1.AreaColor(clrWhiteSmoke); m_dual_slider1.LabelColor(clrBlack); m_dual_slider1.LabelColorLocked(clrSilver); m_dual_slider1.EditColorLocked(clrWhiteSmoke); m_dual_slider1.EditBorderColor(clrSilver); m_dual_slider1.EditBorderColorLocked(clrSilver); m_dual_slider1.EditTextColorLocked(clrSilver); m_dual_slider1.SlotLineDarkColor(clrSilver); m_dual_slider1.SlotLineLightColor(clrWhite); m_dual_slider1.SlotYSize(4); m_dual_slider1.ThumbColorLocked(clrLightGray); m_dual_slider1.ThumbColorPressed(clrSilver); m_dual_slider1.SlotIndicatorColor(C'85,170,255'); m_dual_slider1.SlotIndicatorColorLocked(clrLightGray); //--- Cria o controle if(!m_dual_slider1.CreateSlider(m_chart_id,m_subwin,text,x,y)) return(false); //--- A disponibilidade irá depender do estado atual da quinta caixa de seleção m_dual_slider1.SliderState(m_checkbox5.CheckButtonState()); //--- Adiciona o objeto ao array comum dos grupos de objetos CWndContainer::AddToElementsArray(0,m_dual_slider1); return(true); }
Não se esqueça de colocar a chamada do método do novo controle no método principal para a criação da interface gráfica da maneira que é demonstrado na versão resumida do código abaixo.
//+------------------------------------------------------------------+ //| 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 //--- Caixas de seleção //--- Controles deslizantes if(!CreateDualSlider1("Dual Slider 1:")) return(false); //--- Redesenha o gráfico m_chart.Redraw(); return(true); }
Agora, o estado atual da quinta caixa de seleção irá definir dois controles - o controle deslizante simples e o deslizante duplo.
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- O evento clique no rótulo de texto if(id==CHARTEVENT_CUSTOM+ON_CLICK_LABEL) { ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); //--- Se a quinta caixa de seleção foi clicada if(lparam==m_checkbox5.Id()) { //--- Define o estado dos controles deslizantes m_slider1.SliderState(m_checkbox5.CheckButtonState()); m_dual_slider1.SliderState(m_checkbox5.CheckButtonState()); } } //--- o fim do evento de introdução do valor no campo de entrada if(id==CHARTEVENT_CUSTOM+ON_END_EDIT) { ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); } }
Agora, compile o programa e carregue-o ao gráfico. A imagem abaixo ilustra o resultado:
Fig. 4. O controle duplo deslizante.
Conclusão
Na sexta parte da série, nós consideramos seis controles:
- Caixa de seleção (checkbox).
- O controle campo de edição.
- O campo de edição com a caixa de seleção.
- Lista combinada com a caixa de seleção
- Controle deslizante
- Controle duplo deslizante.
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. 5. Estrutura da biblioteca no atual estágio de desenvolvimento.
Na próxima parte da série, vamos enriquecer a nossa biblioteca com as tabelas e guias.
Você pode baixar o material da parte VI 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 (capítulos) da sexta parte:
- Interfaces Gráficas VI: Os Controles Caixa de Seleção, Campo de Edição e seus Tipos Combinados (Capítulo 1)
- Interfaces Gráficas VI: Os Controles Deslizante e Deslizante Duplo (Capítulo 2)
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2468





- 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
Anatol, uma pergunta: haverá uma possibilidade "padrão" de substituir a localização mútua de objetos em elementos compostos, além de alterar seus tamanhos?
Por exemplo, para compactar os controles deslizantes em painéis de tamanho pequeno, tive que alterar/adicionar métodos de controle deslizante.
Mas isso não é muito prático em termos de compatibilidade com futuras atualizações.
Eu já respondi a essa pergunta para você.
Fórum sobre negociação, sistemas de negociação automatizados e teste de estratégias de negociação
Discussão do artigo "Interfaces gráficas III: Botões simples e multifuncionais (Capítulo 1)"
Anatoli Kazharski, 2016.09.29 18:07
Vou fazer com que você possa alterar qualquer propriedade dos elementos já após a criação.//---
Mas não tudo de uma vez. Todas as coisas mais importantes primeiro. Configurações sutis por último (prioridade mais baixa).
Já respondi a essa pergunta para você.
//---
Mas não tudo de uma vez. Primeiro, as coisas mais importantes. O ajuste fino por último (prioridade mais baixa).
Talvez eu tenha perdido alguma coisa ou, pelo contrário, ainda não cheguei lá )))
Mas meu controle deslizante está constantemente apresentando falhas, ao criar o controle deslizante, ele foge para o valor mais à direita, mesmo que tenha sido definido como a média entre o superior e o inferior. No entanto, o valor mostra o que foi definido ao defini-lo. Depois disso, ao tentar girá-lo de alguma forma, o valor muda para o mínimo extremo ou para o máximo extremo.
Você pode sugerir algo?
Talvez eu tenha perdido alguma coisa ou, pelo contrário, ainda não cheguei lá )))
Mas meu controle deslizante está constantemente apresentando falhas, ao criar o controle deslizante, ele foge para o valor mais à direita, mesmo que tenha sido definido como a média entre o superior e o inferior. No entanto, o valor mostra o que foi definido ao defini-lo. E, depois disso, ao tentar girá-lo de alguma forma, o valor muda para o mínimo extremo ou para o máximo extremo.
Você pode sugerir algo?
Experimente a versão mais recente da biblioteca:
EasyAndFastGUI - biblioteca para criação de interfaces gráficas
Prezado Anatoli Kazharski
Obrigado por seu compartilhamento, ele é muito avançado.
Estou tentando compilar no MT4 MetaEditor, e os seguintes erros ocorreram. Não sei o que está errado, pois sou iniciante.
Aguardo sua resposta.