Gráficos en la biblioteca DoEasy (Parte 99): Desplazando un objeto gráfico extendido con un punto de control
Contenido
Concepto
En el último artículo, creamos la posibilidad de desplazar los puntos de pivote de un objeto gráfico extendido usando formularios de gestión. Sin embargo, aún no disponemos de la funcionalidad necesaria para desplazar un objeto gráfico al completo. Al igual que cualquier objeto, un objeto gráfico estándar puede ser desplazado por completo moviendo su punto central: implementaremos un punto de control central del objeto gráfico para que, moviendo este punto, podamos mover el objeto gráfico como un todo, y no sus puntos de pivote por separado. Dado que para la prueba hemos seleccionado un objeto gráfico compuesto que consta de una línea de tendencia en cuyos extremos se adjuntan objetos de etiqueta de precio, hoy implementaremos todo el trabajo necesario para los objetos gráficos con dos puntos de anclaje, logrando así mover sus extremos, además de un punto central que permitirá mover el objeto gráfico completo (dos puntos para modificar los extremos del objeto y un punto central para mover este). A continuación, crearemos también dichos formularios con puntos de gestión para otros objetos gráficos que tengan más de tres puntos de gestión.
Asimismo, optimizaremos ligeramente el código para calcular las coordenadas de pantalla de los puntos de pivote de un objeto gráfico en cuanto a su división en métodos separados, lo cual simplificará la comprensión de la lógica principal; después de todo, podemos leer con mayor facilidad un código que contiene una llamada a un método que retorna un cierto valor (y dentro de él, se llama a otro método, que a su vez también calcula algo), que si se ha escrito todo el código de dichos métodos en el bloque de cálculo principal, lo que aumenta su voluminosidad y hace la lectura de su lógica más confusa.
En algunos casos, no todo lo que hagamos hoy funcionará correctamente y según lo previsto. Sin embargo, los artículos también describen exactamente el proceso de desarrollo y creación del código para obtener el resultado deseado. A nuestro juicio, resulta mucho más interesante recorrer el camino casi completo desde la planificación de la funcionalidad hasta su implementación que simplemente leer una presentación seca del estilo "cómo ha salido todo al final".
Como la función para obtener las coordenadas de pantalla ChartTimePriceToXY() retorna solo las coordenadas de la parte visible del gráfico, lamentablemente no podremos calcular las coordenadas de pantalla del punto de una línea que vaya más allá del gráfico. La función siempre retornará el valor 0 si solicitamos la coordenada X en píxeles de la pantalla de tiempo que se encuentra fuera del lado izquierdo del gráfico visible en el gráfico. Por ello, al desplazar un objeto gráfico compuesto en la pantalla, cuando su parte izquierda va más allá del borde izquierdo de la pantalla, el punto de pivote izquierdo del objeto permanecerá en la coordenada 0 en los píxeles del gráfico. Esto distorsionará el aspecto del objeto gráfico. Lo mismo se aplica al lado derecho del objeto gráfico y al lado derecho de la pantalla del gráfico (así como a las partes superior e inferior). Por ello, restringiremos la salida del objeto gráfico compuesto fuera de la parte visible del gráfico. Esto evitará que el aspecto del objeto gráfico se distorsione cuando cualquiera de sus lados "se apoye" contra el borde de la pantalla al moverse.
Mejorando las clases de la biblioteca
Como el objeto de formulario que sirve para mostrar el punto de control que gestiona los puntos de pivote del objeto estándar extendido es un objeto importante en la biblioteca de objetos, pero estos formularios no forman parte de la colección de objetos gráficos, necesitaremos definir un nuevo tipo para tales formularios. Para cada objeto significativo de la biblioteca, tenemos nuestros propios nombres para los tipos de objetos de la biblioteca, con los que podemos determinar qué tipo de objeto está activo. Vamos a definir este tipo para los objetos de formulario de gestión de los puntos de pivote como parte de los objetos gráficos extendidos de la biblioteca.
En el archivo \MQL5\Include\DoEasy\Defines.mqh, en la enumeración de los tipos de objeto de la biblioteca, añadimos el nuevo tipo:
//+------------------------------------------------------------------+ //| List of library object types | //+------------------------------------------------------------------+ enum ENUM_OBJECT_DE_TYPE { //--- Graphics OBJECT_DE_TYPE_GBASE = COLLECTION_ID_LIST_END+1, // "Base object of all library graphical objects" object type OBJECT_DE_TYPE_GELEMENT, // "Graphical element" object type OBJECT_DE_TYPE_GFORM, // Form object type OBJECT_DE_TYPE_GFORM_CONTROL, // "Form for managing pivot points of graphical object" object type OBJECT_DE_TYPE_GSHADOW, // Shadow object type //--- Animation OBJECT_DE_TYPE_GFRAME, // "Single animation frame" object type OBJECT_DE_TYPE_GFRAME_TEXT, // "Single text animation frame" object type OBJECT_DE_TYPE_GFRAME_QUAD, // "Single rectangular animation frame" object type OBJECT_DE_TYPE_GFRAME_GEOMETRY, // "Single geometric animation frame" object type OBJECT_DE_TYPE_GANIMATIONS, // "Animations" object type //--- Managing graphical objects ... ... ... }
En el archivo \MQL5\Include\DoEasy\Data.mqh escribimos los índices de los nuevos mensajes:
MSG_LIB_SYS_REQUEST_OUTSIDE_LONG_ARRAY, // Request outside long array MSG_LIB_SYS_REQUEST_OUTSIDE_DOUBLE_ARRAY, // Request outside double array MSG_LIB_SYS_REQUEST_OUTSIDE_STRING_ARRAY, // Request outside string array MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY, // Request outside the array MSG_LIB_SYS_FAILED_CONV_GRAPH_OBJ_COORDS_TO_XY, // Failed to convert graphical object coordinates to screen ones MSG_LIB_SYS_FAILED_CONV_TIMEPRICE_COORDS_TO_XY, // Failed to convert time/price coordinates to screen ones
y los textos de los mensajes que se corresponden con los índices nuevamente añadidos:
{"Запрос за пределами long-массива","Data requested outside the long-array"}, {"Запрос за пределами double-массива","Data requested outside the double-array"}, {"Запрос за пределами string-массива","Data requested outside the string-array"}, {"Запрос за пределами массива","Data requested outside the array"}, {"Не удалось преобразовать координаты графического объекта в экранные","Failed to convert graphics object coordinates to screen coordinates"}, {"Не удалось преобразовать координаты время/цена в экранные","Failed to convert time/price coordinates to screen coordinates"},
Para comprender si hay (o no) un error al convertir las coordenadas de tiempo/precio en coordenadas de pantalla al desarrollar la funcionalidad para desplazar objetos gráficos, si existe tal error, informaremos sobre él, lo cual excluirá esta cadena de la verificación del error en la lógica al realizar su búsqueda.
La función ChartTimePriceToXY(), como resultado de la cual podemos obtener el error de conversión de las coordenadas, también se usa en la clase del objeto de ventana del gráfico, en el archivo \MQL5\Include\DoEasy\Objects\Chart\ChartWnd.mqh. Vamos a añadir al método TimePriceToXY() de esta clase la posibilidad de mostrar un mensaje de error en el diario de registro (al recibir dicho error) cuando intentamos convertir las coordenadas:
//+------------------------------------------------------------------+ //| Convert chart coordinates from the time/price representation | //| to X and Y coordinates | //+------------------------------------------------------------------+ bool CChartWnd::TimePriceToXY(const datetime time,const double price) { ::ResetLastError(); if(!::ChartTimePriceToXY(this.m_chart_id,this.WindowNum(),time,price,this.m_wnd_coord_x,this.m_wnd_coord_y)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_CONV_TIMEPRICE_COORDS_TO_XY); CMessage::ToLog(DFUN,::GetLastError(),true); return false; } return true; } //+------------------------------------------------------------------+
Primero, mostramos en el diario de registro la entrada "Error al convertir el tiempo/precio en coordenadas de pantalla"y luego una descripción del error junto con el código del mismo.
Como ahora hemos declarado el nuevo tipo de objeto de biblioteca para los puntos de control encargados de gestionar los puntos de pivote del objeto gráfico estándar extendido, necesitamos crear la clase de dicho objeto heredada de la clase del objeto de formulario. En ella, añadiremos algunas variables y métodos para simplificar el trabajo con tales objetos.
La escribiremos en el instrumental del objeto gráfico estándar extendido, en el archivo \MQL5\Include\DoEasy\Objects\Graph\Extend\CGStdGraphObjExtToolkit.mqh:
//+------------------------------------------------------------------+ //| CGStdGraphObjExtToolkit.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Graph\Form.mqh" //+------------------------------------------------------------------+ //| Class of the form for managing pivot points of a graphical object| //+------------------------------------------------------------------+ class CFormControl : public CForm { private: bool m_drawn; // Flag indicating that the pivot point is drawn on the form int m_pivot_point; // Pivot point managed by the form public: //--- (1) Return and (2) set the drawn point flag bool IsControlAlreadyDrawn(void) const { return this.m_drawn; } void SetControlPointDrawnFlag(const bool flag) { this.m_drawn=flag; } //--- (1) Return and (2) set the pivot point managed by the form int GraphObjPivotPoint(void) const { return this.m_pivot_point; } void SetGraphObjPivotPoint(const int index) { this.m_pivot_point=index; } //--- Constructor CFormControl(void) { this.m_type=OBJECT_DE_TYPE_GFORM_CONTROL; } CFormControl(const long chart_id,const int subwindow,const string name,const int pivot_point,const int x,const int y,const int w,const int h) : CForm(chart_id,subwindow,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GFORM_CONTROL; this.m_pivot_point=pivot_point; } }; //+------------------------------------------------------------------+ //| Extended standard graphical | //| object toolkit class | //+------------------------------------------------------------------+ class CGStdGraphObjExtToolkit : public CObject
La variable de miembro privada de la clase m_drawn almacenará una bandera que nos indicará que el punto ya se ha dibujado en el formulario. ¿Por qué necesitamos esta variable? Si el cursor del ratón se elimina del área activa del formulario de control para gestionar los puntos de pivote del objeto gráfico, deberemos borrar el punto dibujado en el formulario. Ahora se ha hecho de forma que, en todos estos formularios, el punto dibujado se elimine constantemente si el cursor del ratón no se desplaza sobre el área activa del formulario. Esto no resulta práctico: ¿por qué sobrecargar el sistema con el redibujado constante de todos esos formularios, si podemos mirar en primer lugar la bandera que nos indica que el formulario ya se ha redibujado y no tiene ningún punto? Esta bandera nos indicará si un punto ya ha sido dibujado o borrado. Y dado que en el futuro desarrollaremos algunos efectos visuales para dibujar estos puntos (y no solo para ellos), es mejor tener una bandera que se establezca inmediatamente después de la ejecución del manejador de efectos visuales, y no determinar de alguna forma si el proceso de dibujado ya ha finalizado.
La variable de miembro privada m_pivot_point almacenará el índice del punto de pivote que gestiona el formulario. Un objeto gráfico tiene varios de estos puntos de gestión. Por ejemplo, una línea de tendencia tiene tres de ellos: dos puntos en los extremos de la línea, que sirven para cambiar de forma independiente la ubicación de los extremos de la línea, y un punto central, para mover todo el objeto. Los índices almacenados en los objetos de formulario corresponderán a los índices de los puntos de pivote de dicha línea: 0 y 1 para puntos en los bordes de la línea y 2 para la línea central. Otros objetos gráficos pueden tener puntos de control completamente diferentes, pero todos los índices se corresponderán con los puntos de pivote existentes del objeto + uno adicional (no siempre, discutiremos esto en artículos posteriores) para mover todo el objeto.
Los métodos públicos de la clase se usan para establecer/retornar los valores de las variables descritas anteriormente. La clase también tiene dos constructores. En el constructor por defecto, el tipo de objeto se establece en el nuevo tipo OBJECT_DE_TYPE_GFORM_CONTROL, añadido hoy.
Todos los valores transmitidos al constructor de la clase principal se pasan al constructor paramétrico, más una variable adicional: el índice del punto de pivote del objeto gráficocontrolado por el formulario creado.
Ahora todos los formularios de gestión de los puntos de pivote en la clase CGStdGraphObjExtToolkit tendrán el tipo CFormControl, por eso debemos corregir el tipo del objeto de formulario CForm en CFormControl, y añadir métodos nuevos para trabajar con los formularios de gestión de los puntos de pivote del objeto gráfico:
//+------------------------------------------------------------------+ //| Extended standard graphical | //| object toolkit class | //+------------------------------------------------------------------+ class CGStdGraphObjExtToolkit : public CObject { private: long m_base_chart_id; // Base graphical object chart ID int m_base_subwindow; // Base graphical object chart subwindow ENUM_OBJECT m_base_type; // Base object type string m_base_name; // Base object name int m_base_pivots; // Number of base object reference points datetime m_base_time[]; // Time array of base object reference points double m_base_price[]; // Price array of base object reference points int m_base_x; // Base object X coordinate int m_base_y; // Base object Y coordinate int m_ctrl_form_size; // Size of forms for managing reference points int m_shift; // Shift coordinates for adjusting the form location CArrayObj m_list_forms; // List of form objects for managing reference points //--- Create a form object on a base object reference point CFormControl *CreateNewControlPointForm(const int index); //--- Return X and Y (1) screen coordinates of the specified reference point of the graphical object bool GetControlPointCoordXY(const int index,int &x,int &y); //--- Set the parameters of a form object for managing pivot points void SetControlFormParams(CFormControl *form,const int index); public: //--- Set the parameters of the base object of a composite graphical object void SetBaseObj(const ENUM_OBJECT base_type,const string base_name, const long base_chart_id,const int base_subwindow, const int base_pivots,const int ctrl_form_size, const int base_x,const int base_y, const datetime &base_time[],const double &base_price[]); //--- Set the base object (1) time, (2) price, (3) time and price coordinates void SetBaseObjTime(const datetime time,const int index); void SetBaseObjPrice(const double price,const int index); void SetBaseObjTimePrice(const datetime time,const double price,const int index); //--- Set the base object (1) X, (2) Y, (3) X and Y screen coordinates void SetBaseObjCoordX(const int value) { this.m_base_x=value; } void SetBaseObjCoordY(const int value) { this.m_base_y=value; } void SetBaseObjCoordXY(const int value_x,const int value_y) { this.m_base_x=value_x; this.m_base_y=value_y; } //--- (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 CFormControl *GetControlPointForm(const int index) { return this.m_list_forms.At(index); } CFormControl *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); //--- (1) Draw a control point on the form, (2) draw a control point on the form and delete it on all other forms void DrawControlPoint(CFormControl *form,const uchar opacity,const color clr); void DrawOneControlPoint(CFormControl *form,const uchar opacity=255,const color clr=CTRL_POINT_COLOR); //--- (1) Draw using a default color, (remove) a control point on the form void DrawControlPoint(CFormControl *form) { this.DrawControlPoint(form,255,CTRL_POINT_COLOR);} void ClearControlPoint(CFormControl *form) { this.DrawControlPoint(form,0,CTRL_POINT_COLOR); } //--- Remove all form objects from the list void DeleteAllControlPointForm(void); //--- Event handler void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Constructor/destructor CGStdGraphObjExtToolkit(const ENUM_OBJECT base_type,const string base_name, const long base_chart_id,const int base_subwindow, const int base_pivots,const int ctrl_form_size, const int base_x,const int base_y, const datetime &base_time[],const double &base_price[]) { this.m_list_forms.Clear(); this.SetBaseObj(base_type,base_name,base_chart_id,base_subwindow,base_pivots,ctrl_form_size,base_x,base_y,base_time,base_price); this.CreateAllControlPointForm(); } CGStdGraphObjExtToolkit(){;} ~CGStdGraphObjExtToolkit(){;} }; //+------------------------------------------------------------------+
Vamos a mejorar el método GetControlPointCoordXY(), que retorna las coordenadas X e Y del punto de pivote indicado del objeto gráfico en coordenadas de pantalla.
Antes, el método simplemente retornaba las coordenadas calculadas del punto de pivote indicado del objeto gráfico. Ahora debemos considerar que los objetos gráficos pueden tener un número diferente de puntos de pivote y una ubicación diferente del punto de pivote central. Por ello, implementaremos el cálculo para diferentes tipos de objetos en el interruptor switch(). Al mismo tiempo, deberemos tener en cuenta las coordenadas de qué punto de pivote queremos obtener, uno de los que se encuentran en los bordes del objeto, o uno común a todos, el central. Si el índice del punto de pivote transmitido al método es menor que el número total de puntos de pivote del objeto gráfico, se solicitarán las coordenadas del punto de pivote. En caso contrario, se solicitarán las coordenadas del punto de pivote central.
Por ahora, solo obtendremos las coordenadas X e Y para aquellos objetos gráficos que tengan dos puntos de pivote en los bordes y uno central:
//+------------------------------------------------------------------+ //| Return the X and Y coordinates of the specified pivot point | //| of the graphical object in screen coordinates | //+------------------------------------------------------------------+ bool CGStdGraphObjExtToolkit::GetControlPointCoordXY(const int index,int &x,int &y) { //--- Declare form objects, from which we are to receive their screen coordinates CFormControl *form0=NULL, *form1=NULL; //--- Set X and Y to zero - these values will be received in case of a failure x=0; y=0; //--- Depending on the graphical object type switch(this.m_base_type) { //--- Objects drawn using screen coordinates case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : case OBJ_EVENT : //--- Write object screen coordinates and return 'true' x=this.m_base_x; y=this.m_base_y; return true; //--- Lines (vertical and horizontal) case OBJ_VLINE : break; case OBJ_HLINE : break; //--- Lines case OBJ_TREND : case OBJ_TRENDBYANGLE : case OBJ_CYCLES : case OBJ_ARROWED_LINE : //--- Channels case OBJ_CHANNEL : case OBJ_STDDEVCHANNEL : case OBJ_REGRESSION : //--- Gann case OBJ_GANNLINE : case OBJ_GANNGRID : //--- Fibo case OBJ_FIBO : case OBJ_FIBOTIMES : case OBJ_FIBOFAN : case OBJ_FIBOARC : case OBJ_FIBOCHANNEL : case OBJ_EXPANSION : //--- Calculate coordinates for forms on the line pivot points if(index<this.m_base_pivots) return(::ChartTimePriceToXY(this.m_base_chart_id,this.m_base_subwindow,this.m_base_time[index],this.m_base_price[index],x,y) ? true : false); //--- Calculate the coordinates for the central form located between the line pivot points else { form0=this.GetControlPointForm(0); form1=this.GetControlPointForm(1); if(form0==NULL || form1==NULL) return false; x=(form0.CoordX()+this.m_shift+form1.CoordX()+this.m_shift)/2; y=(form0.CoordY()+this.m_shift+form1.CoordY()+this.m_shift)/2; return true; } //--- Channels case OBJ_PITCHFORK : break; //--- Gann case OBJ_GANNFAN : break; //--- Elliott case OBJ_ELLIOTWAVE5 : break; case OBJ_ELLIOTWAVE3 : break; //--- Shapes case OBJ_RECTANGLE : break; case OBJ_TRIANGLE : break; case OBJ_ELLIPSE : break; //--- Arrows case OBJ_ARROW_THUMB_UP : break; case OBJ_ARROW_THUMB_DOWN : break; case OBJ_ARROW_UP : break; case OBJ_ARROW_DOWN : break; case OBJ_ARROW_STOP : break; case OBJ_ARROW_CHECK : break; case OBJ_ARROW_LEFT_PRICE : break; case OBJ_ARROW_RIGHT_PRICE : break; case OBJ_ARROW_BUY : break; case OBJ_ARROW_SELL : break; case OBJ_ARROW : break; //--- Graphical objects with time/price coordinates case OBJ_TEXT : break; case OBJ_BITMAP : break; //--- default : break; } return false; } //+------------------------------------------------------------------+
El cálculo del punto de pivote se realiza a partir de los valores almacenados en las matrices de coordenadas de los puntos de pivote de la línea m_base_time y m_base_price. Para calcular las coordenadas del punto central, usaremos las coordenadas de los objetos de formulario adjuntos a los puntos de pivote extremos de la línea. Si las coordenadas se calculan con éxito, el método retornará inmediatamente true. En los demás casos, se retornará false, o bien se interrumpirá con break la ejecución del código en case del interruptor switch y saldremos al final del método, donde se retornará false.
En el método que devuelve un puntero al formulario de punto de anclaje por nombre, reemplace CForm con CFormControl:
//+------------------------------------------------------------------+ //| Return the pointer to the pivot point form by name | //+------------------------------------------------------------------+ CFormControl *CGStdGraphObjExtToolkit::GetControlPointForm(const string name,int &index) { index=WRONG_VALUE; for(int i=0;i<this.m_list_forms.Total();i++) { CFormControl *form=this.m_list_forms.At(i); if(form==NULL) continue; if(form.Name()==name) { index=i; return form; } } return NULL; } //+------------------------------------------------------------------+
En el método que crea un objeto de formulario en el punto de pivote del objeto básico, reemplazamos CForm con CFormControl y establecemos los parámetros para el objeto de formulario creado con éxito:
//+------------------------------------------------------------------+ //| Create a form object on a base object reference point | //+------------------------------------------------------------------+ CFormControl *CGStdGraphObjExtToolkit::CreateNewControlPointForm(const int index) { string name=this.m_base_name+"_CP_"+(index<this.m_base_pivots ? (string)index : "X"); CFormControl *form=this.GetControlPointForm(index); if(form!=NULL) return NULL; int x=0, y=0; if(!this.GetControlPointCoordXY(index,x,y)) return NULL; form=new CFormControl(this.m_base_chart_id,this.m_base_subwindow,name,index,x-this.m_shift,y-this.m_shift,this.GetControlFormSize(),this.GetControlFormSize()); //--- Set all the necessary properties for the created form object if(form!=NULL) this.SetControlFormParams(form,index); return form; } //+------------------------------------------------------------------+
En el método que crea un objeto de formulario en los puntos de pivote del objeto básico, reemplazamos CForm con CFormControl y eliminamos las líneas para configurar los parámetros del objeto de formulario creado, ya que los parámetros ahora se configuran directamente al crearse el objeto en el método anterior:
//+------------------------------------------------------------------+ //| 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 CFormControl *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; } } //--- Redraw the chart for displaying changes (if successful) and return the final result if(res) ::ChartRedraw(this.m_base_chart_id); return res; } //+------------------------------------------------------------------+
Ahora el ciclo se ejecuta por el número de puntos de pivote del objeto básico más uno adicional. En otras palabras, el número de formularios creados excederá en uno la cantidad de puntos de pivote que tiene el objeto gráfico. El último formulario será el central, para desplazar el objeto gráfico al completo.
Método que establece los parámetros del objeto de formulario de gestión de puntos de pivote:
//+------------------------------------------------------------------+ //| Set the parameters of a form object for managing pivot points | //+------------------------------------------------------------------+ void CGStdGraphObjExtToolkit::SetControlFormParams(CFormControl *form,const int index) { 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 center of the form, 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 form.SetID(index+1); // Set the form ID form.SetControlPointDrawnFlag(false); // Set the flag that the pivot point is not drawn on the form form.Done(); // Save the initial form object state (its appearance) } //+------------------------------------------------------------------+
Aquí están las líneas de código trasladadas del método anterior. Asimismo, hemos añadido la configuración de la bandera del punto dibujado en el formulario y el identificador del mismo.
En el método que dibuja un punto de control en el formulario, introducimos el cálculo del centro del formulario en una línea separada, para no implementar esencialmente los mismos cálculos cuatro veces, y al finalizar el método , establecemos el bandera del punto dibujado en el formulario:
//+------------------------------------------------------------------+ //| Draw a control point on the form | //+------------------------------------------------------------------+ void CGStdGraphObjExtToolkit::DrawControlPoint(CFormControl *form,const uchar opacity,const color clr) { if(form==NULL) return; int c=int(::floor(form.Width()/2)); // Form center (coordinates) form.DrawCircle(c,c,CTRL_POINT_RADIUS,clr,opacity); // Draw a circle in the form center form.DrawCircleFill(c,c,2,clr,opacity); // Draw a circle in the form center form.SetControlPointDrawnFlag(opacity>0 ? true : false); // Set the flag that the pivot point is drawn on the form } //+------------------------------------------------------------------+
Ahora hemos hecho que al pasar el cursor sobre el formulario de gestión de un punto de pivote de un objeto gráfico, se dibuje un punto sobre él. El punto se borrará solo después de que el cursor abandone el formulario. Pero si acercamos todos los puntos de pivote del objeto para que los formularios construidos en los extremos del objeto gráfico y el formulario central comiencen a superponerse entre sí, al alejar el cursor de uno de ellos, el cursor se moverá a otro que se encuentre cerca. Por ello, es posible lograr un resultado tal que se muestren todos los puntos en todos los formularios del objeto:
Si, en este caso, capturamos un formulario y comenzamos a moverlo, el punto de pivote del objeto se moverá tras él. Pero, al mismo tiempo, los formularios erróneamente visibles permanecerán en el lugar del gráfico donde estaban antes del inicio del movimiento. Y esto no está bien. Por lo tanto, necesitaremos un método que dibuje un punto en un objeto de formulario de un objeto gráfico y simultáneamente borre los puntos en otros objetos de formulario del mismo objeto.
Método que dibuja puntos de control en un formulario y los elimina en todos los demás formularios:
//+------------------------------------------------------------------+ //| Draw a control point on the form, | //| remove it on all other forms | //+------------------------------------------------------------------+ void CGStdGraphObjExtToolkit::DrawOneControlPoint(CFormControl *form,const uchar opacity=255,const color clr=CTRL_POINT_COLOR) { this.DrawControlPoint(form,opacity,clr); for(int i=0;i<this.GetNumControlPointForms();i++) { CFormControl *ctrl=this.GetControlPointForm(i); if(ctrl==NULL || ctrl.ID()==form.ID()) continue; this.ClearControlPoint(ctrl); } } //+------------------------------------------------------------------+
Aquí, transmitimos al método el puntero al formulario sobre el que se encuentra el cursor. Dibujamos un punto en este formulario, después, en un ciclo por todos los formularios de este objeto seleccionamos un formulario y, si el formulario no es el que se ha transmitido al método, borramos el punto en él.
En el manejador de eventos, cambiamos el tipo de formulario de CForm a CFormControl:
//+------------------------------------------------------------------+ //| 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++) { CFormControl *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); } } //+------------------------------------------------------------------+
En la clase de objeto gráfico estándar abstracto, en el archivo \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh, debemos hacer algunas mejoras para optimizar el código del método. Tenemos varios fragmentos repetidos del mismo código en diferentes métodos, por lo que tendrá sentido implementar esos bloques de código en métodos separados y llamarlos donde sea necesario, haciendo que el código resulte más fácil de leer.
En las secciones pública y privada de la clase, declaramos los nuevos métodos a los que luego transferiremos las secciones de código repetidas:
//--- Return (1) the list of dependent objects, (2) dependent graphical object by index and (3) the number of dependent objects CArrayObj *GetListDependentObj(void) { return &this.m_list; } CGStdGraphObj *GetDependentObj(const int index) { return this.m_list.At(index); } int GetNumDependentObj(void) { return this.m_list.Total(); } //--- Return the name of the dependent object by index string NameDependent(const int index); //--- Add the dependent graphical object to the list bool AddDependentObj(CGStdGraphObj *obj); //--- Change X and Y coordinates of the current and all dependent objects bool ChangeCoordsExtendedObj(const int x,const int y,const int modifier,bool redraw=false); //--- Set X and Y coordinates into the appropriate pivot points of a specified subordinate object bool SetCoordsXYtoDependentObj(CGStdGraphObj *dependent_obj); //--- Return the pivot point data object CLinkedPivotPoint*GetLinkedPivotPoint(void) { return &this.m_linked_pivots; }
...
private: //--- Set the X coordinate (1) from the specified property of the base object to the specified subordinate object, (2) from the base object void SetCoordXToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to); void SetCoordXFromBaseObj(const int prop_from,const int modifier_from,const int modifier_to); //--- Set the Y coordinate (1) from the specified property of the base object to the specified subordinate object, (2) from the base object void SetCoordYToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to); void SetCoordYFromBaseObj(const int prop_from,const int modifier_from,const int modifier_to); //--- Set X and Y coordinates into the appropriate pivot point of a specified subordinate object void SetCoordsXYtoDependentObj(CGStdGraphObj *dependent_obj,CLinkedPivotPoint *pivot_point,const int index); //--- Set the (1) integer, (2) real and (3) string property to the specified subordinate property void SetDependentINT(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_INTEGER prop,const long value,const int modifier); void SetDependentDBL(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_DOUBLE prop,const double value,const int modifier); void SetDependentSTR(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_STRING prop,const string value,const int modifier); public: //--- Event handler
En el método que comprueba los cambios en las propiedades del objeto, eliminamos el bloque de código especificado (este código se trasladará a un método aparte):
//--- If subordinate objects are attached to the base one (in a composite graphical object) if(this.m_list.Total()>0) { //--- In the loop by the number of added graphical objects, for(int i=0;i<this.m_list.Total();i++) { //--- get the next graphical object, CGStdGraphObj *dep=m_list.At(i); if(dep==NULL) continue; //--- get the data object of its pivot points, CLinkedPivotPoint *pp=dep.GetLinkedPivotPoint(); if(pp==NULL) continue; //--- 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 in the object selected as the current one in the main loop 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 in the object selected as the current one in the main loop int prop_from=pp.GetPropertyY(j,ny); int modifier_from=pp.GetPropertyModifierY(j,ny); this.SetCoordYToDependentObj(dep,prop_from,modifier_from,ny); } } dep.PropertiesCopyToPrevData(); } //--- Move reference control points to new coordinates if(ExtToolkit!=NULL) { for(int i=0;i<this.Pivots();i++) { ExtToolkit.SetBaseObjTimePrice(this.Time(i),this.Price(i),i); } ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance()); long lparam=0; double dparam=0; string sparam=""; ExtToolkit.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam); } //--- Upon completion of the loop of handling all bound objects, redraw the chart to display all the changes ::ChartRedraw(m_chart_id); }
En lugar de un bloque remoto, escribimos la llamada a un nuevo método:
//--- If subordinate objects are attached to the base one (in a composite graphical object) if(this.m_list.Total()>0) { //--- In the loop by the number of added graphical objects, for(int i=0;i<this.m_list.Total();i++) { //--- get the next graphical object, CGStdGraphObj *dep=m_list.At(i); if(dep==NULL) continue; //--- Set X and Y coordinates to all pivot points of a subordinate object and //--- save the current properties of a subordinate graphical object as the previous ones if(this.SetCoordsXYtoDependentObj(dep)) dep.PropertiesCopyToPrevData(); } //--- Move reference control points to new coordinates if(this.ExtToolkit!=NULL) { for(int i=0;i<this.Pivots();i++) { this.ExtToolkit.SetBaseObjTimePrice(this.Time(i),this.Price(i),i); } this.ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance()); long lparam=0; double dparam=0; string sparam=""; this.ExtToolkit.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam); } //--- Upon completion of the loop of handling all bound objects, redraw the chart to display all the changes ::ChartRedraw(m_chart_id); }
Como ahora la lógica de borrado de un punto dibujado en el objeto de formulario de gestión de los puntos de pivote del objeto gráfico está implementada de tal forma que si el cursor no se encuentra en ninguno de los formularios, cada uno de ellos se redibujará constantemente (lo cual no resulta óptimo y consume muchos recursos), vamos a introducir en el método que redibuja el formulario de gestión de los puntos de pivote del objeto gráfico estándar extendido la posibilidad de verificar la necesidad del borrado de un punto, y si todavía está dibujado, solo en este caso necesitaremos redibujar el formulario para borrar el punto. Bueno, vamos a reemplazar el tipo de objeto de formulario por uno nuevo:
//+------------------------------------------------------------------+ //| 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 CFormControl *form=this.ExtToolkit.GetControlPointForm(i); if(form==NULL) continue; //--- Draw a point and a circle with a specified non-transparency and color //--- If a point should be completely transparent (deleted) //--- and the form still has the point, delete the point, if(opacity==0 && form.IsControlAlreadyDrawn()) this.ExtToolkit.DrawControlPoint(form,0,clr); //--- otherwise, draw the point with a specified non-transparency and color else 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); } } //+------------------------------------------------------------------+
Ahora el punto se borrará solo si realmente necesita borrarse (la opacidad del punto está establecida en cero) y si el punto aún está dibujado (la bandera de punto dibujado está establecida).
También volveremos a trabajar en el método que cambia las coordenadas X e Y de los objetos actuales y todos los dependientes; eliminaremos estas secciones de código, que ahora serán reemplazadas por llamadas al nuevo método:
//+----------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Ahora el método será mucho más simple:
//+----------------------------------------------------------------------+ //| 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 a composite graphical object, //--- and subordinate graphical objects are attached to the object if(this.ExtToolkit!=NULL && this.m_list.Total()>0) { //--- Get the graphical object bound to the 'modifier' point CGStdGraphObj *dep=this.GetDependentObj(modifier); if(dep==NULL) return false; //--- Set X and Y coordinates to all pivot points of a subordinate object and //--- save the current properties of a subordinate graphical object as the previous ones if(this.SetCoordsXYtoDependentObj(dep)) 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; } //+------------------------------------------------------------------+
Vamos a escribir la implementación del método que establece las coordenadas X e Y en el punto de pivote asociado de un objeto secundario especificado según el índice:
//+------------------------------------------------------------------+ //| Set X and Y coordinates to the associated pivot point | //| of a specified subordinate object by index | //+------------------------------------------------------------------+ void CGStdGraphObj::SetCoordsXYtoDependentObj(CGStdGraphObj *dependent_obj,CLinkedPivotPoint *pivot_point,const int index) { //--- get the number of coordinate points of the base object for setting the X coordinate int numx=pivot_point.GetBasePivotsNumX(index); //--- 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 'index' point int prop_from=pivot_point.GetPropertyX(index,nx); int modifier_from=pivot_point.GetPropertyModifierX(index,nx); this.SetCoordXToDependentObj(dependent_obj,prop_from,modifier_from,nx); } //--- Get the number of coordinate points of the base object for setting the Y coordinate int numy=pivot_point.GetBasePivotsNumY(index); //--- 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 'index' point int prop_from=pivot_point.GetPropertyY(index,ny); int modifier_from=pivot_point.GetPropertyModifierY(index,ny); this.SetCoordYToDependentObj(dependent_obj,prop_from,modifier_from,ny); } } //+------------------------------------------------------------------+
De hecho, estos son los mismos bloques de código que eliminamos de los métodos de clase y transferimos a aquel. Ya hemos analizado la lógica de este código en artículos anteriores: todo se explica perfectamente en los comentarios al código, por lo que no se requerirán explicaciones adicionales.
Implementación del método que establece las coordenadas X e Y en los puntos de pivote asociados de un objeto secundario especificado:
//+------------------------------------------------------------------+ //| Set X and Y coordinates to associated pivot points | //| of the specified subordinate object | //+------------------------------------------------------------------+ bool CGStdGraphObj::SetCoordsXYtoDependentObj(CGStdGraphObj *dependent_obj) { //--- Get the object of pivot point data of the bound graphical object CLinkedPivotPoint *pp=dependent_obj.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, //--- set X and Y to all pivot points of a subordinate object for(int j=0;j<num;j++) this.SetCoordsXYtoDependentObj(dependent_obj,pp,j); return true; } //+------------------------------------------------------------------+
El método permite establecer las coordenadas para todos los puntos de pivote de un objeto subordinado. Si adjuntamos otros objetos gráficos a un objeto gráfico compuesto, este método los establecerá en las coordenadas especificadas.
Vamos a mejorar la clase de colección de objetos gráficos en \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh.
Como la función estándar ChartTimePriceToXY() retorna dos coordenadas a la vez: X e Y, para almacenarlas crearemos una estructura en la sección privada que almacenará las coordenadas X e Y y el desplazamiento de estas coordenadas relativas al punto central. Y como un objeto gráfico puede tener varios puntos de pivote, para almacenar las coordenadas de cada uno de sus puntos de pivote para ese objeto gráfico, declararemos una matriz con el tipo de la estructura creada. Luego, en cada celda de esta matriz, tendremos X e Y convertidas de coordenadas de "tiempo/precio" a coordenadas X e Y de pantalla, así como los desplazamientos de las coordenadas del punto de pivote en relación con el punto central del objeto gráfico.
En la sección privada de la clase, crearemos una estructura ydeclararemos la matriz que necesitemos:
//+------------------------------------------------------------------+ //| Collection of graphical objects | //+------------------------------------------------------------------+ #resource "\\"+PATH_TO_EVENT_CTRL_IND; // Indicator for controlling graphical object events packed into the program resources class CGraphElementsCollection : public CBaseObj { private: //--- Pivot point data structure struct SDataPivotPoint { public: int X; // Pivot point X coordinate int Y; // Pivot point Y coordinate int ShiftX; // Pivot point X coordinate shift from the central one int ShiftY; // Pivot point Y coordinate shift from the central one }; SDataPivotPoint m_data_pivot_point[]; // Pivot point data structure array CArrayObj m_list_charts_control; // List of chart management objects CListObj m_list_all_canv_elm_obj; // List of all graphical elements on canvas CListObj m_list_all_graph_obj; // List of all graphical objects CArrayObj m_list_deleted_obj; // List of removed graphical objects CMouseState m_mouse; // "Mouse status" class object bool m_is_graph_obj_event; // Event flag in the list of graphical objects int m_total_objects; // Number of graphical objects int m_delta_graph_obj; // Difference in the number of graphical objects compared to the previous check
En la sección privada de la clase , declararemos un método que retorna a la matriz de la estructura las coordenadas de pantalla de cada punto de pivote del objeto gráfico:
private: //--- Find an object present in the collection but not on a chart CGStdGraphObj *FindMissingObj(const long chart_id); CGStdGraphObj *FindMissingObj(const long chart_id,int &index); //--- Find the graphical object present on a chart but not in the collection string FindExtraObj(const long chart_id); //--- Remove the graphical object class object from the graphical object collection list: (1) specified object, (2) by chart ID bool DeleteGraphObjFromList(CGStdGraphObj *obj); void DeleteGraphObjectsFromList(const long chart_id); //--- Move the graphical object class object to the list of removed graphical objects: (1) specified object, (2) by index bool MoveGraphObjToDeletedObjList(CGStdGraphObj *obj); bool MoveGraphObjToDeletedObjList(const int index); //--- Move all objects by chart ID to the list of removed graphical objects void MoveGraphObjectsToDeletedObjList(const long chart_id); //--- Remove the object of managing charts from the list bool DeleteGraphObjCtrlObjFromList(CChartObjectsControl *obj); //--- Set the flags of scrolling the chart with the mouse, context menu and crosshairs tool for the specified chart void SetChartTools(const long chart_id,const bool flag); //--- Return the screen coordinates of each pivot point of the graphical object bool GetPivotPointCoordsAll(CGStdGraphObj *obj,SDataPivotPoint &array_pivots[]); public:
Escribimos la implementación de este método fuera del cuerpo de la clase:
//+------------------------------------------------------------------+ //| Return screen coordinates | //| of each graphical object pivot point | //+------------------------------------------------------------------+ bool CGraphElementsCollection::GetPivotPointCoordsAll(CGStdGraphObj *obj,SDataPivotPoint &array_pivots[]) { //--- If failed to increase the array of structures to match the number of pivot points, //--- inform of that in the journal and return 'false' if(::ArrayResize(array_pivots,obj.Pivots())!=obj.Pivots()) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); return false; } //--- In the loop by the number of graphical object pivot points for(int i=0;i<obj.Pivots();i++) { //--- Convert the object coordinates into screen ones. If failed, inform of that and return 'false' if(!::ChartTimePriceToXY(obj.ChartID(),obj.SubWindow(),obj.Time(i),obj.Price(i),array_pivots[i].X,array_pivots[i].Y)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_CONV_GRAPH_OBJ_COORDS_TO_XY); return false; } } //--- Depending on the graphical object type switch(obj.TypeGraphObject()) { //--- One pivot point in screen coordinates case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : break; //--- One pivot point (price only) case OBJ_HLINE : break; //--- One pivot point (time only) case OBJ_VLINE : break; case OBJ_EVENT : break; //--- Two pivot points and a central one //--- Lines case OBJ_TREND : case OBJ_TRENDBYANGLE : case OBJ_CYCLES : case OBJ_ARROWED_LINE : //--- Channels case OBJ_CHANNEL : case OBJ_STDDEVCHANNEL : case OBJ_REGRESSION : //--- Gann case OBJ_GANNLINE : case OBJ_GANNGRID : //--- Fibo case OBJ_FIBO : case OBJ_FIBOTIMES : case OBJ_FIBOFAN : case OBJ_FIBOARC : case OBJ_FIBOCHANNEL : case OBJ_EXPANSION : //--- Calculate the shifts of all pivot points from the central one and write them to the structure array array_pivots[0].ShiftX=(array_pivots[1].X-array_pivots[0].X)/2; array_pivots[0].ShiftY=(array_pivots[1].Y-array_pivots[0].Y)/2; array_pivots[1].ShiftX=(array_pivots[0].X-array_pivots[1].X)/2; array_pivots[1].ShiftY=(array_pivots[0].Y-array_pivots[1].Y)/2; return true; //--- Channels case OBJ_PITCHFORK : break; //--- Gann case OBJ_GANNFAN : break; //--- Elliott case OBJ_ELLIOTWAVE5 : break; case OBJ_ELLIOTWAVE3 : break; //--- Shapes case OBJ_RECTANGLE : break; case OBJ_TRIANGLE : break; case OBJ_ELLIPSE : break; //--- Arrows case OBJ_ARROW_THUMB_UP : break; case OBJ_ARROW_THUMB_DOWN : break; case OBJ_ARROW_UP : break; case OBJ_ARROW_DOWN : break; case OBJ_ARROW_STOP : break; case OBJ_ARROW_CHECK : break; case OBJ_ARROW_LEFT_PRICE : break; case OBJ_ARROW_RIGHT_PRICE : break; case OBJ_ARROW_BUY : break; case OBJ_ARROW_SELL : break; case OBJ_ARROW : break; //--- Graphical objects with time/price coordinates case OBJ_TEXT : break; case OBJ_BITMAP : break; //--- default: break; } return false; } //+------------------------------------------------------------------+
Por ahora, este método escribe en la estructura las coordenadas de pantalla solo de aquellos objetos gráficos que tienen dos puntos de pivote y uno central.
El puntero a un objeto gráfico se transmite al método cuyas coordenadas para cada uno de sus puntos de pivote deben escribirse en una matriz de estructuras, que también se transmite al método por enlace. Si la transformación de coordenadas tiene éxito, el método retornará true y una matriz de estructuras completamente llena con las coordenadas de pantalla para cada punto de pivote del objeto gráfico. En caso de fallo, el método retornará false.
En el manejador de eventos de la clase, necesitaremos gestionar el desplazamiento del formulario de gestión del objeto para que, si este es el punto central, el objeto se mueva en su totalidad. Para ello, necesitaremos calcular los desplazamientos de sus formularios extremos con respecto al central (por el que se desplaza el objeto) y mover ambos puntos de pivote del objeto según el desplazamiento calculado y registrado en la estructura. Por lo tanto, todos sus puntos de pivote se desplazarán en la misma cantidad que el punto central movido por el ratón.
Vamos a escribir el siguiente procesamiento del evento de desplazamiento del punto (formulario) de control central del objeto gráfico extendido:
//+------------------------------------------------------------------+ //| 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 coordinates so that the form does not overlap with the one-click trading panel button 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 int shift=(int)::ceil(form.Width()/2)+1; //--- If the form is located on one of the graphical object pivot points, if(form_index<ext.Pivots()) { //--- limit the form coordinates so that they do not move beyond the chart borders 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); } //--- If the form is central for managing all pivot points of a graphical object else { //--- Get screen coordinates of all object pivot points and write them to the m_data_pivot_point structure if(this.GetPivotPointCoordsAll(ext,m_data_pivot_point)) { //--- In the loop by the number of object pivot points, for(int i=0;i<(int)this.m_data_pivot_point.Size();i++) { //--- limit the screen coordinates of the current pivot point so that they do not move beyond the chart borders if(x+shift-this.m_data_pivot_point[i].ShiftX<0) x=-shift+m_data_pivot_point[i].ShiftX; if(x+shift+this.m_data_pivot_point[i].ShiftX>chart_width) x=chart_width-shift-this.m_data_pivot_point[i].ShiftX; if(y+shift+this.m_data_pivot_point[i].ShiftY<0) y=-shift-this.m_data_pivot_point[i].ShiftY; if(y+shift-this.m_data_pivot_point[i].ShiftY>chart_height) y=chart_height-shift+this.m_data_pivot_point[i].ShiftY; //--- set the calculated coordinates to the current object pivot point ext.ChangeCoordsExtendedObj(x+shift-this.m_data_pivot_point[i].ShiftX,y+shift-this.m_data_pivot_point[i].ShiftY,i); } } } } } } } } //--- 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 point with a circle on the form and delete it on all other forms toolkit.DrawOneControlPoint(form); } } } } //--- '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 point with a circle on the form and delete it on all other forms toolkit.DrawOneControlPoint(form); } } } } //--- '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) { } } } } } //+------------------------------------------------------------------+
Además del nuevo manejador para mover el formulario de control central, también añadiremos la llamada al método que dibujará un punto en el objeto de formulario debajo del cursor y borrará estos puntos en otros formularios de este objeto gráfico. Esto evitará el dibujado simultáneo de puntos en varios objetos de formulario si se encuentran cerca y se superponen entre sí, como mostramos arriba.
Ahora, ya estamos listos para poner a prueba la nueva funcionalidad.
Simulación
Para la simulación, tomaremos el asesor del artículo anterior y lo guardaremos en la nueva carpeta \MQL5\Experts\TestDoEasy\Part99\ con el nuevo nombre TestDoEasyPart99.mq5.
No tendremos que hacer ningún cambio en el asesor en sí; hasta ahora, todos los cambios se realizan solo en las clases de la biblioteca.
Vamos a compilar el asesor y a ejecutarlo en el gráfico:
Como podemos ver, si movemos el objeto gráfico compuesto en el formulario en el que ha sido creado, todas las restricciones relacionadas con los puntos de pivote que superan los límites del gráfico funcionan correctamente. Pero si "invertimos" la ubicación de los puntos de pivote en relación con su ubicación inicial, la "configuración" del objeto se distorsionará cuando el punto de pivote vaya más allá de los límites del gráfico. De ello derivará el cálculo incorrecto de las restricciones y la dependencia de qué punto de pivote supera el borde derecho, izquierdo, superior o inferior del gráfico.
Esto no resulta sorprendente, ya que los cambios del punto pivote se calculan en relación con el central. Y este hecho implica que un punto tendrá un desplazamiento positivo y el segundo uno negativo. Nos enfrentamos a un error de cálculo de limitación al cambiar la ubicación de los puntos de pivote con respecto al central. Arreglaremos esto en el siguiente artículo.
¿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
Gráficos en la biblioteca DoEasy (Parte 98): Desplazamiento de los puntos de anclaje de los objetos gráficos estándar ampliados
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/10584
- 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