Gráficos na biblioteca DoEasy (Parte 100): Eliminando bugs ao trabalhar com objetos gráficos padrão estendidos
Conteúdo
Ideia
Nos artigos anteriores, até a presente data, temos desenvolvido funcionalidades para trabalhar com objetos gráficos compostos criados com base em objetos gráficos padrão estendidos. Gradualmente, artigo por artigo, temos avançado no processo de criação dessas funcionalidades. Também tivemos que mudar de tópico várias vezes: primeiro criamos objetos baseados na classe CCanvas, depois passamos para a criação de objetos gráficos, já que a funcionalidade planejada para incorporar objetos gráficos em todos os objetos da biblioteca exigia a presença de, pelo menos, parte da funcionalidade de objetos gráficos padrão já viável. Em seguida, voltamos para os objetos-formas baseados em CCanvas, pois os objetos gráficos estendidos exigiam que as classes baseadas em tela fossem retrabalhadas. Agora, novamente, precisamos continuar o desenvolvimento de objetos na tela.
Sendo assim, hoje vamos retocar, eliminando falhas evidentes ao trabalhar com objetos gráficos estendidos (e padrão) e com objetos-formas na tela, além disso vamos consertar os erros observados durante os testes efetuados no último artigo. E assim vamos concluir esta seção da descrição da biblioteca. Com o próximo artigo, abriremos uma nova seção na qual iniciaremos o desenvolvimento de objetos gráficos na tela que imitam Windows Forms no MS Visual Studio, uma vez que precisaremos desses objetos para continuar desenvolvendo objetos gráficos padrão estendidos e objetos compostos baseados em eles.
Modificando as classes da biblioteca
A existência, na tela, de objetos gráficos que são usados para criar elementos da GUI manualmente ou programaticamente, assim como de outros objetos da biblioteca construídos com base neles, provoca que esses objetos sejam desenhados em cima dos controles, o que não é muito correto e oportuno. Por esse motivo, precisamos desenvolver um mecanismo para rastrear o surgimento de novos objetos gráficos na tela e mover todos os elementos da GUI para o primeiro plano. Para isso, podemos utilizar a propriedade do objeto gráfico ZOrder (prioridade do objeto gráfico para receber o evento de clique do mouse no gráfico (CHARTEVENT_CLICK)).
Citando a documentação:
Por padrão, quando criado, o valor é definido como zero, mas podemos aumentar a prioridade, se necessário. Quando os objetos se sobrepõem, o evento CHARTEVENT_CLICK receberá apenas um objeto, cuja prioridade é maior que os demais.
Porém usaremos essa propriedade mais amplamente, já que o valor dessa propriedade indicará a ordem na qual os elementos da GUI são organizados em relação uns aos outros, bem como em relação a outros objetos gráficos.
No arquivo \MQL5\Include\DoEasy\Defines.mqh , na enumeração de propriedades inteiras do elemento gráfico na tela, adicionamos uma nova propriedade e aumentamos o número de propriedades inteiras de 23 para 24:
//+------------------------------------------------------------------+ //| 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_BELONG, // Graphical element affiliation CANV_ELEMENT_PROP_NUM, // Element index in the list CANV_ELEMENT_PROP_CHART_ID, // Chart ID CANV_ELEMENT_PROP_WND_NUM, // Chart subwindow index CANV_ELEMENT_PROP_COORD_X, // Form's X coordinate on the chart CANV_ELEMENT_PROP_COORD_Y, // Form's Y coordinate on the chart CANV_ELEMENT_PROP_WIDTH, // Element width CANV_ELEMENT_PROP_HEIGHT, // Element height CANV_ELEMENT_PROP_RIGHT, // Element right border CANV_ELEMENT_PROP_BOTTOM, // Element bottom border CANV_ELEMENT_PROP_ACT_SHIFT_LEFT, // Active area offset from the left edge of the element CANV_ELEMENT_PROP_ACT_SHIFT_TOP, // Active area offset from the upper edge of the element CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT, // Active area offset from the right edge of the element CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM, // Active area offset from the bottom edge of the element CANV_ELEMENT_PROP_MOVABLE, // Element moveability flag CANV_ELEMENT_PROP_ACTIVE, // Element activity flag CANV_ELEMENT_PROP_INTERACTION, // Flag of interaction with the outside environment CANV_ELEMENT_PROP_COORD_ACT_X, // X coordinate of the element active area CANV_ELEMENT_PROP_COORD_ACT_Y, // Y coordinate of the element active area CANV_ELEMENT_PROP_ACT_RIGHT, // Right border of the element active area CANV_ELEMENT_PROP_ACT_BOTTOM, // Bottom border of the element active area CANV_ELEMENT_PROP_ZORDER, // Priority of a graphical object for receiving the event of clicking on a chart }; #define CANV_ELEMENT_PROP_INTEGER_TOTAL (24) // Total number of integer properties #define CANV_ELEMENT_PROP_INTEGER_SKIP (0) // Number of integer properties not used in sorting //+------------------------------------------------------------------+
E adicionamos esta nova propriedade à lista de possíveis critérios para ordenar elementos gráficos na tela:
//+------------------------------------------------------------------+ //| 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_BELONG, // Sort by a graphical element affiliation SORT_BY_CANV_ELEMENT_NUM, // Sort by form index in the list SORT_BY_CANV_ELEMENT_CHART_ID, // Sort by chart ID SORT_BY_CANV_ELEMENT_WND_NUM, // Sort by chart window index SORT_BY_CANV_ELEMENT_COORD_X, // Sort by the element X coordinate on the chart SORT_BY_CANV_ELEMENT_COORD_Y, // Sort by the element Y coordinate on the chart SORT_BY_CANV_ELEMENT_WIDTH, // Sort by the element width SORT_BY_CANV_ELEMENT_HEIGHT, // Sort by the element height SORT_BY_CANV_ELEMENT_RIGHT, // Sort by the element right border SORT_BY_CANV_ELEMENT_BOTTOM, // Sort by the element bottom border SORT_BY_CANV_ELEMENT_ACT_SHIFT_LEFT, // Sort by the active area offset from the left edge of the element SORT_BY_CANV_ELEMENT_ACT_SHIFT_TOP, // Sort by the active area offset from the top edge of the element SORT_BY_CANV_ELEMENT_ACT_SHIFT_RIGHT, // Sort by the active area offset from the right edge of the element SORT_BY_CANV_ELEMENT_ACT_SHIFT_BOTTOM, // Sort by the active area offset from the bottom edge of the element SORT_BY_CANV_ELEMENT_MOVABLE, // Sort by the element moveability flag SORT_BY_CANV_ELEMENT_ACTIVE, // Sort by the element activity flag SORT_BY_CANV_ELEMENT_INTERACTION, // Sort by the flag of interaction with the outside environment SORT_BY_CANV_ELEMENT_COORD_ACT_X, // Sort by X coordinate of the element active area SORT_BY_CANV_ELEMENT_COORD_ACT_Y, // Sort by Y coordinate of the element active area SORT_BY_CANV_ELEMENT_ACT_RIGHT, // Sort by the right border of the element active area SORT_BY_CANV_ELEMENT_ACT_BOTTOM, // Sort by the bottom border of the element active area SORT_BY_CANV_ELEMENT_ZORDER, // Sort by the priority of a graphical object for receiving the event of clicking on a chart //--- 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 }; //+------------------------------------------------------------------+
Agora podemos selecionar e classificar elementos gráficos na tela por meio dessa propriedade.
Em nossa biblioteca, todos os objetos gráficos são herdados do objeto base de todos os objetos gráficos da biblioteca, isto é, objetos gráficos padrão e elementos gráficos na tela. No arquivo de classe de objeto gráfico base \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh vamos tornar virtuais os métodos para trabalhar com a propriedade ZOrder:
//--- Set the "Disable displaying the name of a graphical object in the terminal object list" flag bool SetFlagHidden(const bool flag,const bool only_prop) { ::ResetLastError(); if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_SELECTABLE,flag)) || only_prop) { this.m_hidden=flag; return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Set the priority of a graphical object for receiving the event of clicking on a chart virtual bool SetZorder(const long value,const bool only_prop) { ::ResetLastError(); if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_ZORDER,value)) || only_prop) { this.m_zorder=value; return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Set object visibility on all timeframes bool SetVisible(const bool flag,const bool only_prop) { long value=(flag ? OBJ_ALL_PERIODS : OBJ_NO_PERIODS); ::ResetLastError(); if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,value)) || only_prop) { this.m_visible=flag; return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Set visibility flags on timeframes specified as flags
...
//--- Return the values of class variables ENUM_GRAPH_ELEMENT_TYPE TypeGraphElement(void) const { return this.m_type_element; } ENUM_GRAPH_OBJ_BELONG Belong(void) const { return this.m_belong; } ENUM_GRAPH_OBJ_SPECIES Species(void) const { return this.m_species; } ENUM_OBJECT TypeGraphObject(void) const { return this.m_type_graph_obj; } datetime TimeCreate(void) const { return this.m_create_time; } string Name(void) const { return this.m_name; } long ChartID(void) const { return this.m_chart_id; } long ObjectID(void) const { return this.m_object_id; } virtual long Zorder(void) const { return this.m_zorder; } int SubWindow(void) const { return this.m_subwindow; } int ShiftY(void) const { return this.m_shift_y; } int VisibleOnTimeframes(void) const { return this.m_timeframes_visible; } int Digits(void) const { return this.m_digits; } int Group(void) const { return this.m_group; } bool IsBack(void) const { return this.m_back; } bool IsSelected(void) const { return this.m_selected; } bool IsSelectable(void) const { return this.m_selectable; } bool IsHidden(void) const { return this.m_hidden; } bool IsVisible(void) const { return this.m_visible; } //--- Return the graphical object type (ENUM_OBJECT) calculated from the object type (ENUM_OBJECT_DE_TYPE) passed to the method
Temos os mesmos métodos nos objetos-herdeiros da classe do objeto base de todos os objetos gráficos da biblioteca.
Também é preciso tornar esses métodos virtuais.
No arquivo de classe de objeto gráfico padrão abstrato \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh:
//--- Background object bool Back(void) const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_BACK,0); } bool SetFlagBack(const bool flag,const bool only_prop) { if(!CGBaseObj::SetFlagBack(flag,only_prop)) return false; this.SetProperty(GRAPH_OBJ_PROP_BACK,0,flag); return true; } //--- Priority of a graphical object for receiving the event of clicking on a chart virtual long Zorder(void) const { return this.GetProperty(GRAPH_OBJ_PROP_ZORDER,0); } virtual bool SetZorder(const long value,const bool only_prop) { if(!CGBaseObj::SetZorder(value,only_prop)) return false; this.SetProperty(GRAPH_OBJ_PROP_ZORDER,0,value); return true; } //--- Disable displaying the name of a graphical object in the terminal object list bool Hidden(void) const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN,0); } bool SetFlagHidden(const bool flag,const bool only_prop) { if(!CGBaseObj::SetFlagHidden(flag,only_prop)) return false; this.SetProperty(GRAPH_OBJ_PROP_HIDDEN,0,flag); return true; } //--- Object selection
Adicionamos exatamente os mesmos métodos virtuais ao arquivo de classe de objeto do elemento gráfico \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh:
//--- (1) Show and (2) hide the element virtual void Show(void) { CGBaseObj::SetVisible(true,false); } virtual void Hide(void) { CGBaseObj::SetVisible(false,false); } //--- Priority of a graphical object for receiving the event of clicking on a chart virtual long Zorder(void) const { return this.GetProperty(CANV_ELEMENT_PROP_ZORDER); } virtual bool SetZorder(const long value,const bool only_prop) { if(!CGBaseObj::SetZorder(value,only_prop)) return false; this.SetProperty(CANV_ELEMENT_PROP_ZORDER,value); return true; } //+------------------------------------------------------------------+ //| The methods of receiving raster data | //+------------------------------------------------------------------+
Como na classe de objeto do elemento gráfico já preparamos a estrutura da propriedade do objeto para salvá-lo no portador e restaurá-lo, precisamos adicionar um campo para a nova propriedade integer a ele:
//--- Create (1) the object structure and (2) the object from the structure virtual bool ObjectToStruct(void); virtual void StructToObject(void); private: struct SData { //--- Object integer properties int id; // Element ID int type; // Graphical element type int number; // Element index in the list long chart_id; // Chart ID int subwindow; // Chart subwindow index int coord_x; // Form's X coordinate on the chart int coord_y; // Form's Y coordinate on the chart int width; // Element width int height; // Element height int edge_right; // Element right border int edge_bottom; // Element bottom border int act_shift_left; // Active area offset from the left edge of the element int act_shift_top; // Active area offset from the top edge of the element int act_shift_right; // Active area offset from the right edge of the element int act_shift_bottom; // Active area offset from the bottom edge of the element uchar opacity; // Element opacity color color_bg; // Element background color bool movable; // Element moveability flag bool active; // Element activity flag bool interaction; // Flag of interaction with the outside environment int coord_act_x; // X coordinate of the element active area int coord_act_y; // Y coordinate of the element active area int coord_act_right; // Right border of the element active area int coord_act_bottom; // Bottom border of the element active area long zorder; // Priority of a graphical object for receiving the event of clicking on a chart //--- Object real properties //--- Object string properties uchar name_obj[64]; // Graphical element object name uchar name_res[64]; // Graphical resource name }; SData m_struct_obj; // Object structure
No método para criar a estrutura do objeto, adicionamos o salvamento de uma nova propriedade do objeto:
//+------------------------------------------------------------------+ //| Create the object structure | //+------------------------------------------------------------------+ bool CGCnvElement::ObjectToStruct(void) { //--- Save integer properties this.m_struct_obj.id=(int)this.GetProperty(CANV_ELEMENT_PROP_ID); // Element ID this.m_struct_obj.type=(int)this.GetProperty(CANV_ELEMENT_PROP_TYPE); // Graphical element type this.m_struct_obj.number=(int)this.GetProperty(CANV_ELEMENT_PROP_NUM); // Eleemnt ID in the list this.m_struct_obj.chart_id=this.GetProperty(CANV_ELEMENT_PROP_CHART_ID); // Chart ID this.m_struct_obj.subwindow=(int)this.GetProperty(CANV_ELEMENT_PROP_WND_NUM); // Chart subwindow index this.m_struct_obj.coord_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_X); // Form's X coordinate on the chart this.m_struct_obj.coord_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_Y); // Form's Y coordinate on the chart this.m_struct_obj.width=(int)this.GetProperty(CANV_ELEMENT_PROP_WIDTH); // Element width this.m_struct_obj.height=(int)this.GetProperty(CANV_ELEMENT_PROP_HEIGHT); // Element height this.m_struct_obj.edge_right=(int)this.GetProperty(CANV_ELEMENT_PROP_RIGHT); // Element right edge this.m_struct_obj.edge_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_BOTTOM); // Element bottom edge this.m_struct_obj.act_shift_left=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT); // Active area offset from the left edge of the element this.m_struct_obj.act_shift_top=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP); // Active area offset from the top edge of the element this.m_struct_obj.act_shift_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT); // Active area offset from the right edge of the element this.m_struct_obj.act_shift_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM);// Active area offset from the bottom edge of the element this.m_struct_obj.movable=(bool)this.GetProperty(CANV_ELEMENT_PROP_MOVABLE); // Element moveability flag this.m_struct_obj.active=(bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE); // Element activity flag this.m_struct_obj.interaction=(bool)this.GetProperty(CANV_ELEMENT_PROP_INTERACTION); // Flag of interaction with the outside environment this.m_struct_obj.coord_act_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_X); // X coordinate of the element active area this.m_struct_obj.coord_act_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y); // Y coordinate of the element active area this.m_struct_obj.coord_act_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_RIGHT); // Right border of the element active area this.m_struct_obj.coord_act_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM); // Bottom border of the element active area this.m_struct_obj.color_bg=this.m_color_bg; // Element background color this.m_struct_obj.opacity=this.m_opacity; // Element opacity this.m_struct_obj.zorder=this.m_zorder; // Priority of a graphical object for receiving the on-chart mouse click event //--- Save real properties //--- Save string properties ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj);// Graphical element object name ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res);// Graphical resource name //--- Save the structure to the uchar array ::ResetLastError(); if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY,true); return false; } return true; } //+------------------------------------------------------------------+
E no método de recuperação de um objeto da estrutura, adicionamos a leitura desta propriedade ao objeto:
//+------------------------------------------------------------------+ //| Create the object from the structure | //+------------------------------------------------------------------+ void CGCnvElement::StructToObject(void) { //--- Save integer properties this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id); // Element ID this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type); // Graphical element type this.SetProperty(CANV_ELEMENT_PROP_NUM,this.m_struct_obj.number); // Element index in the list this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,this.m_struct_obj.chart_id); // Chart ID this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,this.m_struct_obj.subwindow); // Chart subwindow index this.SetProperty(CANV_ELEMENT_PROP_COORD_X,this.m_struct_obj.coord_x); // Form's X coordinate on the chart this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,this.m_struct_obj.coord_y); // Form's Y coordinate on the chart this.SetProperty(CANV_ELEMENT_PROP_WIDTH,this.m_struct_obj.width); // Element width this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,this.m_struct_obj.height); // Element height this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.m_struct_obj.edge_right); // Element right edge this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.m_struct_obj.edge_bottom); // Element bottom edge this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,this.m_struct_obj.act_shift_left); // Active area offset from the left edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,this.m_struct_obj.act_shift_top); // Active area offset from the upper edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,this.m_struct_obj.act_shift_right); // Active area offset from the right edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,this.m_struct_obj.act_shift_bottom); // Active area offset from the bottom edge of the element this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,this.m_struct_obj.movable); // Element moveability flag this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,this.m_struct_obj.active); // Element activity flag this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,this.m_struct_obj.interaction); // Flag of interaction with the outside environment this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.m_struct_obj.coord_act_x); // X coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.m_struct_obj.coord_act_y); // Y coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.m_struct_obj.coord_act_right); // Right border of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.m_struct_obj.coord_act_bottom); // Bottom border of the element active area this.m_color_bg=this.m_struct_obj.color_bg; // Element background color this.m_opacity=this.m_struct_obj.opacity; // Element opacity this.m_zorder=this.m_struct_obj.zorder; // Priority of a graphical object for receiving the on-chart mouse click event //--- Save real properties //--- Save string properties this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj));// Graphical element object name this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res));// Graphical resource name } //+------------------------------------------------------------------+
Precisamos de tudo isso para salvar corretamente o objeto no disco e recuperá-lo a partir do disco no futuro, quando lemos e gravamos objetos de biblioteca em arquivos. Isso será necessário para que quando o terminal for reiniciado, a biblioteca possa restaurar o estado do programa e seus dados para a continuação normal do trabalho. Mas tudo isso virá depois. Enquanto isso, estamos apenas preparando a funcionalidade necessária.
Vamos voltar ao arquivo de classe de objeto gráfico padrão abstrato \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh.
Como precisaremos saber as coordenadas mínimas e máximas de todo o objeto ao construir objetos gráficos compostos a partir de objetos gráficos padrão estendidos no futuro, agora é a hora de adicionar métodos para encontrar e retornar os valores do X mínimo e máximo e coordenadas Y do objeto gráfico padrão. E no futuro, com base nesses métodos, poderemos obter as coordenadas mínimas/máximas de todo o objeto gráfico composto.
Vamos declarar dois novos métodos na seção pública da classe:
//--- Return (1) the list of dependent objects, (2) dependent graphical object by index and (3) the number of dependent objects CArrayObj *GetListDependentObj(void) { return &this.m_list; } CGStdGraphObj *GetDependentObj(const int index) { return this.m_list.At(index); } int GetNumDependentObj(void) { return this.m_list.Total(); } //--- Return the name of the dependent object by index string NameDependent(const int index); //--- Add the dependent graphical object to the list bool AddDependentObj(CGStdGraphObj *obj); //--- Change X and Y coordinates of the current and all dependent objects bool ChangeCoordsExtendedObj(const int x,const int y,const int modifier,bool redraw=false); //--- Set X and Y coordinates into the appropriate pivot points of a specified subordinate object bool SetCoordsXYtoDependentObj(CGStdGraphObj *dependent_obj); //--- Return (1) the minimum and (2) maximum XY screen coordinate of the specified pivot point bool GetMinCoordXY(int &x,int &y); bool GetMaxCoordXY(int &x,int &y); //--- Return the pivot point data object CLinkedPivotPoint*GetLinkedPivotPoint(void) { return &this.m_linked_pivots; }
Vamos escrever sua implementação fora do corpo da classe.
Método que retorna a mínima coordenada XY de tela de todos os pontos de ancoragem:
//+------------------------------------------------------------------+ //| Return the minimum XY screen coordinate | //| from all pivot points | //+------------------------------------------------------------------+ bool CGStdGraphObj::GetMinCoordXY(int &x,int &y) { //--- Declare the variables storing the minimum time and maximum price datetime time_min=LONG_MAX; double price_max=0; //--- Depending on the object type switch(this.TypeGraphObject()) { //--- Objects constructed based on screen coordinates case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : //--- Write XY screen coordinates to x and y variables and return 'true' x=this.XDistance(); y=this.YDistance(); return true; //--- Objects constructed in time/price coordinates default : //--- In the loop by all pivot points for(int i=0;i<this.Pivots();i++) { //--- Define the minimum time if(this.Time(i)<time_min) time_min=this.Time(i); //--- Define the maximum price if(this.Price(i)>price_max) price_max=this.Price(i); } //--- Return the result of converting time/price coordinates into XY screen coordinates return(::ChartTimePriceToXY(this.ChartID(),this.SubWindow(),time_min,price_max,x,y) ? true : false); } return false; } //+------------------------------------------------------------------+
Para objetos plotados em coordenadas de tela, não há necessidade de buscar a coordenada máxima/mínima, pois tais objetos possuem apenas um ponto de ancoragem ao gráfico, e como ele já está em coordenadas de tela, basta retorná-los.
Mas para objetos construídos por coordenadas de tempo/preço, precisamos obter essas coordenadas usando a função ChartTimePriceToXY(). Porém, além disso, primeiro precisamos encontrar a coordenada de tempo mínimo de todos os pontos de ancoragem que o objeto possui e a coordenada de preço máximo dos mesmos pontos de ancoragem. Por que estamos procurando a coordenada máxima para o preço se precisamos da coordenada mínima no gráfico? A resposta é simples: o preço no gráfico aumenta de baixo para cima, enquanto as coordenadas em pixels no gráfico são contadas a partir do canto superior esquerdo. Assim, quanto maior o preço, menor o valor da coordenada em pixels para esse preço.
Com um loop percorrendo todos os pontos de ancoragem, encontramos o tempo mínimo e o preço máximo, que finalmente passamos para a função ChartTimePriceToXY() para obter as coordenadas em pixels do gráfico.
Método semelhante para obter a coordenada XY máxima de tela para todos os pontos de ancoragem:
//+------------------------------------------------------------------+ //| Return the maximum XY screen coordinate | //| from all pivot points | //+------------------------------------------------------------------+ bool CGStdGraphObj::GetMaxCoordXY(int &x,int &y) { //--- Declare the variables that store the maximum time and minimum price datetime time_max=0; double price_min=DBL_MAX; //--- Depending on the object type switch(this.TypeGraphObject()) { //--- Objects constructed based on screen coordinates case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : //--- Write XY screen coordinates to x and y variables and return 'true' x=this.XDistance(); y=this.YDistance(); return true; //--- Objects constructed in time/price coordinates default : //--- In the loop by all pivot points for(int i=0;i<this.Pivots();i++) { //--- Define the maximum time if(this.Time(i)>time_max) time_max=this.Time(i); //--- Define the maximum price if(this.Price(i)<price_min) price_min=this.Price(i); } //--- Return the result of converting time/price coordinates into XY screen coordinates return(::ChartTimePriceToXY(this.ChartID(),this.SubWindow(),time_max,price_min,x,y) ? true : false); } return false; } //+------------------------------------------------------------------+
O método é semelhante ao anterior, exceto que aqui procuramos o tempo máximo e o preço mínimo.
Repito: precisaremos desses métodos no futuro, quando continuarmos trabalhando em objetos gráficos compostos.
Na classe das ferramentas \MQL5\Include\DoEasy\Objects\Graph\Extend\CGStdGraphObjExtToolkit.mqh do objeto gráfico padrão estendido, vamos corrigir um pouco o método que retorna as coordenadas X e Y do ponto de ancoragem especificado do objeto gráfico em coordenadas de tela. A questão é que precisamos distribuir objetos gráficos entre os case do operador-interruptor switch de forma que cada caso contenha objetos gráficos com base no número de pontos de ancoragem para esses objetos que são construídos de acordo com as coordenadas de tempo/preço. Vamos distribuí-los na ordem correta:
//+------------------------------------------------------------------+ //| Return the X and Y coordinates of the specified pivot point | //| of the graphical object in screen coordinates | //+------------------------------------------------------------------+ bool CGStdGraphObjExtToolkit::GetControlPointCoordXY(const int index,int &x,int &y) { //--- Declare form objects, from which we are to receive their screen coordinates CFormControl *form0=NULL, *form1=NULL; //--- Set X and Y to zero - these values will be received in case of a failure x=0; y=0; //--- Depending on the graphical object type switch(this.m_base_type) { //--- Objects drawn using screen coordinates case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : //--- Write object screen coordinates and return 'true' x=this.m_base_x; y=this.m_base_y; return true; //--- One pivot point (price only) case OBJ_HLINE : break; //--- One pivot point (time only) case OBJ_VLINE : case OBJ_EVENT : break; //--- One reference point (time/price) case OBJ_TEXT : case OBJ_BITMAP : break; //--- Two pivot points and a central one //--- Lines case OBJ_TREND : case OBJ_TRENDBYANGLE : case OBJ_CYCLES : case OBJ_ARROWED_LINE : //--- Channels case OBJ_CHANNEL : case OBJ_STDDEVCHANNEL : case OBJ_REGRESSION : //--- Gann case OBJ_GANNLINE : case OBJ_GANNGRID : //--- Fibo case OBJ_FIBO : case OBJ_FIBOTIMES : case OBJ_FIBOFAN : case OBJ_FIBOARC : case OBJ_FIBOCHANNEL : case OBJ_EXPANSION : //--- Calculate coordinates for forms on the line pivot points if(index<this.m_base_pivots) return(::ChartTimePriceToXY(this.m_base_chart_id,this.m_base_subwindow,this.m_base_time[index],this.m_base_price[index],x,y) ? true : false); //--- Calculate the coordinates for the central form located between the line pivot points else { form0=this.GetControlPointForm(0); form1=this.GetControlPointForm(1); if(form0==NULL || form1==NULL) return false; x=(form0.CoordX()+this.m_shift+form1.CoordX()+this.m_shift)/2; y=(form0.CoordY()+this.m_shift+form1.CoordY()+this.m_shift)/2; return true; } //--- Channels case OBJ_PITCHFORK : break; //--- Gann case OBJ_GANNFAN : break; //--- Elliott case OBJ_ELLIOTWAVE5 : break; case OBJ_ELLIOTWAVE3 : break; //--- Shapes case OBJ_RECTANGLE : break; case OBJ_TRIANGLE : break; case OBJ_ELLIPSE : break; //--- Arrows case OBJ_ARROW_THUMB_UP : break; case OBJ_ARROW_THUMB_DOWN : break; case OBJ_ARROW_UP : break; case OBJ_ARROW_DOWN : break; case OBJ_ARROW_STOP : break; case OBJ_ARROW_CHECK : break; case OBJ_ARROW_LEFT_PRICE : break; case OBJ_ARROW_RIGHT_PRICE : break; case OBJ_ARROW_BUY : break; case OBJ_ARROW_SELL : break; case OBJ_ARROW : break; //--- default : break; } return false; } //+------------------------------------------------------------------+
Os objetos desenhados nativamente com base em coordenadas de tela simplesmente retornam as coordenadas de tela a partir de seu objeto base (ao qual estão vinculados).
Os objetos para os quais se implementa o retorno de coordenadas de tela atualmente possuem dois pontos de ancoragem. As coordenadas da tela são calculadas para todos os pontos de ancoragem do objeto e do ponto central.
Os outros objetos para os quais a funcionalidade ainda não foi implementada são simplesmente divididos em grupos (não todos, alguns são simplesmente arrumados em grupos por tipo e não, pelo número de pontos de ancoragem, já trataremos de sua implementação no devido tempo).
Agora precisamos garantir que todos os controles da GUI sempre permaneçam acima de qualquer um dos objetos gráficos recém-adicionados no gráfico. Além disso, precisamos ter certeza de que, ao reconstruir os elementos da GUI, eles se alinhem seguindo a sequência em que estavam antes de o objeto gráfico ser adicionado ao gráfico.
Para trazer um objeto gráfico para o primeiro plano, devemos ocultá-lo e exibi-lo novamente. Isso é feito redefinindo e definindo o sinalizador de visibilidade do objeto gráfico. Para ocultar um objeto, devemos usar a função ObjectSetInteger() para definir o sinalizador OBJ_NO_PERIODS para a propriedade OBJPROP_TIMEFRAMES. Já para exibição, devemos definir o sinalizador OBJ_ALL_PERIODS. E se fizermos isso para todos os objetos da GUI na ordem em que estão localizados na lista-coleção de elementos gráficos, perderemos a ordem como estão arranjados no gráfico. Ou seja, os objetos serão organizados de modo que o primeiro da lista seja o mais baixo no gráfico e o último seja o mais alto. Mas esses objetos podem estar organizados de uma maneira completamente diferente, maneira essa que será alterada ao redesenhar. E aqui nos voltamos para a nova propriedade que adicionamos hoje, isto é, ZOrder. Precisamos ordenar a lista de elementos gráficos em ordem crescente seguindo a propriedade ZOrder, e então os objetos serão redesenhados acompanhando a disposição correta.
Vamos abrir o arquivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh da coleção de classes de elementos gráficos, e vamos fazer todas as modificações necessárias nele.
Vamos declarar três novos métodos na seção privada da classe:
//--- Event handler void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam); private: //--- Move all objects on canvas to the foreground void BringToTopAllCanvElm(void); //--- (1) Return the maximum ZOrder of all CCanvas elements, //--- (2) Set ZOrde to the specified element and adjust it in all the remaining elements long GetZOrderMax(void); bool SetZOrderMAX(CGCnvElement *obj); //--- Handle the removal of extended graphical objects void DeleteExtendedObj(CGStdGraphObj *obj);
Vamos escrever sua implementação fora do corpo da classe.
Método que traz todos os objetos da tela para frente:
//+------------------------------------------------------------------+ //| Move all objects on canvas to the foreground | //+------------------------------------------------------------------+ void CGraphElementsCollection::BringToTopAllCanvElm(void) { //--- Create a new list, reset the flag of managing memory and sort by ZOrder CArrayObj *list=new CArrayObj(); list.FreeMode(false); list.Sort(SORT_BY_CANV_ELEMENT_ZORDER); //--- Declare the graphical element object CGCnvElement *obj=NULL; //--- In the loop by the total number of all graphical elements, for(int i=0;i<this.m_list_all_canv_elm_obj.Total();i++) { //--- get the next object obj=this.m_list_all_canv_elm_obj.At(i); if(obj==NULL) continue; //--- and insert it to the list in the order of sorting by ZOrder list.InsertSort(obj); } //--- In a loop by the created list sorted by ZOrder, for(int i=0;i<list.Total();i++) { //--- get the next object obj=list.At(i); if(obj==NULL) continue; //--- and move it to the foreground obj.BringToTop(); } //--- Remove the list sorted by 'new' delete list; } //+------------------------------------------------------------------+
Bem, o que vemos aqui: declaramos um novo objeto lista CArrayObj e redefinimos seu sinalizador de gerenciamento de memória. Em seguida, configuramos o sinalizador para classificar a lista pela propriedade ZOrder. Para que serve tudo isso? Para que possamos classificar objetos CObject por determinada propriedade, é necessário definir o valor de tal propriedade como um sinalizador de classificação. Em seguida, o método virtual Compare(), implementado em todos os objetos descendentes da classe CObject, retornará o valor necessário para a operação do método Sort() para comparar as mesmas propriedades (a propriedade configurada como modo de ordenação) de dois objetos.
A redefinição do sinalizador de gerenciamento de memória é necessário para que no array criado possamos obter cópias de objetos a partir da lista coleção, e não ponteiros para objetos na coleção. Isso é importante, porque se forem ponteiros, qualquer alteração neles na nova lista alterará automaticamente o objeto na lista coleção, já que, afinal, são apenas ponteiros localizados em duas listas para o mesmo objeto localizado na lista coleção . Aqui, para que não haja "surpresas", criamos uma lista-cópia independente, cuja alteração não afetará o original. Além disso, após a conclusão do método, devemos excluir a lista criada para evitar vazamentos de memória. Isso também está na documentação da Biblioteca Padrão (FreeMode):
A definição do sinalizador de gerenciamento de memória é um momento importante no uso da classe CArrayObj. Como os elementos de um array são ponteiros para objetos dinâmicos, é importante determinar o que fazer com eles quando forem removidos do array.
Se o sinalizador estiver definido, quando um elemento for removido do array, o elemento será excluído automaticamente pelo operador delete. Se o sinalizador não estiver definido, assume-se que o ponteiro para o objeto a ser excluído permanece em outro lugar no programa do usuário e será liberado por ele (o programa) posteriormente.
Se o programa do usuário redefinir o sinalizador de gerenciamento de memória, o usuário deve entender que é sua responsabilidade remover os elementos do array antes de encerrar o programa, caso contrário, a memória ocupada pelos elementos quando foram criados pelo novo operador permanecerá livre.
Com grandes quantidades de dados, isso pode provocar, no final, até mesmo o mau funcionamento do terminal. Se o programa do usuário não redefinir o sinalizador de gerenciamento de memória, haverá outro «escolho».
O uso de ponteiros-elementos de array armazenados em algum lugar nas variáveis locais após a exclusão do array irá causar um erro crítico e travar o programa do usuário. Por padrão, o sinalizador de gerenciamento de memória está definido, ou seja, a própria classe do array é responsável por liberar a memória dos elementos.
Em seguida, com um loop percorremos a lista-coleção de elementos gráficos e inserimos cada objeto em uma nova lista em ordem de classificação.
Após a conclusão do loop, obteremos uma nova lista, mas classificada em ordem crescente das propriedades ZOrder de todos os elementos gráficos.
Em um loop por essa lista, pegamos cada elemento e o trazemos para o primeiro plano. Além disso, cada um sempre se tornará mais alto que o anterior.
No final do ciclo vamos nos certificar de excluir a lista criada.
Método que retorna o ZOrder máximo de todos os elementos CCanvas:
//+------------------------------------------------------------------+ //| Return the maximum ZOrder of all CCanvas elements | //+------------------------------------------------------------------+ long CGraphElementsCollection::GetZOrderMax(void) { this.m_list_all_canv_elm_obj.Sort(SORT_BY_CANV_ELEMENT_ZORDER); int index=CSelect::FindGraphCanvElementMax(this.GetListCanvElm(),CANV_ELEMENT_PROP_ZORDER); CGCnvElement *obj=this.m_list_all_canv_elm_obj.At(index); return(obj!=NULL ? obj.Zorder() : WRONG_VALUE); } //+------------------------------------------------------------------+
Aqui: ordenamos a lista pela propriedade ZOrder e obtemos o índice do elemento na lista com o valor máximo da propriedade.
Obtemos da lista um ponteiro para o elemento com base no índice recebido e retornamos a propriedade ZOrder desse elemento.
Se o elemento não foi recebido, o método retornará -1.
Se tivermos, por exemplo, três objetos GUI, é lógico que eles tenham três valores ZOrder. Nesse caso, todos os objetos inicialmente têm um valor ZOrder de zero, pois eles estão todos na parte inferior. Assim que capturarmos qualquer objeto, seu ZOrder deve aumentar em 1. Mas primeiro devemos ver se algum outro objeto tem um valor ZOrder igual ou maior que 1, pois o último objeto selecionado deve se tornar maior que todos os outros, isto é, em 1 a mais que o ZOrder máximo em relação a todos os disponíveis. Claro, podemos obter o ZOrder máximo e simplesmente aumentá-lo em 1, e depois não pensar em nada. Mas isso é de alguma forma sermos desleixados, e é melhor fazermos com que dos três objetos possamos ter apenas valores de ZOrder no intervalo de 0 a 2.
Sendo assim, para o último objeto selecionado, precisamos aumentar o ZOrder em 1, ou deixá-lo o mais alto possível (com base no número total de objetos, começando do zero), e para o resto, diminuir o ZOrder em 1. Mas, ao mesmo tempo, se o objeto estiver na parte inferior e seu ZOrder já for zero, não o reduzimos. Portanto, a alteração nos valores de ZOrder será feita “passando uma rodada” de acordo com o número de objetos GUI.
Método que define o ZOrder para o elemento especificado e ajusta os elementos restantes:
//+------------------------------------------------------------------+ //| Set ZOrde to the specified element | //| and adjust it in other elements | //+------------------------------------------------------------------+ bool CGraphElementsCollection::SetZOrderMAX(CGCnvElement *obj) { //--- Get the maximum ZOrder of all graphical elements long max=this.GetZOrderMax(); //--- If an invalid pointer to the object has been passed or the maximum ZOrder has not been received, return 'false' if(obj==NULL || max<0) return false; //--- Declare the variable for storing the method result bool res=true; //--- If the maximum ZOrder is zero, ZOrder is equal to 1, //--- if the maximum ZOrder is less than (the total number of graphical elements)-1, ZOrder will exceed it by 1, //--- otherwise, ZOrder will be equal to (the total number of graphical elements)-1 long value=(max==0 ? 1 : max<this.m_list_all_canv_elm_obj.Total()-1 ? max+1 : this.m_list_all_canv_elm_obj.Total()-1); //--- If failed to set ZOrder for an object passed to the method, return 'false' if(!obj.SetZorder(value,false)) return false; //--- 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,TextByLanguage("Тест: ID ","Test. ID ")+(string)form.ID()+", ZOrder "+(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); //--- Get the list of graphical elements without an object whose ID is equal to the ID of the object passed to the method CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_ID,obj.ID(),NO_EQUAL); //--- If failed to obtain the list and the list size exceeds one, //--- which indicates the presence of other objects in it in addition to the one sorted by ID, return 'false' if(list==NULL && this.m_list_all_canv_elm_obj.Total()>1) return false; //--- 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,TextByLanguage("Тест: ID ","Test. ID ")+(string)form.ID()+", ZOrder "+(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; } //+------------------------------------------------------------------+
Cada linha do método é comentada para explicar a lógica do mesmo. Com a ressalva a que, para teste, a exibição de texto na forma é adicionada aqui com uma indicação de seu valor ZOrder, para que possamos ver a mudança nessas propriedades para cada objeto-forma durante o teste.
Temos dois casos em que precisaremos mover objetos GUI acima de objetos gráficos recém-construídos:
- Adição de um objeto gráfico padrão a um gráfico manualmente,
- Adição de um objeto gráfico (padrão ou composto estendido) programaticamente.
Essas situações são tratadas independentemente na biblioteca.
Portanto, precisamos adicionar uma chamada a um método em lugares diferentes que eleve os objetos GUI para um nível superior.
Ao criar um objeto gráfico programaticamente, podemos fazer uma chamada para redesenhar objetos GUI diretamente em um método privado que adiciona o objeto gráfico padrão criado à lista, este é exatamente o lugar onde é determinado se a criação de um objeto gráfico teve sucesso ou não, e apenas no final podemos adicionar uma chamada de método para trazer todos os objetos GUI para a frente:
//--- Add a newly created standard graphical object to the list bool AddCreatedObjToList(const string source,const long chart_id,const string name,CGStdGraphObj *obj) { if(!this.m_list_all_graph_obj.Add(obj)) { CMessage::ToLog(source,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST); ::ObjectDelete(chart_id,name); delete obj; return false; } //--- Move all the forms above the created object on the chart and redraw the chart this.BringToTopAllCanvElm(); ::ChartRedraw(chart_id); return true; }
Depois de chamar o método, precisamos atualizar o gráfico para exibir as alterações imediatamente.
Ao declarar objetos gráficos manualmente, este evento deve ser definido no método que atualiza a lista de todos os objetos gráficos.
No bloco de código onde a adição de um novo objeto gráfico é definida, vamos escrever uma chamada de método que traz os objetos GUI para o primeiro plano:
//+------------------------------------------------------------------+ //| Update the list of all graphical objects | //+------------------------------------------------------------------+ void CGraphElementsCollection::Refresh(void) { this.RefreshForExtraObjects(); //--- Declare variables to search for charts long chart_id=0; int i=0; //--- In the loop by all open charts in the terminal (no more than 100) while(i<CHARTS_MAX) { //--- Get the chart ID chart_id=::ChartNext(chart_id); if(chart_id<0) break; //--- Get the pointer to the object for managing graphical objects //--- and update the list of graphical objects by chart ID CChartObjectsControl *obj_ctrl=this.RefreshByChartID(chart_id); //--- If failed to get the pointer, move on to the next chart if(obj_ctrl==NULL) continue; //--- If the number of objects on the chart changes if(obj_ctrl.IsEvent()) { //--- If a graphical object is added to the chart if(obj_ctrl.Delta()>0) { //--- Get the list of added graphical objects and move them to the collection list //--- (if failed to move the object to the collection, move on to the next object) if(!this.AddGraphObjToCollection(DFUN_ERR_LINE,obj_ctrl)) continue; //--- Move all the forms above the created object on the chart and redraw the chart this.BringToTopAllCanvElm(); ::ChartRedraw(obj_ctrl.ChartID()); } //--- If the graphical object has been removed else if(obj_ctrl.Delta()<0) { int index=WRONG_VALUE; //--- In the loop by the number of removed graphical objects for(int j=0;j<-obj_ctrl.Delta();j++) { // Find an extra object in the list CGStdGraphObj *obj=this.FindMissingObj(chart_id,index); if(obj!=NULL) { //--- Get the removed object parameters long lparam=obj.ChartID(); string sparam=obj.Name(); double dparam=(double)obj.TimeCreate(); //--- If this is an extended graphical object if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) { this.DeleteExtendedObj(obj); } //--- Move the graphical object class object to the list of removed objects //--- and send the event to the control program chart if(this.MoveGraphObjToDeletedObjList(index)) ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_DELETE,lparam,dparam,sparam); } } } } //--- Increase the loop index i++; } } //+------------------------------------------------------------------+
No último artigo, quando realizamos o teste, determinamos que as coordenadas de um objeto gráfico composto nem sempre estão corretamente circunscritas aos limites do gráfico. Em alguns casos, o objeto começa a "se deformar", dependendo da localização de seus pontos de ancoragem, uma vez atingida a borda do gráfico.
É após o teste que ficamos sabendo por que motivo isso estava acontecendo:
... os deslocamentos dos pontos de ancoragem são calculados em relação ao ponto central. Isso significa que um ponto terá um deslocamento positivo e o outro terá um negativo. Ao alterar a localização dos pontos de ancoragem em relação ao ponto central, temos um erro no cálculo das restrições.
Vamos fazer algumas alterações para corrigir isso. Calcularemos imediatamente as coordenadas do ponto central, e mediremos os deslocamentos desde os pontos de ancoragem do objeto gráfico até seu ponto central. Ao calcular as restrições ao tamanho do gráfico, usaremos um valor de deslocamento sem sinal. Desse modo, o sinal do deslocamento não afetará o cálculo.
No manipulador de eventos, no bloco de processamento de formas do objeto gráfico padrão estendido, inserimos um novo cálculo de restrição e exibimos as informações de depuração no gráfico:
//--- If the form is central for managing all pivot points of a graphical object else { //--- Get screen coordinates of all object pivot points and write them to the m_data_pivot_point structure if(this.GetPivotPointCoordsAll(ext,m_data_pivot_point)) { //--- In the loop by the number of object pivot points, for(int i=0;i<(int)this.m_data_pivot_point.Size();i++) { //--- limit the screen coordinates of the current pivot point so that they do not move beyond the chart borders //--- By X coordinate if(x+shift-::fabs(this.m_data_pivot_point[i].ShiftX)<0) x=-shift+::fabs(m_data_pivot_point[i].ShiftX); if(x+shift+::fabs(this.m_data_pivot_point[i].ShiftX)>chart_width) x=chart_width-shift-::fabs(this.m_data_pivot_point[i].ShiftX); //--- By Y coordinate if(y+shift-::fabs(this.m_data_pivot_point[i].ShiftY)<0) y=-shift+::fabs(this.m_data_pivot_point[i].ShiftY); if(y+shift+::fabs(this.m_data_pivot_point[i].ShiftY)>chart_height) y=chart_height-shift-::fabs(this.m_data_pivot_point[i].ShiftY); //--- set the calculated coordinates to the current object pivot point ext.ChangeCoordsExtendedObj(x+shift+this.m_data_pivot_point[i].ShiftX,y+shift+this.m_data_pivot_point[i].ShiftY,i); } } //--- Display debugging comments on the chart if(m_data_pivot_point.Size()>=2) { int max_x,min_x,max_y,min_y; if(ext.GetMinCoordXY(min_x,min_y) && ext.GetMaxCoordXY(max_x,max_y)) Comment ( "MinX=",min_x,", MaxX=",max_x,"\n", "MaxY=",min_y,", MaxY=",max_y ); } }
Assim que pressionamos o botão do mouse no objeto GUI, precisamos trazê-lo para a frente e definir seu ZOrder máximo.
No manipulador de eventos, no bloco de processamento do cursor dentro da área ativa com o botão do mouse pressionado, inserimos um bloco de código para definir o ZOrder máximo para o objeto:
//--- 'The cursor is inside the active area, any mouse button is clicked' event handler if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED && !move) { pressed_form=true; // the flag of holding the mouse button on the form //--- If the left mouse button is pressed if(this.m_mouse.IsPressedButtonLeft()) { //--- Set flags and form parameters move=true; // movement flag form.SetInteraction(true); // flag of the form interaction with the environment form.BringToTop(); // form on the background - above all others form.SetOffsetX(this.m_mouse.CoordX()-form.CoordX()); // Cursor shift relative to the X coordinate form.SetOffsetY(this.m_mouse.CoordY()-form.CoordY()); // Cursor shift relative to the Y coordinate this.ResetAllInteractionExeptOne(form); // Reset interaction flags for all forms except the current one //--- Get the maximum ZOrder long zmax=this.GetZOrderMax(); //--- If the maximum ZOrder has been received and the form's ZOrder is less than the maximum one or the maximum ZOrder of all forms is equal to zero if(zmax>WRONG_VALUE && (form.Zorder()<zmax || zmax==0)) { //--- If the form is not a control point for managing an extended standard graphical object, //--- set the form's ZOrder above all others if(form.Type()!=OBJECT_DE_TYPE_GFORM_CONTROL) this.SetZOrderMAX(form); } } }
Aqui definimos o ZOrder para objetos-formas que não são objetos gráficos padrão estendidos, já que eles não precisam disso.
Tendo em vista que o manipulador de eventos da coleção de classes de elementos gráficos é bastante volumoso, não faz sentido apresentar seu código completo aqui. Podemos nos familiarizar com suas alterações (discutidas acima) nos arquivos anexados ao artigo.
Vamos alterar o cálculo dos deslocamentos no método que retorna as coordenadas de tela de cada ponto de ancoragem do objeto gráfico.
Agora vamos calcular imediatamente as coordenadas do ponto de ancoragem central (para o qual o objeto se move) e a partir dele já calculamos os deslocamentos. Mais exatamente, vamos calcular os deslocamentos desde cada ponto de ancoragem até o ponto central:
//+------------------------------------------------------------------+ //| Return screen coordinates | //| of each graphical object pivot point | //+------------------------------------------------------------------+ bool CGraphElementsCollection::GetPivotPointCoordsAll(CGStdGraphObj *obj,SDataPivotPoint &array_pivots[]) { //--- Central point coordinates int xc=0, yc=0; //--- If failed to increase the array of structures to match the number of pivot points, //--- inform of that in the journal and return 'false' if(::ArrayResize(array_pivots,obj.Pivots())!=obj.Pivots()) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); return false; } //--- In the loop by the number of graphical object pivot points for(int i=0;i<obj.Pivots();i++) { //--- Convert the object coordinates into screen ones. If failed, inform of that and return 'false' if(!::ChartTimePriceToXY(obj.ChartID(),obj.SubWindow(),obj.Time(i),obj.Price(i),array_pivots[i].X,array_pivots[i].Y)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_CONV_GRAPH_OBJ_COORDS_TO_XY); return false; } } //--- Depending on the graphical object type switch(obj.TypeGraphObject()) { //--- One pivot point in screen coordinates case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : break; //--- One pivot point (price only) case OBJ_HLINE : break; //--- One pivot point (time only) case OBJ_VLINE : case OBJ_EVENT : break; //--- Two pivot points and a central one //--- Lines case OBJ_TREND : case OBJ_TRENDBYANGLE : case OBJ_CYCLES : case OBJ_ARROWED_LINE : //--- Channels case OBJ_CHANNEL : case OBJ_STDDEVCHANNEL : case OBJ_REGRESSION : //--- Gann case OBJ_GANNLINE : case OBJ_GANNGRID : //--- Fibo case OBJ_FIBO : case OBJ_FIBOTIMES : case OBJ_FIBOFAN : case OBJ_FIBOARC : case OBJ_FIBOCHANNEL : case OBJ_EXPANSION : //--- Calculate the central point coordinates xc=(array_pivots[0].X+array_pivots[1].X)/2; yc=(array_pivots[0].Y+array_pivots[1].Y)/2; //--- Calculate the shifts of all pivot points from the central one and write them to the structure array array_pivots[0].ShiftX=array_pivots[0].X-xc; // X shift from 0 to the central point array_pivots[1].ShiftX=array_pivots[1].X-xc; // X shift from 1 to the central point array_pivots[0].ShiftY=array_pivots[0].Y-yc; // Y shift from 0 to the central point array_pivots[1].ShiftY=array_pivots[1].Y-yc; // Y shift from 1 to the central point return true; //--- Channels case OBJ_PITCHFORK : break; //--- Gann case OBJ_GANNFAN : break; //--- Elliott case OBJ_ELLIOTWAVE5 : break; case OBJ_ELLIOTWAVE3 : break; //--- Shapes case OBJ_RECTANGLE : break; case OBJ_TRIANGLE : break; case OBJ_ELLIPSE : break; //--- Arrows case OBJ_ARROW_THUMB_UP : break; case OBJ_ARROW_THUMB_DOWN : break; case OBJ_ARROW_UP : break; case OBJ_ARROW_DOWN : break; case OBJ_ARROW_STOP : break; case OBJ_ARROW_CHECK : break; case OBJ_ARROW_LEFT_PRICE : break; case OBJ_ARROW_RIGHT_PRICE : break; case OBJ_ARROW_BUY : break; case OBJ_ARROW_SELL : break; case OBJ_ARROW : break; //--- Graphical objects with time/price coordinates case OBJ_TEXT : break; case OBJ_BITMAP : break; //--- default: break; } return false; } //+------------------------------------------------------------------+
Agora todos os deslocamentos e restrições devem ser calculados corretamente, o que veremos durante os testes.
Teste
Para testar, vamos pegar o Expert Advisor do artigo anterior e salvá-lo em uma nova pasta \MQL5\Experts\TestDoEasy\Part100\ com o novo nome TestDoEasyPart100.mq5.
Como durante o teste queremos ver imediatamente o valor de propriedade ZOrder em cada forma, no manipulador OnInit() vamos inserir a definição do valor zero dessa propriedade para a forma (o objeto na parte inferior) e vamos adicionar a exibição do identificador do objeto-forma e o valor de sua propriedade ZOrder:
//+------------------------------------------------------------------+ //| 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 form objects CForm *form=NULL; for(int i=0;i<FORMS_TOTAL;i++) { //--- When creating an object, pass all the required parameters to it form=new CForm("Form_0"+string(i+1),30,(form==NULL ? 100 : form.BottomEdge()+20),100,30); if(form==NULL) continue; //--- Set activity and moveability flags for the form form.SetActive(true); form.SetMovable(true); //--- Set the form ID and the index in the list of objects form.SetID(i); form.SetNumber(0); // (0 - main form object) Auxiliary objects may be attached to the main one. The main object is able to manage them //--- Set the opacity of 200 form.SetOpacity(245); //--- The form background color is set as the first color from the color array form.SetColorBackground(array_clr[0]); //--- Form outlining frame color form.SetColorFrame(clrDarkBlue); //--- Draw the shadow drawing flag form.SetShadow(false); //--- Calculate the shadow color as the chart background color converted to the monochrome one color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100); //--- If the settings specify the usage of the chart background color, replace the monochrome color with 20 units //--- Otherwise, use the color specified in the settings for drawing the shadow color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,-20) : InpColorForm3); //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes //--- Set the shadow opacity to 200, while the blur radius is equal to 4 form.DrawShadow(3,3,clr,200,4); //--- Fill the form background with a vertical gradient form.Erase(array_clr,form.Opacity(),true); //--- Draw an outlining rectangle at the edges of the form form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity()); form.Done(); //--- Set ZOrder to zero, display the text describing the gradient type and update the form //--- Text parameters: the text coordinates and the anchor point in the form center //--- Create a new text animation frame with the ID of 0 and display the text on the form form.SetZorder(0,false); form.TextOnBG(0,TextByLanguage("Тест: ID ","Test: ID ")+(string)form.ID()+", ZOrder "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true); //--- Add the form to the list if(!engine.GraphAddCanvElmToCollection(form)) delete form; } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Compilamos e executamos o Expert Advisor no gráfico:
Como podemos ver, criados os objetos-formas, todos têm um valor ZOrder de zero, sendo que os objetos gráficos ainda são construídos "sob eles". A mudança no valor ZOrder de cada objeto ocorre "passando uma rodada", não mais do que o número de objetos-formas no gráfico (contando a partir de zero). Qualquer objeto gráfico construído sempre aparece "abaixo" dos objetos GUI, e sua posição relativa permanece inalterada, o que significa que eles estão distribuídos corretamente na lista de acordo com os valores de propriedade ZOrder. Finalmente, o objeto gráfico composto agora está corretamente limitado às bordas da tela e não é distorcido em nenhum local de seus pontos de ancoragem e, como outros objetos gráficos, é desenhado abaixo dos objetos-formas.
O que virá a seguir?
Com o próximo artigo, abriremos uma grande seção sobre a criação de objetos GUI no estilo Windows Forms.
Mas isso não significa que objetos gráficos estendidos e objetos compostos baseados neles não serão mais desenvolvidos, só precisamos de controles completos para seu desenvolvimento posterior. Assim, à medida que a próxima seção da biblioteca se desenvolve, continuaremos gradualmente a desenvolver e refinar objetos gráficos estendidos.
*Artigos desta série:
Gráficos na biblioteca DoEasy (Parte 93): Preparando a funcionalidade para criar objetos gráficos compostos
Gráficos na biblioteca DoEasy (Parte 94): Objetos gráficos compostos, movimentação e eliminação
Gráficos na biblioteca DoEasy (Parte 95): Controles de objetos gráficos compostos
Gráficos na biblioteca DoEasy (Parte 96): Trabalhando com eventos de mouse/gráfico em objetos-forma
Gráficos na biblioteca DoEasy (Parte 97): Processando o movimento dos objetos-formas independentemente
Gráficos na biblioteca DoEasy (Parte 98): Movendo pontos de ancoragem de objetos gráficos padrão estendidos
Gráficos na biblioteca DoEasy (Parte 99): Movendo um objeto gráfico estendido com um ponto de controle
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/10634
- 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