Gráficos en la biblioteca DoEasy (Parte 98): Desplazamiento de los puntos de anclaje de los objetos gráficos estándar ampliados
Contenido
Concepto
A partir del artículo 93, empezamos a crear objetos gráficos compuestos en la biblioteca. Entonces tuvimos que desviar nuestra atención hacia la mejora de la funcionalidad de los objetos de formulario creados sobre la base de la clase CCanvas, porque en los objetos gráficos compuestos para crear los elementos de control de los puntos de pivote del objeto gráfico, que forma parte del objeto gráfico estándar ampliado, utilizamos objetos de formulario, y necesitábamos la nueva funcionalidad de los objetos de formulario, que, por supuesto, estaba prevista pero aún no se había implementado en la biblioteca.
En el artículo anterior, terminamos de modificar los objetos basados en la clase CCanvas, y hoy vamos a seguir desarrollando el tema de los objetos gráficos estándar extendidos a partir de los cuales se crean los objetos gráficos compuestos.
El propósito de este artículo no es desarrollar ninguna clase nueva. Hoy vamos a hablar de la mejora de la funcionalidad que ya hemos preparado para crear herramientas prácticas que nos ayuden a desplazar los puntos de pivote de los objetos gráficos estándar. Este no va a ser un gran artículo, más bien describiremos la creación de un prototipo de objeto gráfico compuesto. Por cierto, ya lo hemos creado en artículos anteriores: es una línea de tendencia habitual con marcadores de precio adicionales en sus extremos:
Hoy abordaremos la cuestión del desplazamiento de los puntos de pivote de la línea de tendencia para que las marcas de precio que se encuentran en el lado móvil de la línea de tendencia también se muevan. Para realizar el desplazamiento, hemos colocado objetos de formulario en los puntos de pivote de la línea. Al capturar este objeto y moverlo, también desplazaremos el lado correspondiente de la línea de tendencia. Mientras el cursor esté alejado del punto de pivote de la línea de tendencia, el objeto de formulario permanecerá invisible. Pero cuando el cursor se acerque a cierta distancia del punto de pivote (al entrar en la zona totalmente transparente del formulario), se dibujarán un punto y un círculo en el formulario:
De esta forma, dará la impresión de que el objeto de gestión aparece como punto de pivote para la línea de tendencia. Esto hará que el formulario tenga un tamaño mayor que su área activa. El área activa de un formulario es el área del que podemos mover. Puede utilizarse para interactuar con él de otras maneras, usando los botones o la ruleta del ratón.
De esta forma, si el cursor del ratón está dentro de un formulario pero fuera de su área activa, podremos implementar, por ejemplo, un menú contextual de un objeto gráfico compuesto clicando con el botón derecho. Si el cursor se encuentra en el área activa, además del menú contextual, también podremos capturar ese formulario con el ratón y moverlo. En este caso, el extremo de la línea a la que está vinculado el formulario se desplazará detrás.
Naturalmente, se trata solo de un objeto gráfico compuesto de prueba en el que estamos "practicando" la funcionalidad creada. Una vez creemos todas las herramientas necesarias para crear objetos gráficos compuestos y trabajar con los objetos de formulario, crearemos un pequeño conjunto de objetos gráficos compuestos de la biblioteca estándar a partir de los cuales podremos crear los nuestros. Y su creación servirá como ejemplo y descripción de la forma en que debemos crear nuestros propios objetos de este tipo.
Pero hasta ahora solo hemos desarrollado la funcionalidad de la biblioteca y creado los "bloques de construcción" a partir de los cuales podremos hacer nuestros propios objetos. Esos pasos que recorremos, estudiamos e implementamos artículo tras artículo ya estarán listos y serán la base que utilizaremos "tal cual" para no hacer todo desde cero.
Mejorando las clases de la biblioteca
Vamos a abrir el archivo \MQL5\Include\DoEasy\Defines.mqh e introducir algunas mejoras.
Al construir los formularios de control, dibujamos un punto y un círculo en los puntos de pivote. El color con el que se dibujan se ha indicado previamente en el código de forma directa. Añade una macrosustitución en la que escribimos este color por defecto:
//--- 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 | //+------------------------------------------------------------------+
La macrosustitución con el nombre CTRL_POINT_SIZE se renombra como CTRL_POINT_RADIUS, dado que no se trata del tamaño total de su circunferencia, sino de su radio. Simplemente, el nombre de esta macrosustitución era un poco engañoso al calcular el área activa del objeto formulario.
En el archivo de clase del objeto de elemento gráfico \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, mejoramos ligeramente el método de creación de un objeto de elemento gráfico. Al llamar al método CreateBitmapLabel() de la clase CCanvas, lamentablemente no se retorna ningún código de error. Por ello, restableceremos el último código de error antes de llamar a este método, y si la etiqueta gráfica no ha podido ser creada, registraremos un mensaje con el código del error. Esto hará que la depuración resulte un poco más sencilla.
//+------------------------------------------------------------------+ //| 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 qué hemos tenido que hacer esto? Al crear los objetos de formulario, se tardaba mucho en averiguar por qué no se podía crear el recurso gráfico. Al final, resultó que el nombre del recurso gráfico que se estaba creando tenía más de 63 caracteres. Si el método para crear una etiqueta gráfica de la clase CCanvas nos informara del error, no tendríamos que buscar nada, pues recibiríamos un mensaje con el código de error inmediatamente, y no tendríamos que pasar por todas las cadenas de llamadas a diferentes métodos en diferentes clases.
En general, este ajuste tampoco nos dirá el verdadero código de error si el nombre del recurso tiene más de 63 caracteres:
ERR_RESOURCE_NAME_IS_TOO_LONG 4018 The resource name exceeds 63 characters
sino que retornará un código de error
ERR_RESOURCE_NOT_FOUND 4016 Resource with such a name is not found in EX5
pero sigue siendo mejor que nada, e inmediatamente nos hace pensar "¿por qué no se ha creado un recurso gráfico?"...
Vamos a introducir algunas mejoras en el archivo de clase de instrumental del objeto gráfico estándar extendido en el archivo
\MQL5\Include\DoEasy\Objects\Graph\Extend\CGStdGraphObjExtToolkit.mqh.
Cada objeto gráfico tiene uno o más puntos de pivote que podemos usar para posicionar el objeto en el gráfico. A cada uno de estos puntos se le adjunta un objeto de formulario para controlar estos puntos de pivote. Los objetos gráficos estándar tienen sus propios puntos por los que se pueden desplazar. Aparecen al seleccionar un objeto gráfico. En la biblioteca, no vamos a gestionar los objetos gráficos estándar extendidos de esta forma. Para implementar su funcionalidad, nos resultará más cómodo usar objetos de formulario por los que podremos desplazar los puntos de pivote de los objetos gráficos. Puede haber más objetos de formulario de este tipo que puntos de pivote para un objeto gráfico. Por lo tanto, no podemos juzgar el número de objetos de formulario según el número de puntos de pivote de un objeto gráfico. Y necesitamos conocer este número. Por ello, en la sección pública de la clase , añadimos un método que retorna el número de objetos de formulario creados para controlar los puntos de pivote del objeto gráfico, y declaramos un método que dibuja en los objetos de formulario totalmente transparentes los puntos de pivote del objeto gráfico:
//--- (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);
En el método que crea un objeto formulario sobre el punto de pivote del objeto básico, acortamos el nombre del objeto creado — , en lugar de "_TKPP_" escribiremos "_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()); } //+------------------------------------------------------------------+
Precisamente aquí (y en el archivo del asesor de prueba) tuvimos que acortar el nombre del objeto de formulario que estábamos creando, ya que el nombre del recurso gráfico superaba los 63 caracteres y el objeto no se creaba. La razón reside en el método de creación de recursos dinámicos en la clase CCanvas, donde el nombre del recurso creado está formado por los caracteres "::" + el nombre transmitido al método (que especificamos en el método anterior) + el ID del gráfico + el número de milisegundos transcurridos desde el inicio del sistema + un número pseudoaleatorio:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
Todo esto, por desgracia, fija serias limitaciones a la hora de elegir un nombre comprensible para el objeto creado.
En el método encargado de crear objetos de formulario sobre los puntos de pivote del objeto básico, calculamos la sangría de cada lado del objeto de formulario para indicar la ubicación y el tamaño del área activa del mismo, que deberá encontrarse en el centro del formulario. Su tamaño deberá ser igual a los dos valores escritos en la macrosustitución CTRL_POINT_RADIUS. Como se trata del radio, para que el área activa del formulario sea igual a la circunferencia dibujada en su centro, deberemos tomar los dos valores del radio, restarlos a la anchura del formulario (la altura del formulario es igual a su anchura) y dividir el valor resultante entre dos.
El valor calculado del desplazamiento del borde del área activa desde el borde del formulario se indica en el 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 in 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; } //+------------------------------------------------------------------+
Al crear un formulario, podemos dibujar rectángulos para representar el tamaño del formulario y su área activa para la depuración — estas líneas están comentadas. Usando el nuevo método indicado a continuación, dibujamos las circunferencias en el centro del formulario, completamente transparentes (¿deberán dibujarse entonces?).
Método que dibuja un punto de control en un formulario:
//+------------------------------------------------------------------+ //| 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 } //+------------------------------------------------------------------+
El método contiene dos líneas que simplemente hemos movido fuera del método anterior a un método aparte. Para qué lo necesitamos Tenemos que mostrar el punto con la circunferencia en el centro del formulario en diferentes momentos, o bien ocultarlo. Para ello, llamaremos a este método, especificando la opacidad y el color de los formularios a dibujar.
Desde el manejador de eventos , eliminamos la gestión del movimiento del cursor del ratón — No la necesitamos aquí:
//+------------------------------------------------------------------+ //| 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 a mejorar la clase de objeto gráfico estándar abstracto en el archivo \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh.
En la sección pública de la clase , vamos a declarar un método que permita cambiar simultáneamente las coordenadas de los puntos de pivote de un objeto gráfico, y las de los objetos que están unidos a él:
//--- 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; }
Vamos a declarar tres métodos para acceder al formulario con el que gestionaremos los puntos de pivote del objeto gráfico:
//--- 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:
A continuación, añadiremos un método para establecer el tiempo y el precio según las coordenadas de la pantalla:
//--- 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 poder trabajar con los objetos gráficos en las coordenadas X e Y de la pantalla, necesitaremos convertir las coordenadas de la pantalla en coordenadas de tiempo/precio. Este método comprueba el tipo de objeto actual, y si se construye usando coordenadas de pantalla, sus coordenadas de pantalla se cambiarán de inmediato. Si este objeto gráfico se basa en las coordenadas de tiempo/precio: primero tendremos que convertir las coordenadas de pantalla transmitidas al método en valores de tiempo y precio; luego escribiremos estos valores en los parámetros del objeto gráfico.
Método que retorna el formulario de gestión de un punto de pivote del 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); } //+------------------------------------------------------------------+
Aquí todo es simple: si existe un objeto de instrumental del objeto gráfico estándar extendido, se retornará un objeto de formulario según el índice. En caso contrario, se retornará NULL.
Método que retorna el número de objetos de formulario de gestión de puntos de control:
//+------------------------------------------------------------------+ //| Return the number of form objects | //| for managing control points | //+------------------------------------------------------------------+ int CGStdGraphObj::GetNumControlPointForms(void) { return(this.ExtToolkit!=NULL ? this.ExtToolkit.GetNumControlPointForms() : 0); } //+------------------------------------------------------------------+
El método es el mismo que el anterior: si existe un objeto de instrumental del objeto gráfico estándar extendido, se retornará el número de objetos de formulario. En caso contrario, se retornará 0.
Método que redibuja el formulario del punto de control de un objeto gráfico estándar extendido:
//+------------------------------------------------------------------+ //| 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); } } //+------------------------------------------------------------------+
El método se comenta con detalle en el código. Brevemente sobre el significado: primero, redibujamos todos los objetos de formulario adjuntos al objeto actual. Luego recordamos que a este objeto se le pueden adjuntar objetos gráficos dependientes, que a su vez también tienen objetos de formulario. Así que creamos un ciclo a través de todos los objetos dependientes y llamamos a este método para cada uno de los objetos dependientes. Estos, a su vez, también recorrerán su lista de objetos dependientes y llamarán al mismo método sobre ellos. Y así sucesivamente hasta que se hayan redibujado todos los objetos de formulario de todos los objetos gráficos vinculados.
Método que cambia las coordenadas X e Y del objeto actual y de todos los objetos dependientes:
//+----------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
El método se comenta con detalle en el código. En resumen: primero, modificamos las coordenadas del punto de referencia especificado del objeto actual. Además, si el objeto tiene objetos gráficos dependientes unidos a él, entonces en el punto que se ha desplazado y al que se puede vincular el objeto dependiente, también deberemos desplazar dicho objeto gráfico dependiente a las nuevas coordenadas, cosa que sucede en el método.
Desde el manejador de eventos , eliminamos la gestión del movimiento del ratón — . No la necesitaremos aquí:
//+------------------------------------------------------------------+ //| 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 a mejorar la clase de colección de objetos gráficos en \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh.
Cuando movemos el cursor por el gráfico (y si hay objetos en el gráfico), el manejador de eventos de la clase determina el objeto sobre el que se encuentra el cursor del ratón. El manejador del estado del ratón se inicia entonces en relación a este objeto. Cuando colocamos el cursor en el objeto de control del punto de pivote del objeto gráfico estándar extendido, veremos la información tanto del propio formulario como de su índice y del objeto gráfico al que está unido el formulario en el manejador. Para no tener que volver a buscar este formulario y el objeto gráfico fuera del manejador, bastará con almacenar en variables el identificador del objeto gráfico al que está unido el formulario y el índice del formulario sobre el que se encuentra el cursor. Estos datos nos ayudarán a seleccionar rápidamente los objetos necesarios de las listas y a entender que el cursor está sobre el formulario: según el valor de estas variables.
Escribimos estas variables en la declaración del método que retorna el puntero al formulario que se encuentra bajo el 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);
Las variables se transmitirán al método por enlace. En otras palabras, dentro del método, simplemente escribiremos los valores necesarios en ellas, y dichos valores se guardarán en las variables correspondientes, por lo que podremos utilizarlos posteriormente.
En la sección pública de la clase, declaramos dos métodos que retornan los objetos gráficos estándar y extendido según el 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; }
Necesitaremos los métodos para obtener el puntero a un objeto gráfico según su identificador. Vamos a analizar la aplicación de estos métodos.
Método que retorna un objeto gráfico estándar extendido existente según su 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); } //+------------------------------------------------------------------+
Aquí: obtenemos una lista con los objetos gráficos estándar extendidos, dejando en la lista solo los objetos con el identificador gráfico indicado.
En la lista resultante, seleccionamos el objeto con el ID de objeto especificado. Si la lista es válida y no está vacía, retornará el puntero al objeto. En caso contrario, retornará NULL.
Método que retorna un objeto gráfico estándar existente según su 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); } //+------------------------------------------------------------------+
Aquí: recuperamos la lista de objetos gráficos según el ID del gráfico. En la lista resultante, dejamos el objeto con el ID de objeto indicado.
Si la lista resultante es válida y no está vacía, retornará el puntero al objeto buscado. De lo contrario, retornará NULL.
Vamos a mejorar el método que retorna el puntero al formulario que se encuentra debajo del cursor. Necesitamos añadir e inicializar las dos nuevas variables para guardar el identificador del objeto gráfico estándar extendido y el índice del punto de anclaje gestionado por el formulario, y además añadir el bloque de procesamiento de objetos gráficos estándar extendidos: , la búsqueda de los formularios adjuntos a estos objetos y sobre los que se encuentra el cursor del ratón:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
La lógica completa del bloque de código añadido se describe en los comentarios. En resumen: necesitamos encontrar el objeto de formulario sobre el que se sitúa el cursor del ratón. En primer lugar, buscamos los objetos de formulario almacenados en la lista de elementos gráficos de la clase de colección. Si no encontramos ningún formulario, deberemos iterar todos los objetos gráficos estándar extendidos en busca de sus formularios: , el cursor puede estar sobre uno de ellos. Si este es el caso, escribiremos el identificador del objeto gráfico estándar extendido al que se adjunta este formulario y el índice de dicho formulario en las variables transmitidas por enlace al método para saber el punto de pivote de qué objeto gráfico controla este formulario.
Ahora necesitaremos gestionar la interacción del cursor del ratón con los objetos de formulario de los objetos gráficos estándar extendidos en el manejador de eventos. Además, controlaremos el desplazamiento de los objetos del formulario para que no puedan alcanzar la zona del gráfico en la esquina superior derecha, donde se encuentra el botón de activación del modo comercial con un solo clic. Este botón está siempre por encima de todos los objetos, y no queremos que el formulario en movimiento pueda pasar por debajo de él, para que no cliquemos accidentalmente en este botón en lugar de en el formulario. Si el panel de transacciones con un solo clic ya está activado, el formulario tampoco debería estar debajo de él: simplemente no resultará visible si es más pequeño que este panel, lo cual causará inconvenientes al trabajar con el formulario; así, deberemos desactivar el panel de transacciones con un solo clic para volver a ver un formulario que haya resultado accidentalmente debajo de este panel.
Vamos a analizar la posibilidad de realizar mejoras y cambios en el manejador 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 las mejoras del método se comentan con detalle directamente en el código: dejaremos que el lector las estudie por su cuenta. En cualquier caso, cualquier pregunta al respecto podrá formularse en los comentarios al artículo.
Estas son todas las mejoras que necesitábamos hacer hoy.
Simulación
Para la simulación, tomaremos el asesor del artículo anterior y lo guardaremos en la nueva carpeta \MQL5\Experts\TestDoEasy\Part98\ con el nuevo nombre TestDoEasyPart98.mq5.
No realizaremos casi ningún cambio, salvo la creación de tres objetos de formulario:
//+------------------------------------------------------------------+ //| 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[]; //+------------------------------------------------------------------+
y en relación con esto, deberemos ajustar ligeramente el cálculo de las coordenadas de cada formulario creado.
La declaración del objeto de formulario se sitúa fuera del ciclo.
El primer formulario se construirá en la coordenada Y 100, mientras que los demás se construirán con una sangría de 20 píxeles respecto al borde inferior del formulario 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); } //+------------------------------------------------------------------+
Asimismo, en el manejador OnChartEvent(), acortaremos la longitud del nombre de los objetos gráficos creados con un clic del ratón (antes el nombre era "TrendLineExt"), para no superar los 63 caracteres al crear un 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
Vamos a compilar el asesor y a ejecutarlo en el gráfico:
¿Qué podemos ver? El formulario no entra en la zona donde se encuentra el botón de activación del panel de comercio con un solo clic, ni en la zona de este panel si está activado. Los formularios de control de los puntos de pivote del objeto gráfico estándar extendido funcionan según lo previsto y no se expanden más allá del gráfico.
No obstante, también hay algunas deficiencias. Una vez que se crea un objeto gráfico compuesto, al desplazarse sus puntos de pivote, éstos quedan por encima de los objetos de formulario. Puede que sea normal, pero no siempre. Por ejemplo, si tenemos un panel creado, la línea que se desplaza con el cursor deberá seguir estando debajo del panel, no dibujada por encima de él. Si clicamos en cada uno de los formularios con el ratón, estos formularios pasarán a ser más altos que el objeto gráfico compuesto, y este ya no se dibujará por encima de estos formularios al moverse. Si se superponen parcialmente los tres formularios, al pasar el ratón por encima del segundo formulario, el primero se activará. Vamos a arreglar esto: aquí tendremos que usar la "profundidad" de todos los formularios en relación con los demás objetos en el gráfico.
¿Qué es lo próximo?
En el próximo artículo, continuaremos trabajando con los objetos gráficos compuestos y su funcionalidad.
*Artículos de esta serie:
Gráficos en la biblioteca DoEasy (Parte 93): Preparando la funcionalidad para crear objetos gráficos compuestos
Gráficos en la biblioteca DoEasy (Parte 94): Objetos gráficos compuestos, desplazamiento y eliminación
Gráficos en la biblioteca DoEasy (Parte 95): Elementos de control de los objetos gráficos compuestos
Gráficos en la biblioteca DoEasy (Parte 96): Trabajando con los eventos del ratón y los gráficos en los objetos de formulario
Gráficos en la biblioteca DoEasy (Parte 97): Procesamiento independiente del desplazamiento de los objetos de formulario
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/10521
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso