![DoEasy. Controles (Parte 2): Continuamos trabalhando na classe CPanel](https://c.mql5.com/2/49/doeasy_002_600x314.jpg)
DoEasy. Controles (Parte 2): Continuamos trabalhando na classe CPanel
Conteúdo
Ideia
Com o artigo anterior, iniciamos um tópico extenso sobre a criação de controles em MQL5 no estilo do Windows Forms. Mas ainda temos alguns bugs e falhas que têm sido "arrastados" desde que começamos a trabalhar nos objetos gráficos, o que o leitor atento já deve ter notado. Por exemplo, nunca testamos como os objetos gráficos se comportam ao alternar os períodos gráficos. Eles simplesmente não são exibidos no gráfico, enquanto mensagens aparecem no log informando que esses objetos já foram criados. Isso indica que sua criação e desenho não são realizados. Além disso, por exemplo, apenas objetos-forma podem interagir com o mouse. E o objeto elemento gráfico, que é o pai da forma, não possui funcionalidade para trabalhar com o mouse, o que é correto em princípio, porque este é o objeto gráfico mínimo da biblioteca que pode ser usado para qualquer construção gráfica. Porém, se precisarmos de interação, o objeto gráfico mínimo deverá ser um objeto-forma, se bem que seu sucessor seja o controle "Panel", que começamos a desenvolver no último artigo e que também não responde ao mouse. Mas, repito, o objeto gráfico mínimo deverá sê-lo. E isto é, digamos, o custo de adicionar objetos e sua funcionalidade à biblioteca de forma consistente.
Hoje vamos corrigir alguns bugs e erros e continuar adicionando funcionalidade ao objeto de controle "Painel". Isto último irá se tratar de métodos para definir os parâmetros da fonte, que é usada por padrão para todos os objetos de texto do painel, objetos esses que, por sua vez, podem ser localizados nele no futuro. Ou seja, por exemplo, digamos que temos um determinado objeto da classe CPanel, que permite a outros controles serem anexados a ele mesmo. Bem, se o controle anexado tiver algum texto, por padrão, as configurações de fonte serão herdadas do painel ao qual estiver anexado. Por sua vez, o painel, como um elemento gráfico independente, também será dotado da capacidade de desenhar textos dentro de si (como todos os elementos gráficos da biblioteca), e esses textos também usarão essas configurações de fonte por padrão. No entanto, ao desenhar um novo texto em um elemento gráfico, logo antes de ser exibido, nada impede definir outros parâmetros para a fonte mostrada, isto é, o texto será exibido com esses parâmetros especificados explicitamente.
Além disso, qualquer painel, como elemento de controle, deve ser capaz de enquadrar o painel com uma moldura, bem como de controlar os parâmetros dessa moldura. Vamos tornar possível desenhar uma moldura com valores padrão e com ou sem parâmetros explicitamente especificados.
Modificando as classes da biblioteca
Para poder definir rapidamente diferentes estilos de exibição de elementos gráficos, criamos o arquivo \MQL5\Include\DoEasy\GraphINI.mqh.
Ele contém (e posteriormente você pode adicionar os seus próprios seguindo o exemplo dos existentes) parâmetros de diferentes esquemas de cores, tipos e estilos de exibição de elementos gráficos.
Para mostrar os parâmetros de estilo da forma, vamos alterar ligeiramente a ordem dos índices e os valores de estilo correspondentes.
Vamos apenas mover os parâmetros que são os primeiros da lista para o final e assinar seu atributo:
//+------------------------------------------------------------------+ //| List of form style parameter indices | //+------------------------------------------------------------------+ enum ENUM_FORM_STYLE_PARAMS { //--- CForm FORM_STYLE_FRAME_SHADOW_OPACITY, // Shadow opacity FORM_STYLE_FRAME_SHADOW_BLUR, // Shadow blur FORM_STYLE_DARKENING_COLOR_FOR_SHADOW, // Form shadow color darkening FORM_STYLE_FRAME_SHADOW_X_SHIFT, // Shadow X axis shift FORM_STYLE_FRAME_SHADOW_Y_SHIFT, // Shadow Y axis shift //--- CPanel FORM_STYLE_FRAME_WIDTH_LEFT, // Panel frame width to the left FORM_STYLE_FRAME_WIDTH_RIGHT, // Panel frame width to the right FORM_STYLE_FRAME_WIDTH_TOP, // Panel frame width on top FORM_STYLE_FRAME_WIDTH_BOTTOM, // Panel frame width below }; #define TOTAL_FORM_STYLE_PARAMS (9) // Number of form style parameters //+------------------------------------------------------------------+ //| Array containing form style parameters | //+------------------------------------------------------------------+ int array_form_style[TOTAL_FORM_STYLES][TOTAL_FORM_STYLE_PARAMS]= { //--- "Flat form" style parameters { //--- CForm 80, // Shadow opacity 4, // Shadow blur 80, // Form shadow color darkening 2, // Shadow X axis shift 2, // Shadow Y axis shift //--- CPanel 3, // Panel frame width to the left 3, // Panel frame width to the right 3, // Panel frame width on top 3, // Panel frame width below }, //--- "Embossed form" style parameters { //--- CForm 80, // Shadow opacity 4, // Shadow blur 80, // Form shadow color darkening 2, // Shadow X axis shift 2, // Shadow Y axis shift //--- CPanel 3, // Panel frame width to the left 3, // Panel frame width to the right 3, // Panel frame width on top 3, // Panel frame width below }, }; //+------------------------------------------------------------------+
Naturalmente, isto não é de modo algum um ajuste crítico, mas parâmetros corretamente estruturados os tornarão mais fáceis de ler mais tarde.
No arquivo \MQL5\Include\DoEasy\Data.mqh, escrevemos os índices das novas mensagens:
//--- CGraphElementsCollection MSG_GRAPH_ELM_COLLECTION_ERR_OBJ_ALREADY_EXISTS, // Error. A chart control object already exists with chart id MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_CREATE_CTRL_OBJ,// Failed to create chart control object with chart ID MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_GET_CTRL_OBJ, // Failed to get chart control object with chart ID MSG_GRAPH_ELM_COLLECTION_ERR_GR_OBJ_ALREADY_EXISTS,// Such graphical object already exists: MSG_GRAPH_ELM_COLLECTION_ERR_EMPTY_OBJECT, // Error! Empty object MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_GET_ELEMENT, // Failed to get a graphical element from the list
e os textos das mensagens correspondentes aos índices recém-adicionados:
//--- CGraphElementsCollection {"Ошибка. Уже существует объект управления чартами с идентификатором чарта ","Error. A chart control object already exists with chart id "}, {"Не удалось создать объект управления чартами с идентификатором чарта ","Failed to create chart control object with chart id "}, {"Не удалось получить объект управления чартами с идентификатором чарта ","Failed to get chart control object with chart id "}, {"Такой графический объект уже существует: ","Such a graphic object already exists: "}, {"Ошибка! Пустой объект","Error! Empty object"}, {"Не удалось получить графический элемент из списка","Failed to get graphic element from list"},
Existe um conjunto de sinalizadores para controlar o estilo da fonte do texto. Mas para poder selecionar o estilo de fonte necessário e os diferentes tipos de espessura de fonte da lista, devemos dispor de uma enumeração. Então, se a enumeração for passada para qualquer método como um estilo de fonte ou como um tipo de espessura, ao escrever o código, poderemos selecionar o estilo e o tipo na lista suspensa, o que é muito mais conveniente do que lembrar os nomes desses sinalizadores.
Além disso, nosso painel pode ser exibido em sua maior parte enquadrando o objeto com uma moldura. Por isso, precisamos ter uma macro-substituição especificando a espessura da moldura do painel por padrão.
No arquivo \MQL5\Include\DoEasy\Defines.mqh, vamos criar uma macro-substituição que especifica a largura de todos os lados da moldura do painel por padrão:
//--- 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_FORE_COLOR (C'0x2D,0x43,0x48') // Default color for texts of objects on canvas #define DEF_FONT ("Calibri") // Default font #define DEF_FONT_SIZE (8) // Default font 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
e enumeração de estilos de fonte e tipos de espessura no final da lista de arquivos:
//+------------------------------------------------------------------+ //| Font style list | //+------------------------------------------------------------------+ enum ENUM_FONT_STYLE { FONT_STYLE_NORMAL=0, // Normal FONT_STYLE_ITALIC=FONT_ITALIC, // Italic FONT_STYLE_UNDERLINE=FONT_UNDERLINE, // Underline FONT_STYLE_STRIKEOUT=FONT_STRIKEOUT // Strikeout }; //+------------------------------------------------------------------+ //| FOnt width type list | //+------------------------------------------------------------------+ enum ENUM_FW_TYPE { FW_TYPE_DONTCARE=FW_DONTCARE, FW_TYPE_THIN=FW_THIN, FW_TYPE_EXTRALIGHT=FW_EXTRALIGHT, FW_TYPE_ULTRALIGHT=FW_ULTRALIGHT, FW_TYPE_LIGHT=FW_LIGHT, FW_TYPE_NORMAL=FW_NORMAL, FW_TYPE_REGULAR=FW_REGULAR, FW_TYPE_MEDIUM=FW_MEDIUM, FW_TYPE_SEMIBOLD=FW_SEMIBOLD, FW_TYPE_DEMIBOLD=FW_DEMIBOLD, FW_TYPE_BOLD=FW_BOLD, FW_TYPE_EXTRABOLD=FW_EXTRABOLD, FW_TYPE_ULTRABOLD=FW_ULTRABOLD, FW_TYPE_HEAVY=FW_HEAVY, FW_TYPE_BLACK=FW_BLACK }; //+------------------------------------------------------------------+
Como se pode ver, aqui para cada constante de enumeração nós apenas definimos o valor do sinalizador correspondente consoante o nome. Mas quanto ao estilo de fonte, introduzimos uma nova constante para ter uma fonte "normal", isto é, não itálica, não sublinhada e não tachada. E este valor é igual a zero, o que irá redefinir os sinalizadores adicionais de estilo de contorno definidos anteriormente para a fonte.
Na classe do objeto-forma, temos uma classe de animação, para elementos gráficos sobre a tela, e um objeto de sombra de forma. Ambos os objetos são criados com o operador new, e, quando concluídos, são excluídos no destruidor da classe. Assim, esses objetos são sempre rastreados e removidos a tempo.
Mas o problema é ao herdar da classe do objeto-forma. A classe do objeto-painel herda do objeto-forma, e descobri que os objetos acima não são excluídos, causando um vazamento de memória. Você pode verificar isso executando o Expert Advisor do artigo anterior, que, ao ser removido do gráfico, faz o log mostrar mensagens sobre a perda de 4 objetos e o vazamento de 512 bytes de memória:
4 undeleted objects left 1 object of type CAnimations left 3 objects of type CArrayObj left 512 bytes of leaked memory
Para ser sincero, há muito tempo não consigo encontrar o motivo desse erro na eliminação de objetos e... Portanto, atribuiremos tudo isso ao subsistema do terminal.
Para isso, basta criar um objeto da classe CArrayObj e colocar nele os objetos criados na classe CForm. Ao encerrar o trabalho, o próprio terminal limpará a memória, varrendo todos os objetos desta lista.
Vamos abrir o arquivo \MQL5\Include\DoEasy\Objects\Graph\Form.mqh e declarar tal lista.
Também vamos transferir as variáveis para armazenar a largura de cada lado da moldura da forma para a seção protegida da classe:
//+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CForm : public CGCnvElement { private: CArrayObj m_list_tmp; CArrayObj m_list_elements; // List of attached elements CAnimations *m_animations; // Pointer to the animation object CShadowObj *m_shadow_obj; // Pointer to the shadow object CMouseState m_mouse; // "Mouse status" class object ENUM_MOUSE_FORM_STATE m_mouse_form_state; // Mouse status relative to the form ushort m_mouse_state_flags; // Mouse status flags color m_color_frame; // Form frame color int m_offset_x; // Offset of the X coordinate relative to the cursor int m_offset_y; // Offset of the Y coordinate relative to the cursor //--- Reset the array size of (1) text, (2) rectangular and (3) geometric animation frames void ResetArrayFrameT(void); void ResetArrayFrameQ(void); void ResetArrayFrameG(void); //--- Return the name of the dependent object string CreateNameDependentObject(const string base_name) const { return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+1)+"_"+base_name; } //--- Create a new graphical object 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); //--- Create a shadow object void CreateShadowObj(const color colour,const uchar opacity); protected: int m_frame_width_left; // Form frame width to the left int m_frame_width_right; // Form frame width to the right int m_frame_width_top; // Form frame width at the top int m_frame_width_bottom; // Form frame width at the bottom //--- Initialize the variables void Initialize(void); void Deinitialize(void); public:
Precisaremos acessar essas variáveis de classes derivadas, portanto, as variáveis devem estar em uma seção protegida - visível nesta classe e nas classes derivadas.
No método de inicialização, limpamos a nova lista e definimos o sinalizador de lista classificada para ela, definimos o valor da nova macro-substituição para a largura de cada lado da moldura da forma e adicionamos o objeto de animação à nova lista:
//+------------------------------------------------------------------+ //| Initialize the variables | //+------------------------------------------------------------------+ void CForm::Initialize(void) { this.m_list_elements.Clear(); this.m_list_elements.Sort(); this.m_list_tmp.Clear(); this.m_list_tmp.Sort(); this.m_shadow_obj=NULL; this.m_shadow=false; this.m_frame_width_right=DEF_FRAME_WIDTH_SIZE; this.m_frame_width_left=DEF_FRAME_WIDTH_SIZE; this.m_frame_width_top=DEF_FRAME_WIDTH_SIZE; this.m_frame_width_bottom=DEF_FRAME_WIDTH_SIZE; this.m_mouse_state_flags=0; this.m_offset_x=0; this.m_offset_y=0; CGCnvElement::SetInteraction(false); this.m_animations=new CAnimations(CGCnvElement::GetObject()); this.m_list_tmp.Add(m_animations); } //+------------------------------------------------------------------+
No método que cria o objeto sombra, após criar o objeto com sucesso, vamos adicioná-lo a uma nova lista:
//+------------------------------------------------------------------+ //| Create the shadow object | //+------------------------------------------------------------------+ void CForm::CreateShadowObj(const color colour,const uchar opacity) { //--- ... //--- ... //--- Create a new shadow object and set the pointer to it in the variable this.m_shadow_obj=new CShadowObj(this.ChartID(),this.SubWindow(),this.CreateNameDependentObject("Shadow"),x,y,w,h); if(this.m_shadow_obj==NULL) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ)); return; } this.m_list_tmp.Add(m_shadow_obj); //--- ... //--- Move the form object to the foreground this.BringToTop(); } //+------------------------------------------------------------------+
Essa modificação nos salva de vazamentos de memória descontrolados e difíceis de encontrar.
Vamos continuar trabalhando na classe do objeto do controle WinForms CPanel.
Vamos gerar o manuseio dos parâmetros de fonte do painel e de sua moldura.
No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh, na seção privada da classe, declaramos uma variável para armazenar o tipo de espessura da fonte e um método que retorne os sinalizadores definidos para a fonte:
//+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+ class CPanel : public CForm { private: color m_fore_color; // Default text color for all panel objects ENUM_FW_TYPE m_bold_type; // Font width type ENUM_FRAME_STYLE m_border_style; // Panel frame style bool m_autoscroll; // Auto scrollbar flag int m_autoscroll_margin[2]; // Array of fields around the control during an auto scroll bool m_autosize; // Flag of the element auto resizing depending on the content ENUM_CANV_ELEMENT_AUTO_SIZE_MODE m_autosize_mode; // Mode of the element auto resizing depending on the content ENUM_CANV_ELEMENT_DOCK_MODE m_dock_mode; // Mode of binding element borders to the container int m_margin[4]; // Array of gaps of all sides between the fields of the current and adjacent controls int m_padding[4]; // Array of gaps of all sides inside controls //--- Return the font flags uint GetFontFlags(void); public:
Ao definir o valor do tipo de largura da fonte para a classe CCanvas, escreveremos esse valor na variável m_bold_type.
O método que retorna os sinalizadores de fonte retorna todos os parâmetros definidos para ela, isto é: nome, tamanho, sinalizadores e ângulo. Como precisamos manejar sinalizadores, para não declarar variáveis locais em cada método que armazenará todos esses valores de fonte, chamaremos o método que retornará apenas o sinalizadores obtidos a partir das propriedades da fonte da classe CCanvas.
Na seção pública da classe, declararemos métodos para manusear sinalizadores de estilos de traçado e de tipos de espessura de fonte:
public: //--- (1) Set and (2) return the default text color of all panel objects void ForeColor(const color clr) { this.m_fore_color=clr; } color ForeColor(void) const { return this.m_fore_color; } //--- (1) Set and (2) return the Bold font flag void Bold(const bool flag); bool Bold(void); //--- (1) Set and (2) return the Italic font flag void Italic(const bool flag); bool Italic(void); //--- (1) Set and (2) return the Strikeout font flag void Strikeout(const bool flag); bool Strikeout(void); //--- (1) Set and (2) return the Underline font flag void Underline(const bool flag); bool Underline(void); //--- (1) Set and (2) return the font style void FontDrawStyle(ENUM_FONT_STYLE style); ENUM_FONT_STYLE FontDrawStyle(void); //--- (1) Set and (2) return the font width type void FontBoldType(ENUM_FW_TYPE type); ENUM_FW_TYPE FontBoldType(void) const { return this.m_bold_type; } //--- (1) Set and (2) return the frame style
...
e escrevemos métodos para definir e retornar as propriedades da moldura do painel:
//--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields inside the control int PaddingLeft(void) const { return this.m_padding[0]; } int PaddingTop(void) const { return this.m_padding[1]; } int PaddingRight(void) const { return this.m_padding[2]; } int PaddingBottom(void) const { return this.m_padding[3]; } //--- 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 void FrameWidthLeft(const int value) { this.m_frame_width_left=value; } void FrameWidthTop(const int value) { this.m_frame_width_top=value; } void FrameWidthRight(const int value) { this.m_frame_width_right=value; } void FrameWidthBottom(const int value) { this.m_frame_width_bottom=value; } void FrameWidthAll(const int value) { this.FrameWidthLeft(value); this.FrameWidthTop(value); this.FrameWidthRight(value); this.FrameWidthBottom(value); } //--- Return the width of the form frame (1) to the left, (2) at the top, (3) to the right and (4) at the bottom int FrameWidthLeft(void) const { return this.m_frame_width_left; } int FrameWidthTop(void) const { return this.m_frame_width_top; } int FrameWidthRight(void) const { return this.m_frame_width_right; } int FrameWidthBottom(void) const { return this.m_frame_width_bottom; } //--- Constructors
Em cada construtor, escrevemos a configuração padrão do tipo de espessura da fonte:
CPanel(const string name) : CForm(::ChartID(),0,name,0,0,0,0) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.m_fore_color=CLR_FORE_COLOR; this.m_bold_type=FW_TYPE_NORMAL; this.MarginAll(3); this.PaddingAll(0); this.Initialize(); } //--- 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) : CForm(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.m_fore_color=CLR_FORE_COLOR; this.m_bold_type=FW_TYPE_NORMAL; this.MarginAll(3); this.PaddingAll(0); this.Initialize(); } //+------------------------------------------------------------------+ //| Current chart constructor specifying the subwindow | //+------------------------------------------------------------------+ CPanel::CPanel(const int subwindow, const string name, const int x, const int y, const int w, const int h) : CForm(::ChartID(),subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.m_fore_color=CLR_FORE_COLOR; this.m_bold_type=FW_TYPE_NORMAL; this.MarginAll(3); this.PaddingAll(0); this.Initialize(); } //+------------------------------------------------------------------+ //| Constructor on the current chart in the main chart window | //+------------------------------------------------------------------+ CPanel::CPanel(const string name, const int x, const int y, const int w, const int h) : CForm(::ChartID(),0,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.m_fore_color=CLR_FORE_COLOR; this.m_bold_type=FW_TYPE_NORMAL; this.MarginAll(3); this.PaddingAll(0); this.Initialize(); } //+------------------------------------------------------------------+
Esse tipo será definido por padrão para as fontes do painel.
Método privado que retorna sinalizadores de fonte:
//+------------------------------------------------------------------+ //| Return the font flags | //+------------------------------------------------------------------+ uint CPanel::GetFontFlags(void) { string name; int size; uint flags; uint angle; CGCnvElement::GetFont(name,size,flags,angle); return flags; } //+------------------------------------------------------------------+
Como é necessário passar variáveis por referência ao método GetFont() da classe do elemento gráfico, variáveis essa nas quais serão escritos os valores obtidos a partir dos parâmetros de fonte na classe CCanvas, aqui declaramos todas as variáveis necessárias, obtemos seus valores chamando o método GetFont() e retornamos apenas os sinalizadores recebidos.
Método que define o sinalizador de fonte Bold:
//+------------------------------------------------------------------+ //| Set the Bold font flag | //+------------------------------------------------------------------+ void CPanel::Bold(const bool flag) { uint flags=this.GetFontFlags(); if(flag) { this.m_bold_type=FW_TYPE_BOLD; CGCnvElement::SetFontFlags(flags | FW_BOLD); } else this.m_bold_type=FW_TYPE_NORMAL; } //+------------------------------------------------------------------+
Aqui: obtemos os sinalizadores usando o método GetFontFlags() discutido acima, se o sinalizador passado nos argumentos do método estiver definido, escrevemos o valor Bold na variável m_bold_type, que armazena o tipo de espessura da fonte e definimos outro sinalizador para a fonte - FW_BOLD.
Se o sinalizador passado nos argumentos do método não estiver definido, escrevemos o valor padrão na variável m_bold_type .
Método que retorna o sinalizador da fonte Bold:
//+------------------------------------------------------------------+ //| Return the Bold font flag | //+------------------------------------------------------------------+ bool CPanel::Bold(void) { uint flags=this.GetFontFlags(); return(flags &FW_BOLD)==FW_BOLD; } //+------------------------------------------------------------------+
Aqui: recebemos os sinalizadores usando o métodoGetFontFlags() e retornamos o resultado da verificação de que o sinalizador FW_BOLD está presente no arquivo.
Os métodos para definir e retornar o restante dos sinalizadores de espessura da fonte são ligeiramente diferentes, pois eles não exigem a configuração do valor do sinalizador em uma variável.
Quanto ao restante, são idênticos aos anteriores:
//+------------------------------------------------------------------+ //| Set the Italic font flag | //+------------------------------------------------------------------+ void CPanel::Italic(const bool flag) { uint flags=this.GetFontFlags(); if(flag) CGCnvElement::SetFontFlags(flags | FONT_ITALIC); } //+------------------------------------------------------------------+ //| Return the Italic font flag | //+------------------------------------------------------------------+ bool CPanel::Italic(void) { uint flags=this.GetFontFlags(); return(flags &FONT_ITALIC)==FONT_ITALIC; } //+------------------------------------------------------------------+ //| Set the Strikeout font flag | //+------------------------------------------------------------------+ void CPanel::Strikeout(const bool flag) { uint flags=this.GetFontFlags(); if(flag) CGCnvElement::SetFontFlags(flags | FONT_STRIKEOUT); } //+------------------------------------------------------------------+ //| Return the Strikeout font flag | //+------------------------------------------------------------------+ bool CPanel::Strikeout(void) { uint flags=this.GetFontFlags(); return(flags &FONT_STRIKEOUT)==FONT_STRIKEOUT; } //+------------------------------------------------------------------+ //| Set the Underline font flag | //+------------------------------------------------------------------+ void CPanel::Underline(const bool flag) { uint flags=this.GetFontFlags(); if(flag) CGCnvElement::SetFontFlags(flags | FONT_UNDERLINE); } //+------------------------------------------------------------------+ //| Return the Underline font flag | //+------------------------------------------------------------------+ bool CPanel::Underline(void) { uint flags=this.GetFontFlags(); return(flags &FONT_UNDERLINE)==FONT_UNDERLINE; } //+------------------------------------------------------------------+
Eu acho que esses métodos são claros e não precisam de explicações.
Método que define o estilo da fonte:
//+------------------------------------------------------------------+ //| Set the font style | //+------------------------------------------------------------------+ void CPanel::FontDrawStyle(ENUM_FONT_STYLE style) { switch(style) { case FONT_STYLE_ITALIC : this.Italic(true); break; case FONT_STYLE_UNDERLINE : this.Underline(true); break; case FONT_STYLE_STRIKEOUT : this.Strikeout(true); break; default: break; } } //+------------------------------------------------------------------+
Dependendo do estilo da fonte (itálico, sublinhado, tachado) passado ao método, chamamos o método correspondente ao estilo para defini-lo.
Método que retorna o estilo da fonte:
//+------------------------------------------------------------------+ //| Return the font style | //+------------------------------------------------------------------+ ENUM_FONT_STYLE CPanel::FontDrawStyle(void) { return ( this.Italic() ? FONT_STYLE_ITALIC : this.Underline() ? FONT_STYLE_UNDERLINE : this.Strikeout() ? FONT_STYLE_UNDERLINE : FONT_STYLE_NORMAL ); } //+------------------------------------------------------------------+
O mesmo estilo de fonte é retornado dependendo do estilo definido para a fonte e retornado pelos métodos correspondentes.
Caso nenhum dos três estilos seja definido, o Normal é retornado.
Método que define o tipo de espessura da fonte:
//+------------------------------------------------------------------+ //| Set the font width type | //+------------------------------------------------------------------+ void CPanel::FontBoldType(ENUM_FW_TYPE type) { this.m_bold_type=type; uint flags=this.GetFontFlags(); switch(type) { case FW_TYPE_DONTCARE : CGCnvElement::SetFontFlags(flags | FW_DONTCARE); break; case FW_TYPE_THIN : CGCnvElement::SetFontFlags(flags | FW_THIN); break; case FW_TYPE_EXTRALIGHT : CGCnvElement::SetFontFlags(flags | FW_EXTRALIGHT); break; case FW_TYPE_ULTRALIGHT : CGCnvElement::SetFontFlags(flags | FW_ULTRALIGHT); break; case FW_TYPE_LIGHT : CGCnvElement::SetFontFlags(flags | FW_LIGHT); break; case FW_TYPE_REGULAR : CGCnvElement::SetFontFlags(flags | FW_REGULAR); break; case FW_TYPE_MEDIUM : CGCnvElement::SetFontFlags(flags | FW_MEDIUM); break; case FW_TYPE_SEMIBOLD : CGCnvElement::SetFontFlags(flags | FW_SEMIBOLD); break; case FW_TYPE_DEMIBOLD : CGCnvElement::SetFontFlags(flags | FW_DEMIBOLD); break; case FW_TYPE_BOLD : CGCnvElement::SetFontFlags(flags | FW_BOLD); break; case FW_TYPE_EXTRABOLD : CGCnvElement::SetFontFlags(flags | FW_EXTRABOLD); break; case FW_TYPE_ULTRABOLD : CGCnvElement::SetFontFlags(flags | FW_ULTRABOLD); break; case FW_TYPE_HEAVY : CGCnvElement::SetFontFlags(flags | FW_HEAVY); break; case FW_TYPE_BLACK : CGCnvElement::SetFontFlags(flags | FW_BLACK); break; default : CGCnvElement::SetFontFlags(flags | FW_NORMAL); break; } } //+------------------------------------------------------------------+
Aqui: escrevemos o valor passado para o método na variável m_bold_type . Obtemos os sinalizadores de fonte usando o método GetFontFlags().
Dependendo do tipo de largura de fonte passado ao método, adicionamos mais um sinalizador consoante o tipo especificado à variável recebida com sinalizadores de fonte.
Como resultado, a variável m_bold_type conterá o valor de enumeração passado para o método, e o sinalizador com o tipo de largura de fonte correspondente ao valor de enumeração ENUM_FW_TYPE será gravado nos sinalizadores de fonte.
Ao adicionar um objeto elemento gráfico a uma lista-coleção, primeiro verificamos sua presença na lista. Caso tal objeto já exista, podemos não adicioná-lo e informar no log e retornar um erro de adição, ou fazer o mesmo, mas retornar um ponteiro para um objeto existente em vez de um erro. Isso pode ser útil em casos de criação dinâmica de objetos, para fazer com que um objeto criado dinamicamente, mas oculto, seja retornado do método e exibido no gráfico ao tentar criar exatamente o mesmo objeto.
Para que possamos devolver diferentes códigos de retorno a partir do método de adição de m elemento gráfico à lista, criaremos uma enumeração.
No arquivo da classe-coleção de elementos gráficos \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh, antes de declarar a classe, escrevemos a enumeração:
//+------------------------------------------------------------------+ //| Collection of graphical objects | //+------------------------------------------------------------------+ #resource "\\"+PATH_TO_EVENT_CTRL_IND; // Indicator for controlling graphical object events packed into the program resources enum ENUM_ADD_OBJ_RET_CODE // Enumerate the codes of returning the method for adding an object to the list { ADD_OBJ_RET_CODE_SUCCESS, // Successful ADD_OBJ_RET_CODE_EXIST, // Object exists in the collection list ADD_OBJ_RET_CODE_ERROR, // Failed to add to the collection list }; class CGraphElementsCollection : public CBaseObj
Existem três códigos de retorno aqui:
- objeto adicionado com sucesso à lista,
- objeto já existe na lista-coleção,
- falha ao adicionar um objeto à lista-coleção.
Agora podemos retornar de forma flexível o resultado necessário. Nesse caso, dará errado apenas se o objeto não puder ser colocado na lista, enquanto os outros estarão falando que é possível continuar trabalhando com o objeto que foi criado e adicionado à coleção, ou que o objeto já existe e se pode trabalhar com ele.
Para que possamos criar elementos gráficos diretamente a partir da classe-coleção, precisamos adicionar um prefixo ao seu nome, isto é, ao nome do programa com um sublinhado no final. Na seção privada da classe, vamos declarar uma variável para armazenar o prefixo dos objetos gráficos:
class CGraphElementsCollection : public CBaseObj { private: //--- ... bool m_is_graph_obj_event; // Event flag in the list of graphical objects int m_total_objects; // Number of graphical objects int m_delta_graph_obj; // Difference in the number of graphical objects compared to the previous check string m_name_prefix; // Object name prefix //--- Return the flag indicating the graphical element class object presence in the collection list of graphical elements
Também adicionaremos dois métodos privados, isto é: um que cria um novo elemento gráfico ou retorna o identificador de um existente,
bem como outro que retorna o índice do elemento gráfico especificado na coleção-lista:
//--- Reset all interaction flags for all forms except the specified one void ResetAllInteractionExeptOne(CGCnvElement *form); //--- Add the element to the collection list bool AddCanvElmToCollection(CGCnvElement *element); //--- Add the element to the collectionl ist or return the existing one ENUM_ADD_OBJ_RET_CODE AddOrGetCanvElmToCollection(CGCnvElement *element,int &id); //--- Return the graphical elemnt index in the collection list int GetIndexGraphElement(const long chart_id,const string name);
O método que retorna uma lista de elementos gráficos consoante o ID do gráfico e nome do objeto precisa ser corrigido. Acontece que os nomes de todos os objetos gráficos da biblioteca começam com o nome do programa. Este nome é escrito no prefixo dos nomes dos objetos gráficos. Se passarmos apenas o nome de um objeto para o método de pesquisa, esse objeto não será encontrado. Isso acontece porque o nome passado para o método será pesquisado, e os objetos gráficos também possuem um prefixo. Portanto, para tal pesquisa precisamos verificar a presença de um prefixo no nome e, se não houver prefixo, adicioná-lo ao nome do objeto que está sendo pesquisado. Nesse caso, a pesquisa sempre funcionará corretamente, por isso se já houver um prefixo no nome pesquisado, nada será adicionado ao nome e o valor passado para o método será pesquisado. Se não houver prefixo, um prefixo será adicionado ao nome e, assim, a pesquisa será realizada.
Na seção pública da classe, encontramos um método que retorne uma lista de elementos gráficos consoante o ID do gráfico e o nome do objeto, e adicionamos as alterações descritas acima:
//--- Return the list of graphical elements by chart ID and object name CArrayObj *GetListCanvElementByName(const long chart_id,const string name) { string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL); return CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL); }
Aqui declaramos um nome a ser pesquisado, verificamos se o nome contém um prefixo e, se não, acrescentamos um prefixo a ele, caso contrário, não acrescentamos nada.
Em seguida, obtemos uma lista de elementos gráficos consoante o ID do gráfico, e retornamos uma lista filtrada segundo o nome do objeto pesquisado. Se o objeto não for encontrado, o método retornará NULL.
Logo após este método, escrevemos outro método que retorna um elemento gráfico consoante o ID e o nome do gráfico:
//--- Return the graphical element by chart ID and name CGCnvElement *GetCanvElement(const long chart_id,const string name) { CArrayObj *list=this.GetListCanvElementByName(chart_id,name); return(list!=NULL ? list.At(0) : NULL); } //--- Constructor
Aqui: obtemos uma lista de elementos gráficos consoante o ID do gráfico e o nome do objeto especificados. Se a lista for recebida, retornamos um ponteiro para o único objeto que está nela. Caso contrário, retornamos NULL.
Para poder recriar todos os elementos da GUI ao alternar os períodos gráficos, precisamos limpar a lista-coleção de elementos gráficos. Mas como o destruidor de classe, no qual a limpeza de todas as listas já está escrita, é chamado apenas quando o programa é removido do gráfico, precisamos fazer a limpeza no manipulador OnDeinit(). Declaramo-lo:
//--- Update the list of (1) all graphical objects, (2) on the specified chart, fill in the data on the number of new ones and set the event flag void Refresh(void); void Refresh(const long chart_id); //--- Event handlers void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam); void OnDeinit(void); private: //--- Move all objects on canvas to the foreground void BringToTopAllCanvElm(void);
Agora, em cada método que cria um elemento gráfico, vamos substituir este bloco de código:
if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; }
por este bloco:
//--- Create a graphical element object on canvas on a specified chart and subwindow int CreateElement(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, const color clr, const uchar opacity, const bool movable, const bool activity, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,activity,redraw); ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id); if(res==ADD_OBJ_RET_CODE_ERROR) return WRONG_VALUE; if(res==ADD_OBJ_RET_CODE_EXIST) obj.SetID(id); obj.Erase(clr,opacity,redraw); return obj.ID(); } //--- Create a graphical element object on canvas on a specified chart and subwindow with the vertical gradient filling
O que vemos aqui: primeiro, obtemos o código de retorno do método que cria um novo elemento gráfico ou retorna o identificador de um já existente. Então, caso o método retorne um erro, retornamos -1, se o código de retorno indica que o objeto existe, configuramos o identificador obtido do método AddOrGetCanvElmToCollection() nas propriedades do objeto recém-criado. Acontece que inicialmente para o identificador de um novo objeto definimos o valor máximo (número de objetos na lista), por outro lado para um objeto já existente, o identificador deve ser diferente, isto é, deve ser do já existente, então esse identificador é escrito em uma variável passada por referência ao método de adição de objeto à lista ou retorno de um objeto existente.
Assim, adicionamos um novo objeto à lista ou obtemos um ponteiro para um existente e escrevemos seu próprio identificador nele.
Para os métodos de criação de objeto-forma e o método de criação de painel, este bloco de código será um pouco diferente:
//--- Create a graphical object form object on canvas on a specified chart and subwindow int CreateForm(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, const color clr, const uchar opacity, const bool movable, const bool activity, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CForm *obj=new CForm(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(activity); obj.SetMovable(movable); obj.SetColorBackground(clr); obj.SetColorFrame(clr); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Done(); obj.Erase(clr,opacity,redraw); return obj.ID(); } //--- Create a graphical object form object on canvas on a specified chart and subwindow with the vertical gradient filling
Aqui é mais fácil: quando um erro é recebido, retornamos -1, e o identificador não precisa ser restaurado, pois é atribuído no construtor da classe para o novo objeto, e não é especificado no seu método de criação.
No método de criação de objeto-painel, adicionamos a largura da moldura do painel e o tipo de moldura aos argumentos:
//--- Create graphical object WinForms Panel object on canvas on a specified chart and subwindow int CreatePanel(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, const color clr, const uchar opacity, const bool movable, const bool activity, const int frame_width=-1, ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CPanel *obj=new CPanel(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(activity); obj.SetMovable(movable); obj.SetColorBackground(clr); obj.SetColorFrame(clr); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Erase(clr,opacity,redraw); if(frame_width>0) obj.FrameWidthAll(frame_width); obj.SetActiveAreaShift(obj.FrameWidthLeft(),obj.FrameWidthBottom(),obj.FrameWidthRight(),obj.FrameWidthTop()); obj.DrawFormFrame(obj.FrameWidthTop(),obj.FrameWidthBottom(),obj.FrameWidthLeft(),obj.FrameWidthRight(),obj.ColorFrame(),obj.Opacity(),frame_style); obj.Done(); return obj.ID(); }
No corpo do método, preenchemos o painel com cor, se a espessura da moldura for maior que zero, definimos a espessura de todos os lados do quadro nas propriedades do painel, definimos a área ativa do painel dentro da moldura, desenhamos a moldura e corrigimos a aparência do painel.
No construtor da classe, definimos o valor para o prefixo do nome do objeto gráfico, limpamos a lista de todos os elementos gráficos e definimos o sinalizador de lista classificada para ele:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CGraphElementsCollection::CGraphElementsCollection() { this.m_type=COLLECTION_GRAPH_OBJ_ID; this.m_name_prefix=this.m_name_program+"_"; ::ChartSetInteger(::ChartID(),CHART_EVENT_MOUSE_MOVE,true); ::ChartSetInteger(::ChartID(),CHART_EVENT_MOUSE_WHEEL,true); this.m_list_all_graph_obj.Type(COLLECTION_GRAPH_OBJ_ID); this.m_list_all_graph_obj.Sort(SORT_BY_CANV_ELEMENT_ID); this.m_list_all_graph_obj.Clear(); this.m_list_charts_control.Sort(); this.m_list_charts_control.Clear(); this.m_total_objects=0; this.m_is_graph_obj_event=false; this.m_list_deleted_obj.Clear(); this.m_list_deleted_obj.Sort(); this.m_list_all_canv_elm_obj.Clear(); this.m_list_all_canv_elm_obj.Sort(); } //+------------------------------------------------------------------+
Método que adiciona um elemento gráfico em canvas à coleção:
//+------------------------------------------------------------------+ //| Add the graphical element on canvas to the collection | //+------------------------------------------------------------------+ bool CGraphElementsCollection::AddCanvElmToCollection(CGCnvElement *element) { if(!this.m_list_all_canv_elm_obj.Add(element)) { CMessage::ToLog(DFUN+element.Name()+": ",MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST); return false; } return true; } //+------------------------------------------------------------------+
Se não foi possível adicionar um elemento à lista, reportamos isso no log e retornamos false. Se não for assim, retornamos true.
Método que adiciona um elemento à lista-coleção ou retorna um existente:
//+------------------------------------------------------------------+ //| Add the element to the collectionl ist or return the existing one| //+------------------------------------------------------------------+ ENUM_ADD_OBJ_RET_CODE CGraphElementsCollection::AddOrGetCanvElmToCollection(CGCnvElement *element,int &id) { //--- If an invalid pointer is passed, notify of that and return the error code if(element==NULL) { CMessage::ToLog(DFUN,MSG_GRAPH_ELM_COLLECTION_ERR_EMPTY_OBJECT); return ADD_OBJ_RET_CODE_ERROR; } //--- If the graphical element with a specified chart ID and name is already present, if(this.IsPresentCanvElmInList(element.ChartID(),element.Name())) { //--- inform of the object existence, CMessage::ToLog(DFUN+element.Name()+": ",MSG_LIB_SYS_OBJ_ALREADY_IN_LIST); //--- get the element from the collection list. element=this.GetCanvElement(element.ChartID(),element.Name()); //--- If failed to get the object, inform of that and return the error code if(element==NULL) { CMessage::ToLog(DFUN,MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_GET_ELEMENT); return ADD_OBJ_RET_CODE_ERROR; } //--- set the ID of the object obtained from the list to the value returned by the link //--- and return the object existence code in the list id=element.ID(); return ADD_OBJ_RET_CODE_EXIST; } //--- If failed to add the object to the list, remove it and return the error code if(!this.AddCanvElmToCollection(element)) { delete element; return ADD_OBJ_RET_CODE_ERROR; } //--- All is successful return ADD_OBJ_RET_CODE_SUCCESS; } //+------------------------------------------------------------------+
Cada linha do método está detalhada nos comentários e, espero, tudo está claro aqui. Resumindo: um ponteiro para um objeto recém-criado é passado para o método e, se tal objeto estiver na lista, atribuímos o ponteiro - para o objeto existente na lista - ao ponteiro passado ao método. Escrevemos o identificador do objeto existente em uma variável passada ao método por referência, porque, exteriormente, atribuímos essa variável como um identificador de objeto. Se tal objeto não estiver na lista, nós o adicionamos e retornamos o sinalizador que indica operação bem sucedida.
Método que retorna o índice do elemento gráfico na lista-coleação:
//+------------------------------------------------------------------+ //| Return the graphical elemnt index in the collection list | //+------------------------------------------------------------------+ int CGraphElementsCollection::GetIndexGraphElement(const long chart_id,const string name) { for(int i=0;i<this.m_list_all_canv_elm_obj.Total();i++) { CGCnvElement *obj=this.m_list_all_canv_elm_obj.At(i); if(obj==NULL) continue; if(obj.ChartID()==chart_id && obj.Name()==name) return i; } return WRONG_VALUE; } //+------------------------------------------------------------------+
Aqui, com um loop percorremos todos os elementos gráficos da coleção e obtemos o objeto que vem em seguida, e se o ID e o nome do gráfico forem iguais aos passados para o método, retornamos o índice do loop. No final do loop, retornamos -1.
Aqui eu gostaria de esclarecer por que não usamos a pesquisa rápida usando a classe de biblioteca CSelect. A questão é que estamos procurando o índice do objeto em toda a lista de toda a coleção. Por outro lado, a filtragem de lista por propriedade cria novas listas, e nós obtemos o índice do objeto a partir da lista filtrada em vez de da lista-coleção. Naturalmente, seus índices na maioria dos casos não coincidem.
Pelo mesmo motivo, vamos corrigir o erro no método que encontra um objeto que está na coleção mas não está no gráfico e retorna um ponteiro para o objeto e o índice do objeto na lista:
//+------------------------------------------------------------------+ //|Find an object present in the collection but not on a chart | //| Return the pointer to the object and its index in the list. | //+------------------------------------------------------------------+ CGStdGraphObj *CGraphElementsCollection::FindMissingObj(const long chart_id,int &index) { index=WRONG_VALUE; for(int i=0;i<this.m_list_all_graph_obj.Total();i++) { CGStdGraphObj *obj=this.m_list_all_graph_obj.At(i); if(obj==NULL) continue; if(!this.IsPresentGraphObjOnChart(obj.ChartID(),obj.Name())) { index=i; return obj; } } return NULL; } //+------------------------------------------------------------------+
Anteriormente, recebíamos primeiro uma lista de objetos consoante o ID do gráfico e com um loop circulávamos a lista resultante. Isso não estava certo.
Agora com um loop percorremos toda a lista-coleção e, consequentemente, obtemos o índice correto do objeto na lista.
Agora vamos falar sobre o fato de que, no Expert Advisor do artigo anterior, apenas objetos-forma podiam interagir com o mouse.
Objetos elementos gráficos não devem ter interação automática com o mouse, mas todos os objetos herdeiros do objeto-forma também devem herdar dele o manuseio de eventos de mouse. O erro era que verificávamos o tipo de elemento gráfico no manipulador de eventos, e quando esse tipo não era uma forma, não processávamos os eventos.
Agora vamos alterar isso, portanto, verificamos se o tipo de elemento gráfico é uma forma, e mais adiante na hierarquia de herança esses objetos devem ser processados no manipulador de eventos.
Vamos conseguir isso.
Vamos escrever a mudança no método GetFormUnderCursor():
//--- If managed to obtain the list and it is not empty, if(list!=NULL && list.Total()>0) { //--- Get the only graphical element there elm=list.At(0); //--- If the element is a form object or its descendants if(elm.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_FORM) { //--- Assign the pointer to the element for the form object pointer form=elm; //--- Get the mouse status relative to the form mouse_state=form.MouseFormState(id,lparam,dparam,sparam); //--- If the cursor is within the form, return the pointer to the form if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) return form; } } //--- If there is no a single form object with a specified interaction flag, //--- in the loop by all graphical element collection class objects int total=this.m_list_all_canv_elm_obj.Total(); for(int i=0;i<total;i++) { //--- get the next element elm=this.m_list_all_canv_elm_obj.At(i); if(elm==NULL) continue; //--- if the obtained element is a form object or its descendants if(elm.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_FORM) { //--- Assign the pointer to the element for the form object pointer form=elm; //--- Get the mouse status relative to the form mouse_state=form.MouseFormState(id,lparam,dparam,sparam); //--- If the cursor is within the form, return the pointer to the form if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) return form; } } //--- If there is no a single form object from the collection list //--- Get the list of extended standard graphical objects
Anteriormente, aqui se comparava se era igual ("=="). Agora apuramos se é "maior ou igual a". E como os valores das constantes de enumeração dos tipos de elementos gráficos estão em ordem crescente, todos os tipos ulteriores terão um valor constante maior que o valor da constante GRAPH_ELEMENT_TYPE_FORM.
No método que para todas as formas redefine os sinalizadores de interação, exceto o especificado, também faremos uma alteração:
//+----------------------------------------------------------------------+ //| Reset all interaction flags for all forms except the specified one | //+----------------------------------------------------------------------+ void CGraphElementsCollection::ResetAllInteractionExeptOne(CGCnvElement *form_exept) { //--- In the loop by all graphical element collection class objects int total=this.m_list_all_canv_elm_obj.Total(); for(int i=0;i<total;i++) { //--- get the pointer to the object CGCnvElement *obj=this.m_list_all_canv_elm_obj.At(i); //--- if failed to receive the pointer, or it is not a form or its descendants, or it is not a form whose pointer has been passed to the method, move on if(obj==NULL || obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_FORM || (obj.Name()==form_exept.Name() && obj.ChartID()==form_exept.ChartID())) continue; //--- Reset the interaction flag for the current form in the loop obj.SetInteraction(false); } } //+------------------------------------------------------------------+
Anteriormente, comparava-se se era diferente ("!=") e, consequentemente, todos os objetos, exceto os objetos-forma, eram ignorados. Agora, todos os objetos abaixo do objeto-forma na hierarquia de herança são ignorados.
No método SetZOrderMAX(), alteramos um pouco o texto de teste exibido no objeto gráfico (já que esses textos também serão alterados no EA de teste) e corrigimos o erro que impede que os objetos interajam com o mouse:
//+------------------------------------------------------------------+ //| Set ZOrde to the specified element | //| and adjust it in other elements | //+------------------------------------------------------------------+ bool CGraphElementsCollection::SetZOrderMAX(CGCnvElement *obj) { //--- ... //--- Temporarily declare a form object for drawing a text for visually displaying its ZOrder CForm *form=obj; //--- and draw a text specifying ZOrder on the form form.TextOnBG(0,form.TypeElementDescription()+": ID "+(string)form.ID()+", ZD "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true); //--- Sort the list of graphical elements by an element ID this.m_list_all_canv_elm_obj.Sort(SORT_BY_CANV_ELEMENT_ID); //--- ... //--- ... //--- In the loop by the obtained list of remaining graphical element objects for(int i=0;i<list.Total();i++) { //--- get the next object CGCnvElement *elm=list.At(i); //--- If failed to get the object or if this is a control form for managing pivot points of an extended standard graphical object //--- or, if the object's ZOrder is zero, skip the object since there is no need in changing its ZOrder as it is the bottom one if(elm==NULL || elm.Type()==OBJECT_DE_TYPE_GFORM_CONTROL || elm.Zorder()==0) continue; //--- If failed to set the object's ZOrder to 1 less than it already is (decreasing ZOrder by 1), add 'false' to the 'res' value if(!elm.SetZorder(elm.Zorder()-1,false)) res &=false; //--- Temporarily (for the test purpose), if the element is a form, if(elm.Type()>=OBJECT_DE_TYPE_GFORM) { //--- assign the pointer to the element for the form and draw a text specifying ZOrder on the form form=elm; form.TextOnBG(0,form.TypeElementDescription()+": ID "+(string)form.ID()+", ZD "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true); } } //--- Upon the loop completion, return the result set in 'res' return res; } //+------------------------------------------------------------------+
Vamos escrever um manipulador de eventos de desinicialização:
//+------------------------------------------------------------------+ //| Deinitialization event handler | //+------------------------------------------------------------------+ void CGraphElementsCollection::OnDeinit(void) { this.m_list_all_canv_elm_obj.Clear(); } //+------------------------------------------------------------------+
Tudo é simples aqui, limpamos a lista-coleção de elementos gráficos.
Da mesma forma, esse método deve ser chamado a partir do objeto principal da biblioteca CEngine no arquivo \MQL5\Include\DoEasy\Engine.mqh.
Vamos abrir o arquivo desta classe e adicionar uma chamada deste método a partir da classe-coleção de elementos gráficos ao seu manipulador OnDeinit() :
//+------------------------------------------------------------------+ //| Deinitialize library | //+------------------------------------------------------------------+ void CEngine::OnDeinit(void) { this.m_indicators.GetList().Clear(); this.m_graph_objects.OnDeinit(); } //+------------------------------------------------------------------+
Hoje, essas são todas as modificações na biblioteca. Vamos testar o que temos.
Teste
Para testar, vamos pegar Expert Advisor do artigo anterior e salvá-lo na nova pasta \MQL5\Experts\TestDoEasy\Part102\ com o novo nome TestDoEasyPart102.mq5.
Não faremos alterações significativas. Vamos apenas alterar as coordenadas dos objetos gráficos, para que a distância entre eles seja um pouco menor, e aumentamos o tamanho do painel. Para criar um painel, usaremos o seguinte código no manipulador OnInit():
//--- Create WinForms Panel object CPanel *pnl=NULL; obj_id=engine.GetGraphicObjCollection().CreatePanel(ChartID(),0,"WFPanel",elm.RightEdge()+20,50,230,150,array_clr[0],200,true,true); list=engine.GetListCanvElementByID(ChartID(),obj_id); pnl=list.At(0); if(pnl!=NULL) { pnl.FontDrawStyle(FONT_STYLE_NORMAL); pnl.Bold(true); Print(DFUN,EnumToString(pnl.FontDrawStyle())); Print(DFUN,EnumToString(pnl.FontBoldType())); pnl.SetFontSize(10); pnl.TextOnBG(0,pnl.TypeElementDescription()+": ID "+(string)pnl.ID()+", ZD "+(string)pnl.Zorder(),pnl.Width()/2,pnl.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',pnl.Opacity()); pnl.Update(true); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Se o painel for criado com sucesso, definimos seu tipo de texto como "Normal", definimos a fonte como "negrito" e imprimimos uma descrição do estilo de fonte e tipo de espessura no log.
Todos os textos exibidos em todos os objetos gráficos agora mostram automaticamente o tipo de objeto, o identificador e o ZOrder. Em relação ao artigo anterior, apenas tem um formato ligeiramente diferente.
Todas essas alterações podem ser visualizadas nos arquivos anexos ao artigo.
Compilamos o Expert Advisor e o executamos no gráfico:
Como se pode ver, todos os objetos necessários interagem com o mouse, o painel agora tem uma moldura, a fonte nele é exibida em negrito, como definido para ele. Já quando alternamos gráficos, nenhum objeto agora desaparece do gráfico, mas também nenhum deles salva seu novo local. Para corrigir isso, devemos gravar os dados do objeto em um arquivo e lê-los novamente conforme necessário. Faremos tudo isso mas mais tarde, quando tenhamos todos os objetos com a hierarquia de herança planejada.
O que virá a seguir?
No próximo artigo, continuaremos o desenvolvimento da classe do objeto-painel.
*Artigos desta série:
DoEasy. Controles (Parte 1): Primeiros passos
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/10697
![Desenvolvendo um EA de negociação do zero (Parte 26): Em direção ao futuro (I)](https://c.mql5.com/2/48/development__2.png)
![DoEasy. Controles (Parte 1): Primeiros passos](https://c.mql5.com/2/46/MQL5-avatar-doeasy-library-2.png)
![DoEasy. Controles (Parte 3): Criando controles vinculados](https://c.mql5.com/2/46/MQL5-avatar-doeasy-library-2__2.png)
![Desenvolvendo um EA de negociação do zero (Parte 25): Dado robustez ao sistema (II)](https://c.mql5.com/2/48/development__1.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