Gráficos na biblioteca DoEasy (Parte 98): Movendo pontos de ancoragem de objetos gráficos padrão estendidos
Conteúdo
Ideia
A partir do artigo 93, começamos a criar objetos gráficos compostos na biblioteca. Depois disso, tivemos que mudar de tópico e tratar da modificação da funcionalidade dos objetos-formas criados com base na classe CCanvas, já que em objetos gráficos compostos usamos objetos-formas para criar elementos de controle para pontos de ancoragem de um objeto gráfico que faz parte de um objeto gráfico padrão estendido, e precisávamos de uma nova funcionalidade de objetos-formas, que, claro, foi planejada, mas ainda não tem sido implementada na biblioteca.
No último artigo, concluímos a modificação de objetos com base na classe CCanvas, e hoje continuaremos desenvolvendo o tópico de objetos gráficos padrão estendidos, a partir dos quais são criados objetos gráficos compostos.
O objetivo deste artigo não será o de desenvolver novas classes. Hoje falaremos sobre como modificar a funcionalidade previamente preparada que nos permitia criar ferramentas convenientes para mover pontos de ancoragem de objetos gráficos padrão. Este artigo não será longo, ele descreverá a criação de um determinado protótipo de um objeto gráfico composto. A propósito, já o criamos anteriormente, em artigos passados, trata-se de uma linha de tendência regular, que possui objetos-etiquetas de preço em suas extremidades:
Hoje vamos resolver o problema de mover os pontos de ancoragem da linha de tendência para que as etiquetas de preço anexadas ao lado móvel da linha de tendência também se movam. É bom dizer que temos objetos-formas para gerar seu deslocamento nos pontos de ancoragem da linha. Também moveremos o lado correspondente da linha de tendência ao arrastar o objeto em questão. Além disso, enquanto o cursor estiver longe do ponto de ancoragem da linha de tendência, o objeto-forma permanecerá invisível. Mas quando o cursor estiver perto, a uma certa distância, do ponto de ancoragem (i. e., ao entrar na área de uma forma transparente), um ponto com um círculo será desenhado na forma:
Assim, é criada a impressão de que o objeto de controle do ponto de ancoragem da linha de tendência aparece. Nesse caso, a forma terá um tamanho maior que sua área ativa. A área ativa de uma forma é a região sobre a qual a forma pode ser movimentada. Já a forma em si pode ser utilizada para gerar outro tipo de interação com ela por meio dos botões do mouse ou da sua roda.
Assim, se o cursor do mouse estiver dentro da forma, mas fora de sua área ativa, poderemos implementar, por exemplo, o menu de contexto de um objeto gráfico composto pressionando o botão direito do mouse. Se o cursor estiver na área ativa, fora o menu de contexto, podemos pegar essa forma com o mouse e movimentá-la. Nesse caso, o final da linha à qual a forma está anexada se movimentará atrás dela.
Naturalmente, este é apenas um objeto gráfico composto de teste dentro do qual "desenvolvemos" a funcionalidade a ser criada. Depois que todas as ferramentas que permitem gerar objetos gráficos compostos e trabalhar com objetos-formas forem criadas, elaboraremos um pequeno conjunto de objetos gráficos compostos padrão para a biblioteca, e já com base neles poderemos criar nossos próprios. E criá-los servirá como um exemplo e uma descrição de como você deve criar seus próprios objetos.
Mas, por enquanto, estamos apenas desenvolvendo a funcionalidade da biblioteca e criando aqueles "tijolos" a partir dos quais será possível fazer nossos próprios objetos, e aquelas etapas que percorremos, estudamos e implementamos artigo após artigo, ademais de estarem prontas, serão a base que você poderá usar "tal como está", poupando você de fazer tudo com suas próprias mãos do zero.
Modificando as classes da biblioteca
Vamos abrir o arquivo \MQL5\Include\DoEasy\Defines.mqh e fazer algumas melhorias nele.
Ao construir formas para controlar pontos de ancoragem, um ponto e um círculo são desenhados neles. A cor em que eles são desenhados foi previamente especificada diretamente no código. Adicionaremos uma macro-substituição na qual escreveremos esta cor por padrão:
//--- Pending request type IDs #define PENDING_REQUEST_ID_TYPE_ERR (1) // Type of a pending request created based on the server return code #define PENDING_REQUEST_ID_TYPE_REQ (2) // Type of a pending request created by request //--- Timeseries parameters #define SERIES_DEFAULT_BARS_COUNT (1000) // Required default amount of timeseries data #define PAUSE_FOR_SYNC_ATTEMPTS (16) // Amount of pause milliseconds between synchronization attempts #define ATTEMPTS_FOR_SYNC (5) // Number of attempts to receive synchronization with the server //--- Tick series parameters #define TICKSERIES_DEFAULT_DAYS_COUNT (1) // Required number of days for tick data in default series #define TICKSERIES_MAX_DATA_TOTAL (200000) // Maximum number of stored tick data of a single symbol //--- Parameters of the DOM snapshot series #define MBOOKSERIES_DEFAULT_DAYS_COUNT (1) // The default required number of days for DOM snapshots in the series #define MBOOKSERIES_MAX_DATA_TOTAL (200000) // Maximum number of stored DOM snapshots of a single symbol //--- Canvas parameters #define PAUSE_FOR_CANV_UPDATE (16) // Canvas update frequency #define CLR_CANV_NULL (0x00FFFFFF) // Zero for the canvas with the alpha channel #define OUTER_AREA_SIZE (16) // Size of one side of the outer area around the form workspace //--- Graphical object parameters #define PROGRAM_OBJ_MAX_ID (10000) // Maximum value of an ID of a graphical object belonging to a program #define CTRL_POINT_RADIUS (5) // Radius of the control point on the form for managing graphical object pivot points #define CTRL_POINT_COLOR (clrDodgerBlue) // Radius of the control point on the form for managing graphical object pivot points #define CTRL_FORM_SIZE (40) // Size of the control point form for managing graphical object pivot points //+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+
Vamos renomear a macro-substituição chamada CTRL_POINT_SIZE para CTRL_POINT_RADIUS, já que este não é o tamanho total do círculo, mas, sim, seu raio. É que o nome dessa macro-substituição era um pouco enganador ao calcular a área ativa do objeto-forma.
No arquivo da classe do objeto para o elemento gráfico \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, modificamos um pouco o método de criação de objeto gráfico elemento. Ao chamar o método CreateBitmapLabel() da classe CCanvas, infelizmente, nenhum código de erro é retornado. Portanto, antes de chamar este método, redefiniremos o último código de erro, e, caso não seja possível criar a etiqueta, exibiremos uma mensagem com o código de erro no log. Isso tornará a depuração um pouco mais fácil.
//+------------------------------------------------------------------+ //| Create the graphical element object | //+------------------------------------------------------------------+ bool CGCnvElement::Create(const long chart_id, // Chart ID const int wnd_num, // Chart subwindow const string name, // Element name const int x, // X coordinate const int y, // Y coordinate const int w, // Width const int h, // Height const color colour, // Background color const uchar opacity, // Opacity const bool redraw=false) // Flag indicating the need to redraw { ::ResetLastError(); if(this.m_canvas.CreateBitmapLabel(chart_id,wnd_num,name,x,y,w,h,COLOR_FORMAT_ARGB_NORMALIZE)) { this.Erase(CLR_CANV_NULL); this.m_canvas.Update(redraw); this.m_shift_y=(int)::ChartGetInteger(chart_id,CHART_WINDOW_YDISTANCE,wnd_num); return true; } CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //+------------------------------------------------------------------+
Por que tinha que ser desta maneira? Ao criar objetos-formas, foi necessário muito tempo para que eu descobrisse por qual motivo o recurso gráfico não podia ser criado. Como resultado, descobri que o nome do recurso gráfico que estava sendo criado tinha mais de 63 caracteres. Se o método para criar uma etiqueta da classe CCanvas nos informasse sobre um erro, não teríamos que procurar nada, uma vez que uma mensagem com um código de erro seria recebida imediatamente, e não precisaríamos passar por todas as cadeias de chamadas para os vários métodos em várias classes.
Em geral, essa modificação também não nos apontará para o verdadeiro código de erro se o comprimento do nome do recurso exceder 63 caracteres:
ERR_RESOURCE_NAME_IS_TOO_LONG 4018 The resource name exceeds 63 characters
e retornar um código de erro
ERR_RESOURCE_NOT_FOUND 4016 Resource with such a name is not found in EX5
mas isso ainda é melhor, e imediatamente faz pensar: "por que o recurso gráfico não é criado?"...
Modificamos o arquivo da classe de ferramentas do objeto gráfico padrão estendido no arquivo
\MQL5\Include\DoEasy\Objects\Graph\Extend\CGStdGraphObjExtToolkit.mqh.
Cada objeto gráfico possui um ou mais pontos de ancoragem com os quais esse objeto pode ser posicionado no gráfico. Anexado a cada um desses pontos está um objeto-forma para gerenciar tais pontos de ancoragem. Os objetos gráficos padrão têm pontos próprios com os quais podem ser movidos. Eles aparecem quando um objeto gráfico é selecionado. Não gerenciaremos objetos gráficos padrão estendidos na biblioteca dessa maneira. Para implementar nossa funcionalidade, é mais conveniente usarmos objetos-forma que irão facilitar o deslocamento dos pontos de ancoragem dos objetos gráficos. Pode haver mais objetos-formas do que pontos de ancoragem de um objeto gráfico. Logo, não podemos julgar o número de objetos-formas pelo número de pontos de ancoragem de um objeto gráfico, e precisamos saber esse número. Portanto, à seção pública da classe adicionaremos um método que retorna o número de objetos-formas criados para controlar os pontos de ancoragem do objeto gráfico e declararemos um método que desenha os pontos de controle dos pontos de ancoragem do objeto gráfico em objetos-formas completamente transparentes:
//--- (1) Set and (2) return the size of the form of pivot point management control points void SetControlFormSize(const int size); int GetControlFormSize(void) const { return this.m_ctrl_form_size; } //--- Return the pointer to the pivot point form by (1) index and (2) name CForm *GetControlPointForm(const int index) { return this.m_list_forms.At(index); } CForm *GetControlPointForm(const string name,int &index); //--- Return the number of (1) base object pivot points and (2) newly created form objects for managing control points int GetNumPivotsBaseObj(void) const { return this.m_base_pivots; } int GetNumControlPointForms(void) const { return this.m_list_forms.Total(); } //--- Create form objects on the base object pivot points bool CreateAllControlPointForm(void); //--- Draw a reference point on the form void DrawControlPoint(CForm *form,const uchar opacity,const color clr); //--- Remove all form objects from the list void DeleteAllControlPointForm(void);
No método que cria o objeto de formulário no ponto de ancoragem do objeto base, encurte o nome do objeto que está sendo criado - em vez de "_TKPP_" digite "_CP_":
//+------------------------------------------------------------------+ //| Create a form object on a base object reference point | //+------------------------------------------------------------------+ CForm *CGStdGraphObjExtToolkit::CreateNewControlPointForm(const int index) { string name=this.m_base_name+"_CP_"+(index<this.m_base_pivots ? (string)index : "X"); CForm *form=this.GetControlPointForm(index); if(form!=NULL) return NULL; int x=0, y=0; if(!this.GetControlPointCoordXY(index,x,y)) return NULL; return new CForm(this.m_base_chart_id,this.m_base_subwindow,name,x-this.m_shift,y-this.m_shift,this.GetControlFormSize(),this.GetControlFormSize()); } //+------------------------------------------------------------------+
Foi aqui (e no arquivo do EA de teste) que tive que encurtar o nome do objeto-forma criado, pois o nome do recurso gráfico acabou tendo mais de 63 caracteres e o objeto não era criado. O motivo está no método de criação de recurso dinâmico na classe CCanvas, onde o nome do recurso a ser criado consiste nos caracteres "::" + o nome passado para o método (que especificamos no método acima) + o identificador do gráfico + o número de milissegundos decorridos desde que o sistema foi iniciado + um número pseudo-aleatório:
//+------------------------------------------------------------------+ //| Create dynamic resource | //+------------------------------------------------------------------+ bool CCanvas::Create(const string name,const int width,const int height,ENUM_COLOR_FORMAT clrfmt) { Destroy(); //--- prepare data array if(width>0 && height>0 && ArrayResize(m_pixels,width*height)>0) { //--- generate resource name m_rcname="::"+name+(string)ChartID()+(string)(GetTickCount()+MathRand()); //--- initialize data with zeros ArrayInitialize(m_pixels,0); //--- create dynamic resource if(ResourceCreate(m_rcname,m_pixels,width,height,0,0,0,clrfmt)) { //--- successfully created //--- complete initialization m_width =width; m_height=height; m_format=clrfmt; //--- succeed return(true); } } //--- error - destroy object Destroy(); return(false); } //+------------------------------------------------------------------+
Tudo isso, infelizmente, impõe sérias restrições à escolha de um nome intuitivo para o objeto criado.
No método que cria objetos-formas nos pontos de ancoragem do objeto base, calculamos o deslocamento de cada lado do objeto-forma para indicar a localização e o tamanho da área ativa da forma, que deve estar no centro da mesma, e seu tamanho deve ser igual a dois valores registrados na macro-substituição CTRL_POINT_RADIUS . Como se trata do raio, para que a zona ativa da forma seja igual ao círculo desenhado em seu centro, precisamos pegar dois valores do raio, subtraí-los da largura da forma (a altura de a forma é igual à sua largura) e dividir o valor resultante por dois.
O valor calculado para o recuo da borda da área ativa em relação à borda da forma é especificado no método SetActiveAreaShift():
//+------------------------------------------------------------------+ //| Create form objects on the base object pivot points | //+------------------------------------------------------------------+ bool CGStdGraphObjExtToolkit::CreateAllControlPointForm(void) { bool res=true; //--- In the loop by the number of base object pivot points for(int i=0;i<this.m_base_pivots;i++) { //--- Create a new form object on the current pivot point corresponding to the loop index CForm *form=this.CreateNewControlPointForm(i); //--- If failed to create the form, inform of that and add 'false' to the final result if(form==NULL) { CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_CREATE_CTRL_POINT_FORM); res &=false; } //--- If failed to add the form to the list, inform of that, remove the created form and add 'false' to the final result if(!this.m_list_forms.Add(form)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST); delete form; res &=false; } //--- Set all the necessary properties for the created form object form.SetBelong(GRAPH_OBJ_BELONG_PROGRAM); // Object is created programmatically form.SetActive(true); // Form object is active form.SetMovable(true); // Movable object int x=(int)::floor((form.Width()-CTRL_POINT_RADIUS*2)/2);// Shift the active area from the form edge form.SetActiveAreaShift(x,x,x,x); // Object active area is located at the form center, its size is equal to two CTRL_POINT_RADIUS values form.SetFlagSelected(false,false); // Object is not selected form.SetFlagSelectable(false,false); // Object cannot be selected by mouse form.Erase(CLR_CANV_NULL,0); // Fill in the form with transparent color and set the full transparency //form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,clrSilver); // Draw an outlining rectangle for visual display of the form location //form.DrawRectangle(x,x,form.Width()-x-1,form.Height()-x-1,clrSilver);// Draw an outlining rectangle for visual display of the form active area location this.DrawControlPoint(form,0,CTRL_POINT_COLOR); // Draw a circle and a point at the form center form.Done(); // Save the initial form object state (its appearance) } //--- Redraw the chart for displaying changes (if successful) and return the final result if(res) ::ChartRedraw(this.m_base_chart_id); return res; } //+------------------------------------------------------------------+
Para fins de depuração, durante a criação de forma, podemos desenhar retângulos, exibindo o tamanho da forma e sua área ativa, e essas linhas são comentadas. Também desenhamos círculos completamente transparentes no centro da forma com um novo método, visto abaixo. E se são transparentes, vale a pena desenhá-los então?
Método que desenha um ponto de controle na forma:
//+------------------------------------------------------------------+ //| Draw a control point on the form | //+------------------------------------------------------------------+ void CGStdGraphObjExtToolkit::DrawControlPoint(CForm *form,const uchar opacity,const color clr) { if(form==NULL) return; form.DrawCircle((int)::floor(form.Width()/2),(int)::floor(form.Height()/2),CTRL_POINT_RADIUS,clr,opacity);// Draw a circle at the center of the form form.DrawCircleFill((int)::floor(form.Width()/2),(int)::floor(form.Height()/2),2,clr,opacity); // Draw a point at the center of the form } //+------------------------------------------------------------------+
O método contém duas linhas, que simplesmente movemos do método acima para um método à parte. Para que é isso? Precisamos mostrar ou ocultar um ponto com um círculo no centro da forma em momentos diferentes. Para fazer isso, chamamos esse método, e especificamos a opacidade e a cor desejadas das formas desenhadas.
No manipulador de eventos, removemos o processamento de movimento do cursor do mouse , porque não precisamos disso aqui.
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CGStdGraphObjExtToolkit::OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam) { if(id==CHARTEVENT_CHART_CHANGE) { for(int i=0;i<this.m_list_forms.Total();i++) { CForm *form=this.m_list_forms.At(i); if(form==NULL) continue; int x=0, y=0; if(!this.GetControlPointCoordXY(i,x,y)) continue; form.SetCoordX(x-this.m_shift); form.SetCoordY(y-this.m_shift); form.Update(); } ::ChartRedraw(this.m_base_chart_id); } if(id==CHARTEVENT_MOUSE_MOVE) { for(int i=0;i<this.m_list_forms.Total();i++) { CForm *form=this.m_list_forms.At(i); if(form==NULL) continue; form.OnChartEvent(id,lparam,dparam,sparam); } ::ChartRedraw(this.m_base_chart_id); } } //+------------------------------------------------------------------+
Vamos alterar a classe do objeto gráfico padrão abstrato no arquivo \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh.
Na seção pública da classe, declaramos um método que permite alterar simultaneamente as coordenadas dos pontos de ancoragem de um objeto gráfico e dos objetos que estão anexados a ele:
//--- 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); //--- Return the pivot point data object CLinkedPivotPoint*GetLinkedPivotPoint(void) { return &this.m_linked_pivots; }
Para ter acesso à forma que irá controlar os pontos de ancoragem do objeto gráfico, vamos declarar três métodos:
//--- Return the number of base object pivot points for calculating the coordinates in the (1) current and (2) specified object int GetLinkedCoordsNum(void) const { return this.m_linked_pivots.GetNumLinkedCoords(); } int GetLinkedPivotsNum(CGStdGraphObj *obj) const { return(obj!=NULL ? obj.GetLinkedCoordsNum() : 0); } //--- Return the form for managing an object pivot point CForm *GetControlPointForm(const int index); //--- Return the number of form objects for managing reference points int GetNumControlPointForms(void); //--- Redraw the form for managing a reference point of an extended standard graphical object void RedrawControlPointForms(const uchar opacity,const color clr); private:
Adicionamos um método que define o tempo e o preço de acordo com as coordenadas da tela:
//--- Symbol for the Chart object string ChartObjSymbol(void) const { return this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL,0); } bool SetChartObjSymbol(const string symbol) { if(!::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_SYMBOL,symbol)) return false; this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL,0,symbol); return true; } //--- Set the time and price by screen coordinates bool SetTimePrice(const int x,const int y,const int modifier) { bool res=true; ENUM_OBJECT type=this.GraphObjectType(); if(type==OBJ_LABEL || type==OBJ_BUTTON || type==OBJ_BITMAP_LABEL || type==OBJ_EDIT || type==OBJ_RECTANGLE_LABEL) { res &=this.SetXDistance(x); res &=this.SetYDistance(y); } else { int subwnd=0; datetime time=0; double price=0; if(::ChartXYToTimePrice(this.ChartID(),x,y,subwnd,time,price)) { res &=this.SetTime(time,modifier); res &=this.SetPrice(price,modifier); } } return res; } //--- Return the flags indicating object visibility on timeframes
Para podermos trabalhar com objetos gráficos em coordenadas de tela X e Y, precisamos converter as coordenadas de tela em coordenadas de tempo/preço. Este método verifica, primeiro, qual o tipo do objeto atual, e, se for construído a partir de coordenadas de tela, suas coordenadas de tela serão alteradas imediatamente. Se este objeto gráfico for construído de acordo com as coordenadas de tempo/preço, primeiro precisamos converter as coordenadas de tela passadas para o método em valores de tempo e preço e, em seguida, registrar esses valores recebidos nos parâmetros do objeto gráfico.
Método que retorna a forma de controle pertencente ao ponto de ancoragem do objeto:
//+------------------------------------------------------------------+ //| Return the form for managing an object pivot point | //+------------------------------------------------------------------+ CForm *CGStdGraphObj::GetControlPointForm(const int index) { return(this.ExtToolkit!=NULL ? this.ExtToolkit.GetControlPointForm(index) : NULL); } //+------------------------------------------------------------------+
Tudo é simples aqui: caso o objeto da ferramentas pertencentes ao objeto gráfico padrão estendido exista, o objeto-forma será retornado segundo o índice. Se não for assim, será retornado NULL.
Método que retorna o número de objetos-formas que controlam o ponto de controle:
//+------------------------------------------------------------------+ //| Return the number of form objects | //| for managing control points | //+------------------------------------------------------------------+ int CGStdGraphObj::GetNumControlPointForms(void) { return(this.ExtToolkit!=NULL ? this.ExtToolkit.GetNumControlPointForms() : 0); } //+------------------------------------------------------------------+
O método é semelhante ao discutido acima: se o objeto das ferramentas do objeto gráfico padrão estendido existir, o número de objetos-formas será retornado. Se não for assim,será retornado 0.
Método que redesenha a forma que gere o ponto de controle do objeto gráfico padrão estendido:
//+------------------------------------------------------------------+ //| Redraw the form for managing a control point | //| of an extended standard graphical object | //+------------------------------------------------------------------+ void CGStdGraphObj::RedrawControlPointForms(const uchar opacity,const color clr) { //--- Leave if the object has no toolkit of an extended standard graphical object if(this.ExtToolkit==NULL) return; //--- Get the number of pivot point management forms int total_form=this.GetNumControlPointForms(); //--- In the loop by the number of pivot point management forms for(int i=0;i<total_form;i++) { //--- get the next form object CForm *form=this.ExtToolkit.GetControlPointForm(i); if(form==NULL) continue; //--- draw a point and a circle with the specified non-transparency and color this.ExtToolkit.DrawControlPoint(form,opacity,clr); } //--- Get the total number of bound graphical objects int total_dep=this.GetNumDependentObj(); //--- In the loop by all bound graphical objects, for(int i=0;i<total_dep;i++) { //--- get the next graphical object from the list CGStdGraphObj *dep=this.GetDependentObj(i); if(dep==NULL) continue; //--- call the method for it dep.RedrawControlPointForms(opacity,clr); } } //+------------------------------------------------------------------+
O método é comentado em detalhes. Em poucas palavras, primeiro redesenhamos todos os objetos-formas vinculados ao objeto atual. E depois lembramos que objetos gráficos dependentes podem ser anexados a esse objeto, objetos gráficos esses que, por sua vez, também possuem objetos-formas. Sendo assim, em um loop por todos os objetos dependentes, chamamos esse método, para cada um dos objetos dependentes. E esses objetos, por sua vez, também passarão pela lista de seus objetos dependentes e chamarão o mesmo método para eles. E assim por diante até que todos os objetos-formas para todos os objetos gráficos vinculados sejam redesenhados.
Método que altera as coordenadas X e Y do objeto atual e de todos os objetos dependentes:
//+----------------------------------------------------------------------+ //| Change X and Y coordinates of the current and all dependent objects | //+----------------------------------------------------------------------+ bool CGStdGraphObj::ChangeCoordsExtendedObj(const int x,const int y,const int modifier,bool redraw=false) { //--- Set new coordinates for the pivot point specified in 'modifier' if(!this.SetTimePrice(x,y,modifier)) return false; //--- If the object is not a composite graphical object //--- or if subordinate graphical objects are not attached to the object, //--- there is nothing else to do here, return 'true' if(this.ExtToolkit==NULL || this.m_list.Total()==0) return true; //--- Get the graphical object bound to the 'modifier' point CGStdGraphObj *dep=this.GetDependentObj(modifier); if(dep==NULL) return false; //--- Get the object of pivot point data of the bound graphical object CLinkedPivotPoint *pp=dep.GetLinkedPivotPoint(); if(pp==NULL) return false; //--- get the number of coordinate points the object is attached to int num=pp.GetNumLinkedCoords(); //--- In the loop by the object coordinate points, for(int j=0;j<num;j++) { //--- get the number of coordinate points of the base object for setting the X coordinate int numx=pp.GetBasePivotsNumX(j); //--- In the loop by each coordinate point for setting the X coordinate, for(int nx=0;nx<numx;nx++) { //--- get the property for setting the X coordinate, its modifier //--- and set it to the subordinate graphical object attached to the 'modifier' point int prop_from=pp.GetPropertyX(j,nx); int modifier_from=pp.GetPropertyModifierX(j,nx); this.SetCoordXToDependentObj(dep,prop_from,modifier_from,nx); } //--- Get the number of coordinate points of the base object for setting the Y coordinate int numy=pp.GetBasePivotsNumY(j); //--- In the loop by each coordinate point for setting the Y coordinate, for(int ny=0;ny<numy;ny++) { //--- get the property for setting the Y coordinate, its modifier //--- and set it to the subordinate graphical object attached to the 'modifier' point int prop_from=pp.GetPropertyY(j,ny); int modifier_from=pp.GetPropertyModifierY(j,ny); this.SetCoordYToDependentObj(dep,prop_from,modifier_from,ny); } } //--- Save the current properties of a subordinate graphical object as the previous ones dep.PropertiesCopyToPrevData(); //--- Move a reference control point to new coordinates this.ExtToolkit.SetBaseObjTimePrice(this.Time(modifier),this.Price(modifier),modifier); this.ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance()); //--- If the flag is active, redraw the chart if(redraw) ::ChartRedraw(m_chart_id); //--- All is successful - return 'true' return true; } //+------------------------------------------------------------------+
O método é comentado em detalhes. Resumindo: primeiro alteramos as coordenadas do ponto de ancoragem especificado para o objeto atual. Além disso, se o objeto tiver objetos gráficos dependentes vinculados a ele, o objeto gráfico dependente também deve ser deslocado para novas coordenadas no ponto que foi movido e ao qual o objeto dependente pode ser anexado, e isso acontece no método.
No manipulador de eventos, removemos o processamento do movimento do mouse, porque não é necessário aqui:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CGStdGraphObj::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { if(GraphElementType()!=GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) return; string name=this.Name(); if(id==CHARTEVENT_CHART_CHANGE) { if(ExtToolkit==NULL) return; for(int i=0;i<this.Pivots();i++) { ExtToolkit.SetBaseObjTimePrice(this.Time(i),this.Price(i),i); } ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance()); ExtToolkit.OnChartEvent(id,lparam,dparam,name); } if(id==CHARTEVENT_MOUSE_MOVE) { if(ExtToolkit!=NULL) ExtToolkit.OnChartEvent(id,lparam,dparam,name); } } //+------------------------------------------------------------------+
Vamos alterar a classe-coleção de objetos gráficos no arquivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh.
Quando movemos o cursor sobre o gráfico, se houver objetos no gráfico, o objeto sobre o qual o cursor do mouse está é determinado no manipulador de eventos da classe. Em seguida, o manipulador de estado do mouse é iniciado em relação a esse objeto. Quando passamos o cursor sobre o objeto que gere o ponto de controle do objeto gráfico padrão estendido, graças ao manipulador saberemos tanto a própria forma quanto seu índice e o objeto gráfico ao qual essa forma está vinculada. Para não procurar novamente esta forma e o objeto gráfico fora do manipulador, basta armazenar em variáveis o identificador do objeto gráfico ao qual a forma está vinculada e o índice da forma sobre a qual o cursor está localizado. Esses dados nos ajudarão a selecionar rapidamente os objetos desejados das listas e entender que o cursor está sobre a forma de acordo com o valor dessas variáveis.
Escrevemos essas variáveis na declaração de um método que retorna um ponteiro para a forma sob o cursor:
//--- Return the pointer to the form located under the cursor CForm *GetFormUnderCursor(const int id, const long &lparam, const double &dparam, const string &sparam, ENUM_MOUSE_FORM_STATE &mouse_state, long &obj_ext_id, int &form_index);
As variáveis são passadas para o método por referência. Ou seja, dentro do método, simplesmente escrevemos os valores necessários neles, e esses valores são salvos nas variáveis correspondentes, e assim podemos usá-los depois.
Na seção pública da classe, declaramos dois métodos que retornam objetos gráficos estendidos padrão de acordo com o identificador:
//--- Return an (1) existing and (2) removed graphical object by chart name and ID CGStdGraphObj *GetStdGraphObject(const string name,const long chart_id); CGStdGraphObj *GetStdDelGraphObject(const string name,const long chart_id); //--- Return the existing (1) extended and (2) standard graphical object by its ID CGStdGraphObj *GetStdGraphObjectExt(const long id,const long chart_id); CGStdGraphObj *GetStdGraphObject(const long id,const long chart_id); //--- Return the list of (1) chart management objects and (2) removed graphical objects CArrayObj *GetListChartsControl(void) { return &this.m_list_charts_control; } CArrayObj *GetListDeletedObj(void) { return &this.m_list_deleted_obj; }
Serão necessários métodos para obter um ponteiro para um objeto gráfico segundo seu identificador. Vejamos a implementação desses métodos.
Método que retorna um objeto gráfico padrão estendido existente por seu identificador:
//+------------------------------------------------------------------+ //| Return the existing extended standard | //| graphical object by its ID | //+------------------------------------------------------------------+ CGStdGraphObj *CGraphElementsCollection::GetStdGraphObjectExt(const long id,const long chart_id) { CArrayObj *list=this.GetListStdGraphObjectExt(); list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_CHART_ID,0,chart_id,EQUAL); list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_ID,0,id,EQUAL); return(list!=NULL && list.Total()>0 ? list.At(0) : NULL); } //+------------------------------------------------------------------+
Aqui: obtemos uma lista de objetos gráficos padrão estendidos, deixamos apenas objetos com o identificador gráfico especificado na lista.
Na lista resultante, selecionamos um objeto com o identificador do objeto especificado. Se a lista resultante for válida e não estiver vazia, retornamos o ponteiro para o objeto requerido contido nele. Se não for assim, retornamos NULL.
Método que retorna um objeto gráfico padrão existente segundo seu identificador:
//+------------------------------------------------------------------+ //| Return the existing standard | //| graphical object by its ID | //+------------------------------------------------------------------+ CGStdGraphObj *CGraphElementsCollection::GetStdGraphObject(const long id,const long chart_id) { CArrayObj *list=this.GetList(GRAPH_OBJ_PROP_CHART_ID,0,chart_id); list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_ID,0,id,EQUAL); return(list!=NULL && list.Total()>0 ? list.At(0) : NULL); } //+------------------------------------------------------------------+
Aqui: obtemos uma lista de objetos gráficos de acordo com o identificador gráfico. Na lista resultante, deixamos o objeto com o identificador do objeto especificado.
Se a lista resultante for válida e não estiver vazia, retornamos o ponteiro para o objeto contido nela. Se não for assim, retornamos NULL.
Modificamos o método que retorna um ponteiro para a forma sob o cursor. Precisamos adicionar e inicializar duas novas variáveis para armazenar o identificador do objeto gráfico padrão estendido e o índice do ponto de ancoragem, que é controlado pela forma, e também devemos inserir o bloco para processamento de objetos gráficos padrão estendidos, isto é, a busca de formas vinculadas a esses objetos e sobre as quais o cursor do mouse está:
//+------------------------------------------------------------------+ //| Return the pointer to the form located under the cursor | //+------------------------------------------------------------------+ CForm *CGraphElementsCollection::GetFormUnderCursor(const int id, const long &lparam, const double &dparam, const string &sparam, ENUM_MOUSE_FORM_STATE &mouse_state, long &obj_ext_id, int &form_index) { //--- Set the ID of the extended standard graphical object to -1 //--- and the index of the anchor point managed by the form to -1 obj_ext_id=WRONG_VALUE; form_index=WRONG_VALUE; //--- Initialize the mouse status relative to the form mouse_state=MOUSE_FORM_STATE_NONE; //--- Declare the pointers to graphical element collection class objects CGCnvElement *elm=NULL; CForm *form=NULL; //--- Get the list of objects the interaction flag is set for (there should be only one object) CArrayObj *list=CSelect::ByGraphCanvElementProperty(GetListCanvElm(),CANV_ELEMENT_PROP_INTERACTION,true,EQUAL); //--- If managed to obtain the list and it is not empty, if(list!=NULL && list.Total()>0) { //--- Get the only graphical element there elm=list.At(0); //--- If it is a form object if(elm.TypeGraphElement()==GRAPH_ELEMENT_TYPE_FORM) { //--- Assign the pointer to the element for the form object pointer form=elm; //--- Get the mouse status relative to the form mouse_state=form.MouseFormState(id,lparam,dparam,sparam); //--- If the cursor is within the form, return the pointer to the form if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) return form; } } //--- If there is no a single form object with a specified interaction flag, //--- in the loop by all graphical element collection class objects int total=this.m_list_all_canv_elm_obj.Total(); for(int i=0;i<total;i++) { //--- get the next element elm=this.m_list_all_canv_elm_obj.At(i); if(elm==NULL) continue; //--- if the obtained element is a form object if(elm.TypeGraphElement()==GRAPH_ELEMENT_TYPE_FORM) { //--- Assign the pointer to the element for the form object pointer form=elm; //--- Get the mouse status relative to the form mouse_state=form.MouseFormState(id,lparam,dparam,sparam); //--- If the cursor is within the form, return the pointer to the form if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) return form; } } //--- If there is no a single form object from the collection list //--- Get the list of extended standard graphical objects list=this.GetListStdGraphObjectExt(); if(list!=NULL) { //--- in the loop by all extended standard graphical objects for(int i=0;i<list.Total();i++) { //--- get the next graphical object, CGStdGraphObj *obj_ext=list.At(i); if(obj_ext==NULL) continue; //--- get the object of its toolkit, CGStdGraphObjExtToolkit *toolkit=obj_ext.GetExtToolkit(); if(toolkit==NULL) continue; //--- handle the event of changing the chart for the current graphical object obj_ext.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam); //--- Get the total number of form objects created for the current graphical object total=toolkit.GetNumControlPointForms(); //--- In the loop by all form objects for(int j=0;j<total;j++) { //--- get the next form object, form=toolkit.GetControlPointForm(j); if(form==NULL) continue; //--- get the mouse status relative to the form mouse_state=form.MouseFormState(id,lparam,dparam,sparam); //--- If the cursor is inside the form, if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) { //--- set the object ID and form index //--- and return the pointer to the form obj_ext_id=obj_ext.ObjectID(); form_index=j; return form; } } } } //--- Nothing is found - return NULL return NULL; } //+------------------------------------------------------------------+
Toda a lógica do bloco de código adicionado está descrita nos comentários. Resumindo: precisamos encontrar o objeto-forma sobre o qual o cursor do mouse está. Primeiro, procuramos objetos-formas armazenados na lista de elementos gráficos da classe-coleção. Se nenhuma forma for encontrada, precisamos percorrer todos os objetos gráficos padrão estendidos procurando suas formas, o cursor pode estar sobre um deles. Se for esse o caso, nas variáveis passadas por referência ao método, escrevemos o identificador do objeto gráfico padrão estendido ao qual esta forma está vinculada e o índice desta forma, para saber qual ponto de ancoragem de qual objeto gráfico esta forma gere.
Agora precisamos processar a interação do cursor do mouse com os objetos-formas de objetos gráficos padrão estendidos no manipulador de eventos. Além disso, controlaremos o movimento dos objetos-formas para que eles não possam entrar na área do gráfico, nomeadamente em seu canto superior direito, onde está localizado o botão de ativação do modo de negociação com um clique. Este botão está sempre em cima de todos os objetos, e não precisamos que a forma flutuante possa aparecer sob ele, para não clicar acidentalmente neste botão em vez de na forma. Se o painel de negociação com um clique já estiver ativado, a forma também não deve aparece sob ele, porque simplesmente não será visível se for menor que este painel, o que causará inconvenientes ao trabalhar com a forma, fazendo com que você tenha que desabilitar o painel de negociação com um clique para ver novamente a forma que apareceu acidentalmente sob tal painel.
Vejamos as melhorias e alterações no manipulador de eventos:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { CGStdGraphObj *obj_std=NULL; // Pointer to the standard graphical object CGCnvElement *obj_cnv=NULL; // Pointer to the graphical element object on canvas ushort idx=ushort(id-CHARTEVENT_CUSTOM); if(id==CHARTEVENT_OBJECT_CHANGE || id==CHARTEVENT_OBJECT_DRAG || id==CHARTEVENT_OBJECT_CLICK || idx==CHARTEVENT_OBJECT_CHANGE || idx==CHARTEVENT_OBJECT_DRAG || idx==CHARTEVENT_OBJECT_CLICK) { //--- Calculate the chart ID //--- If the event ID corresponds to an event from the current chart, the chart ID is received from ChartID //--- If the event ID corresponds to a user event, the chart ID is received from lparam //--- Otherwise, the chart ID is assigned to -1 long param=(id==CHARTEVENT_OBJECT_CLICK ? ::ChartID() : idx==CHARTEVENT_OBJECT_CLICK ? lparam : WRONG_VALUE); long chart_id=(param==WRONG_VALUE ? (lparam==0 ? ::ChartID() : lparam) : param); //--- Get the object, whose properties were changed or which was relocated, //--- from the collection list by its name set in sparam obj_std=this.GetStdGraphObject(sparam,chart_id); //--- If failed to get the object by its name, it is not on the list, //--- which means its name has been changed if(obj_std==NULL) { //--- Let's search the list for the object that is not on the chart obj_std=this.FindMissingObj(chart_id); //--- If failed to find the object here as well, exit if(obj_std==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(chart_id); //--- set a new name for the collection list object, which does not correspond to any graphical object on the chart, //--- and send an event with the new name of the object to the control program chart if(obj_std.SetNamePrev(obj_std.Name()) && obj_std.SetName(name_new)) ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_RENAME,obj_std.ChartID(),obj_std.TimeCreate(),obj_std.Name()); } //--- Update the properties of the obtained object //--- and check their change obj_std.PropertiesRefresh(); obj_std.PropertiesCheckChanged(); } //--- Handle standard graphical object events in the collection list for(int i=0;i<this.m_list_all_graph_obj.Total();i++) { //--- Get the next graphical object and obj_std=this.m_list_all_graph_obj.At(i); if(obj_std==NULL) continue; //--- call its event handler obj_std.OnChartEvent((id<CHARTEVENT_CUSTOM ? id : idx),lparam,dparam,sparam); } //--- Handle chart changes for extended standard objects if(id==CHARTEVENT_CHART_CHANGE || idx==CHARTEVENT_CHART_CHANGE) { CArrayObj *list=this.GetListStdGraphObjectExt(); if(list!=NULL) { for(int i=0;i<list.Total();i++) { obj_std=list.At(i); if(obj_std==NULL) continue; obj_std.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam); } } } //--- Handling mouse events of graphical objects on canvas //--- If the event is not a chart change else { //--- Check whether the mouse button is pressed bool pressed=(this.m_mouse.ButtonKeyState(id,lparam,dparam,sparam)==MOUSE_BUTT_KEY_STATE_LEFT ? true : false); ENUM_MOUSE_FORM_STATE mouse_state=MOUSE_FORM_STATE_NONE; //--- Declare static variables for the active form and status flags static CForm *form=NULL; static bool pressed_chart=false; static bool pressed_form=false; static bool move=false; //--- Declare static variables for the index of the form for managing an extended standard graphical object and its ID static int form_index=WRONG_VALUE; static long graph_obj_id=WRONG_VALUE; //--- If the button is not pressed on the chart and the movement flag is not set, get the form, above which the cursor is located if(!pressed_chart && !move) form=this.GetFormUnderCursor(id,lparam,dparam,sparam,mouse_state,graph_obj_id,form_index); //--- If the button is not pressed, reset all flags and enable the chart tools if(!pressed) { pressed_chart=false; pressed_form=false; move=false; this.SetChartTools(::ChartID(),true); } //--- If this is a mouse movement event and the movement flag is active, move the form, above which the cursor is located (if the pointer to it is valid) if(id==CHARTEVENT_MOUSE_MOVE && move) { if(form!=NULL) { //--- calculate the cursor movement relative to the form coordinate origin int x=this.m_mouse.CoordX()-form.OffsetX(); int y=this.m_mouse.CoordY()-form.OffsetY(); //--- get the width and height of the chart the form is located at int chart_width=(int)::ChartGetInteger(form.ChartID(),CHART_WIDTH_IN_PIXELS,form.SubWindow()); int chart_height=(int)::ChartGetInteger(form.ChartID(),CHART_HEIGHT_IN_PIXELS,form.SubWindow()); //--- If the form is not within an extended standard graphical object if(form_index==WRONG_VALUE) { //--- Adjust the calculated form coordinates if the form is out of the chart range if(x<0) x=0; if(x>chart_width-form.Width()) x=chart_width-form.Width(); if(y<0) y=0; if(y>chart_height-form.Height()) y=chart_height-form.Height(); //--- If the one-click trading panel is not present on the chart, if(!::ChartGetInteger(form.ChartID(),CHART_SHOW_ONE_CLICK)) { //--- calculate the form coordinate so that the form does not enter the one-click trading button during relocation if(y<17 && x<41) y=17; } //--- If the one-click trading panel is on the chart, else { //--- calculate the form coordinates so that the form does not overlap with the one-click trading panel if(y<80 && x<192) y=80; } } //--- If the form is included into the extended standard graphical object else { if(graph_obj_id>WRONG_VALUE) { //--- Get the list of objects by object ID (there should be one object) CArrayObj *list_ext=CSelect::ByGraphicStdObjectProperty(GetListStdGraphObjectExt(),GRAPH_OBJ_PROP_ID,0,graph_obj_id,EQUAL); //--- If managed to obtain the list and it is not empty, if(list_ext!=NULL && list_ext.Total()>0) { //--- get the graphical object from the list CGStdGraphObj *ext=list_ext.At(0); //--- If the pointer to the object has been received, if(ext!=NULL) { //--- get the object type ENUM_OBJECT type=ext.GraphObjectType(); //--- If the object is built using screen coordinates, set the coordinates to the object if(type==OBJ_LABEL || type==OBJ_BUTTON || type==OBJ_BITMAP_LABEL || type==OBJ_EDIT || type==OBJ_RECTANGLE_LABEL) { ext.SetXDistance(x); ext.SetYDistance(y); } //--- otherwise, if the object is built based on time/price coordinates, else { //--- calculate the coordinate shift and limit the coordinates so that they are not out of the chart range int shift=(int)::ceil(form.Width()/2)+1; if(x+shift<0) x=-shift; if(x+shift>chart_width) x=chart_width-shift; if(y+shift<0) y=-shift; if(y+shift>chart_height) y=chart_height-shift; //--- set the calculated coordinates to the object ext.ChangeCoordsExtendedObj(x+shift,y+shift,form_index); } } } } } //--- Move the form by the obtained coordinates form.Move(x,y,true); } } //--- Display debugging comments on the chart Comment ( (form!=NULL ? form.Name()+":" : ""),"\n", EnumToString((ENUM_CHART_EVENT)id),"\n", EnumToString(this.m_mouse.ButtonKeyState(id,lparam,dparam,sparam)), "\n",EnumToString(mouse_state), "\npressed=",pressed,", move=",move,(form!=NULL ? ", Interaction="+(string)form.Interaction() : ""), "\npressed_chart=",pressed_chart,", pressed_form=",pressed_form, "\nform_index=",form_index,", graph_obj_id=",graph_obj_id ); //--- If the cursor is not above the form if(form==NULL) { //--- If the mouse button is pressed if(pressed) { //--- If the button is still pressed and held on the form, exit if(pressed_form) { return; } //--- If the button hold flag is not enabled yet, set the flags and enable chart tools if(!pressed_chart) { pressed_chart=true; // Button is held on the chart pressed_form=false; // Cursor is not above the form move=false; // movement disabled this.SetChartTools(::ChartID(),true); } } //--- If the mouse button is not pressed else { //--- Get the list of extended standard graphical objects CArrayObj *list_ext=GetListStdGraphObjectExt(); //--- In the loop by all extended graphical objects, int total=list_ext.Total(); for(int i=0;i<total;i++) { //--- get the next graphical object CGStdGraphObj *obj=list_ext.At(i); if(obj==NULL) continue; //--- and redraw it without a point and a circle obj.RedrawControlPointForms(0,CTRL_POINT_COLOR); } } } //--- If the cursor is above the form else { //--- If the button is still pressed and held on the chart, exit if(pressed_chart) { return; } //--- If the flag of holding the button on the form is not set yet if(!pressed_form) { pressed_chart=false; // The button is not pressed on the chart this.SetChartTools(::ChartID(),false); //--- 'The cursor is inside the form, no mouse buttons are clicked' event handler if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED) { //--- If the cursor is above the form for managing the pivot point of an extended graphical object, if(graph_obj_id>WRONG_VALUE) { //--- get the object by its ID and by the chart ID CGStdGraphObj *graph_obj=this.GetStdGraphObjectExt(graph_obj_id,form.ChartID()); if(graph_obj!=NULL) { //--- Get the toolkit of an extended standard graphical object CGStdGraphObjExtToolkit *toolkit=graph_obj.GetExtToolkit(); if(toolkit!=NULL) { //--- Draw a circle with a point on the form toolkit.DrawControlPoint(form,255,CTRL_POINT_COLOR); } } } } //--- 'The cursor is inside the form, a mouse button is clicked (any)' event handler if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_PRESSED) { this.SetChartTools(::ChartID(),false); //--- If the flag of holding the form is not set yet if(!pressed_form) { pressed_form=true; // set the flag of pressing on the form pressed_chart=false; // disable the flag of pressing on the form } } //--- 'The cursor is inside the form, the mouse wheel is being scrolled' event handler workpiece if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_WHEEL) { } //--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED) { //--- Set the cursor shift relative to the form initial coordinates form.SetOffsetX(this.m_mouse.CoordX()-form.CoordX()); form.SetOffsetY(this.m_mouse.CoordY()-form.CoordY()); //--- If the cursor is above the active area of the form for managing the pivot point of an extended graphical object, if(graph_obj_id>WRONG_VALUE) { //--- get the object by its ID and by the chart ID CGStdGraphObj *graph_obj=this.GetStdGraphObjectExt(graph_obj_id,form.ChartID()); if(graph_obj!=NULL) { //--- Get the toolkit of an extended standard graphical object CGStdGraphObjExtToolkit *toolkit=graph_obj.GetExtToolkit(); if(toolkit!=NULL) { //--- Draw a circle with a point on the form toolkit.DrawControlPoint(form,255,CTRL_POINT_COLOR); } } } } //--- '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 this.ResetAllInteractionExeptOne(form); // Reset interaction flags for all forms except the current one 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 } } //--- 'The cursor is inside the active area, the mouse wheel is being scrolled' event handler workpiece if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL) { } //--- 'The cursor is inside the window scrolling area, no mouse buttons are clicked' event handler workpiece if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED) { } //--- 'The cursor is inside the window scrolling area, a mouse button is clicked (any)' event handler workpiece if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED) { } //--- 'The cursor is inside the window scrolling area, the mouse wheel is being scrolled' event handler workpiece if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL) { } } } } } //+------------------------------------------------------------------+
Todas as melhorias no método são comentadas em detalhes diretamente no código, nós as deixamos para um estudo por conta própria. Em qualquer caso, todas as perguntas podem ser colocadas na discussão do artigo.
Estas são todas as melhorias que precisávamos fazer hoje.
Teste
Para testar, vamos pegar o Expert Advisor do artigo anterior e salvá-lo na nova pasta \MQL5\Experts\TestDoEasy\Part98\ com o novo nome TestDoEasyPart98.mq5.
Não faremos praticamente nenhuma alteração, exceto que criaremos três objetos-formas:
//+------------------------------------------------------------------+ //| TestDoEasyPart98.mq5 | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //--- includes #include <DoEasy\Engine.mqh> //--- defines #define FORMS_TOTAL (3) // Number of created forms #define START_X (4) // Initial X coordinate of the shape #define START_Y (4) // Initial Y coordinate of the shape #define KEY_LEFT (188) // Left #define KEY_RIGHT (190) // Right #define KEY_ORIGIN (191) // Initial properties //--- input parameters sinput bool InpMovable = true; // Movable forms flag sinput ENUM_INPUT_YES_NO InpUseColorBG = INPUT_YES; // Use chart background color to calculate shadow color sinput color InpColorForm3 = clrCadetBlue; // Third form shadow color (if not background color) //--- global variables CEngine engine; color array_clr[]; //+------------------------------------------------------------------+
e, a esse respeito, corrigiremos levemente o cálculo das coordenadas de cada forma criada.
A declaração do objeto-forma será retirada do loop.
A primeira forma será construída na coordenada Y 100, já o resto será traçado com um recuo de 20px em relação à borda inferior da forma anterior:
//+------------------------------------------------------------------+ //| 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(); //--- 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.TextOnBG(0,TextByLanguage("Тест 0","Test 0")+string(i+1),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); } //+------------------------------------------------------------------+
Além disso, no manipulador OnChartEvent() , encurtamos o nome dos objetos gráficos criados pelo clique do mouse (anteriormente o nome era "TrendLineExt") para não ultrapassar 63 caracteres ao criar um recurso gráfico:
if(id==CHARTEVENT_CLICK) { if(!IsCtrlKeyPressed()) return; //--- Get the chart click coordinates datetime time=0; double price=0; int sw=0; if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,sw,time,price)) { //--- Get the right point coordinates for a trend line datetime time2=iTime(Symbol(),PERIOD_CURRENT,1); double price2=iOpen(Symbol(),PERIOD_CURRENT,1); //--- Create the "Trend line" object string name_base="TLineExt"; engine.CreateLineTrend(name_base,0,true,time,price,time2,price2); //--- Get the object from the list of graphical objects by chart name and ID CGStdGraphObj *obj=engine.GraphGetStdGraphObjectExt(name_base,ChartID()); //--- Create the "Left price label" object string name_dep="PriceLeftExt"; engine.CreatePriceLabelLeft(name_dep,0,false,time,price); //--- Get the object from the list of graphical objects by chart name and ID and CGStdGraphObj *dep=engine.GraphGetStdGraphObject(name_dep,ChartID()); //--- add it to the list of graphical objects bound to the "Trend line" object obj.AddDependentObj(dep); //--- Set its pivot point by X and Y axis to the trend line left point dep.AddNewLinkedCoord(GRAPH_OBJ_PROP_TIME,0,GRAPH_OBJ_PROP_PRICE,0); //--- Create the "Right price label" object name_dep="PriceRightExt"; engine.CreatePriceLabelRight(name_dep,0,false,time2,price2); //--- Get the object from the list of graphical objects by chart name and ID and dep=engine.GraphGetStdGraphObject(name_dep,ChartID()); //--- add it to the list of graphical objects bound to the "Trend line" object obj.AddDependentObj(dep); //--- Set its pivot point by X and Y axis to the trend line right point dep.AddNewLinkedCoord(GRAPH_OBJ_PROP_TIME,1,GRAPH_OBJ_PROP_PRICE,1); } } //--- Handling graphical element collection events
Compilamos o Expert Advisor e o executamos no gráfico:
Então, o que vemos: a forma não entra na área onde está localizado o botão de ativação do painel de negociação com um clique, nem entra na área deste painel se ela estiver ativada. As formas de controle dos pontos de ancoragem do objeto gráfico padrão estendido funcionam como pretendido e não vão além dos limites do gráfico.
Mas também vemos deficiências. Depois de criar um objeto gráfico composto e ao mover seus pontos de ancoragem, eles ficam acima dos objetos-formas. Talvez isso seja normal, mas nem sempre. Por exemplo, se criamos um painel, a linha movida pelo cursor ainda deve aparecer sob o painel e não ser desenhada em cima dele. Se cada uma das formas for "clicada" com o mouse, essas formas se tornarão mais altas que o objeto gráfico composto e, quando movidas, não serão mais desenhadas sobre essas formas. Se você sobrepor parcialmente três formas umas sobre as outras, quando você passar o mouse sobre o segunda forma, a primeira se tornará ativa. Vamos corrigir isso, aqui será necessário usar, para todas as formas, a "profundidade" de sua localização em relação umas às outras e a outros objetos no gráfico.
O que virá a seguir?
No próximo artigo, continuaremos trabalhando em objetos gráficos compostos e suas funcionalidades.
*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
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/10521
- 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