![DoEasy. Controles (Parte 8): Objetos básicos do WinForms por categoria, controles GroupBox e CheckBox](https://c.mql5.com/2/49/doeasy_008_600x314.jpg)
DoEasy. Controles (Parte 8): Objetos básicos do WinForms por categoria, controles GroupBox e CheckBox
Conteúdo
- Ideia
- Modificando as classes da biblioteca
- Objeto base na categoria Contêineres
- Objeto WinForms GroupBox
- Objeto base na categoria Controles padrão
- Objeto WinForms CheckBox
- Teste
- O que virá a seguir?
Ideia
Os objetos WinForms que criamos são categorizados como no MS Visual Studio:
- Standard controls
- Containers
- Menus and toolbars
- Data
- Components
- Dialog windows
Os objetos de todas as categorias são herdados de um objeto CWinFormBase que serve de base comum, porém os objetos pertencentes à mesma categoria têm funcionalidades coincidentes dentro de sua categoria. Por isso, podemos combinar todas as propriedades e todos os métodos de objetos que coincidem dentro de uma mesma categoria em uma classe comum. O que quer dizer que para cada categoria de objetos WinForms precisamos criar nossa própria classe base herdada da classe base de todos os objetos WinForms. Isso tornará mais fácil e simples escrever código para novos objetos em cada categoria.
Hoje vamos criar dois desses objetos, um para a categoria de objetos-contêineres e outro para a categoria de objetos-controles padrão. Em geral, para entender quais propriedades e métodos podem ser comuns para objetos de uma categoria, devemos criar pelo menos dois objetos em dada categoria.
Na categoria de contêineres, vamos criar uma classe do objeto GroupBox, que é um contêiner que combina visualmente vários dos objetos no interior dele.
Diferente do objeto Painel, que também é um contêiner, o GroupBox tem menos funcionalidade, sendo apenas um contêiner para combinar visualmente objetos em um grupo comum.
Na categoria de controles padrão, vamos criar um objeto CheckBox, que é uma "marca de seleção" com um rótulo que pode ter três estados: marcado, desmarcado e indeterminado. Como este objeto possui uma assinatura, ele será herdado do objeto "Label" (rótulo de texto), mas a ele será adicionada a funcionalidade para desenho de uma marca de seleção (sinalizador de verificação) em diferentes estados.
Todos os objetos criados ainda são estáticos, ou seja, não possuem a funcionalidade de interação com o mouse. Vamos tomar conta disso, depois de criar a maioria dos objetos WinForms que planejamos desenvolver.
Modificando as classes da biblioteca
Para os objetos criados, precisamos adicionar os valores de algumas propriedades que são atribuídas a eles por padrão quando são criados.
No arquivo \MQL5\Include\DoEasy\Defines.mqh, incluímos novas macro-substituições para estes valores:
//--- Canvas parameters #define PAUSE_FOR_CANV_UPDATE (16) // Canvas update frequency #define CLR_CANV_NULL (0x00FFFFFF) // Zero for the canvas with the alpha channel #define CLR_DEF_FORE_COLOR (C'0x2D,0x43,0x48') // Default color for texts of objects on canvas #define CLR_DEF_FORE_COLOR_OPACITY (255) // Default color non-transparency for canvas object texts #define CLR_DEF_FRAME_COLOR (C'0x66,0x6C,0x6F') // Default color for object frames on canvas #define CLR_DEF_FRAME_COLOR_OPACITY (255) // Default color non-transparency for canvas object frames #define CLR_DEF_FRAME_COLOR_DARKNESS (-2.0) // Default color opacity for canvas object frames (when using the background color) #define CLR_DEF_FRAME_GBOX_COLOR (C'0xDC,0xDC,0xDC') // Default color for GroupBox object frames on canvas #define CLR_DEF_OPACITY (200) // Default color non-transparency for canvas objects #define CLR_DEF_SHADOW_COLOR (C'0x6B,0x6B,0x6B') // Default color for canvas object shadows #define CLR_DEF_SHADOW_OPACITY (127) // Default color non-transparency for canvas objects #define DEF_SHADOW_BLUR (4) // Default blur for canvas object shadows #define DEF_FONT ("Calibri") // Default font #define DEF_FONT_SIZE (8) // Default font size #define DEF_CHECK_SIZE (12) // Verification flag default size #define OUTER_AREA_SIZE (16) // Size of one side of the outer area around the form workspace #define DEF_FRAME_WIDTH_SIZE (3) // Default form/panel/window frame width //--- Graphical object parameters
A caixa de seleção que conterá a marca de seleção no objeto CheckBox terá um tamanho padrão de 12x12 pixels.
No momento, na lista de tipos de objetos de biblioteca, na seção WinForms estão registrados os tipos Base, Panel e Label. E isso é redundante, porque temos os mesmos tipos em outra enumeração. Porém, aqui podemos usá-los como indicação da categoria dos objetos WinForms. Por isto, corrigiremos os nomes nesta enumeração para que exibam apenas categorias de objetos:
//+------------------------------------------------------------------+ //| List of library object types | //+------------------------------------------------------------------+ enum ENUM_OBJECT_DE_TYPE { //--- Graphics OBJECT_DE_TYPE_GBASE = COLLECTION_ID_LIST_END+1, // "Base object of all library graphical objects" object type OBJECT_DE_TYPE_GELEMENT, // "Graphical element" object type OBJECT_DE_TYPE_GFORM, // Form object type OBJECT_DE_TYPE_GFORM_CONTROL, // "Form for managing pivot points of graphical object" object type OBJECT_DE_TYPE_GSHADOW, // Shadow object type //--- WinForms OBJECT_DE_TYPE_GWF_BASE, // WinForms Base object type (base abstract WinForms object) OBJECT_DE_TYPE_GWF_CONTAINER, // WinForms container object type OBJECT_DE_TYPE_GWF_COMMON, // WinForms standard control object type //--- Animation
Agora, para objetos WinForms, podemos usar o tipo do objeto de biblioteca como tipo e como categoria do objeto WinForms, e na enumeração da lista de tipos de elementos gráficos vamos especificar o tipo em nossa categoria:
//+------------------------------------------------------------------+ //| The list of graphical element types | //+------------------------------------------------------------------+ enum ENUM_GRAPH_ELEMENT_TYPE { GRAPH_ELEMENT_TYPE_STANDARD, // Standard graphical object GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED, // Extended standard graphical object GRAPH_ELEMENT_TYPE_SHADOW_OBJ, // Shadow object GRAPH_ELEMENT_TYPE_ELEMENT, // Element GRAPH_ELEMENT_TYPE_FORM, // Form GRAPH_ELEMENT_TYPE_WINDOW, // Window //--- WinForms GRAPH_ELEMENT_TYPE_WF_UNDERLAY, // Panel object underlay GRAPH_ELEMENT_TYPE_WF_BASE, // Windows Forms Base GRAPH_ELEMENT_TYPE_WF_CONTAINER, // Windows Forms базовый объект-контейнер GRAPH_ELEMENT_TYPE_WF_PANEL, // Windows Forms Panel GRAPH_ELEMENT_TYPE_WF_GROUPBOX, // Windows Forms GroupBox GRAPH_ELEMENT_TYPE_WF_COMMON_BASE, // Windows Forms базовый стандартный элемент управления GRAPH_ELEMENT_TYPE_WF_LABEL, // Windows Forms Label GRAPH_ELEMENT_TYPE_WF_CHECKBOX, // Windows Forms ChackBox }; //+------------------------------------------------------------------+
A marca de seleção de um objeto CheckBox pode ter três estados. Vamos criar uma enumeração para especificá-los:
//+------------------------------------------------------------------+ //| Control flag status | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_CHEK_STATE { CANV_ELEMENT_CHEK_STATE_UNCHECKED, // Unchecked CANV_ELEMENT_CHEK_STATE_CHECKED, // Checked CANV_ELEMENT_CHEK_STATE_INDETERMINATE, // Undefined }; //+------------------------------------------------------------------+ //| Integer properties of the graphical element on the canvas | //+------------------------------------------------------------------+
Na enumeração das propriedades inteiras do elemento gráfico na tela adicionamos novas propriedades no final
e aumentamos o número de propriedades inteiras de 44 para 48:
//+------------------------------------------------------------------+ //| Integer properties of the graphical element on the canvas | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_PROP_INTEGER { CANV_ELEMENT_PROP_ID = 0, // Element ID CANV_ELEMENT_PROP_TYPE, // Graphical element type //--- ... //--- ... CANV_ELEMENT_PROP_PADDING_LEFT, // Left margin inside the control CANV_ELEMENT_PROP_PADDING_RIGHT, // Right margin inside the control CANV_ELEMENT_PROP_TEXT_ALIGN, // Text position within text label boundaries CANV_ELEMENT_PROP_CHECK_ALIGN, // Position of the verification flag within control borders CANV_ELEMENT_PROP_CHECKED, // Control verification flag status CANV_ELEMENT_PROP_CHECK_STATE, // Status of a control having a verification flag CANV_ELEMENT_PROP_AUTOCHECK, // Auto change flag status when it is selected }; #define CANV_ELEMENT_PROP_INTEGER_TOTAL (48) // Total number of integer properties #define CANV_ELEMENT_PROP_INTEGER_SKIP (0) // Number of integer properties not used in sorting //+------------------------------------------------------------------+
Na lista de critérios para classificar elementos gráficos na tela, vamos adicionar a classificação por uma nova propriedade:
//+------------------------------------------------------------------+ //| Possible sorting criteria of graphical elements on the canvas | //+------------------------------------------------------------------+ #define FIRST_CANV_ELEMENT_DBL_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP) #define FIRST_CANV_ELEMENT_STR_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP) enum ENUM_SORT_CANV_ELEMENT_MODE { //--- Sort by integer properties SORT_BY_CANV_ELEMENT_ID = 0, // Sort by element ID SORT_BY_CANV_ELEMENT_TYPE, // Sort by graphical element type //--- ... //--- ... SORT_BY_CANV_ELEMENT_PADDING_LEFT, // Sort by left margin inside the control SORT_BY_CANV_ELEMENT_PADDING_RIGHT, // Sort by right margin inside the control SORT_BY_CANV_ELEMENT_TEXT_ALIGN, // Sort by text position within text label boundaries SORT_BY_CANV_ELEMENT_CHECK_ALIGN, // Sort by position of the verification flag within control borders SORT_BY_CANV_ELEMENT_CHECKED, // Sort by control verification flag status SORT_BY_CANV_ELEMENT_CHECK_STATE, // Sort by status of a control having a verification flag SORT_BY_CANV_ELEMENT_AUTOCHECK, // Sort by auto change flag status when it is selected //--- Sort by real properties //--- Sort by string properties SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Sort by an element object name SORT_BY_CANV_ELEMENT_NAME_RES, // Sort by the graphical resource name SORT_BY_CANV_ELEMENT_TEXT, // Sort by graphical element text }; //+------------------------------------------------------------------+
Agora poderemos classificar e filtrar listas e selecionar objetos por essas novas propriedades.
A propriedade "Tipo de moldura" (BorderStyle) tem finalidades diferentes para objetos WinForms diferentes. Para um objeto-painel esta propriedade indica o estilo da moldura que envolve o próprio objeto, e para o objeto GroupBox criado hoje é o tipo de moldura desenhada ao redor do grupo de objetos (o objeto em si não tem moldura). Assim, podemos usar o mesmo método em objetos distintos para diferentes propósitos.
Para fazer isso, vamos torná-lo virtual no arquivo de classe base de todos os objetos WinForms \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh:
//--- (1) Set and (2) return the font width type void SetFontBoldType(ENUM_FW_TYPE type); ENUM_FW_TYPE FontBoldType(void) const { return (ENUM_FW_TYPE)this.GetProperty(CANV_ELEMENT_PROP_BOLD_TYPE); } //--- (1) Set and (2) return the frame style virtual void SetBorderStyle(const ENUM_FRAME_STYLE style) { this.SetProperty(CANV_ELEMENT_PROP_BORDER_STYLE,style); } ENUM_FRAME_STYLE BorderStyle(void) const { return (ENUM_FRAME_STYLE)this.GetProperty(CANV_ELEMENT_PROP_BORDER_STYLE); }
Substituir esse método em classes herdadas nos permitirá fazer nossa própria implementação para cada uma delas.
Objeto base na categoria Contêineres
Ao criar um segundo objeto WinForms na categoria "Contêineres", tornou-se evidente que muitas das propriedades e métodos são os mesmos e são repetidos de objeto para objeto. Assim, para não repetir e não fazer métodos do mesmo tipo em objetos diferentes, precisamos transferi-los para uma classe comum, da qual serão herdados objetos dessa categoria. Assim, cada um dos objetos filho receberá todos esses métodos de seu pai. Aqueles métodos que devem diferir em sua implementação para diferentes objetos da mesma categoria devem ser tornados virtuais e redefinidos em classes herdadas.
Na pasta \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\, criamos um novo arquivo contêiner.mqh da classe CContainer. A classe deve ser herdada da classe base de todos os objetos da biblioteca WinForms, cujo arquivo deve ser incluído na classe criada:
//+------------------------------------------------------------------+ //| Container.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\WForms\WinFormBase.mqh" //+------------------------------------------------------------------+ //| Class of the base container object of WForms controls | //+------------------------------------------------------------------+ class CContainer : public CWinFormBase { }
Nesta nova classe, precisamos transferir muitos métodos da classe contêiner já existente Painel, pois esses métodos serão necessários em outras classes desta categoria. Como vimos esses métodos, a maioria dos dessa classe, no sexto artigo ao descrever a criação da classe Painel, aqui consideramos simplesmente todo o corpo da classe e, em seguida, a implementação de seus métodos.
Como um objeto dessa classe não terá um objeto substrato (como um objeto-painel), precisamos indicar o espaço de trabalho do objeto de alguma forma. A área de trabalho é a área do objeto onde você pode colocar outros objetos anexados ao contêiner. Para indicar os limites deste espaço de trabalho, introduzimos novos métodos:
//+------------------------------------------------------------------+ //| Container.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\WForms\WinFormBase.mqh" #include "..\..\WForms\Common Controls\CheckBox.mqh" //+------------------------------------------------------------------+ //| Class of the base container object of WForms controls | //+------------------------------------------------------------------+ class CContainer : public CWinFormBase { private: //--- Create a new graphical object virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); //--- Calculate Dock objects' binding coordinates void CalculateCoords(CArrayObj *list); protected: //--- Adjust the element size to fit its content bool AutoSizeProcess(const bool redraw); public: //--- Return the size and coordinates of the working area int GetWidthWorkspace(void) const { return this.Width()-::fmax(this.FrameWidthLeft(),this.PaddingLeft())-::fmax(this.FrameWidthRight(),this.PaddingRight()); } int GetHeightWorkspace(void) const { return this.Height()-::fmax(this.FrameWidthTop(),this.PaddingTop())-::fmax(this.FrameWidthBottom(),this.PaddingBottom()); } int GetCoordXWorkspace(void) const { return this.CoordX()+::fmax(this.FrameWidthLeft(),this.PaddingLeft()); } int GetCoordYWorkspace(void) const { return this.CoordY()+::fmax(this.FrameWidthTop(),this.PaddingTop()); } int GetRightEdgeWorkspace(void) const { return this.RightEdge()-::fmax(this.FrameWidthRight(),this.PaddingRight()); } int GetBottomEdgeWorkspace(void) const { return this.BottomEdge()-::fmax(this.FrameWidthBottom(),this.PaddingBottom()); } //--- Return the list of bound WinForms objects with (1) any and (2) specified WinForms object type (from the base one and higher) CArrayObj *GetListWinFormsObj(void); CArrayObj *GetListWinFormsObjByType(const ENUM_GRAPH_ELEMENT_TYPE type); //--- Return the pointer to the specified WinForms object with the specified type by index CWinFormBase *GetWinFormsObj(const ENUM_GRAPH_ELEMENT_TYPE type,const int index); //--- Set the (1) X, (2) Y coordinates, (3) element width and (4) height virtual bool SetCoordX(const int coord_x) { return CGCnvElement::SetCoordX(coord_x); } virtual bool SetCoordY(const int coord_y) { return CGCnvElement::SetCoordY(coord_y); } virtual bool SetWidth(const int width) { return CGCnvElement::SetWidth(width); } virtual bool SetHeight(const int height) { return CGCnvElement::SetHeight(height); } //--- Create a new attached element virtual bool CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw); //--- Redraw the object virtual void Redraw(bool redraw) { CWinFormBase::Redraw(redraw); } //--- Reset the size of all bound objects to the initial ones bool ResetSizeAllToInit(void); //--- Place bound objects in the order of their Dock binding virtual bool ArrangeObjects(const bool redraw); //--- Set the (1) field width, (2) height, (3) the height of all fields around the control during auto scrolling void SetAutoScrollMarginWidth(const int value) { this.SetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_W,value); } void SetAutoScrollMarginHeight(const int value) { this.SetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_H,value); } void SetAutoScrollMarginAll(const int value) { this.SetAutoScrollMarginWidth(value); this.SetAutoScrollMarginHeight(value); } void SetAutoScrollMargin(const int width,const int height) { this.SetAutoScrollMarginWidth(width); this.SetAutoScrollMarginHeight(height); } //--- Return the (1) field width and (2) height around the control during auto scrolling int AutoScrollMarginWidth(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_W); } int AutoScrollMarginHeight(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_H); } //--- Set the flag of the element auto resizing depending on the content virtual void SetAutoSize(const bool flag,const bool redraw) { bool prev=this.AutoSize(); if(prev==flag) return; CWinFormBase::SetAutoSize(flag,redraw); if(prev!=this.AutoSize() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); } //--- (1) Set and (2) return the mode of the element auto resizing depending on the content void SetAutoSizeMode(const ENUM_CANV_ELEMENT_AUTO_SIZE_MODE mode,const bool redraw) { ENUM_CANV_ELEMENT_AUTO_SIZE_MODE prev=this.AutoSizeMode(); if(prev==mode) return; this.SetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE,mode); if(prev!=this.AutoSizeMode() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); } ENUM_CANV_ELEMENT_AUTO_SIZE_MODE AutoSizeMode(void) const { return (ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)this.GetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE); } //--- (1) Set and (2) return the mode of binding element borders to the container virtual void SetDockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode,const bool redraw) { if(this.DockMode()==mode) return; CWinFormBase::SetDockMode(mode,redraw); CContainer *base=this.GetBase(); if(base!=NULL) base.ArrangeObjects(redraw); } //--- Set the width of the form frame (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides of the control virtual void SetFrameWidthLeft(const uint value) { this.m_frame_width_left=(int)value; if(this.PaddingLeft()<this.FrameWidthLeft()) this.SetPaddingLeft(this.FrameWidthLeft()); } virtual void SetFrameWidthTop(const uint value) { this.m_frame_width_top=(int)value; if(this.PaddingTop()<this.FrameWidthTop()) this.SetPaddingTop(this.FrameWidthTop()); } virtual void SetFrameWidthRight(const uint value) { this.m_frame_width_right=(int)value; if(this.PaddingRight()<this.FrameWidthRight()) this.SetPaddingRight(this.FrameWidthRight()); } virtual void SetFrameWidthBottom(const uint value) { this.m_frame_width_bottom=(int)value; if(this.PaddingBottom()<this.FrameWidthBottom()) this.SetPaddingBottom(this.FrameWidthBottom()); } virtual void SetFrameWidthAll(const uint value) { this.SetFrameWidthLeft(value); this.SetFrameWidthTop(value); this.SetFrameWidthRight(value); this.SetFrameWidthBottom(value); } //--- Constructors CContainer(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); CContainer(const string name) : CWinFormBase(::ChartID(),0,name,0,0,0,0) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CONTAINER); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CONTAINER); this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; this.SetForeColor(CLR_DEF_FORE_COLOR); this.SetFontBoldType(FW_TYPE_NORMAL); this.SetMarginAll(3); this.SetPaddingAll(0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetAutoScroll(false,false); this.SetAutoScrollMarginAll(0); this.SetAutoSize(false,false); this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false); this.Initialize(); } //--- Destructor ~CContainer(); }; //+------------------------------------------------------------------+
O último artigo, onde vimos a criação do objeto-painel, já não nos diz mais nada sobre o resto dos métodos.
No construtor da classe inicializamos todas as variáveis com seus valores padrão:
//+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CContainer::CContainer(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CWinFormBase(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CONTAINER); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CONTAINER); this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; this.SetForeColor(CLR_DEF_FORE_COLOR); this.SetFontBoldType(FW_TYPE_NORMAL); this.SetMarginAll(3); this.SetPaddingAll(0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetAutoScroll(false,false); this.SetAutoScrollMarginAll(0); this.SetAutoSize(false,false); this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false); this.Initialize(); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetWidthInit(w); this.SetHeightInit(h); } //+------------------------------------------------------------------+
No destruidor da classes, chamamos o método de desinicialização localizado na classe do objeto-forma, que é o pai da classe do objeto base dos objetos da biblioteca WinForms:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CContainer::~CContainer() { CForm::Deinitialize(); } //+------------------------------------------------------------------+
No método que cria um novo objeto gráfico (olhando para frente), existem linhas para criar todos os objetos WinForms disponíveis (e que analisaremos aqui um pouco mais adiante) (e não só) não a partir da categoria de objetos contêiner, exceto um objeto da mesma classe:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CContainer::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { string name=this.CreateNameDependentObject(obj_name); CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); return element; } //+------------------------------------------------------------------+
Por que não é possível criar objetos da categoria contêiner aqui? Basicamente porque eles serão os herdeiros desta mesma classe, e ele ainda não sabe nada sobre eles. Mas é possível criar um objeto de sua própria classe (CContainer) aqui. Como esse método é virtual, nas classes herdadas já poderemos registrar nele a criação de objetos de classes-herdeiros dessa classe.
O método que cria um novo elemento anexado também é retirado da classe de objeto-painel e é simplesmente complementado com novas linhas para definir os parâmetros dos objetos recém-criados:
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CContainer::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw) { //--- If the object type is less than the base WinForms object if(element_type<GRAPH_ELEMENT_TYPE_WF_BASE) { //--- report the error and return 'false' CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE); return false; } //--- If failed to create a new graphical element, return 'false' CWinFormBase *obj=CForm::CreateAndAddNewElement(element_type,main,x,y,w,h,colour,opacity,activity); if(obj==NULL) return false; //--- Set the text color of the created object as that of the base panel obj.SetForeColor(this.ForeColor()); //--- If the object type is a container if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_CONTAINER) { //--- set the frame color equal to the background color obj.SetColorFrame(obj.ColorBackground()); } //--- If the object type is a panel if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_PANEL) { //--- set the frame color equal to the background color obj.SetColorFrame(obj.ColorBackground()); } //--- If the object type is GroupBox if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_GROUPBOX) { //--- set the frame color equal to the background color obj.SetColorFrame(obj.ColorBackground()); } //--- If the object type is a text label, if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_LABEL) { //--- set the object text color depending on the one passed to the method //--- or the panel text color or the one passed to the method and the frame color equal to the text color obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour); obj.SetColorFrame(main!=NULL ? main.ColorBackground() : obj.ForeColor()); } //--- If the object type is CheckBox if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_CHECKBOX) { //--- set the object text color depending on the one passed to the method //--- or the object text color or the one passed to the method and the frame color equal to the text color obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour); obj.SetColorFrame(main!=NULL ? main.ColorBackground() : obj.ForeColor()); } //--- If the panel has auto resize enabled and features bound objects, call the resize method if(this.AutoSize() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); //--- Redraw the panel and all added objects, and return 'true' this.Redraw(redraw); return true; } //+------------------------------------------------------------------+
Neste método, já podemos registrar todos os objetos existentes e futuros (mas já registrados no arquivo Defines.mqh), pois o tipo de objeto base CWinFormBase é criado aqui, e é conhecido aqui, pois é a classe pai. E todos os parâmetros definidos nos blocos de código que determinam a configuração dos parâmetros para o objeto criado são usados na lista da classe CWinFormBase, o que não causará colisões e erros.
Esse método continuará sendo complementado por blocos de códigos que processam objetos recém-criados. Mas se sua inicialização exigir a configuração de parâmetros desconhecidos nessa classe, esse método será virtual e escreveremos o processamento de propriedades desconhecidas de novos objetos no método substituído dessas novas classes.
Método que redefine o tamanho de todos os objetos vinculados para seus tamanhos originais:
//+------------------------------------------------------------------+ //| Reset the size of all bound objects to the initial ones | //+------------------------------------------------------------------+ bool CContainer::ResetSizeAllToInit(void) { bool res=true; CArrayObj *list=this.GetListWinFormsObj(); if(list==NULL) return false; for(int i=0;i<list.Total();i++) { CWinFormBase *obj=list.At(i); if(obj==NULL) { res &=false; continue; } res &=obj.Resize(i,obj.GetWidthInit(),obj.GetHeightInit()); } return res; } //+------------------------------------------------------------------+
Aqui obtemos uma lista apenas de objetos WinForms e, em seguida, em um loop sobre a lista resultante, definimos cada objeto para seu tamanho original.
Método que redimensiona um elemento para caber em seu interior:
//+------------------------------------------------------------------+ //| Adjust the element size to fit its content | //+------------------------------------------------------------------+ bool CContainer::AutoSizeProcess(const bool redraw) { //--- Get the list of bound objects with WinForms type basic and higher CArrayObj *list=this.GetListWinFormsObj(); //--- Get objects with the maximum and minimum X and Y coordinates from the list by their indices CWinFormBase *maxx=list.At(CSelect::FindGraphCanvElementMax(list,CANV_ELEMENT_PROP_COORD_X)); CWinFormBase *minx=list.At(CSelect::FindGraphCanvElementMin(list,CANV_ELEMENT_PROP_COORD_X)); CWinFormBase *maxy=list.At(CSelect::FindGraphCanvElementMax(list,CANV_ELEMENT_PROP_COORD_Y)); CWinFormBase *miny=list.At(CSelect::FindGraphCanvElementMin(list,CANV_ELEMENT_PROP_COORD_Y)); //--- If at least one of the four objects is not received, return 'false' if(maxx==NULL || minx==NULL || maxy==NULL || miny==NULL) return false; //--- Get the minimum X and Y coordinate int min_x=minx.CoordX(); int min_y=fmin(miny.CoordY(),maxy.BottomEdge()); //--- Calculate the total width and height of all bound objects int w=maxx.RightEdge()-min_x; int h=int(::fmax(miny.CoordY(),maxy.BottomEdge())-min_y); //--- Calculate the number of pixels, by which we need to resize the container in width and height int excess_x=w-this.GetWidthWorkspace(); int excess_y=h-this.GetHeightWorkspace(); //--- Calculate the offset, by which the bound objects are to be moved int shift_x=this.GetCoordXWorkspace()-min_x; int shift_y=this.GetCoordYWorkspace()-min_y; //--- If failed to change the container size, return 'true' if(excess_x==0 && excess_y==0) return true; //--- If it is necessary to move the attached objects inside the container along the X or Y coordinate bool res=true; if(shift_x>0 || shift_y>0) { //--- In the loop by all attached objects, for(int i=0;i<list.Total();i++) { //--- get the next object CWinFormBase *obj=list.At(i); if(obj==NULL) continue; //--- If the object needs to be shifted horizontally, write the shift result to 'res' if(shift_x>0) res &=obj.Move(obj.CoordX()+shift_x,obj.CoordY()); //--- If the object needs to be shifted vertically, write the shift result to 'res' if(shift_y>0) res &=obj.Move(obj.CoordX(),obj.CoordY()+shift_y); //--- Set new relative object X and Y coordinates obj.SetCoordXRelative(obj.CoordX()-this.GetCoordXWorkspace()); obj.SetCoordYRelative(obj.CoordY()-this.GetCoordYWorkspace()); } } //--- Return the result of resizing the container return ( //--- If we failed to move at least one bound object, return 'false' !res ? false : //--- Otherwise, if only a size increase this.AutoSizeMode()==CANV_ELEMENT_AUTO_SIZE_MODE_GROW ? this.Resize(this.Width()+(excess_x>0 ? excess_x : 0),this.Height()+(excess_y>0 ? excess_y : 0),redraw) : //--- if both increase and decrease this.Resize(this.Width()+(excess_x!=0 ? excess_x : 0),this.Height()+(excess_y!=0 ? excess_y : 0),redraw) ); } //+------------------------------------------------------------------+
O método também é transferido desde a classe do objeto-painel, mas como essa classe não possui um substrato, em vez dos valores de suas propriedades, aqui usamos os valores dos parâmetros da área de trabalho do objeto.
O método ainda não funciona corretamente e veremos como refiná-lo em conjunto com o método que organiza os objetos no contêiner segundo sua indexação em artigos futuros:
//+------------------------------------------------------------------+ //| Place bound objects in the order of their Dock binding | //+------------------------------------------------------------------+ bool CContainer::ArrangeObjects(const bool redraw) { //--- Get the list of bound objects with WinForms type basic and higher CArrayObj *list=this.GetListWinFormsObj(); CWinFormBase *prev=NULL, *obj=NULL, *elm=NULL; //--- In the loop by all bound objects, for(int i=0;i<list.Total();i++) { //--- Get the current and previous elements from the list obj=list.At(i); prev=list.At(i-1); //--- If the object is not received, move on if(obj==NULL) continue; int x=0, y=0; // Object binding coordinates //--- Depending on the current object binding mode... //--- Top if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_TOP) { //--- If failed to change the object size (for the entire working area width and by the initial object height), move on to the next one if(!obj.Resize(this.GetWidthWorkspace(),obj.GetHeightInit(),false)) continue; //--- Get the object binding coordinates x=this.GetCoordXWorkspace(); y=(prev!=NULL ? prev.BottomEdge()+1 : this.GetCoordYWorkspace()); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; } //--- Bottom if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_BOTTOM) { //--- If failed to change the object size (for the entire working area width and by the initial object height), move on to the next one if(!obj.Resize(this.GetWidthWorkspace(),obj.GetHeightInit(),false)) continue; //--- Get the object binding coordinates x=this.GetCoordXWorkspace(); y=(prev!=NULL ? prev.CoordY()-obj.Height()-1 : this.GetBottomEdgeWorkspace()-obj.Height()-1); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; } //--- Left if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_LEFT) { //--- If failed to change the object size (for the initial object width and the entire working area height), move on to the next one if(!obj.Resize(obj.GetWidthInit(),this.GetHeightWorkspace(),false)) continue; //--- Get the object binding coordinates x=(prev!=NULL ? prev.RightEdge()+1 : this.GetCoordXWorkspace()); y=this.GetCoordYWorkspace(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; } //--- Right if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_RIGHT) { //--- If failed to change the object size (for the initial object width and the entire working area height), move on to the next one if(!obj.Resize(obj.GetWidthInit(),this.GetHeightWorkspace(),false)) continue; //--- Get the object binding coordinates x=(prev!=NULL ? prev.CoordX()-obj.Width()-1 : this.GetRightEdgeWorkspace()-obj.Width()); y=this.GetCoordYWorkspace(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; } //--- Binding with filling if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_FILL) { //--- If failed to change the object size (for the entire working area width and height), move on to the next one if(!obj.Resize(this.GetWidthWorkspace(),this.GetHeightWorkspace(),false)) continue; //--- Get the object binding coordinates x=this.GetCoordXWorkspace(); y=this.GetCoordYWorkspace(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; } //--- No binding if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_NONE) { //--- Reset the object size obj.Resize(obj.GetWidthInit(),obj.GetHeightInit(),false); //--- Get the initial object location coordinates x=this.GetCoordXWorkspace()+obj.CoordXRelativeInit(); y=this.GetCoordYWorkspace()+obj.CoordYRelativeInit(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; } //--- Calculate and set the relative object coordinates obj.SetCoordXRelative(x-this.GetCoordXWorkspace()); obj.SetCoordYRelative(y-this.GetCoordYWorkspace()); } //--- If auto resizing mode is enabled if(this.AutoSize()) this.AutoSizeProcess(false); //--- Redraw the object with the redraw flag and return 'true' this.Redraw(redraw); return true; } //+------------------------------------------------------------------+
Ambos os métodos apresentados acima requerem retrabalho e, portanto, estão aqui apenas pelo motivo de estarem na classe do objeto-painel. Mas eles serão revistos mais tarde.
Os métodos abaixo também são movidos desde a classe de objeto-painel. Movidos sem alterações:
//+------------------------------------------------------------------+ //| Return the list of bound objects | //| of any WinForms base type and higher | //+------------------------------------------------------------------+ CArrayObj *CContainer::GetListWinFormsObj(void) { return CSelect::ByGraphCanvElementProperty(this.GetListElements(),CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BASE,EQUAL_OR_MORE); } //+------------------------------------------------------------------+ //| Return the list of bound objects | //| with the specified WinForms object type | //+------------------------------------------------------------------+ CArrayObj *CContainer::GetListWinFormsObjByType(const ENUM_GRAPH_ELEMENT_TYPE type) { return CSelect::ByGraphCanvElementProperty(this.GetListElements(),CANV_ELEMENT_PROP_TYPE,type,EQUAL); } //+------------------------------------------------------------------+ //| Return the pointer to the specified WinForms object | //| with the specified type by index | //+------------------------------------------------------------------+ CWinFormBase *CContainer::GetWinFormsObj(const ENUM_GRAPH_ELEMENT_TYPE type,const int index) { CArrayObj *list=this.GetListWinFormsObjByType(type); return(list!=NULL ? list.At(index) : NULL); } //+------------------------------------------------------------------+ //| Calculate Dock objects' binding coordinates | //+------------------------------------------------------------------+ void CContainer::CalculateCoords(CArrayObj *list) { } //+------------------------------------------------------------------+
Vimos a classe de objeto-contêiner base em sua totalidade.
Agora vamos criar uma nova classe do objeto GroupBox, que também é um contêiner para armazenar outros objetos WinForms.
Objeto WinForms GroupBox
Vamos criar um novo arquivo GroupBox.mqh da classe CGroupBox na pasta \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\. A classe deve ser derivada da classe base do objeto contêiner recém-criada, e seu arquivo de classe e o arquivo de classe do objeto-painel devem ser incluídos no arquivo desta classe:
//+------------------------------------------------------------------+ //| GroupBox.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Container.mqh" #include "Panel.mqh" //+------------------------------------------------------------------+ //| GroupBox object class of the WForms controls | //+------------------------------------------------------------------+ class CGroupBox : public CContainer { }
Nas seções private, protected e public da classe, escreveremos os métodos já familiares para nós da classe do objeto-contêiner base.
Como o objeto GroupBox tem uma moldura que enquadra um grupo de objetos, na seção privada vamos declarar um método que desenha tal moldura:
//+------------------------------------------------------------------+ //| GroupBox object class of the WForms controls | //+------------------------------------------------------------------+ class CGroupBox : public CContainer { private: //--- Draw a frame void DrawFrame(void); //--- Create a new graphical object virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); protected: //--- Initialize the variables virtual void Initialize(void); public: //--- Clear the element filling it with color and opacity virtual void Erase(const color colour,const uchar opacity,const bool redraw=false); //--- Clear the element with a gradient fill virtual void Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); //--- Clear the element completely virtual void Erase(const bool redraw=false); //--- Set a frame style virtual void SetBorderStyle(const ENUM_FRAME_STYLE style) { if((this.FrameWidthTop()<2 || this.FrameWidthBottom()<2 || this.FrameWidthLeft()<2 || this.FrameWidthRight()<2) && style>FRAME_STYLE_FLAT) this.SetFrameWidthAll(2); this.SetProperty(CANV_ELEMENT_PROP_BORDER_STYLE,style); } //--- Constructors CGroupBox(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); CGroupBox(const string name) : CContainer(::ChartID(),0,name,0,0,0,0) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_GROUPBOX); this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; this.Initialize(); } //--- Destructor ~CGroupBox(){ CForm::Deinitialize(); } }; //+------------------------------------------------------------------+
No método que define o estilo da moldura, verificamos o estilo passado para ele e, se a moldura não for plana, sua largura deve ser de pelo menos dois pixels. E, em caso afirmativo, todos os lados da moldura são definidos com uma largura de 2 pixels.
No construtor paramétrico, definimos as dimensões e coordenadas iniciais do objeto e chamamos o método de inicialização:
//+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CGroupBox::CGroupBox(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CContainer(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_GROUPBOX); this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetWidthInit(w); this.SetHeightInit(h); this.Initialize(); } //+------------------------------------------------------------------+
Como se pode ver, aqui o tipo do objeto WinForms é escrito como GroupBox, e o tipo do objeto de biblioteca é escrito como contêiner.
No método que cria um novo objeto gráfico, podemos criar tanto objetos-painéis, e objetos dessa classe, quanto objetos da classe CheckBox, o que faremos mais adiante neste artigo, mas escreveremos aqui para não retornar ao arquivo desta classe novamente:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CGroupBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { string name=this.CreateNameDependentObject(obj_name); CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); return element; } //+------------------------------------------------------------------+
Método de inicialização variável:
//+------------------------------------------------------------------+ //| Initialize the variables | //+------------------------------------------------------------------+ void CGroupBox::Initialize(void) { //--- Clear all object lists and set sorted list flags for them this.m_list_elements.Clear(); this.m_list_elements.Sort(); this.m_list_tmp.Clear(); this.m_list_tmp.Sort(); //--- GroupBox has no shadow object this.m_shadow_obj=NULL; this.m_shadow=false; //--- The width of the object frame on each side is 1 pixel by default this.SetFrameWidth(1,1,1,1); //--- The object does not have a gradient filling (neither vertical, nor horizontal) this.m_gradient_v=false; this.m_gradient_c=false; //--- Reset all "working" flags and variables this.m_mouse_state_flags=0; this.m_offset_x=0; this.m_offset_y=0; CGCnvElement::SetInteraction(false); //--- Create an animation object and add it to the list for storing such objects this.m_animations=new CAnimations(CGCnvElement::GetObject()); this.m_list_tmp.Add(this.m_animations); //--- Set a transparent background for the object background and the default color for the frame this.SetColorBackground(CLR_CANV_NULL); this.SetOpacity(0); this.SetColorFrame(CLR_DEF_FRAME_GBOX_COLOR); //--- Set the default color and text opacity, as well as the absence of the object frame this.SetForeColor(CLR_DEF_FORE_COLOR); this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY); this.SetBorderStyle(FRAME_STYLE_SIMPLE); //--- Set the default text parameters this.SetFont(DEF_FONT,DEF_FONT_SIZE); this.SetText("GroupBox"); this.SetTextAnchor(FRAME_ANCHOR_LEFT_TOP); this.SetTextAlign(ANCHOR_LEFT_UPPER); //--- Set the default object parameters this.SetAutoSize(false,false); this.SetMarginAll(3); this.SetPaddingAll(3); this.SetEnabled(true); this.SetVisible(true,false); } //+------------------------------------------------------------------+
No método, todas as variáveis de classe são inicializadas com valores padrão. Os valores de inicialização de algumas variáveis diferem dos valores definidos na classe pai.
Métodos para limpeza do objeto:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CGroupBox::Erase(const color colour,const uchar opacity,const bool redraw=false) { //--- Fill the element having the specified color and the redrawing flag CGCnvElement::Erase(colour,opacity,redraw); //--- Draw a frame encasing a group of objects this.DrawFrame(); //--- Draw a header above the frame CGCnvElement::Text(6,0,this.Text(),this.ForeColor(),this.ForeColorOpacity()); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void CGroupBox::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { //--- Fill the element having the specified color array and the redrawing flag CGCnvElement::Erase(colors,opacity,vgradient,cycle,redraw); //--- Draw a frame encasing a group of objects this.DrawFrame(); //--- Draw a header above the frame CGCnvElement::Text(6,0,this.Text(),this.ForeColor(),this.ForeColorOpacity()); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element completely | //+------------------------------------------------------------------+ void CGroupBox::Erase(const bool redraw=false) { //--- Fully clear the element with the redrawing flag CGCnvElement::Erase(redraw); } //+------------------------------------------------------------------+
Os métodos são virtuais, e neles, além de preencher todo o objeto com uma cor de fundo (transparente por padrão), também é desenhada uma moldura e o texto do título do grupo de objetos é exibido em cima dele.
Método que desenha a moldura:
//+------------------------------------------------------------------+ //| Draw the frame | //+------------------------------------------------------------------+ void CGroupBox::DrawFrame(void) { //--- Get half of the text height int w=0; int h=0; this.TextSize(Text(),w,h); int height=this.Height()-h/2; //--- Depending on the frame style, draw its necessary type switch(this.BorderStyle()) { case FRAME_STYLE_FLAT : this.DrawFrameFlat(0,h/2,this.Width(),height,this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.ForeColorOpacity()); break; case FRAME_STYLE_BEVEL : this.DrawFrameBevel(0,h/2,this.Width(),height,this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.ForeColorOpacity()); break; case FRAME_STYLE_STAMP : this.DrawFrameStamp(0,h/2,this.Width(),height,this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.ForeColorOpacity()); break; //--- FRAME_STYLE_SIMPLE default: this.DrawFrameSimple(0,h/2,this.Width(),height,this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.ForeColorOpacity()); break; } //--- If the text set for an object is not an empty string, erase the frame area where a text should be located using the transparent color if(this.Text()!="") this.DrawRectangleFill(5,h/2-1,w+7,h/2+this.FrameWidthTop()+1,CLR_CANV_NULL,0); } //+------------------------------------------------------------------+
A lógica do método é descrita nos comentários ao código. Resumindo, precisamos desenhar uma borda ao redor do grupo de objetos que são colocados no contêiner. A moldura deve ser desenhada ao redor das bordas de todo o objeto e ter o estilo e a cor especificados em suas propriedades. A borda superior da borda não deve correr ao longo da borda superior do objeto, mas no centro do título. Para calcular a coordenada Y inicial da moldura, pegamos o tamanho da fonte em altura e dividimos por 2 - essa será a coordenada Y da moldura. O texto do título não deve ser desenhado diretamente na moldura, ou seja, não deve haver nenhuma linha de moldura no local do texto.
Para fazer isso, basta apagar a linha no lugar certo desenhando um retângulo com uma cor transparente em cima da mesma, cujo tamanho é, de cada lado, um pixel maior que o tamanho do texto.
O objeto WinForms GroupBox está assim pronto.
Vamos abrir o arquivo de classe do painel object\MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh e remover dele todos os métodos transferidos para a classe base. Além disso, no arquivo de classe incluímos o arquivo de classe base do objeto contêiner e o arquivo de classe GroupBox. A classe agora será herdada do objeto-contêiner base:
//+------------------------------------------------------------------+ //| Panel.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Container.mqh" #include "GroupBox.mqh" //+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+ class CPanel : public CContainer
Da seção privada da classe, removeremos as variáveis-ponteiros para os objetos (em cujas coordenadas o objeto Dock está encaixado) e o método que define o substrato com tal objeto, pois agora usaremos os métodos da classe base CContainer por esta:
class CPanel : public CWinFormBase { private: CGCnvElement *m_obj_top; // Pointer to the object whose coordinates the current upper object is bound to CGCnvElement *m_obj_bottom; // Pointer to the object whose coordinates the current bottom object is bound to CGCnvElement *m_obj_left; // Pointer to the object whose coordinates the current left object is bound to CGCnvElement *m_obj_right; // Pointer to the object whose coordinates the current right object is bound to CGCnvElement *m_underlay; // Underlay for placing elements //--- Create a new graphical object virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); //--- Return the initial coordinates of a bound object virtual void GetCoords(int &x,int &y); //--- Create the underlay object bool CreateUnderlayObj(void); //--- Set the underlay as a coordinate system zero void SetUnderlayAsBase(void);
Removemos linhas extras dos construtores de classe, pois agora elas são escritas na classe pai:
//--- Constructors CPanel(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); CPanel(const string name) : CWinFormBase(::ChartID(),0,name,0,0,0,0) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_PANEL); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.SetForeColor(CLR_DEF_FORE_COLOR); this.SetFontBoldType(FW_TYPE_NORMAL); this.SetMarginAll(3); this.SetPaddingAll(0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetAutoScroll(false,false); this.SetAutoScrollMarginAll(0); this.SetAutoSize(false,false); this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false); this.Initialize(); if(this.CreateUnderlayObj()) this.SetUnderlayAsBase(); } //--- Destructor ~CPanel(); }; //+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CPanel::CPanel(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CWinFormBase(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_PANEL); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.SetForeColor(CLR_DEF_FORE_COLOR); this.SetFontBoldType(FW_TYPE_NORMAL); this.SetMarginAll(3); this.SetPaddingAll(0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetAutoScroll(false,false); this.SetAutoScrollMarginAll(0); this.SetAutoSize(false,false); this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false); this.Initialize(); if(this.CreateUnderlayObj()) this.SetUnderlayAsBase(); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetWidthInit(w); this.SetHeightInit(h); } //+------------------------------------------------------------------+
Além disso, nos construtores, escreveremos um novo tipo de objeto e a inicialização da classe CContainer em vez do CWinFormBase anterior na lista de inicialização:
//--- Constructors CPanel(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); CPanel(const string name) : CContainer(::ChartID(),0,name,0,0,0,0) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_PANEL); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; this.CreateUnderlayObj(); } //--- Destructor ~CPanel(){ CForm::Deinitialize(); } }; //+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CPanel::CPanel(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CContainer(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_PANEL); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; this.CreateUnderlayObj(); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetWidthInit(w); this.SetHeightInit(h); } //+------------------------------------------------------------------+
No método que cria um novo objeto gráfico, escrevemos a criação de novos objetos:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { string name=this.CreateNameDependentObject(obj_name); CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); return element; } //+------------------------------------------------------------------+
Veremos a criação do objeto CheckBox aqui um pouco mais tarde.
O resto das mudanças na classe são pequenas, e não há muito sentido em cobri-las aqui - elas podem ser encontradas nos arquivos anexados ao artigo. O principal é a remoção do arquivo de métodos transferidos daqui para a classe base.
Agora precisamos criar o objeto base para os objetos de controle padrão do WinForms da mesma forma.
Objeto base na categoria Controles padrão
Neste objeto, vamos transferir os métodos desde o objeto rótulo de texto, que consideramos no último artigo. A maioria dos métodos deste objeto será duplicada em outros objetos desta categoria, então aqui (e em outras categorias) também precisamos de um objeto base.
No diretório de biblioteca \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\, criamos um novo arquivo Label.mqh da classe CLabel.
A classe deve ser herdada da classe base dos objetos da biblioteca WinForms , e um arquivo desta classe deve estar incluído neste arquivo:
//+------------------------------------------------------------------+ //| CommonBase.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\WForms\WinFormBase.mqh" //+------------------------------------------------------------------+ //| Class of the base WForms standard control object | //+------------------------------------------------------------------+ class CCommonBase : public CWinFormBase { }
A classe é bem pequena. Vamos dar uma olhada em seu corpo inteiro:
//+------------------------------------------------------------------+ //| Class of the base WForms standard control object | //+------------------------------------------------------------------+ class CCommonBase : public CWinFormBase { private: protected: //--- Set the element width and height automatically virtual void AutoSetWH(void) { return; } //--- Initialize the variables virtual void Initialize(void); public: //--- Clear the element filling it with color and opacity virtual void Erase(const color colour,const uchar opacity,const bool redraw=false); //--- Clear the element with a gradient fill virtual void Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); //--- Clear the element completely virtual void Erase(const bool redraw=false); //--- Constructor CCommonBase(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
Um método virtual vazio é declarado aqui para redimensionar automaticamente um objeto. O método deve ser implementado em classes herdadas, pois cada classe pode ter critérios de tamanho próprios pelos quais o tamanho do objeto deve ser definido.
No construtor de classe são definidos os tipos de objeto e definidos todos os valores de propriedade padrão.
Após definir todos os valores, o objeto é redesenhado:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CCommonBase::CCommonBase(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CWinFormBase(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_COMMON_BASE); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_COMMON_BASE); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetCoordX(x); this.SetCoordY(y); this.SetWidth(w); this.SetHeight(h); this.Initialize(); if(this.AutoSize()) this.AutoSetWH(); this.SetWidthInit(this.Width()); this.SetHeightInit(this.Height()); this.SetCoordXInit(x); this.SetCoordYInit(y); this.Redraw(false); } //+------------------------------------------------------------------+
Método de inicialização:
//+------------------------------------------------------------------+ //| Initialize the variables | //+------------------------------------------------------------------+ void CCommonBase::Initialize(void) { //--- Clear all object lists and set sorted list flags for them this.m_list_elements.Clear(); this.m_list_elements.Sort(); this.m_list_tmp.Clear(); this.m_list_tmp.Sort(); //--- Standard control has no shadow object this.m_shadow_obj=NULL; this.m_shadow=false; //--- The width of the object frame on each side is 1 pixel by default this.m_frame_width_right=1; this.m_frame_width_left=1; this.m_frame_width_top=1; this.m_frame_width_bottom=1; //--- The object does not have a gradient filling (neither vertical, nor horizontal) this.m_gradient_v=false; this.m_gradient_c=false; //--- Reset all "working" flags and variables this.m_mouse_state_flags=0; this.m_offset_x=0; this.m_offset_y=0; CGCnvElement::SetInteraction(false); //--- Create an animation object and add it to the list for storing such objects this.m_animations=new CAnimations(CGCnvElement::GetObject()); this.m_list_tmp.Add(this.m_animations); //--- Set the transparent color for the object background this.SetColorBackground(CLR_CANV_NULL); this.SetOpacity(0); //--- Set the default color and text opacity, as well as the absence of the object frame this.SetForeColor(CLR_DEF_FORE_COLOR); this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY); this.SetBorderStyle(FRAME_STYLE_NONE); //--- Set the default text parameters this.SetFont(DEF_FONT,DEF_FONT_SIZE); this.SetText(""); this.SetTextAnchor(FRAME_ANCHOR_LEFT_TOP); this.SetTextAlign(ANCHOR_LEFT_UPPER); //--- Set the default object parameters this.SetAutoSize(false,false); this.SetMarginAll(3); this.SetPaddingAll(0); this.SetEnabled(true); this.SetVisible(true,false); } //+------------------------------------------------------------------+
Métodos para limpar e pintar o fundo de um objeto:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CCommonBase::Erase(const color colour,const uchar opacity,const bool redraw=false) { //--- Fill the element having the specified color and the redrawing flag CGCnvElement::Erase(colour,opacity,redraw); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw) this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),255,this.BorderStyle()); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void CCommonBase::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { //--- Fill the element having the specified color array and the redrawing flag CGCnvElement::Erase(colors,opacity,vgradient,cycle,redraw); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw) this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),255,this.BorderStyle()); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element completely | //+------------------------------------------------------------------+ void CCommonBase::Erase(const bool redraw=false) { //--- Fully clear the element with the redrawing flag CGCnvElement::Erase(redraw); } //+------------------------------------------------------------------+
Todos estes métodos nos são familiares a partir de outros objetos de biblioteca. Todos eles estão comentados no código e não precisam de nenhuma explicação. A inicialização e limpeza para cada objeto são semelhantes, mas existem pequenas diferenças nos valores padrão das variáveis e nos princípios e sequência de pintar o fundo e desenhar alguns elementos sobre ele. Por esse motivo, cada classe pode ter sua própria implementação desses métodos.
Como transferimos alguns métodos desde a classe de objeto "Rótuo de texto" para a classe de objeto base dos controles padrão, precisamos corrigir a classe CLabel no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Label.mqh.
Em vez do arquivo WinFormBase.mqh", anexamos o arquivo de objeto base à classe e herdamos a classe dele:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "CommonBase.mqh" //+------------------------------------------------------------------+ //| Label object class of WForms controls | //+------------------------------------------------------------------+ class CLabel : public CCommonBase
No construtor de classe na lista de inicialização, escrevemos a inicialização da nova classe pai e definimos o novo tipo do objeto de biblioteca:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CLabel::CLabel(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CCommonBase(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_LABEL); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_LABEL); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetCoordX(x); this.SetCoordY(y); this.SetWidth(w); this.SetHeight(h); this.Initialize(); this.SetMargin(3,0,3,0); this.SetWidthInit(this.Width()); this.SetHeightInit(this.Height()); this.SetCoordXInit(x); this.SetCoordYInit(y); this.Redraw(false); } //+------------------------------------------------------------------+
O resto das mudanças na classe são menores e dizem respeito à remoção de métodos transferidos desta classe para o pai.
Elas podem ser encontradas nos arquivos anexados ao artigo.
Objeto WinForms CheckBox
O objeto CheckBox é uma caixa de seleção com um rótulo. O rótulo e a marca de seleção podem estar em nove posições em relação aos limites do objeto:
- Canto superior esquerdo
- Centro esquerdo
- Inferior esquerdo
- Centro inferior
- Inferior direito
- Centro direito
- Canto superior direito
- Centro superior
- Centro
Com várias combinações de localização da marca de seleção e de texto, é necessário ajustar a localização do texto para que, se possível, não sobreponha a marca de seleção. Um objeto pode ter sua moldura exibida.
Como o objeto tem texto e o tamanho do texto (e da caixa de seleção) é redimensionado automaticamente, faz sentido herdar do objeto "Rótulo de texto" e adicionar funcionalidade para exibir a marca de seleção em três combinações:
- Marcada
- Desmarcada
- Indefinida
Um estado indeterminado pode ser se o CheckBox exibe o estado de um grupo dos mesmos objetos, e alguns deles estão selecionados, enquanto outros não.
No diretório de biblioteca \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\, criamos um novo arquivo Label.mqh da classe CLabel. O arquivo de classe CLabel deve ser anexado ao arquivo e a classe deve ser herdada dele:
//+------------------------------------------------------------------+ //| CheckBox.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Label.mqh" //+------------------------------------------------------------------+ //| CheckBox object class of the WForms controls | //+------------------------------------------------------------------+ class CCheckBox : public CLabel { }
Na seção privada da classe, vamos declarar variáveis para armazenar as coordenadas e tamanhos do rótulo de texto e do sinalizador, métodos para alterar o tamanho do objeto e para trabalhar com as coordenadas do texto e do sinalizador:
class CCheckBox : public CLabel { private: int m_text_x; // Text X coordinate int m_text_y; // Text Y coordinate int m_check_x; // Checkbox X coordinate int m_check_y; // Checkbox Y coordinate int m_check_w; // Checkbox width int m_check_h; // Checkbox height //--- Set the element width and height automatically virtual void AutoSetWH(void); //--- Set X and Y coordinates(1) of the checkbox and (2) the text depending on the alignment type void SetCheckFlagCoords(int &x,int &y); void SetTextCoords(int &x,int &y); //--- Set the corrected text coordinates depending on the text alignment and checkbox void SetCorrectTextCoords(void); protected:
Nas seções protected e public, vamos declarar métodos para trabalhar com a classe:
protected: //--- Displays the checkbox for the specified state virtual void ShowControlFlag(const ENUM_CANV_ELEMENT_CHEK_STATE state); //--- (1) Set and (2) return the checkbox size on the element void SetCheckWidth(const int width) { this.m_check_w=(width<5 ? 5 : width); } void SetCheckHeight(const int height) { this.m_check_h=(height<5 ? 5 : height); } int CheckWidth(void) const { return this.m_check_w; } int CheckHeight(void) const { return this.m_check_h; } public: //--- Set the element (1) width and (2) height, virtual bool SetWidth(const int width) { return CGCnvElement::SetWidth(width>this.m_check_w ? width : this.m_check_w); } virtual bool SetHeight(const int height) { return CGCnvElement::SetHeight(height>this.m_check_h ? height : this.m_check_h); } //--- (1) Set and (2) return the element checkbox location angle (alignment type) void SetCheckAlign(const ENUM_ANCHOR_POINT anchor) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_ALIGN,anchor); } ENUM_ANCHOR_POINT CheckAlign(void) const { return (ENUM_ANCHOR_POINT)this.GetProperty(CANV_ELEMENT_PROP_CHECK_ALIGN); } //--- (1) Set and (2) return the checkbox status void SetChecked(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_CHECKED,flag); } bool Checked(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_CHECKED); } //--- (1) Set and (2) return the control status void SetCheckState(const ENUM_CANV_ELEMENT_CHEK_STATE state) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_STATE,state); } ENUM_CANV_ELEMENT_CHEK_STATE CheckState(void) const { return (ENUM_CANV_ELEMENT_CHEK_STATE)this.GetProperty(CANV_ELEMENT_PROP_CHECK_STATE);} //--- Redraw the object virtual void Redraw(bool redraw); //--- Constructor CCheckBox(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
Todos esses métodos estão nas descrições dos métodos e acho que são claros. Vamos considerar sua implementação.
No construtor de classe, definimos as propriedades para seus valores padrão:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CCheckBox::CCheckBox(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CLabel(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CHECKBOX); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CHECKBOX); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetCoordX(x); this.SetCoordY(y); this.SetCheckWidth(DEF_CHECK_SIZE); this.SetCheckHeight(DEF_CHECK_SIZE); this.SetWidth(w); this.SetHeight(h); this.Initialize(); this.SetWidthInit(this.Width()); this.SetHeightInit(this.Height()); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetTextAlign(ANCHOR_LEFT); this.m_text_x=0; this.m_text_y=0; this.m_check_x=0; this.m_check_y=0; this.Redraw(false); } //+------------------------------------------------------------------+
Método que redesenha o objeto:
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CCheckBox::Redraw(bool redraw) { //--- Fill the object with the background color having full transparency this.Erase(this.ColorBackground(),0,true); //--- Set corrected text coordinates relative to the checkbox this.SetCorrectTextCoords(); //--- Draw the text and checkbox within the set coordinates of the object and the binding point, and update the object this.Text(this.m_text_x,this.m_text_y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor()); this.ShowControlFlag(this.CheckState()); this.Update(redraw); } //+------------------------------------------------------------------+
Tudo é simples aqui: primeiro apagamos completamente o objeto, preenchendo-o com uma cor de fundo (transparente por padrão), depois calculamos as coordenadas de texto em relação à marca de seleção, desenhamos o texto e a marca de seleção e atualizamos o objeto.
Método que define as coordenadas X e Y da marca de seleção dependendo do tipo de alinhamento:
//+------------------------------------------------------------------+ //| Set X and Y checkbox coordinates | //| depending on the alignment type | //+------------------------------------------------------------------+ void CCheckBox::SetCheckFlagCoords(int &x,int &y) { //--- Depending on the checkbox location switch(this.CheckAlign()) { //--- The checkbox is located vertically from the left side of the object in the center case ANCHOR_LEFT : x=0; y=(this.Height()-this.CheckHeight())/2; break; //--- The checkbox is located in the lower left corner of the object case ANCHOR_LEFT_LOWER : x=0; y=this.Height()-this.CheckHeight()-1; break; //--- The checkbox is located in the center of the bottom edge of the object case ANCHOR_LOWER : x=(this.Width()-this.CheckWidth())/2; y=this.Height()-this.CheckHeight()-1; break; //--- The checkbox is located in the lower right corner of the object case ANCHOR_RIGHT_LOWER : x=this.Width()-this.CheckWidth()-1; y=this.Height()-this.CheckHeight()-1; break; //--- The checkbox is located vertically from the right side of the object in the center case ANCHOR_RIGHT : x=this.Width()-this.CheckWidth()-1; y=(this.Height()-this.CheckHeight())/2; break; //--- The checkbox is located in the upper right corner of the object case ANCHOR_RIGHT_UPPER : x=this.Width()-this.CheckWidth()-1; y=0; break; //--- The checkbox is located in the center of the upper edge of the object case ANCHOR_UPPER : x=(this.Width()-this.CheckWidth())/2; y=0; break; //--- The checkbox is located in the object center case ANCHOR_CENTER : x=(this.Width()-this.CheckWidth())/2; y=(this.Height()-this.CheckHeight())/2; break; //--- The checkbox is located in the upper left corner of the object //---ANCHOR_LEFT_UPPER default: x=0; y=0; break; } } //+------------------------------------------------------------------+
As variáveis são passadas ao método por referência, variáveis essas onde devem ser escritas as coordenadas calculadas da marca de seleção. Dependendo do método de alinhamento (a localização da marca de seleção dentro dos limites do objeto), calculamos suas coordenadas e as escrevemos nas variáveis passadas ao método.
Método que define as coordenadas X e Y do texto dependendo do tipo de alinhamento:
//+------------------------------------------------------------------+ //| Set X and Y text coordinates | //| depending on the alignment type | //+------------------------------------------------------------------+ void CCheckBox::SetTextCoords(int &x,int &y) { //--- Depending on the element text alignment type switch(this.TextAlign()) { //--- The text is displayed in the upper left corner of the object case ANCHOR_LEFT_UPPER : //--- Set the text binding point at the top left this.SetTextAnchor(FRAME_ANCHOR_LEFT_TOP); //--- Set the text binding point coordinate x=this.FrameWidthLeft(); y=this.FrameWidthTop(); break; //--- The text is drawn vertically from the left side of the object in the center case ANCHOR_LEFT : //--- Set the text binding point at the center left this.SetTextAnchor(FRAME_ANCHOR_LEFT_CENTER); //--- Set the text binding point coordinate x=this.FrameWidthLeft(); y=this.Height()/2; break; //--- The text is displayed in the lower left corner of the object case ANCHOR_LEFT_LOWER : //--- Set the text binding point at the bottom left this.SetTextAnchor(FRAME_ANCHOR_LEFT_BOTTOM); //--- Set the text binding point coordinate x=this.FrameWidthLeft(); y=this.Height()-this.FrameWidthBottom(); break; //--- The text is drawn at the center of the bottom edge of the object case ANCHOR_LOWER : //--- Set the text anchor point at the bottom center this.SetTextAnchor(FRAME_ANCHOR_CENTER_BOTTOM); //--- Set the text binding point coordinate x=this.Width()/2; y=this.Height()-this.FrameWidthBottom(); break; //--- The text is displayed in the lower right corner of the object case ANCHOR_RIGHT_LOWER : //--- Set the text binding point at the bottom right this.SetTextAnchor(FRAME_ANCHOR_RIGHT_BOTTOM); //--- Set the text binding point coordinate x=this.Width()-this.FrameWidthRight(); y=this.Height()-this.FrameWidthBottom(); break; //--- The text is drawn vertically from the right side of the object in the center case ANCHOR_RIGHT : //--- Set the text binding point at the center right this.SetTextAnchor(FRAME_ANCHOR_RIGHT_CENTER); //--- Set the text binding point coordinate x=this.Width()-this.FrameWidthRight(); y=this.Height()/2; break; //--- The text is displayed in the upper right corner of the object case ANCHOR_RIGHT_UPPER : //--- Set the text binding point at the top right this.SetTextAnchor(FRAME_ANCHOR_RIGHT_TOP); //--- Set the text binding point coordinate x=this.Width()-this.FrameWidthRight(); y=this.FrameWidthTop(); break; //--- The text is drawn at the center of the upper edge of the object case ANCHOR_UPPER : //--- Set the text binding point at the center top this.SetTextAnchor(FRAME_ANCHOR_CENTER_TOP); //--- Set the text binding point coordinate x=this.Width()/2; y=this.FrameWidthTop(); break; //--- The text is drawn at the object center //---ANCHOR_CENTER default: //--- Set the text binding point at the center this.SetTextAnchor(FRAME_ANCHOR_CENTER); //--- Set the text binding point coordinate x=this.Width()/2; y=this.Height()/2; break; } } //+------------------------------------------------------------------+
Aqui tudo é semelhante ao método de cálculo das coordenadas da marca de seleção, discutido acima, mas aqui o ponto de ancoragem do texto é alterado adicionalmente, para facilitar o cálculo das novas coordenadas do rótulo de texto.
Método que define as coordenadas de texto corretas dependendo do alinhamento do texto e da marca de seleção:
//+------------------------------------------------------------------+ //| Set valid text coordinates depending on | //| text alignment and checkbox | //+------------------------------------------------------------------+ void CCheckBox::SetCorrectTextCoords(void) { //--- Set checkbox and text coordinates depending on their alignment method this.SetCheckFlagCoords(this.m_check_x,this.m_check_y); this.SetTextCoords(this.m_text_x,this.m_text_y); //--- Get the text size int text_w=0, text_h=0; this.TextSize(this.Text(),text_w,text_h); //--- Depending on the checkbox location within the object boundaries switch(this.CheckAlign()) { //--- The checkbox is located in the upper left corner of the object //--- The checkbox is located vertically from the left side of the object in the center //--- The checkbox is located in the lower left corner of the object case ANCHOR_LEFT_UPPER : case ANCHOR_LEFT : case ANCHOR_LEFT_LOWER : //--- If the text is left-aligned, set the text X coordinate with the checkbox width + 2 pixels if(this.TextAlign()==ANCHOR_LEFT_UPPER || this.TextAlign()==ANCHOR_LEFT || this.TextAlign()==ANCHOR_LEFT_LOWER) this.m_text_x=this.CheckWidth()+2; break; //--- The checkbox is located in the upper right corner of the object //--- The checkbox is located vertically from the right side of the object in the center //--- The checkbox is located in the lower right corner of the object case ANCHOR_RIGHT_UPPER : case ANCHOR_RIGHT : case ANCHOR_RIGHT_LOWER : //--- If the text is right-aligned, set the text X coordinate with the checkbox width + 2 pixels if(this.TextAlign()==ANCHOR_RIGHT_UPPER || this.TextAlign()==ANCHOR_RIGHT || this.TextAlign()==ANCHOR_RIGHT_LOWER) this.m_text_x=this.Width()-this.CheckWidth()-2; break; //--- The checkbox is located in the center of the bottom edge of the object case ANCHOR_LOWER : //--- If the text is bottom-aligned, set the text X coordinate with the checkbox height if(this.TextAlign()==ANCHOR_LEFT_LOWER || this.TextAlign()==ANCHOR_LOWER || this.TextAlign()==ANCHOR_RIGHT_LOWER) this.m_text_y=this.m_check_y; break; //--- The checkbox is located in the center of the upper edge of the object case ANCHOR_UPPER : //--- If the text is top-aligned, set the text X coordinate with the checkbox height if(this.TextAlign()==ANCHOR_LEFT_UPPER || this.TextAlign()==ANCHOR_UPPER || this.TextAlign()==ANCHOR_RIGHT_UPPER) this.m_text_y=this.m_check_h; break; //--- The checkbox is located in the object center //---ANCHOR_CENTER default: break; } } //+------------------------------------------------------------------+
Aqui, dependendo da posição relativa da marca de seleção e do rótulo de texto, novas coordenadas de texto são definidas para que não se sobreponham ao ícone da marca de seleção. Embora não seja possível fazer isso para todas as situações, todas as proporções básicas são levadas em conta aqui. Outras colisões dos dois componentes do objeto podem ser resolvidas alterando o tamanho do próprio objeto ou definindo a relação correta entre as posições do texto e da marca de seleção.
Método que exibe a caixa de seleção para o estado especificado:
//+------------------------------------------------------------------+ //| Display the checkbox for the specified state | //+------------------------------------------------------------------+ void CCheckBox::ShowControlFlag(const ENUM_CANV_ELEMENT_CHEK_STATE state) { //--- Draw the rectangle of checkbox boundaries this.DrawRectangle(this.m_check_x,this.m_check_y,this.m_check_x+this.CheckWidth(),this.m_check_y+this.CheckHeight(),this.ColorFrame()); //--- Create X and Y coordinate arrays for drawing a polyline int array_x[]={m_check_x+2,m_check_x+m_check_w/2-1,m_check_x+m_check_w-2}; int array_y[]={m_check_y+m_check_h/2,m_check_y+m_check_h-3,m_check_y+3}; //--- Depending on the checkbox status passed to the method switch(state) { //--- Set checkbox case CANV_ELEMENT_CHEK_STATE_CHECKED : //--- Draw a polyline in the form of a checkmark inside the checkbox boundaries this.DrawPolylineAA(array_x,array_y,ColorFrame()); break; //--- Unchecked checkbox case CANV_ELEMENT_CHEK_STATE_INDETERMINATE : //--- Draw a filled rectangle inside the checkbox boundaries this.DrawRectangleFill(m_check_x+3,m_check_y+3,m_check_x+m_check_w-3,m_check_y+m_check_h-3,ColorFrame()); break; //--- Undefined state default: break; } } //+------------------------------------------------------------------+
Dependendo do estado do sinalizador passado para o método, desenhamos, dentro das bordas da caixa de seleção, uma marca de seleção ou um retângulo preenchido. As coordenadas para desenhar a polilinha são calculadas para que as proporções da marca de seleção permaneçam sempre as mesmas, independentemente do tamanho das bordas da caixa de seleção. Um retângulo é desenhado para um estado indeterminado. Até agora, existe apenas um tipo de ícones assim: uma marca de seleção e um retângulo com uma cor. No futuro, introduziremos vários outros tipos de exibição dos estados da caixa de seleção.
Método que define automaticamente a largura e a altura do elemento:
//+------------------------------------------------------------------+ //| Set the element width and height automatically | //+------------------------------------------------------------------+ void CCheckBox::AutoSetWH(void) { //--- Define the variables for receiving the label width and height int w=0, h=0; //--- Get the width and height depending on the object text CGCnvElement::TextSize(this.Text()!="" && this.Text()!=NULL ? this.Text() : " ",w,h); //--- Add the Margin values of the object on the left and right to the resulting width, as well as the checkbox size w+=(this.MarginLeft()+this.MarginRight()+this.CheckWidth()); //--- If the width is equal to the size of the checkbox, set it to three pixels + checkbox size if(w==this.CheckWidth()) w=this.CheckWidth()+3; //--- Add the Margin values of the object on the top and bottom to the resulting height h+=(this.MarginTop()+this.MarginBottom()); //--- If failed to get the height, set it as "font size" * ratio if(h==0) h=(int)ceil(FontSize()*1.625); //--- If the height is ultimately less than the size of the checkbox, set the height equal to the height of the checkbox if(h<this.CheckHeight()) h=this.CheckHeight(); //--- Set the object width and height from the received values this.SetWidth(w); this.SetHeight(h); } //+------------------------------------------------------------------+
O método virtual, ao contrário do método da classe pai, leva em consideração o tamanho da caixa de seleção quando o objeto é redimensionado.
Para que possamos criar objetos desta classe a partir da classe contêiner e anexar a ela, iremos incluir o arquivo desta classe no arquivo da classe CContainer (vamos abrir o arquivo da classe contêiner e anexamos esta classe a ele):
//+------------------------------------------------------------------+ //| Container.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\WForms\WinFormBase.mqh" #include "..\..\WForms\Common Controls\CheckBox.mqh" //+------------------------------------------------------------------+ //| Class of the base container object of WForms controls | //+------------------------------------------------------------------+ class CContainer : public CWinFormBase
Para tornar todas as novas classes visíveis na classe coleção de elementos gráficos, vamos anexar o arquivo de classe CGroupBox à classe coleção no arquivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh:
//+------------------------------------------------------------------+ //| GraphElementsCollection.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "ListObj.mqh" #include "..\Services\Select.mqh" #include "..\Objects\Graph\WForms\Containers\GroupBox.mqh" #include "..\Objects\Graph\WForms\Containers\Panel.mqh"
Todas as outras classes estão anexadas a GroupBox.mqh e estarão visíveis aqui.
Vamos escrever um método que cria um objeto gráfico WinForms GroupBox no gráfico e na subjanela especificados:
//--- Create the 'GroupBox' WinForms graphical object on canvas on the specified chart and subwindow int CreateGroupBox(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, const string text, const color text_color=clrNONE, const color frame_color=WRONG_VALUE, const int frame_width=WRONG_VALUE, ENUM_FRAME_STYLE frame_style=FRAME_STYLE_SIMPLE, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CGroupBox *obj=new CGroupBox(chart_id,subwindow,name,x,y,w,h); ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id); if(res==ADD_OBJ_RET_CODE_ERROR) return WRONG_VALUE; obj.SetID(id); obj.SetActive(true); obj.SetMovable(false); obj.SetText(text); obj.SetForeColor(text_color==clrNONE ? CLR_DEF_FORE_COLOR : text_color); obj.SetColorBackground(CLR_CANV_NULL); obj.SetColorFrame(frame_color==clrNONE ? CLR_DEF_FRAME_GBOX_COLOR : frame_color); obj.SetBorderStyle(frame_style!=FRAME_STYLE_NONE ? frame_style : FRAME_STYLE_SIMPLE); obj.SetOpacity(0,false); obj.SetFrameWidthAll(frame_width==WRONG_VALUE ? 1 : frame_width); //--- Draw the shadow drawing flag obj.SetShadow(false); if(redraw) obj.Erase(CLR_CANV_NULL,0,redraw); obj.SetActiveAreaShift(obj.FrameWidthLeft(),obj.FrameWidthBottom(),obj.FrameWidthRight(),obj.FrameWidthTop()); obj.Done(); return obj.ID(); } //--- Create graphical object WinForms Panel object on canvas on a specified chart and subwindow
Os parâmetros necessários para criar o objeto são passados para o método. Após a criação bem-sucedida do objeto, esses parâmetros são definidos para as propriedades do objeto. O método retorna o ID do objeto obtido quando criado.
Para que possamos ter acesso à criação de novos objetos a partir de nossos programas, no arquivo \MQL5\Include\DoEasy\Engine.mqh da classe do objeto da biblioteca principal, escreveremos métodos para criar e receber novos objetos.
Métodos que retornam um objeto WForm GroupBox:
//--- Return the WForm Panel object by object ID CPanel *GetWFPanel(const int element_id) { CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the GroupBox WForm object by object name on the current chart CGroupBox *GetWFGroupBox(const string name) { string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the GroupBox WForm object by chart ID and object name CGroupBox *GetWFGroupBox(const long chart_id,const string name) { string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the WForm GroupBox object by object ID CGroupBox *GetWFGroupBox(const int element_id) { CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Create the WinForm Element object
Em cada um dos métodos, obtemos uma lista de objetos do tipo GroupBox e filtramos a lista resultante de acordo com os parâmetros passados ao método. Se o objeto procurado estiver na lista, será o único da lista, e um ponteiro para ele é devolvido pelo método. Se o objeto não for encontrado na lista, o método retornará NULL.
Vamos adicionar três métodos para criar um objeto WinForm GroupBox:
//--- Create the 'GroupBox' WinForm object CGroupBox *CreateWFGroupBox(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, const string text, const color text_color=clrNONE, const color frame_color=clrNONE, const int frame_width=WRONG_VALUE, const ENUM_FRAME_STYLE frame_style=FRAME_STYLE_SIMPLE, const bool redraw=false) { int obj_id=this.m_graph_objects.CreateGroupBox(chart_id,subwindow,name,x,y,w,h,text,text_color,frame_color,frame_width,frame_style,redraw); return this.GetWFGroupBox(obj_id); } //--- Create the Groupbox WinForm object in the specified subwindow on the current chart CGroupBox *CreateWFGroupBox(const int subwindow, const string name, const int x, const int y, const int w, const int h, const string text, const color text_color=clrNONE, const color frame_color=clrNONE, const int frame_width=WRONG_VALUE, const ENUM_FRAME_STYLE frame_style=FRAME_STYLE_SIMPLE, const bool redraw=false) { return this.CreateWFGroupBox(::ChartID(),subwindow,name,x,y,w,h,text,text_color,frame_color,frame_width,frame_style,redraw); } //--- Create the GroupBox WinForm object in the main window of the current chart CGroupBox *CreateWFGroupBox(const string name, const int x, const int y, const int w, const int h, const string text, const color text_color=clrNONE, const color frame_color=clrNONE, const int frame_width=WRONG_VALUE, const ENUM_FRAME_STYLE frame_style=FRAME_STYLE_SIMPLE, const bool redraw=false) { return this.CreateWFGroupBox(::ChartID(),0,name,x,y,w,h,text,text_color,frame_color,frame_width,frame_style,redraw); } //--- Fill in the array with IDs of the charts opened in the terminal
O primeiro dos métodos cria um objeto GroupBox no gráfico, especificado por ID, na subjanela especificada com os parâmetros passados para o método. Um método é chamado para criar tal objeto desde a classe coleção discutida acima, e um ponteiro para o objeto criado é retornado obtido pelo ID do objeto recém-criado. Todos os outros métodos chamam esse (primeiro) método, especificando explicitamente o identificador do gráfico atual e a janela do gráfico principal na linha de parâmetros.
Isso conclui a criação de novos objetos de biblioteca.
Teste
Para testar, vamos pegar o Expert Advisor do artigo anterior e salvá-lo na nova pasta \MQL5\Experts\TestDoEasy\Part108\ com o novo nome TstDE108.mq5.
O que vamos testar? Vamos criar um objeto-painel e colocar objetos nele: dois objetos-painéis com objetos-rótulos de texto, abaixo desses painéis, criaremos e encaixaremos um objeto GroupBox. E, como hoje não foi possível criar e anexar um objeto CheckBox a partir de objetos contêiner, vamos criar tal objeto separadamente a partir do painel principal, apenas em um gráfico para ver o que ganhamos com isso. Definimos as configurações deste objeto nas configurações do EA, para que seja possível alterá-las rápido e ver o resultado.
Vamos criar duas novas enumerações para as versões em inglês e russo da compilação e novos parâmetros de entrada no escopo global:
//--- enumerations by compilation language #ifdef COMPILE_EN enum ENUM_AUTO_SIZE_MODE { AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW, // Grow AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK // Grow and Shrink }; enum ENUM_BORDER_STYLE { BORDER_STYLE_NONE=FRAME_STYLE_NONE, // None BORDER_STYLE_SIMPLE=FRAME_STYLE_SIMPLE, // Simple BORDER_STYLE_FLAT=FRAME_STYLE_FLAT, // Flat BORDER_STYLE_BEVEL=FRAME_STYLE_BEVEL, // Embossed (bevel) BORDER_STYLE_STAMP=FRAME_STYLE_STAMP, // Embossed (stamp) }; enum ENUM_CHEK_STATE { CHEK_STATE_UNCHECKED=CANV_ELEMENT_CHEK_STATE_UNCHECKED, // Unchecked CHEK_STATE_CHECKED=CANV_ELEMENT_CHEK_STATE_CHECKED, // Checked CHEK_STATE_INDETERMINATE=CANV_ELEMENT_CHEK_STATE_INDETERMINATE, // Indeterminate }; #else enum ENUM_AUTO_SIZE_MODE { AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW, // Increase only AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK // Increase and decrease }; enum ENUM_BORDER_STYLE { BORDER_STYLE_NONE=FRAME_STYLE_NONE, // No frame BORDER_STYLE_SIMPLE=FRAME_STYLE_SIMPLE, // Simple frame BORDER_STYLE_FLAT=FRAME_STYLE_FLAT, // Flat frame BORDER_STYLE_BEVEL=FRAME_STYLE_BEVEL, // Embossed (convex) BORDER_STYLE_STAMP=FRAME_STYLE_STAMP, // Embossed (concave) }; enum ENUM_CHEK_STATE { CHEK_STATE_UNCHECKED=CANV_ELEMENT_CHEK_STATE_UNCHECKED, // Unchecked CHEK_STATE_CHECKED=CANV_ELEMENT_CHEK_STATE_CHECKED, // Checked CHEK_STATE_INDETERMINATE=CANV_ELEMENT_CHEK_STATE_INDETERMINATE, // Undefined }; #endif //--- input parameters sinput bool InpMovable = true; // Movable forms flag sinput ENUM_INPUT_YES_NO InpAutoSize = INPUT_YES; // Autosize sinput ENUM_AUTO_SIZE_MODE InpAutoSizeMode = AUTO_SIZE_MODE_GROW; // Autosize mode sinput ENUM_BORDER_STYLE InpFrameStyle = BORDER_STYLE_NONE; // Label border style sinput ENUM_ANCHOR_POINT InpTextAlign = ANCHOR_LEFT_UPPER; // Label text align sinput ENUM_ANCHOR_POINT InpCheckAlign = ANCHOR_LEFT_UPPER; // Check flag align sinput ENUM_ANCHOR_POINT InpCheckTextAlign = ANCHOR_LEFT_UPPER; // Check label text align sinput ENUM_CHEK_STATE InpCheckState = CHEK_STATE_UNCHECKED; // Check flag state sinput ENUM_INPUT_YES_NO InpCheckAutoSize = INPUT_YES; // CheckBox autosize sinput ENUM_BORDER_STYLE InpCheckFrameStyle= BORDER_STYLE_NONE; // CheckBox border style //--- global variables CEngine engine; color array_clr[]; //+------------------------------------------------------------------+
No manipulador OnInit(), escreveremos o seguinte bloco de código para criar objetos GroupBox e CheckBox e faremos pequenas alterações para modificar o número de objetos criados e suas coordenadas:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set EA global variables ArrayResize(array_clr,2); // Array of gradient filling colors array_clr[0]=C'26,100,128'; // Original ≈Dark-azure color array_clr[1]=C'35,133,169'; // Lightened original color //--- Create the array with the current symbol and set it to be used in the library string array[1]={Symbol()}; engine.SetUsedSymbols(array); //--- Create the timeseries object for the current symbol and period, and show its description in the journal engine.SeriesCreate(Symbol(),Period()); engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions //--- Create WinForms Panel object CPanel *pnl=NULL; pnl=engine.CreateWFPanel("WFPanel",50,50,230,150,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false); if(pnl!=NULL) { //--- Set Padding to 4 pnl.SetPaddingAll(4); //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs pnl.SetMovable(InpMovable); pnl.SetAutoSize(InpAutoSize,false); pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false); //--- In the loop, create 2 bound panel objects CPanel *obj=NULL; for(int i=0;i<2;i++) { //--- create the panel object with calculated coordinates, width of 90 and height of 40 CPanel *prev=pnl.GetElement(i-1); int xb=0, yb=0; int x=(prev==NULL ? xb : xb+prev.Width()+20); int y=0; if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_PANEL,pnl,x,y,90,40,C'0xCD,0xDA,0xD7',200,true,false)) { obj=pnl.GetElement(i); if(obj==NULL) continue; obj.SetFrameWidthAll(3); obj.SetBorderStyle(FRAME_STYLE_BEVEL); obj.SetColorBackground(obj.ChangeColorLightness(obj.ColorBackground(),4*i)); obj.SetForeColor(clrRed); //--- Calculate the width and height of the future text label object int w=obj.Width()-obj.FrameWidthLeft()-obj.FrameWidthRight()-4; int h=obj.Height()-obj.FrameWidthTop()-obj.FrameWidthBottom()-4; //--- Create a text label object obj.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LABEL,obj,2,2,w,h,clrNONE,255,false,false); //--- Get the pointer to a newly created object CLabel *lbl=obj.GetElement(0); if(lbl!=NULL) { //--- If the object has an even or zero index in the list, set the default text color for it if(i % 2==0) lbl.SetForeColor(CLR_DEF_FORE_COLOR); //--- If the object index in the list is odd, set the object opacity to 127 else lbl.SetForeColorOpacity(127); //--- Set the font Black width type and //--- specify the text alignment from the EA settings lbl.SetFontBoldType(FW_TYPE_BLACK); lbl.SetTextAlign(InpTextAlign); //--- For an object with an even or zero index, specify the Bid price for the text, otherwise - the Ask price of the symbol lbl.SetText(GetPrice(i % 2==0 ? SYMBOL_BID : SYMBOL_ASK)); //--- Set the frame width and type for a text label and update the modified object lbl.SetFrameWidthAll(1); lbl.SetBorderStyle((ENUM_FRAME_STYLE)InpFrameStyle); lbl.Update(true); } } } //--- Create the 'GroupBox' WinForms object CGroupBox *gbox=NULL; //--- GroupBox width is a width of the main panel underlay, //--- while the Y coordinate is an indent from attached panels by 6 pixels int w=pnl.GetUnderlay().Width(); int y=obj.BottomEdgeRelative()+6; //--- If the attached GroupBox object is created if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,pnl,0,y,w,100,C'0x91,0xAA,0xAE',0,true,false)) { //--- get the pointer to the GroupBox object by its index in the list of bound objects gbox=pnl.GetElement(2); if(gbox!=NULL) { //--- get the pointer to the GroupBox object by its index in the list of bound objects //--- while the text color is the background color of the last attached panel darkened by 1 gbox.SetBorderStyle(FRAME_STYLE_STAMP); gbox.SetColorFrame(pnl.ColorBackground()); gbox.SetForeColor(gbox.ChangeColorLightness(obj.ColorBackground(),-1)); } } //--- Create an independent CheckBox object separately from the main panel directly on the chart CCheckBox *cbox=new CCheckBox(ChartID(),0,"CBox",pnl.RightEdge()+20,pnl.CoordY()+10,100,60); //--- If the object has been created if(cbox!=NULL) { //--- Add the newly created object to the list of library objects temporarily to avoid memory leaks ListStorage.Add(cbox); //--- Set object parameters from EA inputs cbox.SetAutoSize((bool)InpCheckAutoSize,false); cbox.SetCheckAlign(InpCheckAlign); cbox.SetTextAlign(InpCheckTextAlign); //--- Set the displayed text, frame style and checkbox status cbox.SetText("CheckBox"); cbox.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle); cbox.SetCheckState((ENUM_CANV_ELEMENT_CHEK_STATE)InpCheckState); //--- Redraw the object cbox.Redraw(true); } //--- Redraw all objects according to their hierarchy pnl.Redraw(true); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
No manipulador OnChartEvent(), vamos alterar o tipo de objeto para evitar o erro de conversão do tipo de objeto:
//--- If a key is pressed if(id==CHARTEVENT_KEYDOWN) { CPanel *panel=engine.GetWFPanel(0); if(panel!=NULL && (lparam==KEY_UP || lparam==KEY_DOWN || lparam==KEY_LEFT || lparam==KEY_RIGHT || lparam==KEY_FILL || lparam==KEY_ORIGIN || lparam==KEY_INDEX)) { for(int i=0;i<panel.ElementsTotal();i++) { CWinFormBase *obj=panel.GetElement(i); if(obj!=NULL) { if(lparam==KEY_UP) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_TOP,false); else if(lparam==KEY_DOWN) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_BOTTOM,false); else if(lparam==KEY_LEFT) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_LEFT,false); else if(lparam==KEY_RIGHT) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_RIGHT,false); else if(lparam==KEY_FILL) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_FILL,false); else if(lparam==KEY_ORIGIN) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); else if(lparam==KEY_INDEX) { obj.SetDockMode((ENUM_CANV_ELEMENT_DOCK_MODE)i,true); Sleep(i>0 ? 500 : 0); } } } panel.Redraw(true); }
Anteriormente, estávamos obtendo o tipo específico do objeto CPanel, e não a classe base dos objetos da biblioteca WinForms. Não houve erro porque apenas objetos desse tipo (painel) participaram do teste.
Compilamos o Expert Advisor e o iniciamos no gráfico:
Como podemos ver, o posicionamento dos componentes do objeto CheckBox funciona corretamente, o objeto GroupBox é criado no painel e anexado a ele.
É claro, até agora todos esses objetos são estáticos e não têm nenhuma funcionalidade para interagir com o mouse, mas faremos isso mais tarde, para muitos objetos WinForms de uma só vez.
O que virá a seguir?
No próximo artigo, continuaremos desenvolvendo objetos WinForms.
*Artigos desta série:
DoEasy. Controles (Parte 1): Primeiros passos
DoEasy. Controles (Parte 2): Continuamos trabalhando na classe CPanel
DoEasy. Controles (Parte 3): Criando controles vinculados
DoEasy. Controles (Parte 4): Controle "Painel", parâmetros Padding e Dock
DoEasy. Controles (Parte 5): Objeto base WinForms, controle Painel, parâmetro AutoSize
DoEasy. Controles (Parte 6): Controle "Painel", redimensionamento automático do contêiner para adequá-lo ao seu conteúdo
DoEasy. Controles (Parte 7): Controle "Rótulo de texto"
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/11075
![Desenvolvendo um EA de negociação do zero (Parte 29): Plataforma falante](https://c.mql5.com/2/48/development__5.png)
![Redes neurais de maneira fácil (Parte 17): Redução de dimensionalidade](https://c.mql5.com/2/48/Neural_networks_made_easy_017.png)
![Metamodelos em aprendizado de máquina e negociação: Tempo original das ordens de negociação](https://c.mql5.com/2/42/yandex_catboost__4.png)
![Negociação usando uma grade com ordens limitadas no MOEX](https://c.mql5.com/2/47/moex-trading.png)
![MQL5 - Linguagem para estratégias de negociação inseridas no terminal do cliente MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso