Componentes View e Controller para tabelas no paradigma MVC em MQL5: Elementos de controle simples
Conteúdo
- Introdução
- Componente Controller. Aperfeiçoamento das classes base
- Elementos de controle simples
- Classes auxiliares
- Rótulo de texto
- Botão simples
- Botão de duas posições
- Botão com seta para cima
- Botão com seta para baixo
- Botão com seta para a esquerda
- Botão com seta para a direita
- Checkbox
- Botão de opção
- Testando o resultado
- Considerações finais
Introdução
No âmbito do desenvolvimento do elemento de controle Table View no paradigma MVC (Model-View-Controller), criamos o modelo da tabela (componente Model) e iniciamos a criação do componente View. Na primeira etapa, foi criado um objeto base, que é o progenitor de todos os demais elementos gráficos.
Hoje iniciaremos o desenvolvimento de elementos de controle simples, a partir dos quais, posteriormente, criaremos elementos compostos. Cada elemento de controle será dotado de funcional para interação interativa com o usuário e com outros elementos. Ou seja, trata-se exatamente do funcional do componente Controller.
Como na linguagem MQL modelo de eventos está integrado aos objetos criados por meio dos eventos do gráfico, em todos os elementos de controle organizaremos o processamento de eventos para implementar a ligação do componente View com o componente Controller. Para isso, faremos ajustes na classe base dos elementos gráficos.
Em seguida, criaremos elementos de controle simples, o rótulo de texto e diferentes botões. Cada um desses elementos terá a possibilidade de desenhar um ícone, o que permitirá criar, a partir de botões simples, elementos de controle completamente diferentes. Se observarmos uma linha de uma lista em árvore, onde à esquerda há um ícone e à direita há um texto, isso parece ser um elemento de controle separado. Porém, teremos a possibilidade de criá-lo facilmente utilizando um botão comum. Ao mesmo tempo, será possível configurar os parâmetros da linha de modo que ela ou reaja com a mudança de cor ao passar o cursor do mouse e ao clicar, ou seja estática, mas responda a cliques.
Tudo isso poderá ser feito facilmente com a ajuda de algumas linhas de configuração do objeto após a sua criação. E, a partir desses elementos, posteriormente criaremos elementos de controle compostos complexos, totalmente interativos e prontos para uso.
Componente Controller. Aperfeiçoamento das classes base
Assim, para implementar o que foi planejado, precisamos aperfeiçoar um pouco as classes, macrossubstituições e enumerações já escritas. A maior parte do funcional necessário ficará localizada no objeto base dos elementos gráficos. Portanto, o principal aperfeiçoamento afetará justamente esse objeto.
Anteriormente, essa classe estava localizada no caminho MQL5\Scripts\Tables\Controls\Base.mqh.
Hoje vamos escrever um indicador de teste, portanto precisamos, no diretório de indicadores \MQL5\Indicators, criar uma nova pasta \Tables\Controls\ e colocar nela o arquivo Base.mqh, que é exatamente o que iremos aperfeiçoar hoje.
Posteriormente, nos objetos serão armazenadas listas de elementos de controle anexados. Tais objetos podem ser, por exemplo, contêineres. Para que essas listas possam funcionar corretamente com arquivos, isto é, criar objetos armazenados nas listas, é necessário declarar previamente todas as classes dos elementos que serão criados. Vamos inserir no arquivo Base.mqh declaração das classes, novas macrossubstituições, enumerações e constantes das enumerações:
//+------------------------------------------------------------------+ //| Base.mqh | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| Include libraries | //+------------------------------------------------------------------+ #include <Canvas\Canvas.mqh> // CCanvas class #include <Arrays\List.mqh> // CList class //--- Forward declaration of control element classes class CImagePainter; // Image drawing class class CLabel; // Text label class class CButton; // Simple button class class CButtonTriggered; // Two-position button class class CButtonArrowUp; // Up arrow button class class CButtonArrowDown; // Down arrow button class class CButtonArrowLeft; // Left arrow button class class CButtonArrowRight; // Right arrow button class class CCheckBox; // CheckBox control class class CRadioButton; // RadioButton control class //+------------------------------------------------------------------+ //| Macro substitutions | //+------------------------------------------------------------------+ #define clrNULL 0x00FFFFFF // Transparent color for CCanvas #define MARKER_START_DATA -1 // Data start marker in a file #define DEF_FONTNAME "Calibri" // Default font #define DEF_FONTSIZE 10 // Default font size //+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+ enum ENUM_ELEMENT_TYPE // Enumeration of graphical element types { ELEMENT_TYPE_BASE = 0x10000, // Basic object of graphical elements ELEMENT_TYPE_COLOR, // Color object ELEMENT_TYPE_COLORS_ELEMENT, // Color object of the graphical object element ELEMENT_TYPE_RECTANGLE_AREA, // Rectangular area of the element ELEMENT_TYPE_IMAGE_PAINTER, // Object for drawing images ELEMENT_TYPE_CANVAS_BASE, // Basic canvas object for graphical elements ELEMENT_TYPE_LABEL, // Text label ELEMENT_TYPE_BUTTON, // Simple button ELEMENT_TYPE_BUTTON_TRIGGERED, // Two-position button ELEMENT_TYPE_BUTTON_ARROW_UP, // Up arrow button ELEMENT_TYPE_BUTTON_ARROW_DOWN, // Down arrow button ELEMENT_TYPE_BUTTON_ARROW_LEFT, // Left arrow button ELEMENT_TYPE_BUTTON_ARROW_RIGHT, // Right arrow button ELEMENT_TYPE_CHECKBOX, // CheckBox control ELEMENT_TYPE_RADIOBUTTON, // RadioButton control }; enum ENUM_ELEMENT_STATE // Control state { ELEMENT_STATE_DEF, // By default (e.g. button released etc.) ELEMENT_STATE_ACT, // Activated (e.g. button pressed, etc.) }; enum ENUM_COLOR_STATE // Enumeration of element state colors { COLOR_STATE_DEFAULT, // Normal state color COLOR_STATE_FOCUSED, // Color when hovering over an element COLOR_STATE_PRESSED, // Color when clicking on an element COLOR_STATE_BLOCKED, // Blocked element color }; //+------------------------------------------------------------------+ //| Functions | //+------------------------------------------------------------------+
Ao criar métodos de salvamento e carregamento de objetos em/de arquivos, em cada método existem linhas constantes que se repetem sem alteração de um método para outro:
//--- Check the handle if(file_handle==INVALID_HANDLE) return false; //--- Save data start marker - 0xFFFFFFFFFFFFFFFF if(::FileWriteLong(file_handle,-1)!=sizeof(long)) return false; //--- Save the object type if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE) return false; //--- Save the ID if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE) return false; //--- Save the name if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name)) return false;
E também existem métodos idênticos Description e Print. Portanto, é razoável transferir essas linhas para os métodos de carregamento/salvamento no objeto base. Assim, não será necessário escrevê-las em cada novo método de carregamento/salvamento em cada nova classe onde esteja prevista a работа com arquivos.
Declararemos esses métodos no objeto base:
public: //--- Set (1) name and (2) ID void SetName(const string name) { ::StringToShortArray(name,this.m_name); } void SetID(const int id) { this.m_id=id; } //--- Return (1) name and (2) ID string Name(void) const { return ::ShortArrayToString(this.m_name); } int ID(void) const { return this.m_id; } //--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_BASE); } //--- (1) Return and (2) display the object description in the journal virtual string Description(void); virtual void Print(void); //--- Constructor/destructor CBaseObj (void) : m_id(-1) { this.SetName(""); } ~CBaseObj (void) {} };
E escrever a sua implementação:
//+------------------------------------------------------------------+ //| CBaseObj::Return the object description | //+------------------------------------------------------------------+ string CBaseObj::Description(void) { string nm=this.Name(); string name=(nm!="" ? ::StringFormat(" \"%s\"",nm) : nm); return ::StringFormat("%s%s ID %d",ElementDescription((ENUM_ELEMENT_TYPE)this.Type()),name,this.ID()); } //+------------------------------------------------------------------+ //| CBaseObj::Display the object description in the journal | //+------------------------------------------------------------------+ void CBaseObj::Print(void) { ::Print(this.Description()); } //+------------------------------------------------------------------+ //| CBaseObj::Save to file | //+------------------------------------------------------------------+ bool CBaseObj::Save(const int file_handle) { //--- Check the handle if(file_handle==INVALID_HANDLE) return false; //--- Save data start marker - 0xFFFFFFFFFFFFFFFF if(::FileWriteLong(file_handle,-1)!=sizeof(long)) return false; //--- Save the object type if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE) return false; //--- Save the ID if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE) return false; //--- Save the name if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name)) return false; //--- All is successful return true; } //+------------------------------------------------------------------+ //| CBaseObj::Load from file | //+------------------------------------------------------------------+ bool CBaseObj::Load(const int file_handle) { //--- Check the handle if(file_handle==INVALID_HANDLE) return false; //--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF if(::FileReadLong(file_handle)!=-1) return false; //--- Load the object type if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type()) return false; //--- Load the ID this.m_id=::FileReadInteger(file_handle,INT_VALUE); //--- Load the name if(::FileReadArray(file_handle,this.m_name)!=sizeof(this.m_name)) return false; //--- All is successful return true; }
Agora, para cada nova classe, os métodos Description e Print podem ser declarados e implementados apenas no caso em que a sua lógica seja diferente da lógica definida nessa classe.
E, nos métodos de trabalho com arquivos nas classes herdadas, em vez de escrever constantemente as mesmas linhas em cada método de cada classe, simplesmente chamaremos os métodos de trabalho com arquivos desse objeto base.
De todas as classes subsequentes deste arquivo (Base.mqh), removeremos todos os métodos Print, pois eles já existem no objeto base e o repetem completamente.
Em todos os métodos de trabalho com arquivos removeremos as seguintes linhas:
//+------------------------------------------------------------------+ //| CColor::Save to file | //+------------------------------------------------------------------+ bool CColor::Save(const int file_handle) { //--- Check the handle if(file_handle==INVALID_HANDLE) return false; //--- Save data start marker - 0xFFFFFFFFFFFFFFFF if(::FileWriteLong(file_handle,-1)!=sizeof(long)) return false; //--- Save the object type if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE) return false; //--- Save the color if(::FileWriteInteger(file_handle,this.m_color,INT_VALUE)!=INT_VALUE) return false; //--- Save the ID if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE) return false; //--- Save the name if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name)) return false; //--- All is successful return true; }
Agora em vez dessas linhas, temos simplesmente a chamada do método da classe base:
//+------------------------------------------------------------------+ //| CColor::Save to file | //+------------------------------------------------------------------+ bool CColor::Save(const int file_handle) { //--- Save the parent object data if(!CBaseObj::Save(file_handle)) return false; //--- Save the color if(::FileWriteInteger(file_handle,this.m_color,INT_VALUE)!=INT_VALUE) return false; //--- All is successful return true; }
Essas alterações já foram feitas em todos os métodos de trabalho com arquivos deste arquivo. Ao escrever as classes subsequentes, levaremos essas alterações em consideração.
Na classe CColorElement, substituiremos nos construtores das classes linhas idênticas e repetidas
//+-----------------------------------------------------------------------------+ //| CColorControl::Constructor with setting the transparent colors of the object| //+-----------------------------------------------------------------------------+ CColorElement::CColorElement(void) { this.InitColors(clrNULL,clrNULL,clrNULL,clrNULL); this.m_default.SetName("Default"); this.m_default.SetID(1); this.m_focused.SetName("Focused"); this.m_focused.SetID(2); this.m_pressed.SetName("Pressed"); this.m_pressed.SetID(3); this.m_blocked.SetName("Blocked"); this.m_blocked.SetID(4); this.SetCurrentAs(COLOR_STATE_DEFAULT); this.m_current.SetName("Current"); this.m_current.SetID(0); }
por um único método Init():
public: //--- Return a new color color NewColor(color base_color, int shift_red, int shift_green, int shift_blue); //--- Class initialization void Init(void); //--- Initialize colors for different states
...
A sua implementação:
//+------------------------------------------------------------------+ //| CColorControl::Class initialization | //+------------------------------------------------------------------+ void CColorElement::Init(void) { this.m_default.SetName("Default"); this.m_default.SetID(1); this.m_focused.SetName("Focused"); this.m_focused.SetID(2); this.m_pressed.SetName("Pressed"); this.m_pressed.SetID(3); this.m_blocked.SetName("Blocked"); this.m_blocked.SetID(4); this.SetCurrentAs(COLOR_STATE_DEFAULT); this.m_current.SetName("Current"); this.m_current.SetID(0); } //+------------------------------------------------------------------+
Se no método de inicialização da cor for passado um valor de cor transparente, então não é necessário alterá-lo de forma alguma para quaisquer estados.
Levaremos isso em conta na implementação do método:
//+----------------------------------------------------------------------+ //| CColorControl::Set the colors for all states based on the current one| //+----------------------------------------------------------------------+ void CColorElement::InitColors(const color clr) { this.InitDefault(clr); this.InitFocused(clr!=clrNULL ? this.NewColor(clr,-20,-20,-20) : clrNULL); this.InitPressed(clr!=clrNULL ? this.NewColor(clr,-40,-40,-40) : clrNULL); this.InitBlocked(clrWhiteSmoke); }
Na classe CBound, é necessário adicionar um método que retorne o sinalizador de o cursor estar dentro da área retangular — pois isso será necessário na implementação do componente Controller:
//+------------------------------------------------------------------+ //| Rectangular region class | //+------------------------------------------------------------------+ class CBound : public CBaseObj { protected: CRect m_bound; // Rectangular area structure public: //--- Change the bounding rectangular (1) width, (2) height and (3) size void ResizeW(const int size) { this.m_bound.Width(size); } void ResizeH(const int size) { this.m_bound.Height(size); } void Resize(const int w,const int h) { this.m_bound.Width(w); this.m_bound.Height(h); } //--- Set (1) X, (2) Y and (3) both coordinates of the bounding rectangle void SetX(const int x) { this.m_bound.left=x; } void SetY(const int y) { this.m_bound.top=y; } void SetXY(const int x,const int y) { this.m_bound.LeftTop(x,y); } //--- (1) Set and (2) shift the bounding rectangle by the specified coordinates/offset size void Move(const int x,const int y) { this.m_bound.Move(x,y); } void Shift(const int dx,const int dy) { this.m_bound.Shift(dx,dy); } //--- Returns the object coordinates, dimensions, and boundaries int X(void) const { return this.m_bound.left; } int Y(void) const { return this.m_bound.top; } int Width(void) const { return this.m_bound.Width(); } int Height(void) const { return this.m_bound.Height(); } int Right(void) const { return this.m_bound.right-(this.m_bound.Width() >0 ? 1 : 0);} int Bottom(void) const { return this.m_bound.bottom-(this.m_bound.Height()>0 ? 1 : 0);} //--- Returns the flag indicating whether the cursor is inside the area bool Contains(const int x,const int y) const { return this.m_bound.Contains(x,y); } //--- Return the object description virtual string Description(void); //--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_RECTANGLE_AREA); } //--- Constructors/destructor CBound(void) { ::ZeroMemory(this.m_bound); } CBound(const int x,const int y,const int w,const int h) { this.SetXY(x,y); this.Resize(w,h); } ~CBound(void) { ::ZeroMemory(this.m_bound); } };
Agora é necessário adicionar tudo o que for preciso para a implementação do componente Controller na classe base do canvas dos elementos gráficos CCanvasBase.
Na interação dos elementos gráficos com o mouse, é necessário desativar algumas propriedades do gráfico, como a rolagem do gráfico com a rodinha do mouse, o menu do botão direito do mouse, etc. Isso será feito por cada objeto dos elementos gráficos. Porém, na primeira execução, é necessário memorizar o estado das propriedades do gráfico que existia antes da execução do programa. E, após o término do trabalho, restaurar tudo ao estado original.
Para isso, na seção privada da classe CCanvasBase, declararemos variáveis para armazenar os valores das propriedades do gráfico que devem ser memorizadas, bem como um método para definir as restrições dessas propriedades do gráfico.
//+------------------------------------------------------------------+ //| Base class of graphical elements canvas | //+------------------------------------------------------------------+ class CCanvasBase : public CBaseObj { private: bool m_chart_mouse_wheel_flag; // Flag for sending mouse wheel scroll messages bool m_chart_mouse_move_flag; // Flag for sending mouse cursor movement messages bool m_chart_object_create_flag; // Flag for sending messages about the graphical object creation event bool m_chart_mouse_scroll_flag; // Flag for scrolling the chart with the left button and mouse wheel //--- Set chart restrictions (wheel scrolling, context menu, and crosshair) void SetFlags(const bool flag); protected:
Os elementos gráficos podem ter dois estados (podem ser mais, mas por enquanto são dois). Por exemplo, para um botão, pressionado e solto. Isso significa que precisamos controlar os estados das cores do elemento em seus dois estados. Na seção protegida da classe, definiremos uma variável para armazenar o estado do elemento, mais um conjunto de objetos de gerenciamento de cores e separaremos o controle de transparência do canvas de fundo e do primeiro plano de forma independente:
protected: CCanvas m_background; // Background canvas CCanvas m_foreground; // Foreground canvas CBound m_bound; // Object boundaries CCanvasBase *m_container; // Parent container object CColorElement m_color_background; // Background color control object CColorElement m_color_foreground; // Foreground color control object CColorElement m_color_border; // Border color control object CColorElement m_color_background_act; // Activated element background color control object CColorElement m_color_foreground_act; // Activated element foreground color control object CColorElement m_color_border_act; // Activated element frame color control object ENUM_ELEMENT_STATE m_state; // Control state (e.g. buttons (on/off)) long m_chart_id; // Chart ID int m_wnd; // Chart subwindow index int m_wnd_y; // Cursor Y coordinate offset in the subwindow int m_obj_x; // Graphical object X coordinate int m_obj_y; // Graphical object Y coordinate uchar m_alpha_bg; // Background transparency uchar m_alpha_fg; // Foreground transparency uint m_border_width; // Frame width string m_program_name; // Program name bool m_hidden; // Hidden object flag bool m_blocked; // Blocked element flag bool m_focused; // Element flag in focus
Aqui também declararemos métodos para controle do cursor do mouse, gerenciamento de cores e manipuladores virtuais de eventos:
//--- Limit the graphical object by the container dimensions virtual void ObjectTrim(void); //--- Returns the flag indicating whether the cursor is inside the object bool Contains(const int x,const int y); //--- Check if the set color is equal to the specified one bool CheckColor(const ENUM_COLOR_STATE state) const; //--- Change the background, text, and border colors depending on the condition void ColorChange(const ENUM_COLOR_STATE state); //--- Initialize (1) the class object and (2) default object colors void Init(void); virtual void InitColors(void); //--- (1) Cursor hovering (Focus), (2) button clicks (Press), (3) wheel scrolling (Wheel), //--- (4) release (Release), (5) graphical object creation (Create) event handlers. Should be identified in the descendants virtual void OnFocusEvent(const int id, const long lparam, const double dparam, const string sparam); virtual void OnPressEvent(const int id, const long lparam, const double dparam, const string sparam); virtual void OnReleaseEvent(const int id, const long lparam, const double dparam, const string sparam); virtual void OnCreateEvent(const int id, const long lparam, const double dparam, const string sparam); virtual void OnWheelEvent(const int id, const long lparam, const double dparam, const string sparam) { return; } // handler is disabled here //--- Handlers for custom events of the element when hovering, clicking, and scrolling the wheel in the object area virtual void MouseMoveHandler(const int id, const long lparam, const double dparam, const string sparam) { return; } // handler is disabled here virtual void MousePressHandler(const int id, const long lparam, const double dparam, const string sparam) { return; } // handler is disabled here virtual void MouseWheelHandler(const int id, const long lparam, const double dparam, const string sparam) { return; } // handler is disabled here public:
Na seção pública da classe, adicionaremos métodos para obter os objetos de gerenciamento de cores do elemento no estado ativado e métodos para obter as cores nos diferentes estados do elemento:
public: //--- Return the pointer to the canvas (1) background and (2) foreground CCanvas *GetBackground(void) { return &this.m_background; } CCanvas *GetForeground(void) { return &this.m_foreground; } //--- Return the pointer to the color management object for the (1) background, (2) foreground and (3) border CColorElement *GetBackColorControl(void) { return &this.m_color_background; } CColorElement *GetForeColorControl(void) { return &this.m_color_foreground; } CColorElement *GetBorderColorControl(void) { return &this.m_color_border; } //--- Return the pointer to the color management object for the (1) background, (2) foreground and (3) activated element border CColorElement *GetBackColorActControl(void) { return &this.m_color_background_act; } CColorElement *GetForeColorActControl(void) { return &this.m_color_foreground_act; } CColorElement *GetBorderColorActControl(void) { return &this.m_color_border_act; } //--- Return the (1) background, (2) foreground and (3) border color color BackColor(void) const { return(!this.State() ? this.m_color_background.GetCurrent() : this.m_color_background_act.GetCurrent()); } color ForeColor(void) const { return(!this.State() ? this.m_color_foreground.GetCurrent() : this.m_color_foreground_act.GetCurrent()); } color BorderColor(void) const { return(!this.State() ? this.m_color_border.GetCurrent() : this.m_color_border_act.GetCurrent()); } //--- Return the DEFAULT color of (1) background, (2) foreground, (3) border color BackColorDefault(void) const { return(!this.State() ? this.m_color_background.GetDefault() : this.m_color_background_act.GetDefault()); } color ForeColorDefault(void) const { return(!this.State() ? this.m_color_foreground.GetDefault() : this.m_color_foreground_act.GetDefault()); } color BorderColorDefault(void)const { return(!this.State() ? this.m_color_border.GetDefault() : this.m_color_border_act.GetDefault()); } //--- Return the FOCUSED preset color of the (1) background, (2) foreground, (3) border color BackColorFocused(void) const { return(!this.State() ? this.m_color_background.GetFocused() : this.m_color_background_act.GetFocused()); } color ForeColorFocused(void) const { return(!this.State() ? this.m_color_foreground.GetFocused() : this.m_color_foreground_act.GetFocused()); } color BorderColorFocused(void)const { return(!this.State() ? this.m_color_border.GetFocused() : this.m_color_border_act.GetFocused()); } //--- Return the preset PRESSED color of the (1) background, (2) foreground, (3) frame color BackColorPressed(void) const { return(!this.State() ? this.m_color_background.GetPressed() : this.m_color_background_act.GetPressed()); } color ForeColorPressed(void) const { return(!this.State() ? this.m_color_foreground.GetPressed() : this.m_color_foreground_act.GetPressed()); } color BorderColorPressed(void)const { return(!this.State() ? this.m_color_border.GetPressed() : this.m_color_border_act.GetPressed()); } //--- Return the BLOCKED color of (1) background, (2) foreground, (3) border color BackColorBlocked(void) const { return this.m_color_background.GetBlocked(); } color ForeColorBlocked(void) const { return this.m_color_foreground.GetBlocked(); } color BorderColorBlocked(void) const { return this.m_color_border.GetBlocked(); } //--- Set background colors for all states
Agora, em cada um dos métodos de obtenção de cor, o estado do elemento é verificado, ativado ou desativado, e a cor necessária é retornada de acordo com o estado do elemento.
Adicionaremos métodos para definir as cores do elemento ativado e aperfeiçoaremos os métodos para definir as cores dos estados do elemento em relação ao cursor do mouse, levando em conta o estado do elemento como ativado ou desativado:
//--- Set background colors for all states void InitBackColorsAct(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked) { this.m_color_background_act.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked); } void InitBackColorsAct(const color clr) { this.m_color_background_act.InitColors(clr); } //--- Set foreground colors for all states void InitForeColorsAct(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked) { this.m_color_foreground_act.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked); } void InitForeColorsAct(const color clr) { this.m_color_foreground_act.InitColors(clr); } //--- Set border colors for all states void InitBorderColorsAct(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked) { this.m_color_border_act.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked); } void InitBorderColorsAct(const color clr) { this.m_color_border_act.InitColors(clr); } //--- Initialize the color of (1) background, (2) foreground and (3) frame with initial values void InitBackColorActDefault(const color clr) { this.m_color_background_act.InitDefault(clr); } void InitForeColorActDefault(const color clr) { this.m_color_foreground_act.InitDefault(clr); } void InitBorderColorActDefault(const color clr){ this.m_color_border_act.InitDefault(clr); } //--- Initialize the color of (1) background, (2) foreground and (3) frame on hover with initial values void InitBackColorActFocused(const color clr) { this.m_color_background_act.InitFocused(clr); } void InitForeColorActFocused(const color clr) { this.m_color_foreground_act.InitFocused(clr); } void InitBorderColorActFocused(const color clr){ this.m_color_border_act.InitFocused(clr); } //--- Initialize the color of (1) background, (2) foreground and (3) frame on click with initial values void InitBackColorActPressed(const color clr) { this.m_color_background_act.InitPressed(clr); } void InitForeColorActPressed(const color clr) { this.m_color_foreground_act.InitPressed(clr); } void InitBorderColorActPressed(const color clr){ this.m_color_border_act.InitPressed(clr); } //--- Set the current background color to different states bool BackColorToDefault(void) { return(!this.State() ? this.m_color_background.SetCurrentAs(COLOR_STATE_DEFAULT) : this.m_color_background_act.SetCurrentAs(COLOR_STATE_DEFAULT)); } bool BackColorToFocused(void) { return(!this.State() ? this.m_color_background.SetCurrentAs(COLOR_STATE_FOCUSED) : this.m_color_background_act.SetCurrentAs(COLOR_STATE_FOCUSED)); } bool BackColorToPressed(void) { return(!this.State() ? this.m_color_background.SetCurrentAs(COLOR_STATE_PRESSED) : this.m_color_background_act.SetCurrentAs(COLOR_STATE_PRESSED)); } bool BackColorToBlocked(void) { return this.m_color_background.SetCurrentAs(COLOR_STATE_BLOCKED); } //--- Set the current foreground color to different states bool ForeColorToDefault(void) { return(!this.State() ? this.m_color_foreground.SetCurrentAs(COLOR_STATE_DEFAULT) : this.m_color_foreground_act.SetCurrentAs(COLOR_STATE_DEFAULT)); } bool ForeColorToFocused(void) { return(!this.State() ? this.m_color_foreground.SetCurrentAs(COLOR_STATE_FOCUSED) : this.m_color_foreground_act.SetCurrentAs(COLOR_STATE_FOCUSED)); } bool ForeColorToPressed(void) { return(!this.State() ? this.m_color_foreground.SetCurrentAs(COLOR_STATE_PRESSED) : this.m_color_foreground_act.SetCurrentAs(COLOR_STATE_PRESSED)); } bool ForeColorToBlocked(void) { return this.m_color_foreground.SetCurrentAs(COLOR_STATE_BLOCKED); } //--- Set the current frame color to different states bool BorderColorToDefault(void) { return(!this.State() ? this.m_color_border.SetCurrentAs(COLOR_STATE_DEFAULT) : this.m_color_border_act.SetCurrentAs(COLOR_STATE_DEFAULT)); } bool BorderColorToFocused(void) { return(!this.State() ? this.m_color_border.SetCurrentAs(COLOR_STATE_FOCUSED) : this.m_color_border_act.SetCurrentAs(COLOR_STATE_FOCUSED)); } bool BorderColorToPressed(void) { return(!this.State() ? this.m_color_border.SetCurrentAs(COLOR_STATE_PRESSED) : this.m_color_border_act.SetCurrentAs(COLOR_STATE_PRESSED)); } bool BorderColorToBlocked(void) { return this.m_color_border.SetCurrentAs(COLOR_STATE_BLOCKED); }
Adicionaremos métodos para definir e retornar o estado do elemento:
//--- Create OBJ_BITMAP_LABEL bool Create(const long chart_id,const int wnd,const string object_name,const int x,const int y,const int w,const int h); //--- (1) Set and (2) return the state void SetState(ENUM_ELEMENT_STATE state) { this.m_state=state; this.ColorsToDefault(); } ENUM_ELEMENT_STATE State(void) const { return this.m_state; } //--- Return (1) the object's belonging to the program, the flag (2) of a hidden element, (3) a blocked element, (4) element in focus and (5) the graphical object name (background, text)
Ao definir o estado do elemento, é necessário, após definir o sinalizador de estado, gravar todas as cores do elemento como atuais — se o elemento estiver ativado, por exemplo, um botão pressionado, todas as cores atuais são definidas como as cores do botão pressionado. Caso contrário, as cores atuais são obtidas da lista de cores do botão não pressionado.
Como agora separamos a definição e o retorno da transparência para o fundo e o primeiro plano, adicionaremos novos métodos para definir e retornar a transparência:
string NameBG(void) const { return this.m_background.ChartObjectName(); } string NameFG(void) const { return this.m_foreground.ChartObjectName(); } //--- (1) Return and (2) set background transparency uchar AlphaBG(void) const { return this.m_alpha_bg; } void SetAlphaBG(const uchar value) { this.m_alpha_bg=value; } //--- (1) Return and (2) set the foreground transparency uchar AlphaFG(void) const { return this.m_alpha_fg; } void SetAlphaFG(const uchar value) { this.m_alpha_fg=value; } //--- Sets the background and foreground transparency void SetAlpha(const uchar value) { this.m_alpha_fg=this.m_alpha_bg=value; } //--- (1) Return and (2) set the frame width
Declararemos um manipulador de eventos que deverá ser chamado a partir do manipulador de eventos do programa controlador:
//--- Event handler | void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Constructors/destructor CCanvasBase(void) : m_program_name(::MQLInfoString(MQL_PROGRAM_NAME)), m_chart_id(::ChartID()), m_wnd(0), m_alpha_bg(0), m_alpha_fg(255), m_hidden(false), m_blocked(false), m_focused(false), m_border_width(0), m_wnd_y(0), m_state(0) { this.Init(); } CCanvasBase(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h); ~CCanvasBase(void); };
No construtor, faremos a indicação correta das propriedades da fonte desenhada no canvas e chamaremos o método Init() para memorizar as propriedades do gráfico e do mouse:
//+------------------------------------------------------------------+ //| CCanvasBase::Constructor | //+------------------------------------------------------------------+ CCanvasBase::CCanvasBase(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : m_program_name(::MQLInfoString(MQL_PROGRAM_NAME)), m_wnd(wnd<0 ? 0 : wnd), m_alpha_bg(0), m_alpha_fg(255), m_hidden(false), m_blocked(false), m_focused(false), m_border_width(0), m_state(0) { //--- Get the adjusted chart ID and the distance in pixels along the vertical Y axis //--- between the upper frame of the indicator subwindow and the upper frame of the chart main window this.m_chart_id=this.CorrectChartID(chart_id); this.m_wnd_y=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOW_YDISTANCE,this.m_wnd); //--- If the graphical resource and graphical object are created if(this.Create(this.m_chart_id,this.m_wnd,object_name,x,y,w,h)) { //--- Clear the background and foreground canvases and set the initial coordinate values, //--- names of graphic objects and properties of text drawn in the foreground this.Clear(false); this.m_obj_x=x; this.m_obj_y=y; this.m_color_background.SetName("Background"); this.m_color_foreground.SetName("Foreground"); this.m_color_border.SetName("Border"); this.m_foreground.FontSet(DEF_FONTNAME,-DEF_FONTSIZE*10,FW_MEDIUM); this.m_bound.SetName("Perimeter"); //--- Remember permissions for the mouse and chart tools this.Init(); } }
No destrutor da classe, destruímos o objeto gráfico criado e restauramos as propriedades do gráfico e as permissões do mouse que foram memorizadas:
//+------------------------------------------------------------------+ //| CCanvasBase::Destructor | //+------------------------------------------------------------------+ CCanvasBase::~CCanvasBase(void) { //--- Destroy the object this.Destroy(); //--- Return permissions for the mouse and chart tools ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_WHEEL, this.m_chart_mouse_wheel_flag); ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_MOVE, this.m_chart_mouse_move_flag); ::ChartSetInteger(this.m_chart_id, CHART_EVENT_OBJECT_CREATE, this.m_chart_object_create_flag); ::ChartSetInteger(this.m_chart_id, CHART_MOUSE_SCROLL, this.m_chart_mouse_scroll_flag); ::ChartSetInteger(this.m_chart_id, CHART_CONTEXT_MENU, this.m_chart_context_menu_flag); ::ChartSetInteger(this.m_chart_id, CHART_CROSSHAIR_TOOL, this.m_chart_crosshair_tool_flag); }
No método de criação do elemento gráfico, o nome do objeto gráfico criado não deve conter espaços. Corrigiremos isso, substituindo os espaços no nome por sublinhados:
//+------------------------------------------------------------------+ //| CCanvasBase::Create background and foreground graphical objects | //+------------------------------------------------------------------+ bool CCanvasBase::Create(const long chart_id,const int wnd,const string object_name,const int x,const int y,const int w,const int h) { //--- Get the adjusted chart ID long id=this.CorrectChartID(chart_id); //--- Correct the passed object name string nm=object_name; ::StringReplace(nm," ","_"); //--- Create a graphical object name for the background and create a canvas string obj_name=nm+"_BG"; if(!this.m_background.CreateBitmapLabel(id,(wnd<0 ? 0 : wnd),obj_name,x,y,(w>0 ? w : 1),(h>0 ? h : 1),COLOR_FORMAT_ARGB_NORMALIZE)) { ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name); return false; } //--- Create a graphical object name for the foreground and create a canvas obj_name=nm+"_FG"; if(!this.m_foreground.CreateBitmapLabel(id,(wnd<0 ? 0 : wnd),obj_name,x,y,(w>0 ? w : 1),(h>0 ? h : 1),COLOR_FORMAT_ARGB_NORMALIZE)) { ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name); return false; } //--- If created successfully, enter the program name into the OBJPROP_TEXT property of the graphical object ::ObjectSetString(id,this.NameBG(),OBJPROP_TEXT,this.m_program_name); ::ObjectSetString(id,this.NameFG(),OBJPROP_TEXT,this.m_program_name); //--- Set the dimensions of the rectangular area and return 'true' this.m_bound.SetXY(x,y); this.m_bound.Resize(w,h); return true; }
O método que retorna o sinalizador de o cursor estar dentro do objeto:
//+-----------------------------------------------------------------------------------+ //| CCanvasBase::Return the flag indicating whether the cursor is inside the object | //+-----------------------------------------------------------------------------------+ bool CCanvasBase::Contains(const int x,const int y) { //--- check and return the result int left=::fmax(this.X(),this.ObjectX()); int right=::fmin(this.Right(),this.ObjectRight()); int top=::fmax(this.Y(),this.ObjectY()); int bottom=::fmin(this.Bottom(),this.ObjectBottom()); return(x>=left && x<=right && y>=top && y<=bottom); }
Como o tamanho do objeto e o tamanho do canvas podem diferir (o método ObjectTrim altera o tamanho do canvas sem modificar o tamanho do objeto), então aqui, como limites dentro dos quais o cursor se encontra, é utilizado um dos valores: ou o limite do objeto, ou a borda correspondente do canvas. O método retorna o sinalizador de as coordenadas passadas estarem dentro dos limites obtidos.
No método de bloqueio do elemento, a definição do sinalizador de bloqueio deve estar antesda chamada do método de desenho do elemento, vamos corrigir isso:
//+------------------------------------------------------------------+ //| CCanvasBase::Block the element | //+------------------------------------------------------------------+ void CCanvasBase::Block(const bool chart_redraw) { //--- If the element has already been blocked, leave if(this.m_blocked) return; //--- Set the current colors as the colors of the blocked element, //--- set the lock flag and redraw the object this.ColorsToBlocked(); this.m_blocked=true; this.Draw(chart_redraw); }
Essa correção permite desenhar corretamente o elemento bloqueado. Até essa alteração, ao desenhar o elemento, as cores eram obtidas do estado padrão e não do estado bloqueado, pois o sinalizador era definido apenas após o bloqueio.
Método para definir as restrições do gráfico:
//+------------------------------------------------------------------+ //| CCanvasBase::Set chart restrictions | //| (wheel scrolling, context menu and crosshair) | //+------------------------------------------------------------------+ void CCanvasBase::SetFlags(const bool flag) { //--- If you need to set flags, and they have already been set before, leave if(flag && this.m_flags_state) return; //--- If we need to reset the flags, and they have already been reset earlier, leave if(!flag && !this.m_flags_state) return; //--- Set the required flag for the context menu, //--- crosshair tool and scrolling the chart with the mouse wheel. //--- After installation, remember the value of the set flag ::ChartSetInteger(this.m_chart_id, CHART_CONTEXT_MENU, flag); ::ChartSetInteger(this.m_chart_id, CHART_CROSSHAIR_TOOL,flag); ::ChartSetInteger(this.m_chart_id, CHART_MOUSE_SCROLL, flag); this.m_flags_state=flag; //--- Update the chart to immediately apply the set flags ::ChartRedraw(this.m_chart_id); }
Método de inicialização da classe:
//+------------------------------------------------------------------+ //| CCanvasBase::Class initialization | //+------------------------------------------------------------------+ void CCanvasBase::Init(void) { //--- Remember permissions for the mouse and chart tools this.m_chart_mouse_wheel_flag = ::ChartGetInteger(this.m_chart_id, CHART_EVENT_MOUSE_WHEEL); this.m_chart_mouse_move_flag = ::ChartGetInteger(this.m_chart_id, CHART_EVENT_MOUSE_MOVE); this.m_chart_object_create_flag = ::ChartGetInteger(this.m_chart_id, CHART_EVENT_OBJECT_CREATE); this.m_chart_mouse_scroll_flag = ::ChartGetInteger(this.m_chart_id, CHART_MOUSE_SCROLL); this.m_chart_context_menu_flag = ::ChartGetInteger(this.m_chart_id, CHART_CONTEXT_MENU); this.m_chart_crosshair_tool_flag= ::ChartGetInteger(this.m_chart_id, CHART_CROSSHAIR_TOOL); //--- Set permissions for the mouse and chart ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_WHEEL, true); ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_MOVE, true); ::ChartSetInteger(this.m_chart_id, CHART_EVENT_OBJECT_CREATE, true); //--- Initialize the object default colors this.InitColors(); }
Método de inicialização das cores do objeto por padrão:
//+------------------------------------------------------------------+ //| CCanvasBase::Initialize the object default colors | //+------------------------------------------------------------------+ void CCanvasBase::InitColors(void) { //--- Initialize the background colors for the normal and activated states and make it the current background color this.InitBackColors(clrWhiteSmoke); this.InitBackColorsAct(clrWhiteSmoke); this.BackColorToDefault(); //--- Initialize the foreground colors for the normal and activated states and make it the current text color this.InitForeColors(clrBlack); this.InitForeColorsAct(clrBlack); this.ForeColorToDefault(); //--- Initialize the border colors for the normal and activated states and make it the current border color this.InitBorderColors(clrDarkGray); this.InitBorderColorsAct(clrDarkGray); this.BorderColorToDefault(); //--- Initialize the border color and foreground color for the disabled element this.InitBorderColorBlocked(clrLightGray); this.InitForeColorBlocked(clrSilver); }
Método que verifica se a cor definida é igual à especificada:
//+------------------------------------------------------------------+ //| CCanvasBase::Check if the set color is equal to the specified one| //+------------------------------------------------------------------+ bool CCanvasBase::CheckColor(const ENUM_COLOR_STATE state) const { bool res=true; //--- Depending on the event being checked switch(state) { //--- check that all STANDARD background, text, and frame colors are equal to the preset values case COLOR_STATE_DEFAULT : res &=this.BackColor()==this.BackColorDefault(); res &=this.ForeColor()==this.ForeColorDefault(); res &=this.BorderColor()==this.BorderColorDefault(); break; //--- check if all FOCUSED background, text, and border colors are equal to the preset values case COLOR_STATE_FOCUSED : res &=this.BackColor()==this.BackColorFocused(); res &=this.ForeColor()==this.ForeColorFocused(); res &=this.BorderColor()==this.BorderColorFocused(); break; //--- check if all PRESSED background, text, and border colors are equal to the preset values case COLOR_STATE_PRESSED : res &=this.BackColor()==this.BackColorPressed(); res &=this.ForeColor()==this.ForeColorPressed(); res &=this.BorderColor()==this.BorderColorPressed(); break; //--- check if all BLOCKED background, text, and border colors are equal to the preset values case COLOR_STATE_BLOCKED : res &=this.BackColor()==this.BackColorBlocked(); res &=this.ForeColor()==this.ForeColorBlocked(); res &=this.BorderColor()==this.BorderColorBlocked(); break; default: res=false; break; } return res; }
Para que as cores do elemento sejam alteradas apenas no momento da troca do estado do elemento, esse método retorna um sinalizador indicando se as cores correspondentes ao estado do elemento já estão definidas. Se as cores atuais do elemento não forem iguais às cores estabelecidas para o estado verificado, o método permite a mudança de cor e o redesenho do elemento gráfico. Se as cores já estiverem definidas de acordo com o estado do elemento, não há necessidade de alterar as cores nem de redesenhar o objeto — o método proíbe a mudança de cor.
Método que altera as cores dos elementos do objeto por evento:
//+------------------------------------------------------------------+ //| CCanvasBase::Change the color of object elements by event | //+------------------------------------------------------------------+ void CCanvasBase::ColorChange(const ENUM_COLOR_STATE state) { //--- Depending on the event, set the event colors as primary ones switch(state) { case COLOR_STATE_DEFAULT : this.ColorsToDefault(); break; case COLOR_STATE_FOCUSED : this.ColorsToFocused(); break; case COLOR_STATE_PRESSED : this.ColorsToPressed(); break; case COLOR_STATE_BLOCKED : this.ColorsToBlocked(); break; default : break; } }
Dependendo do evento pelo qual é necessário alterar a cor, são definidas as cores atuais de acordo com o evento, isto é, com o estado do elemento.
Manipulador de eventos:
//+------------------------------------------------------------------+ //| CCanvasBase::Event handler | //+------------------------------------------------------------------+ void CCanvasBase::OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- If the chart changes if(id==CHARTEVENT_CHART_CHANGE) { //--- adjust the distance between the upper frame of the indicator subwindow and the upper frame of the chart main window this.m_wnd_y=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOW_YDISTANCE,this.m_wnd); } //--- If the element is blocked or hidden, leave if(this.IsBlocked() || this.IsHidden()) return; //--- Mouse cursor coordinates int x=(int)lparam; int y=(int)dparam-this.m_wnd_y; // Adjust Y by the height of the indicator window //--- Event of cursor movement or mouse button click if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_OBJECT_CLICK) { //--- If the cursor is within the object if(this.Contains(x, y)) { //--- If the object is not a part of the container, disable chart scrolling, the context menu, and the Crosshair tool if(this.m_container==NULL) this.SetFlags(false); //--- Get the state of the mouse buttons; if they are pressed, call the click handler if(sparam=="1" || sparam=="2" || sparam=="16") this.OnPressEvent(id, lparam, dparam, sparam); //--- buttons are not pressed - handle the cursor movement else this.OnFocusEvent(id, lparam, dparam, sparam); } //--- Cursor outside the object else { //--- Handle the cursor moving beyond the object boundaries this.OnReleaseEvent(id,lparam,dparam,sparam); //--- If the object is not a part of the container, enable chart scrolling, the context menu, and the Crosshair tool if(this.m_container==NULL) this.SetFlags(true); } } //--- Mouse wheel scroll event if(id==CHARTEVENT_MOUSE_WHEEL) { this.OnWheelEvent(id,lparam,dparam,sparam); } //--- Graphical object creation event if(id==CHARTEVENT_OBJECT_CREATE) { this.OnCreateEvent(id,lparam,dparam,sparam); } //--- If a custom chart event has arrived if(id>CHARTEVENT_CUSTOM) { //--- do not handle its own events if(sparam==this.NameBG()) return; //--- bring the custom event in line with the standard ones ENUM_CHART_EVENT chart_event=ENUM_CHART_EVENT(id-CHARTEVENT_CUSTOM); //--- If clicking an object if(chart_event==CHARTEVENT_OBJECT_CLICK) { this.MousePressHandler(chart_event, lparam, dparam, sparam); } //--- If the mouse cursor is moving if(chart_event==CHARTEVENT_MOUSE_MOVE) { this.MouseMoveHandler(chart_event, lparam, dparam, sparam); } //--- If the mouse wheel is scrolling if(chart_event==CHARTEVENT_MOUSE_WHEEL) { this.MouseWheelHandler(chart_event, lparam, dparam, sparam); } } }
No objeto base dos elementos gráficos foi organizada a lógica de processamento da interação do cursor do mouse com os elementos gráficos. Para os diversos eventos monitorados, são chamados manipuladores virtuais. Alguns manipuladores são implementados diretamente nesta classe, enquanto outros simplesmente não fazem nada e precisam ser implementados nos objetos que herdam desta classe.
Os manipuladores de eventos cujo nome termina em *Handler são destinados ao processamento da interação interna nos elementos de controle entre os componentes que os compõem. Já os manipuladores que possuem *Event em seu nome processam diretamente os eventos do gráfico e enviam ao gráfico eventos do usuário, pelos quais é possível determinar o tipo de evento e de qual elemento de controle ele foi enviado. Isso permitirá ao usuário tratar tais eventos em seu próprio programa.
Manipulador de perda de foco:
//+------------------------------------------------------------------+ //| CCanvasBase::Out of focus handler | //+------------------------------------------------------------------+ void CCanvasBase::OnReleaseEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- The element is not in focus when the cursor is moved away this.m_focused=false; //--- restore the original colors, reset the Focused flag and redraw the object if(!this.CheckColor(COLOR_STATE_DEFAULT)) { this.ColorChange(COLOR_STATE_DEFAULT); this.Draw(true); } }
Manipulador de passagem do cursor:
//+------------------------------------------------------------------+ //| CCanvasBase::Hover positioning handler | //+------------------------------------------------------------------+ void CCanvasBase::OnFocusEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Element in focus this.m_focused=true; //--- If the object colors are not for Focused mode if(!this.CheckColor(COLOR_STATE_FOCUSED)) { //--- set the colors and the Focused flag and redraw the object this.ColorChange(COLOR_STATE_FOCUSED); this.Draw(true); } }
Manipulador de clique no objeto:
//+------------------------------------------------------------------+ //| CCanvasBase::Object click handler | //+------------------------------------------------------------------+ void CCanvasBase::OnPressEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- The element is in focus when clicked on this.m_focused=true; //--- If the object colors are not for Pressed mode if(!this.CheckColor(COLOR_STATE_PRESSED)) { //--- set the Pressed colors and redraw the object this.ColorChange(COLOR_STATE_PRESSED); this.Draw(true); } //--- send a custom event to the chart with the passed values in lparam, dparam, and the object name in sparam ::EventChartCustom(this.m_chart_id, (ushort)CHARTEVENT_OBJECT_CLICK, lparam, dparam, this.NameBG()); }
Manipulador do evento de criação do objeto gráfico:
//+------------------------------------------------------------------+ //| CCanvasBase::Graphical object creation event handler | //+------------------------------------------------------------------+ void CCanvasBase::OnCreateEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- if this is an object belonging to this program, leave if(this.IsBelongsToThis(sparam)) return; //--- bring the object to the front this.BringToTop(true); }
A lógica de todos os manipuladores está detalhadamente comentada no código. Essencialmente, aqui apenas é organizada a reação ao evento na forma de alteração das cores do elemento gráfico e, onde necessário, o envio de eventos do usuário para o gráfico. O último manipulador reage à criação do objeto gráfico no gráfico e move os elementos gráficos para o primeiro plano. Isso permitirá, por exemplo, que painéis permaneçam sempre no primeiro plano.
Todos esses manipuladores são virtuais e, se necessário, devem ser sobrescritos nas classes herdadas.
Com o aperfeiçoamento do objeto base de todos os elementos gráficos, finalizamos esta etapa. Agora, com base no componente Controller criado no objeto base e no componente View criado anteriormente, iniciaremos a criação dos elementos gráficos mais simples, que também fazem parte do componente View. Eles se tornarão os "blocos de construção" a partir dos quais, ao final, serão criados elementos de controle complexos e, em particular, o elemento de controle Table View, cuja criação estamos desenvolvendo ao longo de vários artigos.
Elementos de controle simples
Na mesma pasta \MQL5\Indicators\Tables\Controls\ criaremos um novo arquivo incluído Controls.mqh.
Ao arquivo criado incluiremos o arquivo do objeto base dos elementos gráficos Base.mqh e adicionaremos algumas macrossubstituições e enumerações:
//+------------------------------------------------------------------+ //| Controls.mqh | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| Include libraries | //+------------------------------------------------------------------+ #include "Base.mqh" //+------------------------------------------------------------------+ //| Macro substitutions | //+------------------------------------------------------------------+ #define DEF_LABEL_W 40 // Text label default width #define DEF_LABEL_H 16 // Text label default height #define DEF_BUTTON_W 50 // Default button width #define DEF_BUTTON_H 16 // Default button height //+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+ enum ENUM_ELEMENT_COMPARE_BY // Compared properties { ELEMENT_SORT_BY_ID = 0, // Comparison by element ID ELEMENT_SORT_BY_NAME, // Comparison by element name ELEMENT_SORT_BY_TEXT, // Comparison by element text ELEMENT_SORT_BY_COLOR, // Comparison by element color ELEMENT_SORT_BY_ALPHA, // Comparison by element transparency ELEMENT_SORT_BY_STATE, // Comparison by element state }; //+------------------------------------------------------------------+ //| Functions | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Classes | //+------------------------------------------------------------------+
Nas macrossubstituições definimos os tamanhos padrão para rótulos de texto e botões. Na enumeração indicamos as propriedades existentes do elemento gráfico base. Com base nessas propriedades, será possível buscar objetos, classificá-los e compará-los. Ao adicionar novas propriedades a quaisquer objetos, incluiremos novas constantes nessa enumeração.
Classes auxiliares
Cada elemento gráfico pode ter em sua composição uma imagem. Isso permitirá desenhar ícones para botões, linhas de texto, etc.
Criaremos uma classe especial para o desenho de imagens, que será uma parte integrante dos elementos de controle simples.
Classe para desenho de imagens em uma área especificada
//+------------------------------------------------------------------+ //| Classes | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Image drawing class | //+------------------------------------------------------------------+ class CImagePainter : public CBaseObj { protected: CCanvas *m_canvas; // Pointer to the canvas where we draw CBound m_bound; // Image coordinates and boundaries uchar m_alpha; // Transparency //--- Check the canvas validity and correct dimensions bool CheckBound(void); public: //--- (1) Assigns the canvas to draw on, (2) sets and (3) returns transparency void CanvasAssign(CCanvas *canvas) { this.m_canvas=canvas; } void SetAlpha(const uchar value) { this.m_alpha=value; } uchar Alpha(void) const { return this.m_alpha; } //--- (1) Set the coordinates and (2) change the area size void SetXY(const int x,const int y) { this.m_bound.SetXY(x,y); } void SetSize(const int w,const int h) { this.m_bound.Resize(w,h); } //--- Set the area coordinates and dimensions void SetBound(const int x,const int y,const int w,const int h) { this.SetXY(x,y); this.SetSize(w,h); } //--- Returns the image boundaries and dimensions int X(void) const { return this.m_bound.X(); } int Y(void) const { return this.m_bound.Y(); } int Right(void) const { return this.m_bound.Right(); } int Bottom(void) const { return this.m_bound.Bottom(); } int Width(void) const { return this.m_bound.Width(); } int Height(void) const { return this.m_bound.Height(); } //--- Clear the area bool Clear(const int x,const int y,const int w,const int h,const bool update=true); //--- Draw a filled (1) up, (2) down, (3) left and (4) right arrow bool ArrowUp(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool ArrowDown(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool ArrowLeft(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool ArrowRight(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); //--- Draw (1) checked and (2) unchecked CheckBox bool CheckedBox(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool UncheckedBox(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); //--- Draw (1) checked and (2) unchecked RadioButton bool CheckedRadioButton(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool UncheckedRadioButton(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); //--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_IMAGE_PAINTER); } //--- Constructors/destructor CImagePainter(void) : m_canvas(NULL) { this.SetBound(1,1,DEF_BUTTON_H-2,DEF_BUTTON_H-2); this.SetName("Image Painter"); } CImagePainter(CCanvas *canvas) : m_canvas(canvas) { this.SetBound(1,1,DEF_BUTTON_H-2,DEF_BUTTON_H-2); this.SetName("Image Painter"); } CImagePainter(CCanvas *canvas,const int id,const string name) : m_canvas(canvas) { this.m_id=id; this.SetName(name); this.SetBound(1,1,DEF_BUTTON_H-2,DEF_BUTTON_H-2); } CImagePainter(CCanvas *canvas,const int id,const int dx,const int dy,const int w,const int h,const string name) : m_canvas(canvas) { this.m_id=id; this.SetName(name); this.SetBound(dx,dy,w,h); } ~CImagePainter(void) {} };
Analisemos os métodos da classe.
Método para comparar dois objetos de desenho:
//+------------------------------------------------------------------+ //| CImagePainter::Compare two objects | //+------------------------------------------------------------------+ int CImagePainter::Compare(const CObject *node,const int mode=0) const { const CImagePainter *obj=node; switch(mode) { case ELEMENT_SORT_BY_NAME : return(this.Name() >obj.Name() ? 1 : this.Name() <obj.Name() ? -1 : 0); case ELEMENT_SORT_BY_ALPHA : return(this.Alpha()>obj.Alpha() ? 1 : this.Alpha()<obj.Alpha()? -1 : 0); default : return(this.ID() >obj.ID() ? 1 : this.ID() <obj.ID() ? -1 : 0); } }
O método é necessário para a busca do objeto de desenho desejado. Por padrão, a busca é realizada pelo identificador do objeto. O método será necessário quando, nos objetos dos elementos de controle, houver listas nas quais são armazenados objetos de desenho. No momento, em cada elemento de controle será declarado um único objeto de desenho, destinado ao desenho do ícone principal do elemento.
Método que verifica a validade do canvas e a correção das dimensões da área da imagem:
//+------------------------------------------------------------------+ //|CImagePainter::Check the canvas validity and correct dimensions | //+------------------------------------------------------------------+ bool CImagePainter::CheckBound(void) { if(this.m_canvas==NULL) { ::PrintFormat("%s: Error. First you need to assign the canvas using the CanvasAssign() method",__FUNCTION__); return false; } if(this.Width()==0 || this.Height()==0) { ::PrintFormat("%s: Error. First you need to set the area size using the SetSize() or SetBound() methods",__FUNCTION__); return false; } return true; }
Se nenhum ponteiro para o canvas for passado ao objeto, ou se a largura e a altura da área da imagem não estiverem definidas, o método retorna false. Caso contrário, retorna true.
Método que limpa a área da imagem:
//+------------------------------------------------------------------+ //| CImagePainter::Clear the area | //+------------------------------------------------------------------+ bool CImagePainter::Clear(const int x,const int y,const int w,const int h,const bool update=true) { //--- If the image area is not valid, return 'false' if(!this.CheckBound()) return false; //--- Clear the entire image area with transparent color this.m_canvas.FillRectangle(x,y,x+w-1,y+h-1,clrNULL); //--- If specified, update the canvas if(update) this.m_canvas.Update(false); //--- All is successful return true; }
O método limpa completamente toda a área da imagem, preenchendo-a com uma cor transparente.
Método que desenha uma seta preenchida para cima:
//+------------------------------------------------------------------+ //| CImagePainter::Draw a filled up arrow | //+------------------------------------------------------------------+ bool CImagePainter::ArrowUp(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- If the image area is not valid, return 'false' if(!this.CheckBound()) return false; //--- Calculate the coordinates of the arrow corners inside the image area int hw=(int)::floor(w/2); // Half width if(hw==0) hw=1; int x1 = x + 1; // X. Base (left point) int y1 = y + h - 4; // Y. Left base point int x2 = x1 + hw; // X. Vertex (central top point) int y2 = y + 3; // Y. Vertex (highest point) int x3 = x1 + w - 1; // X. Base (right point) int y3 = y1; // Y. Base (right point) //--- Draw a triangle this.m_canvas.FillTriangle(x1, y1, x2, y2, x3, y3, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Método que desenha uma seta preenchida para baixo:
//+------------------------------------------------------------------+ //| CImagePainter::Draw a filled down arrow | //+------------------------------------------------------------------+ bool CImagePainter::ArrowDown(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- If the image area is not valid, return 'false' if(!this.CheckBound()) return false; //--- Calculate the coordinates of the arrow corners inside the image area int hw=(int)::floor(w/2); // Half width if(hw==0) hw=1; int x1=x+1; // X. Base (left point) int y1=y+4; // Y. Left base point int x2=x1+hw; // X. Vertex (central bottom point) int y2=y+h-3; // Y. Vertex (lowest point) int x3=x1+w-1; // X. Base (right point) int y3=y1; // Y. Base (right point) //--- Draw a triangle this.m_canvas.FillTriangle(x1, y1, x2, y2, x3, y3, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Método que desenha uma seta preenchida para a esquerda:
//+------------------------------------------------------------------+ //| CImagePainter::Draw a filled left arrow | //+------------------------------------------------------------------+ bool CImagePainter::ArrowLeft(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- If the image area is not valid, return 'false' if(!this.CheckBound()) return false; //--- Calculate the coordinates of the arrow corners inside the image area int hh=(int)::floor(h/2); // Half height if(hh==0) hh=1; int x1=x+w-4; // X. Base (right side) int y1=y+1; // Y. Base upper corner int x2=x+3; // X. Vertex (left center point) int y2=y1+hh; // Y. Central point (vertex) int x3=x1; // X. Bottom base corner int y3=y1+h-1; // Y. Bottom base corner //--- Draw a triangle this.m_canvas.FillTriangle(x1, y1, x2, y2, x3, y3, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Método que desenha uma seta preenchida para a direita:
//+------------------------------------------------------------------+ //| CImagePainter::Draw a filled right arrow | //+------------------------------------------------------------------+ bool CImagePainter::ArrowRight(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- If the image area is not valid, return 'false' if(!this.CheckBound()) return false; //--- Calculate the coordinates of the arrow corners inside the image area int hh=(int)::floor(h/2); // Half height if(hh==0) hh=1; int x1=x+4; // X. Triangle base (left side) int y1=y+1; // Y. Base upper corner int x2=x+w-3; // X. Vertex (right center point) int y2=y1+hh; // Y. Central point (vertex) int x3=x1; // X. Bottom base corner int y3=y1+h-1; // Y. Bottom base corner //--- Draw a triangle this.m_canvas.FillTriangle(x1, y1, x2, y2, x3, y3, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Dentro da área da imagem é definida uma área para a seta com um recuo de um pixel em cada um dos lados da área retangular, e dentro dela é desenhada a seta preenchida.
Método que desenha um CheckBox marcado:
//+------------------------------------------------------------------+ //| CImagePainter::Draw a checked CheckBox | //+------------------------------------------------------------------+ bool CImagePainter::CheckedBox(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- If the image area is not valid, return 'false' if(!this.CheckBound()) return false; //--- Rectangle coordinates int x1=x+1; // Upper left corner, X int y1=y+1; // Upper left corner, Y int x2=x+w-2; // Bottom right corner, X int y2=y+h-2; // Bottom right corner, Y //--- Draw a rectangle this.m_canvas.Rectangle(x1, y1, x2, y2, ::ColorToARGB(clr, alpha)); //--- Checkmark coordinates int arrx[3], arry[3]; arrx[0]=x1+(x2-x1)/4; // X. Left point arrx[1]=x1+w/3; // X. Central point arrx[2]=x2-(x2-x1)/4; // X. Right point arry[0]=y1+1+(y2-y1)/2; // Y. Left point arry[1]=y2-(y2-y1)/3; // Y. Central point arry[2]=y1+(y2-y1)/3; // Y. Right point //--- Draw a "tick" with a double-thickness line this.m_canvas.Polyline(arrx, arry, ::ColorToARGB(clr, alpha)); arrx[0]++; arrx[1]++; arrx[2]++; this.m_canvas.Polyline(arrx, arry, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Método que desenha um CheckBox desmarcado:
//+------------------------------------------------------------------+ //| CImagePainter::Draw unchecked CheckBox | //+------------------------------------------------------------------+ bool CImagePainter::UncheckedBox(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- If the image area is not valid, return 'false' if(!this.CheckBound()) return false; //--- Rectangle coordinates int x1=x+1; // Upper left corner, X int y1=y+1; // Upper left corner, Y int x2=x+w-2; // Bottom right corner, X int y2=y+h-2; // Bottom right corner, Y //--- Draw a rectangle this.m_canvas.Rectangle(x1, y1, x2, y2, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Método que desenha um RadioButton marcado:
//+------------------------------------------------------------------+ //| CImagePainter::Draw checked RadioButton | //+------------------------------------------------------------------+ bool CImagePainter::CheckedRadioButton(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- If the image area is not valid, return 'false' if(!this.CheckBound()) return false; //--- Circle coordinates and radius int x1=x+1; // Circle region upper left corner, X int y1=y+1; // Circle region upper left corner, Y int x2=x+w-2; // Circle region lower right corner, X int y2=y+h-2; // Circle region lower right corner, Y //--- Circle coordinates and radius int d=::fmin(x2-x1,y2-y1); // Shorter side diameter (width or height) int r=d/2; // Radius int cx=x1+r; // Center X coordinate int cy=y1+r; // Center Y coordinate //--- Draw a circle this.m_canvas.CircleWu(cx, cy, r, ::ColorToARGB(clr, alpha)); //--- Label radius r/=2; if(r<1) r=1; //--- Draw a label this.m_canvas.FillCircle(cx, cy, r, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Método que desenha um RadioButton desmarcado:
//+------------------------------------------------------------------+ //| CImagePainter::Draw unchecked RadioButton | //+------------------------------------------------------------------+ bool CImagePainter::UncheckedRadioButton(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- If the image area is not valid, return 'false' if(!this.CheckBound()) return false; //--- Circle coordinates and radius int x1=x+1; // Circle region upper left corner, X int y1=y+1; // Circle region upper left corner, Y int x2=x+w-2; // Circle region lower right corner, X int y2=y+h-2; // Circle region lower right corner, Y //--- Circle coordinates and radius int d=::fmin(x2-x1,y2-y1); // Shorter side diameter (width or height) int r=d/2; // Radius int cx=x1+r; // Center X coordinate int cy=y1+r; // Center Y coordinate //--- Draw a circle this.m_canvas.CircleWu(cx, cy, r, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Esses são métodos simples, que apenas fornecem a possibilidade de desenhar as figuras necessárias sem a necessidade de escrevê-las manualmente. Mais adiante, adicionaremos aqui outros métodos que desenham diferentes ícones para a formatação dos elementos gráficos.
Métodos para salvar a área de desenho em arquivo e carregá-la a partir de arquivo:
//+------------------------------------------------------------------+ //| CImagePainter::Save to file | //+------------------------------------------------------------------+ bool CImagePainter::Save(const int file_handle) { //--- Save the parent object data if(!CBaseObj::Save(file_handle)) return false; //--- Save transparency if(::FileWriteInteger(file_handle,this.m_alpha,INT_VALUE)!=INT_VALUE) return false; //--- Save area data if(!this.m_bound.Save(file_handle)) return false; //--- All is successful return true; } //+------------------------------------------------------------------+ //| CImagePainter::Load from file | //+------------------------------------------------------------------+ bool CImagePainter::Load(const int file_handle) { //--- Load parent object data if(!CBaseObj::Load(file_handle)) return false; //--- Load transparency this.m_alpha=(uchar)::FileReadInteger(file_handle,INT_VALUE); //--- Load area data if(!this.m_bound.Load(file_handle)) return false; //--- All is successful return true; }
Agora já podemos prosseguir para as classes dos elementos de controle simples. O objeto mínimo desse tipo será a classe de rótulo de texto. A partir desse elemento serão herdadas as classes de outros elementos de controle.
Neste mesmo arquivo Controls.mqh continuaremos a escrever o código das classes.
Classe do elemento de controle "Rótulo de texto"
Nesta classe haverá um conjunto de variáveis e métodos que permitem trabalhar com qualquer elemento de controle, definir e obter os parâmetros do objeto, salvar e carregar as suas propriedades. A interatividade de todos os elementos de controle, o componente Controller, nós adicionamos hoje à classe base de todos os elementos de controle. Analisemos a classe do rótulo de texto:
//+------------------------------------------------------------------+ //| Text label class | //+------------------------------------------------------------------+ class CLabel : public CCanvasBase { protected: CImagePainter m_painter; // Drawing class ushort m_text[]; // Text ushort m_text_prev[]; // Previous text int m_text_x; // Text X coordinate (offset relative to the object left border) int m_text_y; // Text Y coordinate (offset relative to the object upper border) //--- (1) Set and (2) return the previous text void SetTextPrev(const string text) { ::StringToShortArray(text,this.m_text_prev); } string TextPrev(void) const { return ::ShortArrayToString(this.m_text_prev);} //--- Delete the text void ClearText(void); public: //--- Return the pointer to the drawing class CImagePainter *Painter(void) { return &this.m_painter; } //--- (1) Set and (2) return the text void SetText(const string text) { ::StringToShortArray(text,this.m_text); } string Text(void) const { return ::ShortArrayToString(this.m_text); } //--- Return the text (1) X and (2) Y coordinate int TextX(void) const { return this.m_text_x; } int TextY(void) const { return this.m_text_y; } //--- Set the text (1) X and (2) Y coordinate void SetTextShiftH(const int x) { this.m_text_x=x; } void SetTextShiftV(const int y) { this.m_text_y=y; } //--- (1) Set the coordinates and (2) change the image area size void SetImageXY(const int x,const int y) { this.m_painter.SetXY(x,y); } void SetImageSize(const int w,const int h) { this.m_painter.SetSize(w,h); } //--- Set the area coordinates and image area dimensions void SetImageBound(const int x,const int y,const int w,const int h) { this.SetImageXY(x,y); this.SetImageSize(w,h); } //--- Return the (1) X, (2) Y coordinate, (3) width, (4) height, (5) right, (6) bottom image area border int ImageX(void) const { return this.m_painter.X(); } int ImageY(void) const { return this.m_painter.Y(); } int ImageWidth(void) const { return this.m_painter.Width(); } int ImageHeight(void) const { return this.m_painter.Height(); } int ImageRight(void) const { return this.m_painter.Right(); } int ImageBottom(void) const { return this.m_painter.Bottom(); } //--- Display the text void DrawText(const int dx, const int dy, const string text, const bool chart_redraw); //--- Draw the appearance virtual void Draw(const bool chart_redraw); //--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_LABEL); } //--- Constructors/destructor CLabel(void); CLabel(const string object_name, const string text, const int x, const int y, const int w, const int h); CLabel(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h); CLabel(const string object_name, const long chart_id, const int wnd, const string text, const int x, const int y, const int w, const int h); ~CLabel(void) {} };
Na classe estão definidos dois arrays de símbolos ushort, para o texto atual e para o texto anterior do rótulo. Isso permite, durante o desenho, ter acesso às dimensões do texto anterior e apagar corretamente a área coberta pelo texto antes de exibir o novo texto no canvas.
Analisemos os métodos declarados.
A classe possui quatro construtores, que permitem criar o objeto com diferentes conjuntos de parâmetros:
//+------------------------------------------------------------------+ //| CLabel::Default constructor. Build a label in the main window | //| of the current chart at coordinates 0,0 with default dimensions | //+------------------------------------------------------------------+ CLabel::CLabel(void) : CCanvasBase("Label",::ChartID(),0,0,0,DEF_LABEL_W,DEF_LABEL_H), m_text_x(0), m_text_y(0) { //--- Assign the foreground canvas to the drawing object and //--- reset the coordinates and dimensions, which makes it inactive this.m_painter.CanvasAssign(this.GetForeground()); this.m_painter.SetXY(0,0); this.m_painter.SetSize(0,0); //--- Set the current and previous text this.SetText("Label"); this.SetTextPrev(""); //--- Background is transparent, foreground is not this.SetAlphaBG(0); this.SetAlphaFG(255); } //+-----------------------------------------------------------------------------+ //| CLabel::Parametric constructor. Build a label in the main window | //| of the current chart with the specified text, coordinates and dimensions | //+-----------------------------------------------------------------------------+ CLabel::CLabel(const string object_name, const string text,const int x,const int y,const int w,const int h) : CCanvasBase(object_name,::ChartID(),0,x,y,w,h), m_text_x(0), m_text_y(0) { //--- Assign the foreground canvas to the drawing object and //--- reset the coordinates and dimensions, which makes it inactive this.m_painter.CanvasAssign(this.GetForeground()); this.m_painter.SetXY(0,0); this.m_painter.SetSize(0,0); //--- Set the current and previous text this.SetText(text); this.SetTextPrev(""); //--- Background is transparent, foreground is not this.SetAlphaBG(0); this.SetAlphaFG(255); } //+-----------------------------------------------------------------------------+ //| CLabel::Parametric constructor. Builds a label in the specified window | //| of the current chart with the specified text, coordinates and dimensions | //+-----------------------------------------------------------------------------+ CLabel::CLabel(const string object_name, const string text,const int wnd,const int x,const int y,const int w,const int h) : CCanvasBase(object_name,::ChartID(),wnd,x,y,w,h), m_text_x(0), m_text_y(0) { //--- Assign the foreground canvas to the drawing object and //--- reset the coordinates and dimensions, which makes it inactive this.m_painter.CanvasAssign(this.GetForeground()); this.m_painter.SetXY(0,0); this.m_painter.SetSize(0,0); //--- Set the current and previous text this.SetText(text); this.SetTextPrev(""); //--- Background is transparent, foreground is not this.SetAlphaBG(0); this.SetAlphaFG(255); } //+------------------------------------------------------------------------------+ //| CLabel::Parametric constructor. Builds a label in the specified window | //| of the specified chart with the specified text, coordinates and dimensions | //+------------------------------------------------------------------------------+ CLabel::CLabel(const string object_name,const long chart_id,const int wnd,const string text,const int x,const int y,const int w,const int h) : CCanvasBase(object_name,chart_id,wnd,x,y,w,h), m_text_x(0), m_text_y(0) { //--- Assign the foreground canvas to the drawing object and //--- reset the coordinates and dimensions, which makes it inactive this.m_painter.CanvasAssign(this.GetForeground()); this.m_painter.SetXY(0,0); this.m_painter.SetSize(0,0); //--- Set the current and previous text this.SetText(text); this.SetTextPrev(""); //--- Background is transparent, foreground is not this.SetAlphaBG(0); this.SetAlphaFG(255); }
A área de desenho, a ícone do elemento, é definida com dimensões zero, o que significa a ausência de ícone no elemento. O texto do elemento é definido e é atribuída transparência total ao fundo e opacidade total ao primeiro plano.
Método para comparação de dois objetos:
//+------------------------------------------------------------------+ //| CLabel::Compare two objects | //+------------------------------------------------------------------+ int CLabel::Compare(const CObject *node,const int mode=0) const { const CLabel *obj=node; switch(mode) { case ELEMENT_SORT_BY_NAME : return(this.Name() >obj.Name() ? 1 : this.Name() <obj.Name() ? -1 : 0); case ELEMENT_SORT_BY_TEXT : return(this.Text() >obj.Text() ? 1 : this.Text() <obj.Text() ? -1 : 0); case ELEMENT_SORT_BY_COLOR : return(this.ForeColor()>obj.ForeColor() ? 1 : this.ForeColor()<obj.ForeColor() ? -1 : 0); case ELEMENT_SORT_BY_ALPHA : return(this.AlphaFG() >obj.AlphaFG() ? 1 : this.AlphaFG() <obj.AlphaFG() ? -1 : 0); default : return(this.ID() >obj.ID() ? 1 : this.ID() <obj.ID() ? -1 : 0); } }
A comparação é possível pelo nome do objeto, texto do rótulo, cor, transparência e identificador. Por padrão, os objetos são comparados pelo identificador do objeto, pois quando os objetos estão em uma mesma lista, é melhor distingui-los pelos identificadores para acesso rápido ao necessário.
Método que apaga o texto do rótulo:
//+------------------------------------------------------------------+ //| CLabel::Delete the text | //+------------------------------------------------------------------+ void CLabel::ClearText(void) { int w=0, h=0; string text=this.TextPrev(); //--- Get the dimensions of the previous text if(text!="") this.m_foreground.TextSize(text,w,h); //--- If the dimensions are received, draw a transparent rectangle in place of the text erasing it if(w>0 && h>0) this.m_foreground.FillRectangle(this.AdjX(this.m_text_x),this.AdjY(this.m_text_y),this.AdjX(this.m_text_x+w),this.AdjY(this.m_text_y+h),clrNULL); //--- Otherwise, clear the entire foreground else this.m_foreground.Erase(clrNULL); }
Se anteriormente algum texto foi escrito, ele pode ser apagado preenchendo completamente a sua área com um retângulo totalmente transparente de acordo com o tamanho do texto. Se anteriormente não havia texto, toda a área do canvas do objeto é apagada.
Método que exibe o texto no canvas:
//+------------------------------------------------------------------+ //| CLabel::Display the text | //+------------------------------------------------------------------+ void CLabel::DrawText(const int dx,const int dy,const string text,const bool chart_redraw) { //--- Clear the previous text and set the new one this.ClearText(); this.SetText(text); //--- Display the set text this.m_foreground.TextOut(this.AdjX(dx),this.AdjY(dy),this.Text(),::ColorToARGB(this.ForeColor(),this.AlphaFG())); //--- If the text goes beyond the object right border if(this.Width()-dx<this.m_foreground.TextWidth(text)) { //--- Get the dimensions of the "..." text int w=0,h=0; this.m_foreground.TextSize("... ",w,h); if(w>0 && h>0) { //--- Erase the text at the right edge of the object to the size of the "..." text and replace the end of the label text with "..." this.m_foreground.FillRectangle(this.AdjX(this.Width()-w),this.AdjY(this.m_text_y),this.AdjX(this.Width()),this.AdjY(this.m_text_y+h),clrNULL); this.m_foreground.TextOut(this.AdjX(this.Width()-w),this.AdjY(dy),"...",::ColorToARGB(this.ForeColor(),this.AlphaFG())); } } //--- Update the foreground canvas and remember the new text coordinates this.m_foreground.Update(chart_redraw); this.m_text_x=dx; this.m_text_y=dy; //--- Save the rendered text as the previous one this.SetTextPrev(text); }
Aqui, primeiro o texto anterior é apagado do canvas e, em seguida, o novo texto é exibido. Se o novo texto ultrapassar os limites do objeto, no local em que ele extrapola os limites do elemento à direita é exibida uma reticência, indicando que o texto não coube na área do objeto, aproximadamente assim: "Este texto não cab...".
Método que desenha a aparência externa:
//+------------------------------------------------------------------+ //| CLabel::Draw the appearance | //+------------------------------------------------------------------+ void CLabel::Draw(const bool chart_redraw) { this.DrawText(this.m_text_x,this.m_text_y,this.Text(),chart_redraw); }
Aqui é simplesmente chamado o método de desenho do texto do rótulo.
Métodos de trabalho com arquivos:
//+------------------------------------------------------------------+ //| CLabel::Save to file | //+------------------------------------------------------------------+ bool CLabel::Save(const int file_handle) { //--- Save the parent object data if(!CCanvasBase::Save(file_handle)) return false; //--- Save the text if(::FileWriteArray(file_handle,this.m_text)!=sizeof(this.m_text)) return false; //--- Save the previous text if(::FileWriteArray(file_handle,this.m_text_prev)!=sizeof(this.m_text_prev)) return false; //--- Save the text X coordinate if(::FileWriteInteger(file_handle,this.m_text_x,INT_VALUE)!=INT_VALUE) return false; //--- Save the text Y coordinate if(::FileWriteInteger(file_handle,this.m_text_y,INT_VALUE)!=INT_VALUE) return false; //--- All is successful return true; } //+------------------------------------------------------------------+ //| CLabel::Load from file | //+------------------------------------------------------------------+ bool CLabel::Load(const int file_handle) { //--- Load parent object data if(!CCanvasBase::Load(file_handle)) return false; //--- Load the text if(::FileReadArray(file_handle,this.m_text)!=sizeof(this.m_text)) return false; //--- Load the previous text if(::FileReadArray(file_handle,this.m_text_prev)!=sizeof(this.m_text_prev)) return false; //--- Load the text X coordinate this.m_text_x=::FileReadInteger(file_handle,INT_VALUE); //--- Load the text Y coordinate this.m_text_y=::FileReadInteger(file_handle,INT_VALUE); //--- All is successful return true; }
Com base na classe analisada, criaremos a classe de botão simples.
Classe do elemento de controle "Botão simples"
//+------------------------------------------------------------------+ //| Simple button class | //+------------------------------------------------------------------+ class CButton : public CLabel { public: //--- Draw the appearance virtual void Draw(const bool chart_redraw); //--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle) { return CLabel::Save(file_handle); } virtual bool Load(const int file_handle) { return CLabel::Load(file_handle); } virtual int Type(void) const { return(ELEMENT_TYPE_BUTTON); } //--- Constructors/destructor CButton(void); CButton(const string object_name, const string text, const int x, const int y, const int w, const int h); CButton(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h); CButton(const string object_name, const long chart_id, const int wnd, const string text, const int x, const int y, const int w, const int h); ~CButton (void) {} };
A classe do botão simples difere da classe do rótulo de texto apenas pelo método de desenho da aparência externa.
A classe possui quatro construtores, que permitem criar um botão com os parâmetros especificados:
//+-------------------------------------------------------------------+ //| CButton::Default constructor. Builds a button in the main window | //| of the current chart at coordinates 0,0 with default dimensions | //+-------------------------------------------------------------------+ CButton::CButton(void) : CLabel("Button",::ChartID(),0,"Button",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { this.SetState(ELEMENT_STATE_DEF); this.SetAlpha(255); } //+-----------------------------------------------------------------------------+ //| CButton::Parametric constructor. Builds a button in the main window | //| of the current chart with the specified text, coordinates and dimensions | //+-----------------------------------------------------------------------------+ CButton::CButton(const string object_name,const string text,const int x,const int y,const int w,const int h) : CLabel(object_name,::ChartID(),0,text,x,y,w,h) { this.SetState(ELEMENT_STATE_DEF); this.SetAlpha(255); } //+-------------------------------------------------------------------------------+ //| CButton::Parametric constructor. Builds a button in the specified window | //| of the current chart with the specified text, coordinates and dimensions | //+-------------------------------------------------------------------------------+ CButton::CButton(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) : CLabel(object_name,::ChartID(),wnd,text,x,y,w,h) { this.SetState(ELEMENT_STATE_DEF); this.SetAlpha(255); } //+-------------------------------------------------------------------------------+ //| CButton::Parametric constructor. Builds a button in the specified window | //| of the specified chart with the specified text, coordinates and dimensions | //+-------------------------------------------------------------------------------+ CButton::CButton(const string object_name,const long chart_id,const int wnd,const string text,const int x,const int y,const int w,const int h) : CLabel(object_name,chart_id,wnd,text,x,y,w,h) { this.SetState(ELEMENT_STATE_DEF); this.SetAlpha(255); }
Definimos o estado "botão não pressionado" e atribuímos opacidade total ao fundo e ao primeiro plano.
Método de comparação de dois objetos:
//+------------------------------------------------------------------+ //| CButton::Compare two objects | //+------------------------------------------------------------------+ int CButton::Compare(const CObject *node,const int mode=0) const { const CButton *obj=node; switch(mode) { case ELEMENT_SORT_BY_NAME : return(this.Name() >obj.Name() ? 1 : this.Name() <obj.Name() ? -1 : 0); case ELEMENT_SORT_BY_TEXT : return(this.Text() >obj.Text() ? 1 : this.Text() <obj.Text() ? -1 : 0); case ELEMENT_SORT_BY_COLOR : return(this.BackColor()>obj.BackColor() ? 1 : this.BackColor()<obj.BackColor() ? -1 : 0); case ELEMENT_SORT_BY_ALPHA : return(this.AlphaBG() >obj.AlphaBG() ? 1 : this.AlphaBG() <obj.AlphaBG() ? -1 : 0); default : return(this.ID() >obj.ID() ? 1 : this.ID() <obj.ID() ? -1 : 0); } }
O método é idêntico ao método da classe do rótulo de texto. Muito provavelmente, se outras propriedades não surgirem para os botões, esse método poderá ser removido da classe e será utilizado o método da classe pai.
Método que desenha a aparência externa do botão:
//+------------------------------------------------------------------+ //| CButton::Draw the appearance | //+------------------------------------------------------------------+ void CButton::Draw(const bool chart_redraw) { //--- Fill the button with the background color, draw the frame and update the background canvas this.Fill(this.BackColor(),false); this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG())); this.m_background.Update(false); //--- Display the button text CLabel::Draw(false); //--- If specified, update the chart if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Primeiro, o fundo é preenchido com a cor definida, em seguida a borda é desenhada e o texto do botão é exibido.
Com base nessa classe, criaremos a classe do botão de duas posições.
Classe do elemento de controle "Botão de duas posições"
//+------------------------------------------------------------------+ //| Toggle button class | //+------------------------------------------------------------------+ class CButtonTriggered : public CButton { public: //--- Draw the appearance virtual void Draw(const bool chart_redraw); //--- Mouse button click event handler (Press) virtual void OnPressEvent(const int id, const long lparam, const double dparam, const string sparam); //--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle) { return CButton::Save(file_handle); } virtual bool Load(const int file_handle) { return CButton::Load(file_handle); } virtual int Type(void) const { return(ELEMENT_TYPE_BUTTON_TRIGGERED); } //--- Initialize the object default colors virtual void InitColors(void); //--- Constructors/destructor CButtonTriggered(void); CButtonTriggered(const string object_name, const string text, const int x, const int y, const int w, const int h); CButtonTriggered(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h); CButtonTriggered(const string object_name, const long chart_id, const int wnd, const string text, const int x, const int y, const int w, const int h); ~CButtonTriggered (void) {} };
O objeto possui quatro construtores, que permitem criar um botão com os parâmetros especificados:
//+------------------------------------------------------------------+ //| CButtonTriggered::Default constructor. | //| Builds a button in the main window of the current chart | //| at 0,0 coordinates with default dimensions | //+------------------------------------------------------------------+ CButtonTriggered::CButtonTriggered(void) : CButton("Button",::ChartID(),0,"Button",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { this.InitColors(); } //+------------------------------------------------------------------+ //| CButtonTriggered::Parametric constructor. | //| Builds a button in the main window of the current chart | //| with the specified text, coordinates and dimensions | //+------------------------------------------------------------------+ CButtonTriggered::CButtonTriggered(const string object_name,const string text,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),0,text,x,y,w,h) { this.InitColors(); } //+------------------------------------------------------------------+ //| CButtonTriggered::Parametric constructor. | //| Builds a button in the specified window of the current chart | //| with the specified text, coordinates and dimensions | //+------------------------------------------------------------------+ CButtonTriggered::CButtonTriggered(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),wnd,text,x,y,w,h) { this.InitColors(); } //+------------------------------------------------------------------+ //| CButtonTriggered::Parametric constructor. | //| Builds a button in the specified window of the specified chart | //| with the specified text, coordinates and dimensions | //+------------------------------------------------------------------+ CButtonTriggered::CButtonTriggered(const string object_name,const long chart_id,const int wnd,const string text,const int x,const int y,const int w,const int h) : CButton(object_name,chart_id,wnd,text,x,y,w,h) { this.InitColors(); }
Em cada construtor é chamado o método de inicialização das cores padrão:
//+------------------------------------------------------------------+ //| CButtonTriggered::Initialize the object default colors | //+------------------------------------------------------------------+ void CButtonTriggered::InitColors(void) { //--- Initialize the background colors for the normal and activated states and make it the current background color this.InitBackColors(clrWhiteSmoke); this.InitBackColorsAct(clrLightBlue); this.BackColorToDefault(); //--- Initialize the foreground colors for the normal and activated states and make it the current text color this.InitForeColors(clrBlack); this.InitForeColorsAct(clrBlack); this.ForeColorToDefault(); //--- Initialize the border colors for the normal and activated states and make it the current border color this.InitBorderColors(clrDarkGray); this.InitBorderColorsAct(clrGreen); this.BorderColorToDefault(); //--- Initialize the border color and foreground color for the disabled element this.InitBorderColorBlocked(clrLightGray); this.InitForeColorBlocked(clrSilver); }
Estas são as cores padrão definidas para um botão recém-criado. Após a criação do objeto, todas as cores podem ser configuradas conforme desejado.
No método de comparaçãofoi adicionada a comparação pelo estado do botão:
//+------------------------------------------------------------------+ //| CButtonTriggered::Compare two objects | //+------------------------------------------------------------------+ int CButtonTriggered::Compare(const CObject *node,const int mode=0) const { const CButtonTriggered *obj=node; switch(mode) { case ELEMENT_SORT_BY_NAME : return(this.Name() >obj.Name() ? 1 : this.Name() <obj.Name() ? -1 : 0); case ELEMENT_SORT_BY_TEXT : return(this.Text() >obj.Text() ? 1 : this.Text() <obj.Text() ? -1 : 0); case ELEMENT_SORT_BY_COLOR : return(this.BackColor()>obj.BackColor() ? 1 : this.BackColor()<obj.BackColor() ? -1 : 0); case ELEMENT_SORT_BY_ALPHA : return(this.AlphaBG() >obj.AlphaBG() ? 1 : this.AlphaBG() <obj.AlphaBG() ? -1 : 0); case ELEMENT_SORT_BY_STATE : return(this.State() >obj.State() ? 1 : this.State() <obj.State() ? -1 : 0); default : return(this.ID() >obj.ID() ? 1 : this.ID() <obj.ID() ? -1 : 0); } }
Método que desenha a aparência externa do botão:
//+------------------------------------------------------------------+ //| CButtonTriggered::Draw the appearance | //+------------------------------------------------------------------+ void CButtonTriggered::Draw(const bool chart_redraw) { //--- Fill the button with the background color, draw the frame and update the background canvas this.Fill(this.BackColor(),false); this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG())); this.m_background.Update(false); //--- Display the button text CLabel::Draw(false); //--- If specified, update the chart if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
O método é idêntico ao método da classe pai e, se nenhuma modificação adicional da classe for realizada posteriormente, esse método poderá ser removido, sendo utilizado o método de desenho da classe pai.
O botão de duas posições possui dois estados:
- Pressionado,
- Não pressionado.
Para o rastreamento e a alternância de seus estados, aqui foi sobrescrito o manipulador de pressionamento dos botões do mouse OnPressEvent da classe pai:
//+------------------------------------------------------------------+ //| CButtonTriggered::Mouse button click event handler (Press) | //+------------------------------------------------------------------+ void CButtonTriggered::OnPressEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Set the button state to the opposite of the one already set ENUM_ELEMENT_STATE state=(this.State()==ELEMENT_STATE_DEF ? ELEMENT_STATE_ACT : ELEMENT_STATE_DEF); this.SetState(state); //--- Call the parent object handler with the ID in lparam and the state in dparam CCanvasBase::OnPressEvent(id,this.m_id,this.m_state,sparam); }
Com base na classe CButton criaremos quatro botões com setas: para cima, para baixo, para a esquerda e para a direita. Os objetos utilizarão a classe de desenho de imagens para desenhar as setas.
Classe do elemento de controle "Botão com seta para cima"
//+------------------------------------------------------------------+ //| Up arrow button class | //+------------------------------------------------------------------+ class CButtonArrowUp : public CButton { public: //--- Draw the appearance virtual void Draw(const bool chart_redraw); //--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle) { return CButton::Save(file_handle); } virtual bool Load(const int file_handle) { return CButton::Load(file_handle); } virtual int Type(void) const { return(ELEMENT_TYPE_BUTTON_ARROW_UP);} //--- Constructors/destructor CButtonArrowUp(void); CButtonArrowUp(const string object_name, const int x, const int y, const int w, const int h); CButtonArrowUp(const string object_name, const int wnd, const int x, const int y, const int w, const int h); CButtonArrowUp(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CButtonArrowUp (void) {} };
Quatro construtores permitem criar um objeto com os parâmetros especificados:
//+------------------------------------------------------------------+ //| CButtonArrowUp::Default constructor. | //| Builds a button in the main window of the current chart | //| at 0,0 coordinates with default dimensions | //+------------------------------------------------------------------+ CButtonArrowUp::CButtonArrowUp(void) : CButton("Arrow Up Button",::ChartID(),0,"",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowUp::Parametric constructor. | //| Builds a button in the main window of the current chart | //| with the specified text, coordinates and dimensions | //+------------------------------------------------------------------+ CButtonArrowUp::CButtonArrowUp(const string object_name,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),0,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowUp::Parametric constructor. | //| Builds a button in the specified window of the current chart | //| with the specified text, coordinates and dimensions | //+------------------------------------------------------------------+ CButtonArrowUp::CButtonArrowUp(const string object_name,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowUp::Parametric constructor. | //| Builds a button in the specified window of the specified chart | //| with the specified text, coordinates and dimensions | //+------------------------------------------------------------------+ CButtonArrowUp::CButtonArrowUp(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,chart_id,wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); }
Nos construtores são inicializadas as cores padrão e são definidas as coordenadas e os tamanhos da área da imagem.
Método que desenha a aparência externa do botão:
//+------------------------------------------------------------------+ //| CButtonArrowUp::Draw the appearance | //+------------------------------------------------------------------+ void CButtonArrowUp::Draw(const bool chart_redraw) { //--- Fill the button with the background color, draw the frame and update the background canvas this.Fill(this.BackColor(),false); this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG())); this.m_background.Update(false); //--- Display the button text CLabel::Draw(false); //--- Clear the drawing area this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false); //--- Set the arrow color for the normal and disabled states of the button and draw the up arrow color clr=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),90,90,90) : this.ForeColor()); this.m_painter.ArrowUp(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true); //--- If specified, update the chart if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
O método é semelhante ao método de desenho do botão, porém adicionalmente é exibido o desenho da seta para cima por meio do método ArrowUp do objeto de desenho.
Todos os demais classes são idênticas à analisada, porém nos métodos de desenho são renderizados os ícones correspondentes à finalidade do botão.
Classe do elemento de controle "Botão com seta para baixo"
//+------------------------------------------------------------------+ //| Down arrow button class | //+------------------------------------------------------------------+ class CButtonArrowDown : public CButton { public: //--- Draw the appearance virtual void Draw(const bool chart_redraw); //--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle) { return CButton::Save(file_handle); } virtual bool Load(const int file_handle) { return CButton::Load(file_handle); } virtual int Type(void) const { return(ELEMENT_TYPE_BUTTON_ARROW_DOWN); } //--- Constructors/destructor CButtonArrowDown(void); CButtonArrowDown(const string object_name, const int x, const int y, const int w, const int h); CButtonArrowDown(const string object_name, const int wnd, const int x, const int y, const int w, const int h); CButtonArrowDown(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CButtonArrowDown (void) {} }; //+------------------------------------------------------------------+ //| CButtonArrowDown::Default constructor. | //| Builds a button in the main window of the current chart | //| at 0,0 coordinates with default dimensions | //+------------------------------------------------------------------+ CButtonArrowDown::CButtonArrowDown(void) : CButton("Arrow Up Button",::ChartID(),0,"",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowDown::Parametric constructor. | //| Builds a button in the main window of the current chart | //| with the specified text, coordinates and dimensions | //+------------------------------------------------------------------+ CButtonArrowDown::CButtonArrowDown(const string object_name,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),0,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowDown::Parametric constructor. | //| Builds a button in the specified window of the current chart | //| with the specified text, coordinates and dimensions | //+------------------------------------------------------------------+ CButtonArrowDown::CButtonArrowDown(const string object_name,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowDown::Parametric constructor. | //| Builds a button in the specified window of the specified chart | //| with the specified text, coordinates and dimensions | //+------------------------------------------------------------------+ CButtonArrowDown::CButtonArrowDown(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,chart_id,wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowDown::Draw the appearance | //+------------------------------------------------------------------+ void CButtonArrowDown::Draw(const bool chart_redraw) { //--- Fill the button with the background color, draw the frame and update the background canvas this.Fill(this.BackColor(),false); this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG())); this.m_background.Update(false); //--- Display the button text CLabel::Draw(false); //--- Clear the drawing area this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false); //--- Set the arrow color for the normal and disabled states of the button and draw the down arrow color clr=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),90,90,90) : this.ForeColor()); this.m_painter.ArrowDown(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true); //--- If specified, update the chart if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Classe do elemento de controle "Botão com seta para a esquerda"
//+------------------------------------------------------------------+ //| Left arrow button class | //+------------------------------------------------------------------+ class CButtonArrowLeft : public CButton { public: //--- Draw the appearance virtual void Draw(const bool chart_redraw); //--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle) { return CButton::Save(file_handle); } virtual bool Load(const int file_handle) { return CButton::Load(file_handle); } virtual int Type(void) const { return(ELEMENT_TYPE_BUTTON_ARROW_DOWN); } //--- Constructors/destructor CButtonArrowLeft(void); CButtonArrowLeft(const string object_name, const int x, const int y, const int w, const int h); CButtonArrowLeft(const string object_name, const int wnd, const int x, const int y, const int w, const int h); CButtonArrowLeft(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CButtonArrowLeft (void) {} }; //+------------------------------------------------------------------+ //| CButtonArrowLeft::Default constructor. | //| Builds a button in the main window of the current chart | //| at 0,0 coordinates with default dimensions | //+------------------------------------------------------------------+ CButtonArrowLeft::CButtonArrowLeft(void) : CButton("Arrow Up Button",::ChartID(),0,"",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowLeft::Parametric constructor. | //| Builds a button in the main window of the current chart | //| with the specified text, coordinates and dimensions | //+------------------------------------------------------------------+ CButtonArrowLeft::CButtonArrowLeft(const string object_name,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),0,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowLeft::Parametric constructor. | //| Builds a button in the specified window of the current chart | //| with the specified text, coordinates and dimensions | //+------------------------------------------------------------------+ CButtonArrowLeft::CButtonArrowLeft(const string object_name,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowLeft::Parametric constructor. | //| Builds a button in the specified window of the specified chart | //| with the specified text, coordinates and dimensions | //+------------------------------------------------------------------+ CButtonArrowLeft::CButtonArrowLeft(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,chart_id,wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowLeft::Draw the appearance | //+------------------------------------------------------------------+ void CButtonArrowLeft::Draw(const bool chart_redraw) { //--- Fill the button with the background color, draw the frame and update the background canvas this.Fill(this.BackColor(),false); this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG())); this.m_background.Update(false); //--- Display the button text CLabel::Draw(false); //--- Clear the drawing area this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false); //--- Set the arrow color for the normal and disabled states of the button and draw the left arrow color clr=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),90,90,90) : this.ForeColor()); this.m_painter.ArrowLeft(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true); //--- If specified, update the chart if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Classe do elemento de controle "Botão com seta para a direita"
//+------------------------------------------------------------------+ //| Right arrow button class | //+------------------------------------------------------------------+ class CButtonArrowRight : public CButton { public: //--- Draw the appearance virtual void Draw(const bool chart_redraw); //--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle) { return CButton::Save(file_handle); } virtual bool Load(const int file_handle) { return CButton::Load(file_handle); } virtual int Type(void) const { return(ELEMENT_TYPE_BUTTON_ARROW_DOWN); } //--- Constructors/destructor CButtonArrowRight(void); CButtonArrowRight(const string object_name, const int x, const int y, const int w, const int h); CButtonArrowRight(const string object_name, const int wnd, const int x, const int y, const int w, const int h); CButtonArrowRight(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CButtonArrowRight (void) {} }; //+------------------------------------------------------------------+ //| CButtonArrowRight::Default constructor. | //| Builds a button in the main window of the current chart | //| at 0,0 coordinates with default dimensions | //+------------------------------------------------------------------+ CButtonArrowRight::CButtonArrowRight(void) : CButton("Arrow Up Button",::ChartID(),0,"",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowRight::Parametric constructor. | //| Builds a button in the main window of the current chart | //| with the specified text, coordinates and dimensions | //+------------------------------------------------------------------+ CButtonArrowRight::CButtonArrowRight(const string object_name,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),0,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowRight::Parametric constructor. | //| Builds a button in the specified window of the current chart | //| with the specified text, coordinates and dimensions | //+------------------------------------------------------------------+ CButtonArrowRight::CButtonArrowRight(const string object_name,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowRight::Parametric constructor. | //| Builds a button in the specified window of the specified chart | //| with the specified text, coordinates and dimensions | //+------------------------------------------------------------------+ CButtonArrowRight::CButtonArrowRight(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,chart_id,wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowRight::Draw the appearance | //+------------------------------------------------------------------+ void CButtonArrowRight::Draw(const bool chart_redraw) { //--- Fill the button with the background color, draw the frame and update the background canvas this.Fill(this.BackColor(),false); this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG())); this.m_background.Update(false); //--- Display the button text CLabel::Draw(false); //--- Clear the drawing area this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false); //--- Set the arrow color for the normal and disabled states of the button and draw the right arrow color clr=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),90,90,90) : this.ForeColor()); this.m_painter.ArrowRight(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true); //--- If specified, update the chart if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Classe do elemento de controle "Checkbox"
A classe do elemento de controle "checkbox" é semelhante às classes dos botões com setas, porém aqui o fundo será totalmente transparente. Ou seja, serão desenhados apenas o texto e o ícone do checkbox. O checkbox possui dois estados, marcado e desmarcado, portanto será herdado da classe de botão de duas posições:
//+------------------------------------------------------------------+ //| Checkbox control class | //+------------------------------------------------------------------+ class CCheckBox : public CButtonTriggered { public: //--- Draw the appearance virtual void Draw(const bool chart_redraw); //--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle) { return CButton::Save(file_handle); } virtual bool Load(const int file_handle) { return CButton::Load(file_handle); } virtual int Type(void) const { return(ELEMENT_TYPE_CHECKBOX); } //--- Initialize the object default colors virtual void InitColors(void); //--- Constructors/destructor CCheckBox(void); CCheckBox(const string object_name, const string text, const int x, const int y, const int w, const int h); CCheckBox(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h); CCheckBox(const string object_name, const long chart_id, const int wnd, const string text, const int x, const int y, const int w, const int h); ~CCheckBox (void) {} };
Todas as classes dos elementos de controle possuem quatro construtores:
//+------------------------------------------------------------------+ //| CCheckBox::Default constructor. | //| Builds a button in the main window of the current chart | //| at 0,0 coordinates with default dimensions | //+------------------------------------------------------------------+ CCheckBox::CCheckBox(void) : CButtonTriggered("CheckBox",::ChartID(),0,"CheckBox",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { //--- Set default colors, background and foreground transparency, //--- as well as coordinates and boundaries of the button icon image area this.InitColors(); this.SetAlphaBG(0); this.SetAlphaFG(255); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CCheckBox::Parametric constructor. | //| Builds a button in the main window of the current chart | //| with the specified text, coordinates and dimensions | //+------------------------------------------------------------------+ CCheckBox::CCheckBox(const string object_name,const string text,const int x,const int y,const int w,const int h) : CButtonTriggered(object_name,::ChartID(),0,text,x,y,w,h) { //--- Set default colors, background and foreground transparency, //--- as well as coordinates and boundaries of the button icon image area this.InitColors(); this.SetAlphaBG(0); this.SetAlphaFG(255); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CCheckBox::Parametric constructor. | //| Builds a button in the specified window of the current chart | //| with the specified text, coordinates and dimensions | //+------------------------------------------------------------------+ CCheckBox::CCheckBox(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) : CButtonTriggered(object_name,::ChartID(),wnd,text,x,y,w,h) { //--- Set default colors, background and foreground transparency, //--- as well as coordinates and boundaries of the button icon image area this.InitColors(); this.SetAlphaBG(0); this.SetAlphaFG(255); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CCheckBox::Parametric constructor. | //| Builds a button in the specified window of the specified chart | //| with the specified text, coordinates and dimensions | //+------------------------------------------------------------------+ CCheckBox::CCheckBox(const string object_name,const long chart_id,const int wnd,const string text,const int x,const int y,const int w,const int h) : CButtonTriggered(object_name,chart_id,wnd,text,x,y,w,h) { //--- Set default colors, background and foreground transparency, //--- as well as coordinates and boundaries of the button icon image area this.InitColors(); this.SetAlphaBG(0); this.SetAlphaFG(255); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); }
Aqui são inicializadas as cores padrão do objeto, é definido um fundo totalmente transparente e um primeiro plano opaco, e em seguida são configurados os tamanhos e as coordenadas da área da imagem.
O método de comparação retorna o resultado da chamada do método de comparação da classe pai:
//+------------------------------------------------------------------+ //| CCheckBox::Compare two objects | //+------------------------------------------------------------------+ int CCheckBox::Compare(const CObject *node,const int mode=0) const { return CButtonTriggered::Compare(node,mode); }
Método de inicialização das cores padrão do objeto:
//+------------------------------------------------------------------+ //| CCheckBox::Initialize the object default colors | //+------------------------------------------------------------------+ void CCheckBox::InitColors(void) { //--- Initialize the background colors for the normal and activated states and make it the current background color this.InitBackColors(clrNULL); this.InitBackColorsAct(clrNULL); this.BackColorToDefault(); //--- Initialize the foreground colors for the normal and activated states and make it the current text color this.InitForeColors(clrBlack); this.InitForeColorsAct(clrBlack); this.InitForeColorFocused(clrNavy); this.InitForeColorActFocused(clrNavy); this.ForeColorToDefault(); //--- Initialize the border colors for the normal and activated states and make it the current border color this.InitBorderColors(clrNULL); this.InitBorderColorsAct(clrNULL); this.BorderColorToDefault(); //--- Initialize the border color and foreground color for the disabled element this.InitBorderColorBlocked(clrNULL); this.InitForeColorBlocked(clrSilver); }
Método de desenho da aparência externa do checkbox:
//+------------------------------------------------------------------+ //| CCheckBox::Draw the appearance | //+------------------------------------------------------------------+ void CCheckBox::Draw(const bool chart_redraw) { //--- Fill the button with the background color, draw the frame and update the background canvas this.Fill(this.BackColor(),false); this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG())); this.m_background.Update(false); //--- Display the button text CLabel::Draw(false); //--- Clear the drawing area this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false); //--- Draw the checked icon for the active state of the button, if(this.m_state) this.m_painter.CheckedBox(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true); //--- and unchecked - for inactive state else this.m_painter.UncheckedBox(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true); //--- If specified, update the chart if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Dependendo do estado do elemento, é desenhado um quadrado marcado com um visto ou apenas um quadrado vazio.
Agora, com base neste objeto, criaremos a classe do elemento de controle "radiobutton".
Classe do elemento de controle "Radiobutton"
Como o radiobutton sempre funciona em grupo, ele só pode ser desativado quando outro botão do grupo é ativado, portanto aqui também precisamos sobrescrever o manipulador de clique no objeto da classe pai.
//+------------------------------------------------------------------+ //| Radio Button control class | //+------------------------------------------------------------------+ class CRadioButton : public CCheckBox { public: //--- Draw the appearance virtual void Draw(const bool chart_redraw); //--- Mouse button click event handler (Press) virtual void OnPressEvent(const int id, const long lparam, const double dparam, const string sparam); //--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle) { return CButton::Save(file_handle); } virtual bool Load(const int file_handle) { return CButton::Load(file_handle); } virtual int Type(void) const { return(ELEMENT_TYPE_RADIOBUTTON); } //--- Constructors/destructor CRadioButton(void); CRadioButton(const string object_name, const string text, const int x, const int y, const int w, const int h); CRadioButton(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h); CRadioButton(const string object_name, const long chart_id, const int wnd, const string text, const int x, const int y, const int w, const int h); ~CRadioButton (void) {} };
Construtores:
//+------------------------------------------------------------------+ //| CRadioButton::Default constructor. | //| Builds a button in the main window of the current chart | //| at 0,0 coordinates with default dimensions | //+------------------------------------------------------------------+ CRadioButton::CRadioButton(void) : CCheckBox("RadioButton",::ChartID(),0,"",0,0,DEF_BUTTON_H,DEF_BUTTON_H) { } //+------------------------------------------------------------------+ //| CRadioButton::Parametric constructor. | //| Builds a button in the main window of the current chart | //| with the specified text, coordinates and dimensions | //+------------------------------------------------------------------+ CRadioButton::CRadioButton(const string object_name,const string text,const int x,const int y,const int w,const int h) : CCheckBox(object_name,::ChartID(),0,text,x,y,w,h) { } //+------------------------------------------------------------------+ //| CRadioButton::Parametric constructor. | //| Builds a button in the specified window of the current chart | //| with the specified text, coordinates and dimensions | //+------------------------------------------------------------------+ CRadioButton::CRadioButton(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) : CCheckBox(object_name,::ChartID(),wnd,text,x,y,w,h) { } //+------------------------------------------------------------------+ //| CRadioButton::Parametric constructor. | //| Builds a button in the specified window of the specified chart | //| with the specified text, coordinates and dimensions | //+------------------------------------------------------------------+ CRadioButton::CRadioButton(const string object_name,const long chart_id,const int wnd,const string text,const int x,const int y,const int w,const int h) : CCheckBox(object_name,chart_id,wnd,text,x,y,w,h) { }
Nenhuma ação adicional após a chamada do construtor da classe pai é necessária aqui. Portanto, os construtores possuem corpo vazio.
O método de comparação retorna o resultado da chamada do método de comparação da classe pai:
//+------------------------------------------------------------------+ //| CRadioButton::Compare two objects | //+------------------------------------------------------------------+ int CRadioButton::Compare(const CObject *node,const int mode=0) const { return CCheckBox::Compare(node,mode); }
Método de desenho da aparência externa do botão:
//+------------------------------------------------------------------+ //| CRadioButton::Draw the appearance | //+------------------------------------------------------------------+ void CRadioButton::Draw(const bool chart_redraw) { //--- Fill the button with the background color, draw the frame and update the background canvas this.Fill(this.BackColor(),false); this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG())); this.m_background.Update(false); //--- Display the button text CLabel::Draw(false); //--- Clear the drawing area this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false); //--- Draw the checked icon for the active state of the button, if(this.m_state) this.m_painter.CheckedRadioButton(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true); //--- and unchecked - for inactive state else this.m_painter.UncheckedRadioButton(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true); //--- If specified, update the chart if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Esse método é idêntico ao método da classe pai, porém aqui são desenhados os ícones do radiobutton, selecionado e não selecionado.
Manipulador de eventos de pressionamento dos botões do mouse:
//+------------------------------------------------------------------+ //| CRadioButton::Mouse button click event handler (Press) | //+------------------------------------------------------------------+ void CRadioButton::OnPressEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- If the button is already checked, leave if(this.m_state) return; //--- Set the button state to the opposite of the one already set ENUM_ELEMENT_STATE state=(this.State()==ELEMENT_STATE_DEF ? ELEMENT_STATE_ACT : ELEMENT_STATE_DEF); this.SetState(state); //--- Call the parent object handler with the ID in lparam and the state in dparam CCanvasBase::OnPressEvent(id,this.m_id,this.m_state,sparam); }
Aqui, se o botão já estiver em estado ligado, não é necessário executar nenhuma ação — saímos do manipulador. Se o botão estiver desligado, invertemos o seu estado e chamamos o manipulador da classe pai, o objeto base de todos os elementos de controle CCanvasBase.
Por hoje, estes são todos os elementos de controle que minimamente era necessário implementar para a realização de elementos de controle complexos.
Vamos testar o que obtivemos.
Testando o resultado
Na pasta \MQL5\Indicators\Tables\ criaremos um novo indicador com o nome iTestLabel.mq5.
Definiremos o número de buffers de cálculo e de séries gráficas do indicador como zero — não é necessário desenhar nenhum gráfico. Conectaremos a biblioteca criada de elementos gráficos. O indicador irá desenhar elementos gráficos, que, no momento da criação, salvaremos em uma lista, cujo arquivo de classe está conectado ao arquivo do indicador:
//+------------------------------------------------------------------+ //| iTestLabel.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 0 #property indicator_plots 0 //+------------------------------------------------------------------+ //| Include libraries | //+------------------------------------------------------------------+ #include <Arrays\ArrayObj.mqh> #include "Controls\Controls.mqh" CArrayObj list; // List for storing tested objects CCanvasBase *base =NULL; // Pointer to the base graphical element CLabel *label1=NULL; // Pointer to the Label graphical element CLabel *label2=NULL; // Pointer to the Label graphical element CLabel *label3=NULL; // Pointer to the Label graphical element CButton *button1=NULL; // Pointer to the Button graphical element CButtonTriggered *button_t1=NULL; // Pointer to the ButtonTriggered graphical element CButtonTriggered *button_t2=NULL; // Pointer to the ButtonTriggered graphical element CButtonArrowUp *button_up=NULL; // Pointer to the CButtonArrowUp graphical element CButtonArrowDown *button_dn=NULL; // Pointer to the CButtonArrowDown graphical element CButtonArrowLeft *button_lt=NULL; // Pointer to the CButtonArrowLeft graphical element CButtonArrowRight*button_rt=NULL; // Pointer to the CButtonArrowRight graphical element CCheckBox *checkbox_lt=NULL; // Pointer to the CCheckBox graphical element CCheckBox *checkbox_rt=NULL; // Pointer to the CCheckBox graphical element CRadioButton *radio_bt_lt=NULL; // Pointer to the CRadioButton graphical element CRadioButton *radio_bt_rt=NULL; // Pointer to the CRadioButton graphical element //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+
Aqui, para simplificação, já são criados ponteiros para os elementos gráficos que serão criados, aos quais iremos nos referir após a criação do elemento para trabalhar com os objetos.
Todos os objetos serão criados no manipulador OnInit() do indicador. Faremos o seguinte: criaremos um objeto base e o coloriremos de modo que lembre algum tipo de painel.
Dentro dessa "base" criaremos todos os elementos gráficos e indicaremos esse objeto base como contêiner para eles.
Escreveremos em OnInit() o seguinte código:
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Search for the chart subwindow int wnd=ChartWindowFind(); //--- Create a basic graphical element list.Add(base=new CCanvasBase("Rectangle",0,wnd,100,40,260,160)); base.SetAlphaBG(250); // Transparency base.SetBorderWidth(6); // Border width //--- Initialize the background color, specify the color for the blocked element //--- and set the default background color of the element as the current color base.InitBackColors(clrWhiteSmoke); base.InitBackColorBlocked(clrLightGray); base.BackColorToDefault(); //--- Fill the background with color and draw a frame with an indent of one pixel from the set frame width base.Fill(base.BackColor(),false); uint wd=base.BorderWidth(); base.GetBackground().Rectangle(0,0,base.Width()-1,base.Height()-1,ColorToARGB(clrDimGray)); base.GetBackground().Rectangle(wd-2,wd-2,base.Width()-wd+1,base.Height()-wd+1,ColorToARGB(clrLightGray)); base.Update(false); //--- set the name and ID of the element and display its description in the journal base.SetName("Rectangle 1"); base.SetID(1); base.Print(); //--- Create a text label inside the base object //--- and specify the base element as a container for the label string text="Simple button:"; int shift_x=20; int shift_y=8; int x=base.X()+shift_x-10; int y=base.Y()+shift_y+2; int w=base.GetForeground().TextWidth(text); int h=DEF_LABEL_H; list.Add(label1=new CLabel("Label 1",0,wnd,text,x,y,w,h)); label1.SetContainerObj(base); //--- Set the hover and click color to red //--- (this is a change to the standard parameters of a text label after its creation). label1.InitForeColorFocused(clrRed); label1.InitForeColorPressed(clrRed); //--- Set the element ID, draw the element //--- and display its description to the journal. label1.SetID(2); label1.Draw(false); label1.Print(); //--- Create a simple button inside the base object //--- and specify the base element as a button container x=label1.Right()+shift_x; y=label1.Y(); w=DEF_BUTTON_W; h=DEF_BUTTON_H; list.Add(button1=new CButton("Simple Button",0,wnd,"Button 1",x,y,w,h)); button1.SetContainerObj(base); //--- Set the button text offset along the X axis button1.SetTextShiftH(2); //--- Set the element ID, draw the element //--- and display its description to the journal. button1.SetID(3); button1.Draw(false); button1.Print(); //--- Create a text label inside the base object //--- and specify the base element as a container for the label text="Triggered button:"; x=label1.X(); y=label1.Bottom()+shift_y; w=base.GetForeground().TextWidth(text); h=DEF_LABEL_H; list.Add(label2=new CLabel("Label 2",0,wnd,text,x,y,w,h)); label2.SetContainerObj(base); //--- Set the hover and click color to red //--- (this is a change to the standard parameters of a text label after its creation). label2.InitForeColorFocused(clrRed); label2.InitForeColorPressed(clrRed); //--- Set the element ID, draw the element //--- and display its description to the journal. label2.SetID(4); label2.Draw(false); label2.Print(); //--- Create the toggle button inside the base object //--- and specify the base element as a button container x=button1.X(); y=button1.Bottom()+shift_y; w=DEF_BUTTON_W; h=DEF_BUTTON_H; list.Add(button_t1=new CButtonTriggered("Triggered Button 1",0,wnd,"Button 2",x,y,w,h)); button_t1.SetContainerObj(base); //--- Set the button text offset along the X axis button_t1.SetTextShiftH(2); //--- Set the ID and activated state of the element, //--- draw the element and display its description to the journal. button_t1.SetID(5); button_t1.SetState(true); button_t1.Draw(false); button_t1.Print(); //--- Create the toggle button inside the base object //--- and specify the base element as a button container x=button_t1.Right()+4; y=button_t1.Y(); w=DEF_BUTTON_W; h=DEF_BUTTON_H; list.Add(button_t2=new CButtonTriggered("Triggered Button 2",0,wnd,"Button 3",x,y,w,h)); button_t2.SetContainerObj(base); //--- Set the button text offset along the X axis button_t2.SetTextShiftH(2); //--- Set the element ID, draw the element //--- and display its description to the journal. button_t2.SetID(6); button_t2.Draw(false); button_t2.Print(); //--- Create a text label inside the base object //--- and specify the base element as a container for the label text="Arrowed buttons:"; x=label1.X(); y=label2.Bottom()+shift_y; w=base.GetForeground().TextWidth(text); h=DEF_LABEL_H; list.Add(label3=new CLabel("Label 3",0,wnd,text,x,y,w,h)); label3.SetContainerObj(base); //--- Set the hover and click color to red //--- (this is a change to the standard parameters of a text label after its creation). label3.InitForeColorFocused(clrRed); label3.InitForeColorPressed(clrRed); //--- Set the element ID, draw the element //--- and display its description to the journal. label3.SetID(7); label3.Draw(false); label3.Print(); //--- Create the up arrow button inside the base object //--- and specify the base element as a button container x=button1.X(); y=button_t1.Bottom()+shift_y; w=DEF_BUTTON_H-1; h=DEF_BUTTON_H-1; list.Add(button_up=new CButtonArrowUp("Arrow Up Button",0,wnd,x,y,w,h)); button_up.SetContainerObj(base); //--- Set the image size and offset along the X axis button_up.SetImageBound(1,1,w-4,h-3); //--- Here we can customize the appearance of the button, for example, remove the border //button_up.InitBorderColors(button_up.BackColor(),button_up.BackColorFocused(),button_up.BackColorPressed(),button_up.BackColorBlocked()); //button_up.ColorsToDefault(); //--- Set the element ID, draw the element //--- and display its description to the journal. button_up.SetID(8); button_up.Draw(false); button_up.Print(); //--- Create the down arrow button inside the base object //--- and specify the base element as a button container x=button_up.Right()+4; y=button_up.Y(); w=DEF_BUTTON_H-1; h=DEF_BUTTON_H-1; list.Add(button_dn=new CButtonArrowDown("Arrow Down Button",0,wnd,x,y,w,h)); button_dn.SetContainerObj(base); //--- Set the image size and offset along the X axis button_dn.SetImageBound(1,1,w-4,h-3); //--- Set the element ID, draw the element //--- and display its description to the journal. button_dn.SetID(9); button_dn.Draw(false); button_dn.Print(); //--- Create the left arrow button inside the base object //--- and specify the base element as a button container x=button_dn.Right()+4; y=button_up.Y(); w=DEF_BUTTON_H-1; h=DEF_BUTTON_H-1; list.Add(button_lt=new CButtonArrowLeft("Arrow Left Button",0,wnd,x,y,w,h)); button_lt.SetContainerObj(base); //--- Set the image size and offset along the X axis button_lt.SetImageBound(1,1,w-3,h-4); //--- Set the element ID, draw the element //--- and display its description to the journal. button_lt.SetID(10); button_lt.Draw(false); button_lt.Print(); //--- Create the right arrow button inside the base object //--- and specify the base element as a button container x=button_lt.Right()+4; y=button_up.Y(); w=DEF_BUTTON_H-1; h=DEF_BUTTON_H-1; list.Add(button_rt=new CButtonArrowRight("Arrow Right Button",0,wnd,x,y,w,h)); button_rt.SetContainerObj(base); //--- Set the image size and offset along the X axis button_rt.SetImageBound(1,1,w-3,h-4); //--- Set the element ID, draw the element //--- and display its description to the journal. button_rt.SetID(11); button_rt.Draw(false); button_rt.Print(); //--- Inside the base object, create a checkbox with a header on the right (left checkbox) //--- and specify the base element as a button container x=label1.X(); y=label3.Bottom()+shift_y; w=DEF_BUTTON_W+30; h=DEF_BUTTON_H; list.Add(checkbox_lt=new CCheckBox("CheckBoxL",0,wnd,"CheckBox L",x,y,w,h)); checkbox_lt.SetContainerObj(base); //--- Set the area coordinates and image area dimensions checkbox_lt.SetImageBound(2,1,h-2,h-2); //--- Set the button text offset along the X axis checkbox_lt.SetTextShiftH(checkbox_lt.ImageRight()+2); //--- Set the element ID, draw the element //--- and display its description to the journal. checkbox_lt.SetID(12); checkbox_lt.Draw(false); checkbox_lt.Print(); //--- Inside the base object, create a checkbox with a header on the left (right checkbox) //--- and specify the base element as a button container x=checkbox_lt.Right()+4; y=checkbox_lt.Y(); w=DEF_BUTTON_W+30; h=DEF_BUTTON_H; list.Add(checkbox_rt=new CCheckBox("CheckBoxR",0,wnd,"CheckBox R",x,y,w,h)); checkbox_rt.SetContainerObj(base); //--- Set the area coordinates and image area dimensions checkbox_rt.SetTextShiftH(2); //--- Set the button text offset along the X axis checkbox_rt.SetImageBound(checkbox_rt.Width()-h+2,1,h-2,h-2); //--- Set the ID and activated state of the element, //--- draw the element and display its description to the journal. checkbox_rt.SetID(13); checkbox_rt.SetState(true); checkbox_rt.Draw(false); checkbox_rt.Print(); //--- Inside the base object, create a radio button with a header on the right (left RadioButton) //--- and specify the base element as a button container x=checkbox_lt.X(); y=checkbox_lt.Bottom()+shift_y; w=DEF_BUTTON_W+46; h=DEF_BUTTON_H; list.Add(radio_bt_lt=new CRadioButton("RadioButtonL",0,wnd,"RadioButton L",x,y,w,h)); radio_bt_lt.SetContainerObj(base); //--- Set the area coordinates and image area dimensions radio_bt_lt.SetImageBound(2,1,h-2,h-2); //--- Set the button text offset along the X axis radio_bt_lt.SetTextShiftH(radio_bt_lt.ImageRight()+2); //--- Set the ID and activated state of the element, //--- draw the element and display its description to the journal. radio_bt_lt.SetID(14); radio_bt_lt.SetState(true); radio_bt_lt.Draw(false); radio_bt_lt.Print(); //--- Inside the base object, create a radio button with a header on the left (right RadioButton) //--- and specify the base element as a button container x=radio_bt_lt.Right()+4; y=radio_bt_lt.Y(); w=DEF_BUTTON_W+46; h=DEF_BUTTON_H; list.Add(radio_bt_rt=new CRadioButton("RadioButtonR",0,wnd,"RadioButton R",x,y,w,h)); radio_bt_rt.SetContainerObj(base); //--- Set the button text offset along the X axis radio_bt_rt.SetTextShiftH(2); //--- Set the area coordinates and image area dimensions radio_bt_rt.SetImageBound(radio_bt_rt.Width()-h+2,1,h-2,h-2); //--- Set the element ID, draw the element //--- and display its description to the journal. radio_bt_rt.SetID(15); radio_bt_rt.Draw(true); radio_bt_rt.Print(); //--- Successful initialization return(INIT_SUCCEEDED); }
Estude atentamente todos os comentários do código — aqui todos os passos de criação dos objetos estão descritos de forma bastante detalhada.
No manipulador OnDeinit() do indicador destruímos todos os objetos da lista:
//+------------------------------------------------------------------+ //| Custom deindicator initialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { list.Clear(); }
O manipulador OnCalculate() está vazio — não calculamos nem exibimos nada no gráfico:
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- return value of prev_calculated for the next call return(rates_total); }
Para dar vida aos elementos gráficos criados, é necessário, no manipulador OnChartEvent()percorrer a lista de objetos criados e chamar o manipulador homônimo para cada elemento. Como, por enquanto, as radiobotões não estão de nenhuma forma ligadas em grupos — isso será feito nos próximos artigos — emularemos a comutação das radiobotões como deveria ocorrer em um grupo de elementos:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Call the event handler of each of the created objects for(int i=0;i<list.Total();i++) { CCanvasBase *obj=list.At(i); if(obj!=NULL) obj.OnChartEvent(id,lparam,dparam,sparam); } //--- Emulate radio buttons in the group --- //--- If a custom event is received if(id>=CHARTEVENT_CUSTOM) { //--- If the left radio button is clicked if(sparam==radio_bt_lt.NameBG()) { //--- If the button state changed (was not selected) if(radio_bt_lt.State()) { //--- make the right radio button unselected and redraw it radio_bt_rt.SetState(false); radio_bt_rt.Draw(true); } } //--- If the right radio button is clicked if(sparam==radio_bt_rt.NameBG()) { //--- If the button state changed (was not selected) if(radio_bt_rt.State()) { //--- make the left radio button unselected and redraw it radio_bt_lt.SetState(false); radio_bt_lt.Draw(true); } } } }
Vamos compilar o indicador e executá-lo no gráfico:

Todos os elementos de controle reagem à interação com o mouse, as radiobotões alternam como se estivessem agrupadas. As rótulos de texto foram feitas para mudar de cor ao passar o cursor, para uma apresentação visual clara de que os elementos de controle podem ser configurados conforme desejado. No estado normal, os textos dos rótulos são estáticos.
Mas aqui há uma omissão — ao passar o cursor do mouse sobre um elemento de controle, aparece um tooltip desnecessário com o nome do indicador. Para eliminar esse comportamento, é necessário escrever o valor "\n" na propriedade OBJPROP_TOOLTIP de cada objeto gráfico. Vamos corrigir isso.
Na classe CCanvasBase, no método Create adicionaremos duas linhas configurando os tooltips para os objetos gráficos de fundo e de primeiro plano:
//+------------------------------------------------------------------+ //| CCanvasBase::Create background and foreground graphical objects | //+------------------------------------------------------------------+ bool CCanvasBase::Create(const long chart_id,const int wnd,const string object_name,const int x,const int y,const int w,const int h) { //--- Get the adjusted chart ID long id=this.CorrectChartID(chart_id); //--- Correct the passed object name string nm=object_name; ::StringReplace(nm," ","_"); //--- Create a graphical object name for the background and create a canvas string obj_name=nm+"_BG"; if(!this.m_background.CreateBitmapLabel(id,(wnd<0 ? 0 : wnd),obj_name,x,y,(w>0 ? w : 1),(h>0 ? h : 1),COLOR_FORMAT_ARGB_NORMALIZE)) { ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name); return false; } //--- Create a graphical object name for the foreground and create a canvas obj_name=nm+"_FG"; if(!this.m_foreground.CreateBitmapLabel(id,(wnd<0 ? 0 : wnd),obj_name,x,y,(w>0 ? w : 1),(h>0 ? h : 1),COLOR_FORMAT_ARGB_NORMALIZE)) { ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name); return false; } //--- If created successfully, enter the program name into the OBJPROP_TEXT property of the graphical object ::ObjectSetString(id,this.NameBG(),OBJPROP_TEXT,this.m_program_name); ::ObjectSetString(id,this.NameFG(),OBJPROP_TEXT,this.m_program_name); ::ObjectSetString(id,this.NameBG(),OBJPROP_TOOLTIP,"\n"); ::ObjectSetString(id,this.NameFG(),OBJPROP_TOOLTIP,"\n"); //--- Set the dimensions of the rectangular area and return 'true' this.m_bound.SetXY(x,y); this.m_bound.Resize(w,h); return true; }
Recompilaremos o indicador e verificaremos:

