Gráficos na biblioteca DoEasy (Parte 86): coleção de objetos gráficos, controlamos a modificação de propriedades
Sumário
- Ideia
- Rastreamento de modificações de propriedades de objetos gráficos
- Rastreamento de remoção de objetos gráficos
- Teste
- O que vem agora?
Ideia
No artigo anterior tornamos possível armazenar objetos gráficos padrão recém-criados numa classe-coleção de objetos gráficos - monitoramos a aparência de novos objetos no gráfico, criamos objetos de classe, que correspondem ao tipo de objeto no gráfico, e os adicionamos à lista-coleção. Mas isso não é suficiente para um gerenciamento completo de objetos gráficos. Precisamos controlar todas as alterações nas propriedades dos objetos gráficos no gráfico, bem como sua exclusão ou renomeação.
Como para leitura das propriedades dos objetos gráficos são utilizadas funções da série ObjectGetXXX, não poderemos verificar constantemente os valores de cada propriedade de cada objeto gráfico no temporizador porque estas funções são síncronas, ou seja, esperar-se que os comandos sejam executados, o que pode consumir muitos recursos se houver um grande número de objetos gráficos.
Neste ponto estamos perante um dilema: devemos quer seja usar o temporizador para consultar cada propriedade de cada objeto gráfico, com todas as implicações da sincronização da função de consulta da propriedade ou selecionar o uso de modelo de evento — reagimos ao evento no manipulador OnChartEvent(), que infelizmente não roda no testador de estratégias (lembramos que criamos a operação do temporizador no testador na biblioteca através dos manipuladores OnTick() e OnCalculate()).
Depois de pesar todos os prós e contras, decidi realizar o rastreamento das alterações nas propriedades dos objetos gráficos no manipulador de eventos do gráfico, quer dizer, usaremos o modelo orientado a eventos que, apesar de simplificar a escrita do código, mas impõe restrições para trabalhar no testador. No entanto, no testador (pelo menos por agora) não podemos trabalhar com objetos gráficos - adicioná-los à janela do testador e, de alguma forma, alterar as propriedades. Consequentemente, devemos trabalhar com objetos gráficos apenas em gráficos "ao vivo", onde o manipulador de eventos está sendo executado.
Hoje faremos uma versão de avaliação do processamento de eventos de objetos gráficos apenas para o gráfico atual - aquele no qual o programa está sendo executado. Uma vez entendido que tudo funciona corretamente, faremos manipuladores de eventos completos - para cada gráfico aberto - que enviarão os eventos para o gráfico principal do programa, onde a biblioteca os coletará e processará em respectiva coleção de objetos gráficos.
Rastreamento de modificações de propriedades de objetos gráficos
Quanto ao manipulador OnChartEvent() estamos interessados nos eventos:
- CHARTEVENT_OBJECT_CREATE — Criação de objeto gráfico (se para o gráfico está definida a propriedade CHART_EVENT_OBJECT_CREATE=true);
- CHARTEVENT_OBJECT_CHANGE — Mudança de propriedades do objeto através da caixa de diálogo de propriedades;
- CHARTEVENT_OBJECT_DELETE — Eliminação de objeto gráfico (se para o gráfico estiver definida a propriedade CHART_EVENT_OBJECT_DELETE=true);
- CHARTEVENT_OBJECT_DRAG — Deslocamento de objeto gráfico com o mouse.
Em princípio, no artigo anterior já criamos um evento de criação de objeto gráfico, sem referência ao manipulador OnChartEvent().
Precisamos do evento de alteração das propriedades de objeto gráfico através da caixa de diálogo de propriedades no terminal para controlar a alteração de propriedades do objeto manualmente pelo usuário do terminal cliente.
Também já implementamos o evento de exclusão de objeto gráfico - a biblioteca rastreia o número de objetos gráficos em todos os gráficos do terminal e tem sinalizadores de evento para cada gráfico aberto - quando o número de objetos no gráfico diminui, podemos descobrir o número de objetos excluídos do gráfico e processar a situação.
Precisamos de um evento de deslocamento de objeto gráfico para controlar as mudanças de posição de objeto gráfico, como um todo, e de seus pontos de ancoragem individuais, em particular.
É importante ressaltar sobre o evento de deslocamento que também é ativado quando o objeto é criado manualmente. Quando clicamos no gráfico para definir o objeto e mantemos pressionado, o objeto já é criado e a biblioteca o vê, cria um objeto de classe e o adiciona à coleção. Além disso, nem todos os valores das propriedades do objeto estão configurados corretamente (afinal, ainda não liberamos o botão e podemos mover o objeto com o mouse ou definir seus outros pontos de ancoragem se o objeto for desenhado com vários pontos). Mas quando soltamos o botão do mouse e o objeto já definiu todos os seus pontos de ancoragem, é criado um evento de deslocamento de objeto gráfico. Quando monitoramos este evento e alteramos os valores das propriedades de um objeto já criado da classe de acordo com os parâmetros definidos do objeto gráfico criado, estaremos definindo assim os valores corretos para todas as propriedades do objeto que acabamos de criar.
Com a alteração do nome de um objeto estaremos disparando três eventos - remoção de objeto, criação de objeto e alteração de propriedades de objeto. Podemos rastrear estes três eventos para ver se algum dos nomes dos objetos existentes mudou. Mas iremos pelo caminho mais simples. Na verdade, quando o nome de um objeto muda, em qualquer caso, o último evento será o evento CHARTEVENT_OBJECT_CHANGE, do qual trataremos. E como todos os objetos são selecionados no terminal por nome e ID do gráfico, podemos verificar qual objeto dos presentes no gráfico ficou faltando na lista-coleção. Encontramos o nome de um objeto no gráfico para o qual não há objeto de classe na coleção (1), encontramos um objeto na coleção para o qual não haja nenhum objeto correspondente pelo nome no gráfico (2) e escrevemos este nome no (1) objeto de classe encontrado na lista-coleção. Parece "assustador", mas na realidade tudo é simples.
Para entender qual propriedade do objeto foi alterada (afinal, recebemos um evento, e ele contém apenas o nome do objeto alterado, mas não há indicação da propriedade alterada), precisamos comparar todas as propriedades do objeto - seus valores atuais com aqueles que estavam antes de receber um evento de alteração das propriedades de objeto. Por isso, precisamos criar mais três matrizes que armazenarão as propriedades do objeto "passadas".
Abrimos o arquivo \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh da classe de objeto gráfico abstrato padrão e na seção privada inserimos as novas matrizes para armazenar as propriedades do objeto "passadas":
//+------------------------------------------------------------------+ //| The class of the abstract standard graphical object | //+------------------------------------------------------------------+ class CGStdGraphObj : public CGBaseObj { private: long m_long_prop[GRAPH_OBJ_PROP_INTEGER_TOTAL]; // Integer properties double m_double_prop[GRAPH_OBJ_PROP_DOUBLE_TOTAL]; // Real properties string m_string_prop[GRAPH_OBJ_PROP_STRING_TOTAL]; // String properties long m_long_prop_prev[GRAPH_OBJ_PROP_INTEGER_TOTAL]; // Integer properties before change double m_double_prop_prev[GRAPH_OBJ_PROP_DOUBLE_TOTAL]; // Real properties before change string m_string_prop_prev[GRAPH_OBJ_PROP_STRING_TOTAL]; // String properties before change //--- Return the index of the array the (1) double and (2) string properties are actually located at int IndexProp(ENUM_GRAPH_OBJ_PROP_DOUBLE property) const { return(int)property-GRAPH_OBJ_PROP_INTEGER_TOTAL; } int IndexProp(ENUM_GRAPH_OBJ_PROP_STRING property) const { return(int)property-GRAPH_OBJ_PROP_INTEGER_TOTAL-GRAPH_OBJ_PROP_DOUBLE_TOTAL; } public:
Na seção pública da classe incorporamos os métodos para definir e retornar as propriedades do objeto "passadas":
public: //--- Set object's (1) integer, (2) real and (3) string properties void SetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property,long value) { this.m_long_prop[property]=value; } void SetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property,double value) { this.m_double_prop[this.IndexProp(property)]=value; } void SetProperty(ENUM_GRAPH_OBJ_PROP_STRING property,string value) { this.m_string_prop[this.IndexProp(property)]=value; } //--- Return object’s (1) integer, (2) real and (3) string property from the properties array long GetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property) const { return this.m_long_prop[property]; } double GetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property) const { return this.m_double_prop[this.IndexProp(property)]; } string GetProperty(ENUM_GRAPH_OBJ_PROP_STRING property) const { return this.m_string_prop[this.IndexProp(property)]; } //--- Set object's previous (1) integer, (2) real and (3) string properties void SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property,long value) { this.m_long_prop_prev[property]=value; } void SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property,double value){ this.m_double_prop_prev[this.IndexProp(property)]=value;} void SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property,string value){ this.m_string_prop_prev[this.IndexProp(property)]=value;} //--- Return object’s (1) integer, (2) real and (3) string property from the previous properties array long GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property) const { return this.m_long_prop_prev[property]; } double GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property) const { return this.m_double_prop_prev[this.IndexProp(property)]; } string GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property) const { return this.m_string_prop_prev[this.IndexProp(property)]; } //--- Return itself CGStdGraphObj *GetObject(void) { return &this;}
Temos uma propriedade "identificador de objeto" no objeto da classe que descreve o objeto gráfico, necessário para definir um determinado rótulo de objeto, para identificá-lo.
Esta propriedade não deve estar envolvida na definição dos eventos do objeto. Por isso, no método para definir o identificador de objeto, vamos escrever o valor passado para o método em ambas as propriedades - na atual (que escrevemos anteriormente) e na passada (que adicionamos agora):
public: //+--------------------------------------------------------------------+ //|Methods of simplified access and setting graphical object properties| //+--------------------------------------------------------------------+ //--- Object index in the list int Number(void) const { return (int)this.GetProperty(GRAPH_OBJ_PROP_NUM); } void SetNumber(const int number) { this.SetProperty(GRAPH_OBJ_PROP_NUM,number); } //--- Object ID long ObjectID(void) const { return this.GetProperty(GRAPH_OBJ_PROP_ID); } void SetObjectID(const long obj_id) { CGBaseObj::SetObjectID(obj_id); this.SetProperty(GRAPH_OBJ_PROP_ID,obj_id); this.SetPropertyPrev(GRAPH_OBJ_PROP_ID,obj_id); } //--- Graphical object type
O algoritmo de reação ao evento será o seguinte: após o recebimento do evento, precisamos atualizar todos os dados do objeto de classe que descreve o objeto gráfico para que todas as suas propriedades tenham valores reais. Como não sabemos qual propriedade foi alterada, atualizamos todas as propriedades do objeto e, em seguida, comparamos as propriedades atuais com aquelas que o objeto tinha antes de receber o evento de alteração do objeto. Compararemos em três loops todas as três matrizes de propriedades de objetos com as matrizes correspondentes de propriedades anteriores. No momento da comparação, e se a propriedade atual for o seu valor, não for igual ao valor anterior, então temporariamente (ainda estamos fazendo um caso de teste) exibiremos no log uma mensagem sobre a alteração desta propriedade. E assim, para cada diferença encontrada nos valores comparados em cada matriz de propriedades do objeto. Posteriormente, quando já estivermos fazendo o controle das alterações de propriedade para cada um dos gráficos abertos, faremos isso de uma maneira ligeiramente diferente - iremos "adicionar" todas as propriedades alteradas num objeto de evento e receberemos cada um desses objetos na classe-coleção a fim de sinalizar ainda mais em um programa de controle sobre a alteração das propriedades de cada um dos objetos em cada gráfico aberto.
Na seção pública da classedeclaramos um método para sobrescrever todas as propriedades do objeto. Ele será necessário quando um evento de alteração de propriedade for detectado, para, assim, percorrer todas as propriedades de um objeto gráfico e inseri-las nas propriedades do objeto-classe.
Método que verifica se há mudanças nas propriedades do objeto, - o método irá comparar todas as propriedades atuais do objeto com seu estado anterior.
Na seção privada da classe iremos declarar três métodos para obter todas as propriedades do objeto gráfico e gravá-los nas propriedades do objeto-classe.
E o método que copia as propriedades atuais para as anteriores, de modo que na próxima verificação, quando um evento for detectado, elas já possam ser comparadas com as propriedades alteradas recentemente:
//--- Return the description of the object visibility on timeframes string VisibleOnTimeframeDescription(void); //--- Re-write all graphical object properties void PropertiesRefresh(void); //--- Check object property changes void PropertiesCheckChanged(void); private: //--- Get and save (1) integer, (2) real and (3) string properties void GetAndSaveINT(void); void GetAndSaveDBL(void); void GetAndSaveSTR(void); //--- Copy the current data to the previous one void PropertiesCopyToPrevData(void); }; //+------------------------------------------------------------------+
Vamos simplificar o construtor paramétrico protegido. Anteriormente todas as propriedades inerentes a todos os objetos gráficos foram obtidas a partir do objeto gráfico e escritas no objeto de classe:
//+------------------------------------------------------------------+ //| Protected parametric constructor | //+------------------------------------------------------------------+ CGStdGraphObj::CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type, const ENUM_GRAPH_OBJ_BELONG belong, const ENUM_GRAPH_OBJ_GROUP group, const long chart_id,const string name) { //--- Set the object (1) type, type of graphical (2) object, (3) element, (4) subwindow affiliation and (5) index, as well as (6) chart symbol Digits this.m_type=obj_type; CGBaseObj::SetChartID(chart_id); CGBaseObj::SetTypeGraphObject(CGBaseObj::GraphObjectType(obj_type)); CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_STANDARD); CGBaseObj::SetBelong(belong); CGBaseObj::SetGroup(group); CGBaseObj::SetSubwindow(chart_id,name); CGBaseObj::SetDigits((int)::SymbolInfoInteger(::ChartSymbol(chart_id),SYMBOL_DIGITS)); //--- Save integer properties //--- properties inherent in all graphical objects but not present in a graphical object this.m_long_prop[GRAPH_OBJ_PROP_CHART_ID] = CGBaseObj::ChartID(); // Chart ID this.m_long_prop[GRAPH_OBJ_PROP_WND_NUM] = CGBaseObj::SubWindow(); // Chart subwindow index this.m_long_prop[GRAPH_OBJ_PROP_TYPE] = CGBaseObj::TypeGraphObject(); // Graphical object type (ENUM_OBJECT) this.m_long_prop[GRAPH_OBJ_PROP_ELEMENT_TYPE]= CGBaseObj::TypeGraphElement(); // Graphical element type (ENUM_GRAPH_ELEMENT_TYPE) this.m_long_prop[GRAPH_OBJ_PROP_BELONG] = CGBaseObj::Belong(); // Graphical object affiliation this.m_long_prop[GRAPH_OBJ_PROP_GROUP] = CGBaseObj::Group(); // Graphical object group this.m_long_prop[GRAPH_OBJ_PROP_ID] = 0; // Object ID this.m_long_prop[GRAPH_OBJ_PROP_NUM] = 0; // Object index in the list //--- Properties inherent in all graphical objects and present in a graphical object this.m_long_prop[GRAPH_OBJ_PROP_CREATETIME] = ::ObjectGetInteger(chart_id,name,OBJPROP_CREATETIME); // Object creation time this.m_long_prop[GRAPH_OBJ_PROP_TIMEFRAMES] = ::ObjectGetInteger(chart_id,name,OBJPROP_TIMEFRAMES); // Object visibility on timeframes this.m_long_prop[GRAPH_OBJ_PROP_BACK] = ::ObjectGetInteger(chart_id,name,OBJPROP_BACK); // Background object this.m_long_prop[GRAPH_OBJ_PROP_ZORDER] = ::ObjectGetInteger(chart_id,name,OBJPROP_ZORDER); // Priority of a graphical object for receiving the event of clicking on a chart this.m_long_prop[GRAPH_OBJ_PROP_HIDDEN] = ::ObjectGetInteger(chart_id,name,OBJPROP_HIDDEN); // Disable displaying the name of a graphical object in the terminal object list this.m_long_prop[GRAPH_OBJ_PROP_SELECTED] = ::ObjectGetInteger(chart_id,name,OBJPROP_SELECTED); // Object selection this.m_long_prop[GRAPH_OBJ_PROP_SELECTABLE] = ::ObjectGetInteger(chart_id,name,OBJPROP_SELECTABLE); // Object availability this.m_long_prop[GRAPH_OBJ_PROP_TIME] = ::ObjectGetInteger(chart_id,name,OBJPROP_TIME); // First point time coordinate this.m_long_prop[GRAPH_OBJ_PROP_COLOR] = ::ObjectGetInteger(chart_id,name,OBJPROP_COLOR); // Color this.m_long_prop[GRAPH_OBJ_PROP_STYLE] = ::ObjectGetInteger(chart_id,name,OBJPROP_STYLE); // Style this.m_long_prop[GRAPH_OBJ_PROP_WIDTH] = ::ObjectGetInteger(chart_id,name,OBJPROP_WIDTH); // Line width //--- Properties belonging to different graphical objects this.m_long_prop[GRAPH_OBJ_PROP_FILL] = 0; // Object color filling this.m_long_prop[GRAPH_OBJ_PROP_READONLY] = 0; // Ability to edit text in the Edit object this.m_long_prop[GRAPH_OBJ_PROP_LEVELS] = 0; // Number of levels this.m_long_prop[GRAPH_OBJ_PROP_LEVELCOLOR] = 0; // Level line color this.m_long_prop[GRAPH_OBJ_PROP_LEVELSTYLE] = 0; // Level line style this.m_long_prop[GRAPH_OBJ_PROP_LEVELWIDTH] = 0; // Level line width this.m_long_prop[GRAPH_OBJ_PROP_ALIGN] = 0; // Horizontal text alignment in the Edit object (OBJ_EDIT) this.m_long_prop[GRAPH_OBJ_PROP_FONTSIZE] = 0; // Font size this.m_long_prop[GRAPH_OBJ_PROP_RAY_LEFT] = 0; // Ray goes to the left this.m_long_prop[GRAPH_OBJ_PROP_RAY_RIGHT] = 0; // Ray goes to the right this.m_long_prop[GRAPH_OBJ_PROP_RAY] = 0; // Vertical line goes through all windows of a chart this.m_long_prop[GRAPH_OBJ_PROP_ELLIPSE] = 0; // Display the full ellipse of the Fibonacci Arc object this.m_long_prop[GRAPH_OBJ_PROP_ARROWCODE] = 0; // Arrow code for the "Arrow" object this.m_long_prop[GRAPH_OBJ_PROP_ANCHOR] = 0; // Position of the binding point of the graphical object this.m_long_prop[GRAPH_OBJ_PROP_XDISTANCE] = 0; // Distance from the base corner along the X axis in pixels this.m_long_prop[GRAPH_OBJ_PROP_YDISTANCE] = 0; // Distance from the base corner along the Y axis in pixels this.m_long_prop[GRAPH_OBJ_PROP_DIRECTION] = 0; // Gann object trend this.m_long_prop[GRAPH_OBJ_PROP_DEGREE] = 0; // Elliott wave marking level this.m_long_prop[GRAPH_OBJ_PROP_DRAWLINES] = 0; // Display lines for Elliott wave marking this.m_long_prop[GRAPH_OBJ_PROP_STATE] = 0; // Button state (pressed/released) this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID] = 0; // Chart object ID (OBJ_CHART). this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PERIOD] = 0; // Chart object period< this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE] = 0; // Time scale display flag for the Chart object this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE] = 0; // Price scale display flag for the Chart object this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE] = 0; // Chart object scale this.m_long_prop[GRAPH_OBJ_PROP_XSIZE] = 0; // Object width along the X axis in pixels. this.m_long_prop[GRAPH_OBJ_PROP_YSIZE] = 0; // Object height along the Y axis in pixels. this.m_long_prop[GRAPH_OBJ_PROP_XOFFSET] = 0; // X coordinate of the upper-left corner of the visibility area. this.m_long_prop[GRAPH_OBJ_PROP_YOFFSET] = 0; // Y coordinate of the upper-left corner of the visibility area. this.m_long_prop[GRAPH_OBJ_PROP_BGCOLOR] = 0; // Background color for OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL this.m_long_prop[GRAPH_OBJ_PROP_CORNER] = 0; // Chart corner for binding a graphical object this.m_long_prop[GRAPH_OBJ_PROP_BORDER_TYPE] = 0; // Border type for "Rectangle border" this.m_long_prop[GRAPH_OBJ_PROP_BORDER_COLOR] = 0; // Border color for OBJ_EDIT and OBJ_BUTTON //--- Save real properties this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_PRICE)] = ::ObjectGetDouble(chart_id,name,OBJPROP_PRICE); // Price coordinate this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELVALUE)] = 0; // Level value this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_SCALE)] = 0; // Scale (property of Gann objects and Fibonacci Arcs objects) this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_ANGLE)] = 0; // Angle this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_DEVIATION)] = 0; // Deviation of the standard deviation channel //--- Save string properties this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_NAME)] = name; // Object name this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TEXT)] = ::ObjectGetString(chart_id,name,OBJPROP_TEXT); // Object description (the text contained in the object) this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TOOLTIP)] = ::ObjectGetString(chart_id,name,OBJPROP_TOOLTIP);// Tooltip text this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELTEXT)] = ""; // Level description this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_FONT)] = ""; // Font this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_BMPFILE)] = ""; // BMP file name for the "Bitmap Level" object this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL)]= ""; // Chart object symbol //--- Save basic properties in the parent object this.m_create_time=(datetime)this.GetProperty(GRAPH_OBJ_PROP_CREATETIME); this.m_back=(bool)this.GetProperty(GRAPH_OBJ_PROP_BACK); this.m_selected=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED); this.m_selectable=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE); this.m_hidden=(bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN); this.m_name=this.GetProperty(GRAPH_OBJ_PROP_NAME); } //+-------------------------------------------------------------------+
Agora todas estas linhas serão transferidas para métodos separados que serão chamado desde o método PropertiesRefresh().
Por isso, removemos essas linhas, e agora o construtor fica assim:
//+------------------------------------------------------------------+ //| Protected parametric constructor | //+------------------------------------------------------------------+ CGStdGraphObj::CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type, const ENUM_GRAPH_OBJ_BELONG belong, const ENUM_GRAPH_OBJ_GROUP group, const long chart_id,const string name) { //--- Set the object (1) type, type of graphical (2) object, (3) element, (4) subwindow affiliation and (5) index, as well as (6) chart symbol Digits this.m_type=obj_type; this.SetName(name); CGBaseObj::SetChartID(chart_id); CGBaseObj::SetTypeGraphObject(CGBaseObj::GraphObjectType(obj_type)); CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_STANDARD); CGBaseObj::SetBelong(belong); CGBaseObj::SetGroup(group); CGBaseObj::SetSubwindow(chart_id,name); CGBaseObj::SetDigits((int)::SymbolInfoInteger(::ChartSymbol(chart_id),SYMBOL_DIGITS)); //--- Save integer properties //--- properties inherent in all graphical objects but not present in a graphical object this.m_long_prop[GRAPH_OBJ_PROP_CHART_ID] = CGBaseObj::ChartID(); // Chart ID this.m_long_prop[GRAPH_OBJ_PROP_WND_NUM] = CGBaseObj::SubWindow(); // Chart subwindow index this.m_long_prop[GRAPH_OBJ_PROP_TYPE] = CGBaseObj::TypeGraphObject(); // Graphical object type (ENUM_OBJECT) this.m_long_prop[GRAPH_OBJ_PROP_ELEMENT_TYPE]= CGBaseObj::TypeGraphElement(); // Graphical element type (ENUM_GRAPH_ELEMENT_TYPE) this.m_long_prop[GRAPH_OBJ_PROP_BELONG] = CGBaseObj::Belong(); // Graphical object affiliation this.m_long_prop[GRAPH_OBJ_PROP_GROUP] = CGBaseObj::Group(); // Graphical object group this.m_long_prop[GRAPH_OBJ_PROP_ID] = 0; // Object ID this.m_long_prop[GRAPH_OBJ_PROP_NUM] = 0; // Object index in the list //--- Save the properties inherent in all graphical objects and present in a graphical object this.PropertiesRefresh(); //--- Save basic properties in the parent object this.m_create_time=(datetime)this.GetProperty(GRAPH_OBJ_PROP_CREATETIME); this.m_back=(bool)this.GetProperty(GRAPH_OBJ_PROP_BACK); this.m_selected=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED); this.m_selectable=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE); this.m_hidden=(bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN); //--- Save the current properties to the previous ones this.PropertiesCopyToPrevData(); } //+-------------------------------------------------------------------+
Para que o método PropertiesRefresh() funcione corretamente, ele deve saber o nome do objeto gráfico do qual receberá os dados. Anteriormente, o nome era escrito quase no final - no bloco para leitura de parâmetros de string. Agora calculamos o nome do objeto gráfico e o escrevemos nas propriedades do objeto-classe imediatamente após entrar no construtor. Bem no final, depois de escrever todas as propriedades para o objeto de classe, vamos chamar o método PropertiesCopyToPrevData(), que gravará todas as propriedades já salvas do objeto nas matrizes de propriedades "passadas" - para controlar suas mudanças.
Métodos que recebem do objeto gráfico e armazenam num objeto de classe as propriedades inteiras, reais e de string:
//+------------------------------------------------------------------+ //| Get and save the integer properties | //+------------------------------------------------------------------+ void CGStdGraphObj::GetAndSaveINT(void) { //--- Properties inherent in all graphical objects and present in a graphical object this.m_long_prop[GRAPH_OBJ_PROP_CREATETIME] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CREATETIME); // Object creation time this.m_long_prop[GRAPH_OBJ_PROP_TIMEFRAMES] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_TIMEFRAMES); // Object visibility on timeframes this.m_long_prop[GRAPH_OBJ_PROP_BACK] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BACK); // Background object this.m_long_prop[GRAPH_OBJ_PROP_ZORDER] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ZORDER); // Priority of a graphical object for receiving the event of clicking on a chart this.m_long_prop[GRAPH_OBJ_PROP_HIDDEN] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_HIDDEN); // Disable displaying the name of a graphical object in the terminal object list this.m_long_prop[GRAPH_OBJ_PROP_SELECTED] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_SELECTED); // Object selection this.m_long_prop[GRAPH_OBJ_PROP_SELECTABLE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_SELECTABLE); // Object availability this.m_long_prop[GRAPH_OBJ_PROP_TIME] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_TIME); // First point time coordinate this.m_long_prop[GRAPH_OBJ_PROP_COLOR] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_COLOR); // Color this.m_long_prop[GRAPH_OBJ_PROP_STYLE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STYLE); // Style this.m_long_prop[GRAPH_OBJ_PROP_WIDTH] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_WIDTH); // Line width //--- Properties belonging to different graphical objects this.m_long_prop[GRAPH_OBJ_PROP_FILL] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_FILL); // Fill an object with color this.m_long_prop[GRAPH_OBJ_PROP_READONLY] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_READONLY); // Ability to edit text in the Edit object this.m_long_prop[GRAPH_OBJ_PROP_LEVELS] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELS); // Number of levels this.m_long_prop[GRAPH_OBJ_PROP_LEVELCOLOR] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELCOLOR); // Level line color this.m_long_prop[GRAPH_OBJ_PROP_LEVELSTYLE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELSTYLE); // Level line style this.m_long_prop[GRAPH_OBJ_PROP_LEVELWIDTH] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELWIDTH); // Level line width this.m_long_prop[GRAPH_OBJ_PROP_ALIGN] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ALIGN); // Horizontal text alignment in the Edit object (OBJ_EDIT) this.m_long_prop[GRAPH_OBJ_PROP_FONTSIZE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_FONTSIZE); // Font size this.m_long_prop[GRAPH_OBJ_PROP_RAY_LEFT] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY_LEFT); // Ray goes to the left this.m_long_prop[GRAPH_OBJ_PROP_RAY_RIGHT] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY_RIGHT); // Ray goes to the right this.m_long_prop[GRAPH_OBJ_PROP_RAY] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY); // Vertical line goes through all windows of a chart this.m_long_prop[GRAPH_OBJ_PROP_ELLIPSE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ELLIPSE); // Display the full ellipse of the Fibonacci Arc object this.m_long_prop[GRAPH_OBJ_PROP_ARROWCODE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ARROWCODE); // Arrow code for the "Arrow" object this.m_long_prop[GRAPH_OBJ_PROP_ANCHOR] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ANCHOR); // Position of the binding point of the graphical object this.m_long_prop[GRAPH_OBJ_PROP_XDISTANCE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XDISTANCE); // Distance from the base corner along the X axis in pixels this.m_long_prop[GRAPH_OBJ_PROP_YDISTANCE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YDISTANCE); // Distance from the base corner along the Y axis in pixels this.m_long_prop[GRAPH_OBJ_PROP_DIRECTION] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DIRECTION); // Gann object trend this.m_long_prop[GRAPH_OBJ_PROP_DEGREE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DEGREE); // Elliott wave marking level this.m_long_prop[GRAPH_OBJ_PROP_DRAWLINES] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DRAWLINES); // Display lines for Elliott wave marking this.m_long_prop[GRAPH_OBJ_PROP_STATE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STATE); // Button state (pressed/released) this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CHART_ID); // Chart object ID (OBJ_CHART). this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PERIOD] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_PERIOD); // Chart object period this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DATE_SCALE); // Time scale display flag for the Chart object this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE]= ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_PRICE_SCALE);// Price scale display flag for the Chart object this.m_long_prop[GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE]= ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CHART_SCALE);// Chart object scale this.m_long_prop[GRAPH_OBJ_PROP_XSIZE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XSIZE); // Object width along the X axis in pixels. this.m_long_prop[GRAPH_OBJ_PROP_YSIZE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YSIZE); // Object height along the Y axis in pixels. this.m_long_prop[GRAPH_OBJ_PROP_XOFFSET] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XOFFSET); // X coordinate of the upper-left corner of the visibility area. this.m_long_prop[GRAPH_OBJ_PROP_YOFFSET] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YOFFSET); // Y coordinate of the upper-left corner of the visibility area. this.m_long_prop[GRAPH_OBJ_PROP_BGCOLOR] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BGCOLOR); // Background color for OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL this.m_long_prop[GRAPH_OBJ_PROP_CORNER] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CORNER); // Chart corner for binding a graphical object this.m_long_prop[GRAPH_OBJ_PROP_BORDER_TYPE] = ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_TYPE);// Border type for "Rectangle border" this.m_long_prop[GRAPH_OBJ_PROP_BORDER_COLOR]= ::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_COLOR);// Border color for OBJ_EDIT and OBJ_BUTTON } //+------------------------------------------------------------------+ //| Get and save the real properties | //+------------------------------------------------------------------+ void CGStdGraphObj::GetAndSaveDBL(void) { this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_PRICE)] = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_PRICE); // Price coordinate this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELVALUE)] = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_LEVELVALUE); // Level value this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_SCALE)] = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_SCALE); // Scale (property of Gann objects and Fibonacci Arcs objects) this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_ANGLE)] = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_ANGLE); // Corner this.m_double_prop[this.IndexProp(GRAPH_OBJ_PROP_DEVIATION)] = ::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_DEVIATION); // Deviation of the standard deviation channel } //+------------------------------------------------------------------+ //| Get and save the string properties | //+------------------------------------------------------------------+ void CGStdGraphObj::GetAndSaveSTR(void) { this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TEXT)] = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_TEXT); // Object description (the text contained in the object) this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_TOOLTIP)] = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_TOOLTIP); // Tooltip text this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_LEVELTEXT)] = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_LEVELTEXT);// Level description this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_FONT)] = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_FONT); // Font this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_BMPFILE)] = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_BMPFILE); // BMP file name for the "Bitmap Level" object this.m_string_prop[this.IndexProp(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL)] = ::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_SYMBOL); // Chart object symbol } //+------------------------------------------------------------------+
A linhas excluídas do construtor da classe são simplesmente transferidas a estes três métodos - elas agora são substituídas por uma chamada do método que substitui todas as propriedades do objeto gráfico:
//+------------------------------------------------------------------+ //| Overwrite all graphical object properties | //+------------------------------------------------------------------+ void CGStdGraphObj::PropertiesRefresh(void) { this.GetAndSaveINT(); this.GetAndSaveDBL(); this.GetAndSaveSTR(); } //+------------------------------------------------------------------+
Aqui, todos os três métodos acima são chamados sucessivamente.
Método que copia as propriedades atuais do objeto de classe para as anteriores:
//+------------------------------------------------------------------+ //| Copy the current data to the previous one | //+------------------------------------------------------------------+ void CGStdGraphObj::PropertiesCopyToPrevData(void) { ::ArrayCopy(this.m_long_prop_prev,this.m_long_prop); ::ArrayCopy(this.m_double_prop_prev,this.m_double_prop); ::ArrayCopy(this.m_string_prop_prev,this.m_string_prop); } //+------------------------------------------------------------------+
Aqui com as funções de cópia de matrizes copiamos as matrizes de propriedades inteiras, reais e de string, por sua vez, para as matrizes correspondentes de propriedades anteriores.
Método que verifica as alterações nas propriedades do objeto:
//+------------------------------------------------------------------+ //| Check object property changes | //+------------------------------------------------------------------+ void CGStdGraphObj::PropertiesCheckChanged(void) { bool changed=false; int beg=0, end=GRAPH_OBJ_PROP_INTEGER_TOTAL; for(int i=beg; i<end; i++) { ENUM_GRAPH_OBJ_PROP_INTEGER prop=(ENUM_GRAPH_OBJ_PROP_INTEGER)i; if(!this.SupportProperty(prop)) continue; if(this.GetProperty(prop)!=this.GetPropertyPrev(prop)) { changed=true; ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop)); } } beg=end; end+=GRAPH_OBJ_PROP_DOUBLE_TOTAL; for(int i=beg; i<end; i++) { ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i; if(!this.SupportProperty(prop)) continue; if(this.GetProperty(prop)!=this.GetPropertyPrev(prop)) { changed=true; ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop)); } } beg=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL; for(int i=beg; i<end; i++) { ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i; if(!this.SupportProperty(prop)) continue; if(this.GetProperty(prop)!=this.GetPropertyPrev(prop)) { changed=true; ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop)); } } if(changed) PropertiesCopyToPrevData(); } //+------------------------------------------------------------------+
Aqui: em três loops - separadamente para propriedades inteiras, reais e de string,obtemos a próxima propriedade da matriz correspondente, comparamo-la com a mesma propriedade na matriz de propriedades anteriores e, se os valores comparados das propriedades atuais e anteriores não forem iguais, então esta propriedade mudou - definimos o sinalizador de mudança nas propriedades do objeto e imprimimos uma mensagem no log.
Este é um método de teste - apenas para testar a ideia de pesquisa de alterações nas propriedades do objeto e, além disso, em artigos futuros, refinaremos para um estado totalmente funcional - rastrearemos as alterações em todos os objetos gráficos em todos os gráficos abertos e enviaremos eventos sobre mudanças nas propriedades do objeto para o gráfico do programa de controle que a biblioteca pode processar posteriormente.
Rastreamento de remoção de objetos gráficos
A modificação das propriedades de um objeto gráfico é rastreada na classe deste objeto, uma vez que as propriedades de um objeto gráfico pertencem a este objeto, são gravadas na classe do objeto e podem ser verificadas nela, mas todos os métodos criados acima para a atualização das propriedades do objeto e a verificação de suas alterações serão chamadas a partir da classe-coleção de objetos gráficos. Mas podemos rastrear a adição e remoção de objetos gráficos para o gráfico apenas na classe-coleção de objetos gráficos - esta classe controla a lista completa de objetos em todos os gráficos abertos e mantém o controle deles em sua própria lista-coleção.
Abrimos o arquivo da classe-coleção de objetos gráficos \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh e fazemos as devidas alterações:
Na classe de gerenciamento de objetos de gráfico, que também está localizada no mesmo arquivo, declaramos um método privado que define a permissão para rastrear eventos de mouse e de objetos gráficos. Precisamos disso para que as permissões para rastrear tais eventos sejam definidas para cada um dos gráficos abertos.
Nos construtores de classe, vamos chamar esses métodos para definir as permissões para os gráficos para os quais esses objetos de controle serão criados, e no final da lista de classes declaramos um manipulador de eventos (lidaremos com sua implementação no próximo artigo):
//+------------------------------------------------------------------+ //| Chart object management class | //+------------------------------------------------------------------+ class CChartObjectsControl : public CObject { private: CArrayObj m_list_new_graph_obj; // List of added graphical objects ENUM_TIMEFRAMES m_chart_timeframe; // Chart timeframe long m_chart_id; // Chart ID string m_chart_symbol; // Chart symbol bool m_is_graph_obj_event; // Event flag in the list of graphical objects int m_total_objects; // Number of graphical objects int m_last_objects; // Number of graphical objects during the previous check int m_delta_graph_obj; // Difference in the number of graphical objects compared to the previous check //--- Return the name of the last graphical object added to the chart string LastAddedGraphObjName(void); //--- Set the permission to track mouse events and graphical objects void SetMouseEvent(void); public: //--- Return the variable values ENUM_TIMEFRAMES Timeframe(void) const { return this.m_chart_timeframe; } long ChartID(void) const { return this.m_chart_id; } string Symbol(void) const { return this.m_chart_symbol; } bool IsEvent(void) const { return this.m_is_graph_obj_event; } int TotalObjects(void) const { return this.m_total_objects; } int Delta(void) const { return this.m_delta_graph_obj; } //--- Create a new standard graphical object CGStdGraphObj *CreateNewGraphObj(const ENUM_OBJECT obj_type,const long chart_id, const string name); //--- Return the list of newly added objects CArrayObj *GetListNewAddedObj(void) { return &this.m_list_new_graph_obj;} //--- Check the chart objects void Refresh(void); //--- Constructors CChartObjectsControl(void) { this.m_list_new_graph_obj.Clear(); this.m_list_new_graph_obj.Sort(); this.m_chart_id=::ChartID(); this.m_chart_timeframe=(ENUM_TIMEFRAMES)::ChartPeriod(this.m_chart_id); this.m_chart_symbol=::ChartSymbol(this.m_chart_id); this.m_is_graph_obj_event=false; this.m_total_objects=0; this.m_last_objects=0; this.m_delta_graph_obj=0; this.SetMouseEvent(); } CChartObjectsControl(const long chart_id) { this.m_list_new_graph_obj.Clear(); this.m_list_new_graph_obj.Sort(); this.m_chart_id=chart_id; this.m_chart_timeframe=(ENUM_TIMEFRAMES)::ChartPeriod(this.m_chart_id); this.m_chart_symbol=::ChartSymbol(this.m_chart_id); this.m_is_graph_obj_event=false; this.m_total_objects=0; this.m_last_objects=0; this.m_delta_graph_obj=0; this.SetMouseEvent(); } //--- Compare CChartObjectsControl objects by a chart ID (for sorting the list by an object property) virtual int Compare(const CObject *node,const int mode=0) const { const CChartObjectsControl *obj_compared=node; return(this.ChartID()>obj_compared.ChartID() ? 1 : this.ChartID()<obj_compared.ChartID() ? -1 : 0); } //--- Event handler void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam); }; //+------------------------------------------------------------------+
Fora do corpo da classe, escreveremos a implementação do método para definir permissões para rastrear eventos de mouse e objetos gráficos:
//+------------------------------------------------------------------+ //| Set the permission | //| to track mouse and graphical object events for the chart | //+------------------------------------------------------------------+ void CChartObjectsControl::SetMouseEvent(void) { ::ChartSetInteger(this.ChartID(),CHART_EVENT_MOUSE_MOVE,true); ::ChartSetInteger(this.ChartID(),CHART_EVENT_MOUSE_WHEEL,true); ::ChartSetInteger(this.ChartID(),CHART_EVENT_OBJECT_CREATE,true); ::ChartSetInteger(this.ChartID(),CHART_EVENT_OBJECT_DELETE,true); } //+------------------------------------------------------------------+
Agora, vamos passar para a classe de coleção de objetos gráficos.
Na seção privada da classe declaramos novos métodos, cuja descrição mostra sua finalidade claramente:
//+------------------------------------------------------------------+ //| Collection of graphical objects | //+------------------------------------------------------------------+ class CGraphElementsCollection : public CBaseObj { private: CArrayObj m_list_charts_control; // List of chart management objects CListObj m_list_all_canv_elm_obj; // List of all graphical elements on canvas CListObj m_list_all_graph_obj; // List of all graphical objects 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 //--- Return the flag indicating the graphical element object presence in the collection list of graphical elements bool IsPresentGraphElmInList(const int id,const ENUM_GRAPH_ELEMENT_TYPE type_obj); //--- Return the flag indicating the graphical element object presence in the collection list of graphical objects bool IsPresentGraphObjInList(const long chart_id,const string name); //--- Return the flag indicating the presence of a graphical object on a chart by name bool IsPresentGraphObjOnChart(const long chart_id,const string name); //--- Return the pointer to the object of managing objects of the specified chart CChartObjectsControl *GetChartObjectCtrlObj(const long chart_id); //--- Create a new object of managing graphical objects of a specified chart and add it to the list CChartObjectsControl *CreateChartObjectCtrlObj(const long chart_id); //--- Update the list of graphical objects by chart ID CChartObjectsControl *RefreshByChartID(const long chart_id); //--- Return the first free ID of the graphical (1) object and (2) element on canvas long GetFreeGraphObjID(void); long GetFreeCanvElmID(void); //--- Add a graphical object to the collection bool AddGraphObjToCollection(const string source,CChartObjectsControl *obj_control); //--- Find an object present in the collection but not on a chart CGStdGraphObj *FindMissingObj(const long chart_id); //--- Find the graphical object present on a chart but not in the collection string FindExtraObj(const long chart_id); //--- Remove the graphical object from the graphical object collection list bool DeleteGraphObjFromList(CGStdGraphObj *obj); public:
Na seção pública da classe declaramos um método que retorna um objeto gráfico por nome e identificador de gráfico e manipulador de eventos de gráfico:
public: //--- Return itself CGraphElementsCollection *GetObject(void) { return &this; } //--- Return the full collection list of standard graphical objects "as is" CArrayObj *GetListGraphObj(void) { return &this.m_list_all_graph_obj; } //--- Return the full collection list of graphical elements on canvas "as is" CArrayObj *GetListCanvElm(void) { return &this.m_list_all_canv_elm_obj;} //--- Return the list of graphical elements by a selected (1) integer, (2) real and (3) string properties meeting the compared criterion CArrayObj *GetList(ENUM_CANV_ELEMENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode); } CArrayObj *GetList(ENUM_CANV_ELEMENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode); } CArrayObj *GetList(ENUM_CANV_ELEMENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode); } //--- Return the list of graphical objects by a selected (1) integer, (2) real and (3) string properties meeting the compared criterion CArrayObj *GetList(ENUM_GRAPH_OBJ_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,value,mode); } CArrayObj *GetList(ENUM_GRAPH_OBJ_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,value,mode); } CArrayObj *GetList(ENUM_GRAPH_OBJ_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,value,mode); } //--- Return the number of new graphical objects, (3) the flag of the occurred change in the list of graphical objects int NewObjects(void) const { return this.m_delta_graph_obj; } bool IsEvent(void) const { return this.m_is_graph_obj_event; } //--- Return a graphical object by chart name and ID CGStdGraphObj *GetStdGraphObject(const string name,const long chart_id); //--- Constructor CGraphElementsCollection(); //--- Display the description of the object properties in the journal (full_prop=true - all properties, false - supported ones only - implemented in descendant classes) virtual void Print(const bool full_prop=false,const bool dash=false); //--- Display a short description of the object in the journal virtual void PrintShort(const bool dash=false,const bool symbol=false); //--- Create the list of chart management objects and return the number of charts int CreateChartControlList(void); //--- 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 handler void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam); }; //+------------------------------------------------------------------+
No método que atualiza a lista de todos os objetos gráficos, escrevemos um bloco para processar a exclusão de objeto gráfico do gráfico:
//+------------------------------------------------------------------+ //| Update the list of all graphical objects | //+------------------------------------------------------------------+ void CGraphElementsCollection::Refresh(void) { //--- 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(!AddGraphObjToCollection(DFUN_ERR_LINE,obj_ctrl)) continue; } //--- If the graphical object has been removed else if(obj_ctrl.Delta()<0) { // Find an extra object in the list CGStdGraphObj *obj=this.FindMissingObj(chart_id); if(obj!=NULL) { //--- Display a short description of a detected object deleted from a chart in the journal obj.PrintShort(); //--- Remove the class object of a removed graphical object from the collection list if(!this.DeleteGraphObjFromList(obj)) CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST); } } //--- otherwise else { } } //--- Increase the loop index i++; } } //+------------------------------------------------------------------+
Neste método, num loop ao longo do número total de objetos de controle do gráfico, verificamos a presença de evento em cada objeto de controle e, se houver um, verificamos a diferença de objetos no gráfico. Escrevemos o processamento da adição de objeto no último artigo e hoje escrevemos o processamento do valor negativo da mudança no número de objetos no gráfico.
E aqui tudo é simples: procuramos um objeto na lista-coleção para o qual não há nenhum objeto gráfico no gráfico e o removemos da lista-coleção.
Método que procura um objeto que está presente na coleção, mas está ausente do gráfico:
//+------------------------------------------------------------------+ //|Find an object present in the collection but not on a chart | //+------------------------------------------------------------------+ CGStdGraphObj *CGraphElementsCollection::FindMissingObj(const long chart_id) { CArrayObj *list=CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),GRAPH_OBJ_PROP_CHART_ID,chart_id,EQUAL); if(list==NULL) return NULL; for(int i=0;i<list.Total();i++) { CGStdGraphObj *obj=list.At(i); if(obj==NULL) continue; if(!this.IsPresentGraphObjOnChart(obj.ChartID(),obj.Name())) return obj; } return NULL; } //+------------------------------------------------------------------+
Aqui: obtemos uma lista de todos os objetos para os quais o identificador do gráfico é igual ao especificado nos parâmetros do método.
Num loop ao longo da lista resultante obtemos o próximo objeto da classe do objeto gráfico padrão e, se não houver nenhum objeto com esse nome no gráfico, retornamos um ponteiro para este objeto.
No final do ciclo retornamos NULL.
Método que procura um objeto no gráfico, mas não na coleção:
//+------------------------------------------------------------------+ //|Find an object present on a chart but not in the collection | //+------------------------------------------------------------------+ string CGraphElementsCollection::FindExtraObj(const long chart_id) { int total=::ObjectsTotal(chart_id); for(int i=0;i<total;i++) { string name=::ObjectName(chart_id,i); if(!this.IsPresentGraphObjInList(chart_id,name)) return name; } return NULL; } //+------------------------------------------------------------------+
Aqui: num loop ao longo de todos os objetos na lista do terminal obtemos o nome do próximo objeto e, se um objeto com o mesmo nome e identificador de gráfico não estiver na lista-coleção, retornamos o nome do objeto gráfico. No final do loop, retornamos NULL.
Método que retorna o sinalizador de presença de objeto de classe de objeto gráfico na lista-coleção de objetos gráficos:
//+-----------------------------------------------------------------------------------+ //| Return the flag indicating the presence of the graphical object class object | //| in the graphical object collection list | //+-----------------------------------------------------------------------------------+ bool CGraphElementsCollection::IsPresentGraphObjInList(const long chart_id,const string name) { CArrayObj *list=CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),GRAPH_OBJ_PROP_CHART_ID,chart_id,EQUAL); list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_NAME,name,EQUAL); return(list==NULL || list.Total()==0 ? false : true); } //+------------------------------------------------------------------+
Obtemos uma lista de objetos que possuem o identificador especificado. A partir da lista resultante, obtemos um ponteiro para um objeto cujo nome corresponde ao desejado. Se a lista não puder ser obtida ou se estiver vazia, devolvemos false — objeto não encontrado, caso contrário, retornamos true.
Método que retorna o sinalizador de presença de objeto gráfico no gráfico por nome:
//+----------------------------------------------------------------------------------+ //| Return the flag indicating the presence of a graphical object on a chart by name | //+----------------------------------------------------------------------------------+ bool CGraphElementsCollection::IsPresentGraphObjOnChart(const long chart_id,const string name) { int total=::ObjectsTotal(chart_id); for(int i=0;i<total;i++) if(::ObjectName(chart_id,i)==name) return true; return false; } //+-------------------------------------------------------------------+
Num loop pelo número total de objetos gráficos no gráfico especificado pelo identificador obtemos o nome do próximo objeto e, se o nome for o mesmo que o que estamos procurando, retornamos true. No final do loop, retornamos false.
Método que remove um objeto gráfico da lista-coleção de objetos gráficos:
//+---------------------------------------------------------------------+ //|Remove the graphical object from the graphical object collection list| //+---------------------------------------------------------------------+ bool CGraphElementsCollection::DeleteGraphObjFromList(CGStdGraphObj *obj) { this.m_list_all_graph_obj.Sort(); int index=this.m_list_all_graph_obj.Search(obj); return(index==WRONG_VALUE ? false : this.m_list_all_graph_obj.Delete(index)); } //+------------------------------------------------------------------+
Ao método é passado o ponteiro para um objeto a ser removido da lista.
Definimos o sinalizador de lista classificada para a lista (a pesquisa é realizada apenas em listas ordenadas),
obtemos o índice do objeto com ajuda do método Search() da Biblioteca padrão.
Se o índice não for encontrado, retornamos false,
caso contrário, retornamos o resultado do objeto da lista com o método Delete() da Biblioteca padrão.
Método que retorna um ponteiro para um objeto gráfico por nome e identificador de gráfico:
//+------------------------------------------------------------------+ //| Return a graphical object by chart name and ID | //+------------------------------------------------------------------+ CGStdGraphObj *CGraphElementsCollection::GetStdGraphObject(const string name,const long chart_id) { CArrayObj *list=this.GetList(GRAPH_OBJ_PROP_CHART_ID,chart_id); list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_NAME,name,EQUAL); return(list!=NULL && list.Total()>0 ? list.At(0) : NULL); } //+------------------------------------------------------------------+
Obtemos uma lista de objetos, o identificador do gráfico que é igual ao passado para o método. Da lista resultante, obtemos uma lista contendo um objeto cujo nome coincide com o desejado (deve haver apenas um objeto). Se conseguirmos obter a lista e ela não estiver vazia, retornamos um ponteiro para o primeiro (e único) objeto da lista. Caso contrário, retornamos NULL.
Manipulador de eventos.
Hoje faremos uma versão de teste do manipulador de eventos, que processará eventos de objetos gráficos apenas no gráfico atual. O manipulador irá manipular eventos para alterar ou mover objetos gráficos. Este método é suficiente para determinarmos os eventos sonoros e, ao mesmo tempo, o problema do preenchimento incompleto das propriedades dos objetos que são construídos com mais de um clique do mouse também foi solucionado lateralmente - já discutimos acima que o primeiro clicar no gráfico cria um evento para a geração de objeto, e a biblioteca imediatamente cria o objeto correspondente.
Além disso, nem todas as propriedades são gravadas corretamente nele, já que o objeto é construído em alguns cliques do mouse. E agora, após a conclusão da construção do objeto, é criado um evento de movimento, ao qual iremos reagir e sobrescrever as propriedades do objeto (queremos rastrear este evento durante o movimento real, mas ao criar um objeto com vários pontos de ancoragem, nós também receberemos um evento de movimento, que fará com que as propriedades sejam atualizadas objeto, e elas serão sobrescritas com os dados corretos).
No código do método, toda a lógica é descrita nos comentários. Vamos ver os métodos para serem considerados por conta própria:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { CGStdGraphObj *obj=NULL; if(id==CHARTEVENT_OBJECT_CHANGE || id==CHARTEVENT_OBJECT_DRAG) { //--- If the object, whose properties were changed or which was relocated, //--- is successfully received from the collection list by its name set in sparam obj=this.GetStdGraphObject(sparam,::ChartID()); if(obj!=NULL) { //--- Update the properties of the obtained object //--- and check their change obj.PropertiesRefresh(); obj.PropertiesCheckChanged(); } //--- If failed to get the object by its name, it is not on the list, //--- which means its name has been changed else { //--- Let's search the list for the object that is not on the chart obj=this.FindMissingObj(::ChartID()); if(obj==NULL) return; //--- Get the name of the renamed graphical object on the chart, which is not in the collection list string name_new=this.FindExtraObj(::ChartID()); //--- Set a new name for the collection list object, which does not correspond to any graphical object on the chart, //--- update the chart properties and check their change obj.SetName(name_new); obj.PropertiesRefresh(); obj.PropertiesCheckChanged(); } } } //+------------------------------------------------------------------+
Resta um pequeno toque: precisamos acessar a coleção de objetos gráficos de programas criados com base na biblioteca.
Para isso, no objeto principal da biblioteca, em \MQL5\Include\DoEasy\Engine.mqh criamos um método que devolve um ponteiro para uma classe-coleção de objetos gráficos da biblioteca:
//--- Launch the new pause countdown void Pause(const ulong pause_msc,const datetime time_start=0) { this.PauseSetWaitingMSC(pause_msc); this.PauseSetTimeBegin(time_start*1000); while(!this.PauseIsCompleted() && !::IsStopped()){} } //--- Return the graphical object collection CGraphElementsCollection *GetGraphicObjCollection(void) { return &this.m_graph_objects; } //--- Constructor/destructor CEngine(); ~CEngine(); private:
Teste
Para teste pegamos no Expert Advisor do último artigo e o salvamos na nova pasta \MQL5\Experts\TestDoEasy\Part8\6 com o novo nome TestDoEasyPart86.mq5.
Do manipuladorOnInit() removemos as linhas com a configuração de permissões para rastrear eventos de mouse:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set the permissions to send cursor movement and mouse scroll events ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true); ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true); //--- Set EA global variables ArrayResize(array_clr,2); // Array of gradient filling colors array_clr[0]=C'246,244,244'; // Original ≈pale gray array_clr[1]=C'249,251,250'; // Final ≈pale gray-green //--- 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 //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Agora, as permissões para rastrear eventos de mouse e para acompanhamento de eventos de objetos gráficos são definidas na classe de gerenciamento de gráfico e definimos essas permissões para todos os gráficos abertos.
No final do manipulador OnChartEvent() escrevemos a chamada do manipulador de eventos da coleção de classes de objetos gráficos:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- If working in the tester, exit if(MQLInfoInteger(MQL_TESTER)) return; //--- If the mouse is moved if(id==CHARTEVENT_MOUSE_MOVE) { CForm *form=NULL; datetime time=0; double price=0; int wnd=0; //--- If Ctrl is not pressed, if(!IsCtrlKeyPressed()) { //--- clear the list of created form objects, allow scrolling a chart with the mouse and show the context menu list_forms.Clear(); ChartSetInteger(ChartID(),CHART_MOUSE_SCROLL,true); ChartSetInteger(ChartID(),CHART_CONTEXT_MENU,true); return; } //--- If X and Y chart coordinates are successfully converted into time and price, if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price)) { //--- get the bar index the cursor is hovered over int index=iBarShift(Symbol(),PERIOD_CURRENT,time); if(index==WRONG_VALUE) return; //--- Get the bar index by index CBar *bar=engine.SeriesGetBar(Symbol(),Period(),index); if(bar==NULL) return; //--- Convert the coordinates of a chart from the time/price representation of the bar object to the X and Y coordinates int x=(int)lparam,y=(int)dparam; if(!ChartTimePriceToXY(ChartID(),0,bar.Time(),(bar.Open()+bar.Close())/2.0,x,y)) return; //--- Disable moving a chart with the mouse and showing the context menu ChartSetInteger(ChartID(),CHART_MOUSE_SCROLL,false); ChartSetInteger(ChartID(),CHART_CONTEXT_MENU,false); //--- Create the form object name and hide all objects except one having such a name string name="FormBar_"+(string)index; HideFormAllExceptOne(name); //--- If the form object with such a name does not exist yet, if(!IsPresentForm(name)) { //--- create a new form object form=bar.CreateForm(index,name,x,y,114,16); if(form==NULL) return; //--- Set activity and unmoveability flags for the form form.SetActive(true); form.SetMovable(false); //--- Set the opacity of 200 form.SetOpacity(200); //--- 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(C'47,70,59'); //--- Draw the shadow drawing flag form.SetShadow(true); //--- 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(2,2,clr,200,3); //--- Fill the form background with a vertical gradient form.Erase(array_clr,form.Opacity()); //--- Draw an outlining rectangle at the edges of the form form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity()); //--- If failed to add the form object to the list, remove the form and exit the handler if(!list_forms.Add(form)) { delete form; return; } //--- Capture the form appearance form.Done(); } //--- If the form object exists, if(form!=NULL) { //--- draw a text with the bar type description on it and show the form. The description corresponds to the mouse cursor position form.TextOnBG(0,bar.BodyTypeDescription(),form.Width()/2,form.Height()/2-1,FRAME_ANCHOR_CENTER,C'7,28,21'); form.Show(); } //--- Redraw the chart ChartRedraw(); } } engine.GetGraphicObjCollection().OnChartEvent(id,lparam,dparam,sparam); } //+------------------------------------------------------------------+
E isso era tudo o que precisava ser finalizado. Vamos compilar o Expert Advisor e executá-lo no gráfico. Ao criar um objeto, alterar suas propriedades e excluir, os registros sobre esses eventos serão exibidos no log do terminal do cliente:
Até agora, são apenas entradas de registro, mas...
O que vem agora?
No próximo artigo, criaremos manipuladores de eventos de objeto para cada gráfico aberto e enviaremos esses eventos ao gráfico do programa de controle para que este programa possa processá-los totalmente.
Se você tiver dúvidas, comentários e sugestões, pode expressá-los nos comentários ao artigo.
*Artigos desta série:
Gráficos na biblioteca DoEasy (Parte 83): classe abstrata de objetos gráficos padrão
Gráficos na biblioteca DoEasy (Parte 84): classes herdeiras do objeto gráfico abstrato padrão
Gráficos na biblioteca DoEasy (Parte 85): coleção de objetos gráficos, adicionamos recém-criados
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/10018
- 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