Agora tudo está correto.
Considerações finais
Hoje demos mais um passo na criação do elemento de controle Table Control. Todos os elementos de controle complexos serão montados a partir de objetos simples como estes, porém bastante funcionais.
Hoje adicionamos a todos os objetos o componente Controller, que permite ao usuário interagir de forma interativa com os elementos de controle e aos próprios elementos interagirem entre si.
No próximo artigo, prepararemos os elementos painel e contêiner, que são os principais componentes para a colocação de outros elementos dentro deles. Ao mesmo tempo, o contêiner oferece a possibilidade de rolagem dos elementos filhos em seu interior.
Programas utilizados no artigo:
| # | Nome | Tipo | Descrição |
|---|---|---|---|
| 1 | Base.mqh | Biblioteca de classes | Classes para a criação do objeto base dos elementos de controle |
| 2 | Controls.mqh | Biblioteca de classes | Classes dos elementos de controle |
| 3 | iTestLabel.mq5 | Indicador de teste | Indicador para testar o trabalho com as classes de elementos de controle |
| 4 | MQL5.zip | Arquivo compactado | Arquivo com os arquivos apresentados acima, para extração no diretório MQL5 do terminal cliente |
Classes que compõem a biblioteca Base.mqh:
| # | Nome | Descrição |
|---|---|---|
| 1 | CBaseObj | Classe base para todos os objetos gráficos |
| 2 | CColor | Classe para gerenciamento de cores |
| 3 | CColorElement | Classe para gerenciamento das cores dos diferentes estados do elemento gráfico |
| 4 | CBound | Classe para gerenciamento de área retangular |
| 5 | CCanvasBase | Classe base para trabalhar com elementos gráficos no canvas |
Classes que compõem a biblioteca Controls.mqh:
| # | Nome | Descrição |
|---|---|---|
| 1 | CImagePainter | Classe para desenho de imagens em uma área definida por coordenadas e dimensões |
| 2 | CLabel | Classe do elemento de controle "rótulo de texto" |
| 3 | CButton | Classe do elemento de controle "botão simples" |
| 4 | CButtonTriggered | Classe do elemento de controle "botão de duas posições" |
| 5 | CButtonArrowUp | Classe do elemento de controle "botão com seta para cima" |
| 6 | CButtonArrowDown | Classe do elemento de controle "botão com seta para baixo" |
| 7 | CButtonArrowLeft | Classe do elemento de controle "botão com seta para a esquerda" |
| 8 | CButtonArrowRight | Classe do elemento de controle "botão com seta para a direita" |
| 9 | CCheckBox | Classe do elemento de controle "checkbox" |
| 10 | CRadioButton | Classe do elemento de controle "radiobutton" |
Todos os arquivos criados são anexados ao artigo para estudo individual. O arquivo compactado pode ser extraído para a pasta do terminal, e todos os arquivos estarão localizados na pasta correta: \MQL5\Indicators\Tables.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/18221
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Redes neurais em trading: Extração eficiente de características para classificação precisa (Construção de objetos)
Negociamos opções sem opções (Parte 1): Fundamentos da teoria e emulação por meio de ativos subjacentes
Indicador do modelo CAPM no mercado Forex
Redes neurais em trading: Extração eficiente de características para classificação precisa (Conclusão)
- 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