Gráficos en la biblioteca DoEasy (Parte 75): Métodos de trabajo con primitivas y texto en el elemento gráfico básico
Contenido
- Concepto
- Mejorando las clases de la biblioteca
- Métodos de trabajo con primitivas
- Métodos de trabajo con texto
- Simulación
- ¿Qué es lo próximo?
Concepto
Seguimos desarrollando la clase de objeto de elemento gráfico básico, que supone la base para crear objetos gráficos más complejos de la biblioteca. En el artículo anterior, desarrollamos el concepto de construcción del objeto gráfico básico, creamos un elemento gráfico y lo dotamos de propiedades básicas que podemos configurar, modificar y recibir.
Como la clase CCanvas está pensada para dibujar "en el lienzo", esta contiene métodos para trabajar con primitivas gráficas y texto. En esta ocasión, vamos a escribir varios métodos de la clase de objeto de elemento que nos permitirán llamar a los métodos de dibujado de la clase CCanvas y trabajar con ellos. Dichos métodos serán simples, y los usaremos como base para crear métodos de dibujado más avanzados en los objetos herederos de la clase de objeto de elemento.
Además de crear los métodos para trabajar con primitivas, crearemos métodos para trabajar con archivos: nuestros objetos gráficos (que serán elementos de la GUI de los programas de usuario) deberán "recordar" sus propiedades, su estado y su ubicación en el gráfico, por ejemplo, al cambiar el marco temporal. Para hacer esto, guardaremos todas las propiedades del objeto en un archivo, y al construir el objeto, leeremos las propiedades del archivo, si es que las hay.
Pero como el trabajo con archivos debe organizarse en una clase de colección de objetos gráficos, y todavía no hemos llegado a esta, hoy solo escribiremos los métodos para guardar y cargar las propiedades de un objeto gráfico. Al crear la clase para la colección de objetos gráficos, utilizaremos estos métodos para guardar y cargar las propiedades que escribiremos hoy para el objeto de elemento gráfico.
Asimismo, en lo sucesivo, necesitaremos la clase para trabajar con el color. Hoy también añadiremos esto a la biblioteca.
Tomaremos la clase de la Biblioteca de códigos de MQL5.com, escrita por Dimitri Fedoséev y cedida amablemente a la comunidad.
Como resultado, hoy tendremos un elemento gráfico prácticamente preparado para su uso posterior, y que además nos permitirá crear más objetos gráficos de la biblioteca.
Mejorando las clases de la biblioteca
Al trabajar con lienzo, si necesitamos borrar un objeto de la clase CCanvas que tiene transparencia, deberemos usar el método Erase(), en el cual se transmite cero por defecto:
//--- clear/fill color void Erase(const uint clr=0);
Para nuestro caso, esta resultará una solución incorrecta, porque al limpiar el lienzo usando un cero, perderemos de vista su canal alfa (el canal de transparencia del color). Esto puede provocar la aparición de artefactos al realizarse el dibujado.
Para limpiar un lienzo con el canal alfa en lugar de cero, usaremos el valor 0x00FFFFFF.
Se trata del color completamente negro en el formato ARGB (Alpha = 0, Red = 255, Green = 255, Blue = 255).
En el archivo \MQL5\Include\DoEasy\Defines.mqh, escribimos la macrosustitución para indicar este color:
//--- Canvas parameters #define PAUSE_FOR_CANV_UPDATE (16) // Canvas update frequency #define NULL_COLOR (0x00FFFFFF) // Zero for the canvas with the alpha channel //+------------------------------------------------------------------+
Al enviar un texto al lienzo con el método TextOut(), podemos asignar al mensaje de texto un ángulo de anclaje, es decir, el punto central para el texto: un rectángulo delimitador respecto al cual se ubicará el mensaje en sí. Los puntos de anclaje se indican usando seis banderas, combinaciones de dos banderas que enumeramos al completo a continuación:
Banderas de alineación de texto en horizontal:
- TA_LEFT — punto de anclaje en el lado izquierdo del rectángulo delimitador
- TA_CENTER — el punto de anclaje en horizontal se encuentra en la mitad del rectángulo delimitador
- TA_RIGHT — punto de anclaje en el lado derecho del rectángulo delimitador
Banderas de alineación de texto en vertical:
- TA_TOP — punto de anclaje en el lado superior del rectángulo delimitador
- TA_VCENTER — el punto de anclaje en vertical se encuentra en la mitad del rectángulo delimitador
- TA_BOTTOM — punto de anclaje en el lado inferior del rectángulo delimitador
Las posibles combinaciones de banderas y los métodos de anclaje establecidos por ellas se muestran en la figura:
Para no confundirnos respecto a qué bandera escribir en primer lugar, en segundo lugar, etcétera, simplemente estableceremos una enumeración propia que indicará todas las combinaciones posibles de banderas para alinear el texto respecto a su punto de anclaje:
//+------------------------------------------------------------------+ //| Data for handling graphical elements | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| List of anchoring methods | //| (horizontal and vertical text alignment) | //+------------------------------------------------------------------+ enum ENUM_TEXT_ANCHOR { TEXT_ANCHOR_LEFT_TOP = 0, // Text anchor point at the upper left corner of the bounding rectangle TEXT_ANCHOR_CENTER_TOP = 1, // Text anchor point at the top center side of the bounding rectangle TEXT_ANCHOR_RIGHT_TOP = 2, // Text anchor point at the upper right corner of the bounding rectangle TEXT_ANCHOR_LEFT_CENTER = 4, // Text anchor point at the left center side of the bounding rectangle TEXT_ANCHOR_CENTER = 5, // Text anchor point at the center of the bounding rectangle TEXT_ANCHOR_RIGHT_CENTER = 6, // Text anchor point at the right center side of the bounding rectangle TEXT_ANCHOR_LEFT_BOTTOM = 8, // Text anchor point at the bottom left corner of the bounding rectangle TEXT_ANCHOR_CENTER_BOTTOM = 9, // Text anchor point at the bottom center side of the bounding rectangle TEXT_ANCHOR_RIGHT_BOTTOM = 10, // Text anchor point at the bottom right corner of the bounding rectangle }; //+------------------------------------------------------------------+ //| The list of graphical element types | //+------------------------------------------------------------------+
Aquí hemos indicado tres banderas para cada nivel de anclaje del texto:
- Punto vertical de anclaje en la parte superior (TA_TOP) — valor 0:
- punto horizontal de anclaje en la parte izquierda (TA_LEFT) — valor 0:
- punto horizontal de anclaje en la parte central (TA_CENTER) — valor 1:
- punto horizontal de anclaje en la parte derecha (TA_RIGHT) — valor 2:
- punto horizontal de anclaje en la parte izquierda (TA_LEFT) — valor 0:
- Punto vertical de anclaje en la parte central (TA_VCENTER) — valor 4:
- punto horizontal de anclaje en la parte izquierda (TA_LEFT) — valor 0:
- punto horizontal de anclaje en la parte central (TA_CENTER) — valor 1:
- punto horizontal de anclaje en la parte derecha (TA_RIGHT) — valor 2:
- punto horizontal de anclaje en la parte izquierda (TA_LEFT) — valor 0:
- Punto vertical de anclaje en la parte inferior (TA_BOTTOM) — valor 8:
- punto horizontal de anclaje en la parte izquierda (TA_LEFT) — valor 0:
- punto horizontal de anclaje en la parte central (TA_CENTER) — valor 1:
- punto horizontal de anclaje en la parte derecha (TA_RIGHT) — valor 2:
- punto horizontal de anclaje en la parte izquierda (TA_LEFT) — valor 0:
Cada uno de los valores de la enumeración ENUM_TEXT_ANCHOR se corresponde con el valor de la combinación de las banderas que hemos enumerado antes, correctamente colocadas:
- TEXT_ANCHOR_LEFT_TOP = (TA_LEFT | TA_TOP) = 0,
- TEXT_ANCHOR_CENTER_TOP = (TA_CENTER | TA_TOP) = 1,
- TEXT_ANCHOR_RIGHT_TOP = (TA_RIGHT | TA_TOP) = 2,
- TEXT_ANCHOR_LEFT_CENTER = (TA_LEFT | TA_VCENTER) = 4,
- TEXT_ANCHOR_CENTER = (TA_CENTER | TA_VCENTER) = 5,
- TEXT_ANCHOR_RIGHT_CENTER = (TA_RIGHT | TA_VCENTER) = 6,
- TEXT_ANCHOR_LEFT_BOTTOM = (TA_LEFT | TA_BOTTOM) = 8,
- TEXT_ANCHOR_CENTER_BOTTOM = (TA_CENTER | TA_BOTTOM) = 9,
- TEXT_ANCHOR_RIGHT_BOTTOM = (TA_RIGHT | TA_BOTTOM) = 10.
En lo sucesivo, usaremos esta enumeración para indicar la alineación del texto respecto a su punto de anclaje.
Como estamos trabajando en la creación de la parte gráfica de la biblioteca, necesitaremos diferentes métodos para trabajar con el color en el futuro.
En la biblioteca de códigos fuente de MQL5.com hay una magnífica biblioteca de funciones para trabajar con el color, amablemente cedida por Dimitri Fedoséev para el uso común.
Vamos a tomar la clase CColors y corregirla ligeramente. En concreto, la haremos estática, para, en lugar de establecer un objeto de clase, recurrir directamente a sus métodos con la ayuda del operador de resolución de contexto (::), por ejemplo:
class_name::variable
Por consiguiente, teniendo el archivo de clase CColors vinculado a la biblioteca, podemos llamar a los métodos de clase en cualquier lugar de nuestro código (incluso en un programa de usuario), por ejemplo, para mezclar dos colores; azul con una opacidad 128 y rojo con una opacidad 64:
CColors::BlendColors(ColorToARGB(clrBlue,128),ColorToARGB(clrRed,64));
El archivo de clase lo guardamos en el directorio de la biblioteca \MQL5\Include\DoEasy\Services\, en el archivo Colors.mqh.
//+------------------------------------------------------------------+ //| Colors.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://www.mql5.com/en/users/integer" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Class for working with color | //+------------------------------------------------------------------+ class CColors { private: static double Arctan2(const double x,const double y); static double Hue_To_RGB(double v1,double v2,double vH); public: //+--------------------------------------------------------------------+ //| The list of functions from http://www.easyrgb.com/index.php?X=MATH | //+--------------------------------------------------------------------+ static void RGBtoXYZ(const double aR,const double aG,const double aB,double &oX,double &oY,double &oZ); static void XYZtoRGB(const double aX,const double aY,const double aZ,double &oR,double &oG,double &oB); static void XYZtoYxy(const double aX,const double aY,const double aZ,double &oY,double &ox,double &oy); static void YxyToXYZ(const double aY,const double ax,const double ay,double &oX,double &oY,double &oZ); static void XYZtoHunterLab(const double aX,const double aY,const double aZ,double &oL,double &oa,double &ob); static void HunterLabToXYZ(const double aL,const double aa,const double ab,double &oX,double &oY,double &oZ); static void XYZtoCIELab(const double aX,const double aY,const double aZ,double &oCIEL,double &oCIEa,double &oCIEb); static void CIELabToXYZ(const double aCIEL,const double aCIEa,const double aCIEb,double &oX,double &oY,double &oZ); static void CIELabToCIELCH(const double aCIEL,const double aCIEa,const double aCIEb,double &oCIEL,double &oCIEC,double &oCIEH); static void CIELCHtoCIELab(const double aCIEL,const double aCIEC,const double aCIEH,double &oCIEL,double &oCIEa,double &oCIEb); static void XYZtoCIELuv(const double aX,const double aY,const double aZ,double &oCIEL,double &oCIEu,double &oCIEv); static void CIELuvToXYZ(const double aCIEL,const double aCIEu,const double aCIEv,double &oX,double &oY,double &oZ); static void RGBtoHSL(const double aR,const double aG,const double aB,double &oH,double &oS,double &oL); static void HSLtoRGB(const double aH,const double aS,const double aL,double &oR,double &oG,double &oB); static void RGBtoHSV(const double aR,const double aG,const double aB,double &oH,double &oS,double &oV); static void HSVtoRGB(const double aH,const double aS,const double aV,double &oR,double &oG,double &oB); static void RGBtoCMY(const double aR,const double aG,const double aB,double &oC,double &oM,double &oY); static void CMYtoRGB(const double aC,const double aM,const double aY,double &oR,double &oG,double &oB); static void CMYtoCMYK(const double aC,const double aM,const double aY,double &oC,double &oM,double &oY,double &oK); static void CMYKtoCMY(const double aC,const double aM,const double aY,const double aK,double &oC,double &oM,double &oY); static void RGBtoLab(const double aR,const double aG,const double aB,double &oL,double &oa,double &ob); //+------------------------------------------------------------------+ //| Other functions for working with color | //+------------------------------------------------------------------+ static void ColorToRGB(const color aColor,double &aR,double &aG,double &aB); static double GetR(const color aColor); static double GetG(const color aColor); static double GetB(const color aColor); static double GetA(const color aColor); static color RGBToColor(const double aR,const double aG,const double aB); static color MixColors(const color aCol1,const color aCol2,const double aK); static color BlendColors(const uint lower_color,const uint upper_color); static void Gradient(color &aColors[],color &aOut[],int aOutCount,bool aCycle=false); static void RGBtoXYZsimple(double aR,double aG,double aB,double &oX,double &oY,double &oZ); static void XYZtoRGBsimple(const double aX,const double aY,const double aZ,double &oR,double &oG,double &oB); static color Negative(const color aColor); static color StandardColor(const color aColor,int &aIndex); static double RGBtoGray(double aR,double aG,double aB); static double RGBtoGraySimple(double aR,double aG,double aB); }; //+------------------------------------------------------------------+ //| Class methods | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Arctan2 | //+------------------------------------------------------------------+ double CColors::Arctan2(const double x,const double y) { if(y==0) return(x<0 ? M_PI : 0); else { if(x>0) return(::atan(y/x)); if(x<0) return(y>0 ? atan(y/x)+M_PI : atan(y/x)-M_PI); else return(y<0 ? -M_PI_2 : M_PI_2); } } //+------------------------------------------------------------------+ //| Hue_To_RGB | //+------------------------------------------------------------------+ double CColors::Hue_To_RGB(double v1,double v2,double vH) { if(vH<0) vH+=1.0; if(vH>1.0) vH-=1; if((6.0*vH)<1.0) return(v1+(v2-v1)*6.0*vH); if((2.0*vH)<1.0) return(v2); if((3.0*vH)<2.0) return(v1+(v2-v1)*((2.0/3.0)-vH)*6.0); //--- return(v1); } //+------------------------------------------------------------------+ //| Conversion of RGB into XYZ | //+------------------------------------------------------------------+ void CColors::RGBtoXYZ(const double aR,const double aG,const double aB,double &oX,double &oY,double &oZ) { double var_R=aR/255; double var_G=aG/255; double var_B=aB/255; //--- if(var_R>0.04045) var_R=::pow((var_R+0.055)/1.055,2.4); else var_R=var_R/12.92; //--- if(var_G>0.04045) var_G=::pow((var_G+0.055)/1.055,2.4); else var_G=var_G/12.92; //--- if(var_B>0.04045) var_B=::pow((var_B+0.055)/1.055,2.4); else var_B=var_B/12.92; //--- var_R =var_R*100.0; var_G =var_G*100.0; var_B =var_B*100.0; oX =var_R*0.4124+var_G*0.3576+var_B*0.1805; oY =var_R*0.2126+var_G*0.7152+var_B*0.0722; oZ =var_R*0.0193+var_G*0.1192+var_B*0.9505; } //+------------------------------------------------------------------+ //| Conversion of XYZ into RGB | //+------------------------------------------------------------------+ void CColors::XYZtoRGB(const double aX,const double aY,const double aZ,double &oR,double &oG,double &oB) { double var_X =aX/100; double var_Y =aY/100; double var_Z =aZ/100; double var_R =var_X*3.2406+var_Y*-1.5372+var_Z*-0.4986; double var_G =var_X*(-0.9689)+var_Y*1.8758+var_Z*0.0415; double var_B =var_X*0.0557+var_Y*(-0.2040)+var_Z*1.0570; //--- if(var_R>0.0031308) var_R=1.055*(::pow(var_R,1.0/2.4))-0.055; else var_R=12.92*var_R; //--- if(var_G>0.0031308) var_G=1.055*(::pow(var_G,1.0/2.4))-0.055; else var_G=12.92*var_G; //--- if(var_B>0.0031308) var_B=1.055*(::pow(var_B,1.0/2.4))-0.055; else var_B=12.92*var_B; //--- oR =var_R*255.0; oG =var_G*255.0; oB =var_B*255.0; } //+------------------------------------------------------------------+ //| Conversion of XYZ into Yxy | //+------------------------------------------------------------------+ void CColors::XYZtoYxy(const double aX,const double aY,const double aZ,double &oY,double &ox,double &oy) { oY =aY; ox =aX/(aX+aY+aZ); oy =aY/(aX+aY+aZ); } //+------------------------------------------------------------------+ //| Conversion of Yxy into XYZ | //+------------------------------------------------------------------+ void CColors::YxyToXYZ(const double aY,const double ax,const double ay,double &oX,double &oY,double &oZ) { oX =ax*(aY/ay); oY =aY; oZ =(1.0-ax-ay)*(aY/ay); } //+------------------------------------------------------------------+ //| Conversion of XYZ into HunterLab | //+------------------------------------------------------------------+ void CColors::XYZtoHunterLab(const double aX,const double aY,const double aZ,double &oL,double &oa,double &ob) { oL =10.0*::sqrt(aY); oa =17.5*(((1.02*aX)-aY)/::sqrt(aY)); ob =7.0*((aY-(0.847*aZ))/::sqrt(aY)); } //+------------------------------------------------------------------+ //| Conversion of HunterLab into XYZ | //+------------------------------------------------------------------+ void CColors::HunterLabToXYZ(const double aL,const double aa,const double ab,double &oX,double &oY,double &oZ) { double var_Y =aL/10.0; double var_X =aa/17.5*aL/10.0; double var_Z =ab/7.0*aL/10.0; //--- oY =::pow(var_Y,2); oX =(var_X+oY)/1.02; oZ =-(var_Z-oY)/0.847; } //+------------------------------------------------------------------+ //| Conversion of XYZ into CIELab | //+------------------------------------------------------------------+ void CColors::XYZtoCIELab(const double aX,const double aY,const double aZ,double &oCIEL,double &oCIEa,double &oCIEb) { double ref_X =95.047; double ref_Y =100.0; double ref_Z =108.883; double var_X =aX/ref_X; double var_Y =aY/ref_Y; double var_Z =aZ/ref_Z; //--- if(var_X>0.008856) var_X=::pow(var_X,1.0/3.0); else var_X=(7.787*var_X)+(16.0/116.0); //--- if(var_Y>0.008856) var_Y=::pow(var_Y,1.0/3.0); else var_Y=(7.787*var_Y)+(16.0/116.0); //--- if(var_Z>0.008856) var_Z=::pow(var_Z,1.0/3.0); else var_Z=(7.787*var_Z)+(16.0/116.0); //--- oCIEL =(116.0*var_Y)-16.0; oCIEa =500.0*(var_X-var_Y); oCIEb =200*(var_Y-var_Z); } //+------------------------------------------------------------------+ //| Conversion of CIELab into ToXYZ | //+------------------------------------------------------------------+ void CColors::CIELabToXYZ(const double aCIEL,const double aCIEa,const double aCIEb,double &oX,double &oY,double &oZ) { double var_Y =(aCIEL+16.0)/116.0; double var_X =aCIEa/500.0+var_Y; double var_Z =var_Y-aCIEb/200.0; //--- if(::pow(var_Y,3)>0.008856) var_Y=::pow(var_Y,3); else var_Y=(var_Y-16.0/116.0)/7.787; //--- if(::pow(var_X,3)>0.008856) var_X=::pow(var_X,3); else var_X=(var_X-16.0/116.0)/7.787; //--- if(::pow(var_Z,3)>0.008856) var_Z=::pow(var_Z,3); else var_Z=(var_Z-16.0/116.0)/7.787; //--- double ref_X =95.047; double ref_Y =100.0; double ref_Z =108.883; //--- oX =ref_X*var_X; oY =ref_Y*var_Y; oZ =ref_Z*var_Z; } //+------------------------------------------------------------------+ //| Conversion of CIELab into CIELCH | //+------------------------------------------------------------------+ void CColors::CIELabToCIELCH(const double aCIEL,const double aCIEa,const double aCIEb,double &oCIEL,double &oCIEC,double &oCIEH) { double var_H=Arctan2(aCIEb,aCIEa); //--- if(var_H>0) var_H=(var_H/M_PI)*180.0; else var_H=360.0-(::fabs(var_H)/M_PI)*180.0; //--- oCIEL =aCIEL; oCIEC =::sqrt(::pow(aCIEa,2)+::pow(aCIEb,2)); oCIEH =var_H; } //+------------------------------------------------------------------+ //| Conversion of CIELCH into CIELab | //+------------------------------------------------------------------+ void CColors::CIELCHtoCIELab(const double aCIEL,const double aCIEC,const double aCIEH,double &oCIEL,double &oCIEa,double &oCIEb) { //--- Arguments from 0 to 360° oCIEL =aCIEL; oCIEa =::cos(M_PI*aCIEH/180.0)*aCIEC; oCIEb =::sin(M_PI*aCIEH/180)*aCIEC; } //+------------------------------------------------------------------+ //| Conversion of XYZ into CIELuv | //+------------------------------------------------------------------+ void CColors::XYZtoCIELuv(const double aX,const double aY,const double aZ,double &oCIEL,double &oCIEu,double &oCIEv) { double var_U =(4.0*aX)/(aX+(15.0*aY)+(3.0*aZ)); double var_V =(9.0*aY)/(aX+(15.0*aY)+(3.0*aZ)); double var_Y =aY/100.0; //--- if(var_Y>0.008856) var_Y=::pow(var_Y,1.0/3.0); else var_Y=(7.787*var_Y)+(16.0/116.0); //--- double ref_X =95.047; double ref_Y =100.000; double ref_Z =108.883; double ref_U =(4.0*ref_X)/(ref_X+(15.0*ref_Y)+(3.0*ref_Z)); double ref_V =(9.0*ref_Y)/(ref_X+(15.0*ref_Y)+(3.0*ref_Z)); //--- oCIEL =(116.0*var_Y)-16.0; oCIEu =13.0*oCIEL*(var_U-ref_U); oCIEv =13.0*oCIEL*(var_V-ref_V); } //+------------------------------------------------------------------+ //| Conversion of CIELuv into XYZ | //+------------------------------------------------------------------+ void CColors::CIELuvToXYZ(const double aCIEL,const double aCIEu,const double aCIEv,double &oX,double &oY,double &oZ) { double var_Y=(aCIEL+16.0)/116.0; //--- if(::pow(var_Y,3)>0.008856) var_Y=::pow(var_Y,3); else var_Y=(var_Y-16.0/116.0)/7.787; //--- double ref_X =95.047; double ref_Y =100.000; double ref_Z =108.883; double ref_U =(4.0*ref_X)/(ref_X+(15.0*ref_Y)+(3.0*ref_Z)); double ref_V =(9.0*ref_Y)/(ref_X+(15.0*ref_Y)+(3.0*ref_Z)); double var_U =aCIEu/(13.0*aCIEL)+ref_U; double var_V =aCIEv/(13.0*aCIEL)+ref_V; //--- oY=var_Y*100.0; oX=-(9.0*oY*var_U)/((var_U-4.0)*var_V-var_U*var_V); oZ=(9.0*oY-(15.0*var_V*oY)-(var_V*oX))/(3.0*var_V); } //+------------------------------------------------------------------+ //| Conversion of RGB into HSL | //+------------------------------------------------------------------+ void CColors::RGBtoHSL(const double aR,const double aG,const double aB,double &oH,double &oS,double &oL) { double var_R =(aR/255); double var_G =(aG/255); double var_B =(aB/255); double var_Min =::fmin(var_R,::fmin(var_G,var_B)); double var_Max =::fmax(var_R,::fmax(var_G,var_B)); double del_Max =var_Max-var_Min; //--- oL=(var_Max+var_Min)/2; //--- if(del_Max==0) { oH=0; oS=0; } else { if(oL<0.5) oS=del_Max/(var_Max+var_Min); else oS=del_Max/(2.0-var_Max-var_Min); //--- double del_R =(((var_Max-var_R)/6.0)+(del_Max/2.0))/del_Max; double del_G =(((var_Max-var_G)/6.0)+(del_Max/2.0))/del_Max; double del_B =(((var_Max-var_B)/6.0)+(del_Max/2.0))/del_Max; //--- if(var_R==var_Max) oH=del_B-del_G; else if(var_G==var_Max) oH=(1.0/3.0)+del_R-del_B; else if(var_B==var_Max) oH=(2.0/3.0)+del_G-del_R; //--- if(oH<0) oH+=1.0; //--- if(oH>1) oH-=1.0; } } //+------------------------------------------------------------------+ //| Conversion of HSL into RGB | //+------------------------------------------------------------------+ void CColors::HSLtoRGB(const double aH,const double aS,const double aL,double &oR,double &oG,double &oB) { if(aS==0) { oR=aL*255; oG=aL*255; oB=aL*255; } else { double var_2=0.0; //--- if(aL<0.5) var_2=aL*(1.0+aS); else var_2=(aL+aS)-(aS*aL); //--- double var_1=2.0*aL-var_2; oR =255.0*Hue_To_RGB(var_1,var_2,aH+(1.0/3.0)); oG =255.0*Hue_To_RGB(var_1,var_2,aH); oB =255.0*Hue_To_RGB(var_1,var_2,aH-(1.0/3.0)); } } //+------------------------------------------------------------------+ //| Conversion of RGB into HSV | //+------------------------------------------------------------------+ void CColors::RGBtoHSV(const double aR,const double aG,const double aB,double &oH,double &oS,double &oV) { const double var_R =(aR/255.0); const double var_G =(aG/255.0); const double var_B =(aB/255.0); const double var_Min =::fmin(var_R,::fmin(var_G, var_B)); const double var_Max =::fmax(var_R,::fmax(var_G,var_B)); const double del_Max =var_Max-var_Min; //--- oV=var_Max; //--- if(del_Max==0) { oH=0; oS=0; } else { oS=del_Max/var_Max; const double del_R =(((var_Max-var_R)/6.0)+(del_Max/2))/del_Max; const double del_G =(((var_Max-var_G)/6.0)+(del_Max/2))/del_Max; const double del_B =(((var_Max-var_B)/6.0)+(del_Max/2))/del_Max; //--- if(var_R==var_Max) oH=del_B-del_G; else if(var_G==var_Max) oH=(1.0/3.0)+del_R-del_B; else if(var_B==var_Max) oH=(2.0/3.0)+del_G-del_R; //--- if(oH<0) oH+=1.0; //--- if(oH>1.0) oH-=1.0; } } //+------------------------------------------------------------------+ //| Conversion of HSV into RGB | //+------------------------------------------------------------------+ void CColors::HSVtoRGB(const double aH,const double aS,const double aV,double &oR,double &oG,double &oB) { if(aS==0) { oR =aV*255.0; oG =aV*255.0; oB =aV*255.0; } else { double var_h=aH*6.0; //--- if(var_h==6) var_h=0; //--- int var_i =int(var_h); double var_1 =aV*(1.0-aS); double var_2 =aV*(1.0-aS*(var_h-var_i)); double var_3 =aV*(1.0-aS*(1.0-(var_h-var_i))); double var_r =0.0; double var_g =0.0; double var_b =0.0; //--- if(var_i==0) { var_r =aV; var_g =var_3; var_b =var_1; } else if(var_i==1.0) { var_r=var_2; var_g=aV; var_b=var_1; } else if(var_i==2.0) { var_r=var_1; var_g=aV; var_b=var_3; } else if(var_i==3) { var_r=var_1; var_g=var_2; var_b=aV; } else if(var_i==4) { var_r=var_3; var_g=var_1; var_b=aV; } else { var_r=aV; var_g=var_1; var_b=var_2; } //--- oR =var_r*255.0; oG =var_g*255.0; oB =var_b*255.0; } } //+------------------------------------------------------------------+ //| Conversion of RGB into CMY | //+------------------------------------------------------------------+ void CColors::RGBtoCMY(const double aR,const double aG,const double aB,double &oC,double &oM,double &oY) { oC =1.0-(aR/255.0); oM =1.0-(aG/255.0); oY =1.0-(aB/255.0); } //+------------------------------------------------------------------+ //| Conversion of CMY into RGB | //+------------------------------------------------------------------+ void CColors::CMYtoRGB(const double aC,const double aM,const double aY,double &oR,double &oG,double &oB) { oR =(1.0-aC)*255.0; oG =(1.0-aM)*255.0; oB =(1.0-aY)*255.0; } //+------------------------------------------------------------------+ //| Conversion of CMY into CMYK | //+------------------------------------------------------------------+ void CColors::CMYtoCMYK(const double aC,const double aM,const double aY,double &oC,double &oM,double &oY,double &oK) { double var_K=1; //--- if(aC<var_K) var_K=aC; if(aM<var_K) var_K=aM; if(aY<var_K) var_K=aY; //--- if(var_K==1.0) { oC =0; oM =0; oY =0; } else { oC =(aC-var_K)/(1.0-var_K); oM =(aM-var_K)/(1.0-var_K); oY =(aY-var_K)/(1.0-var_K); } //--- oK=var_K; } //+------------------------------------------------------------------+ //| Conversion of CMYK into CMY | //+------------------------------------------------------------------+ void CColors::CMYKtoCMY(const double aC,const double aM,const double aY,const double aK,double &oC,double &oM,double &oY) { oC =(aC*(1.0-aK)+aK); oM =(aM*(1.0-aK)+aK); oY =(aY*(1.0-aK)+aK); } //+------------------------------------------------------------------+ //| Conversion of RGB into Lab | //+------------------------------------------------------------------+ void CColors::RGBtoLab(const double aR,const double aG,const double aB,double &oL,double &oa,double &ob) { double X=0,Y=0,Z=0; RGBtoXYZ(aR,aG,aB,X,Y,Z); XYZtoHunterLab(X,Y,Z,oL,oa,ob); } //+------------------------------------------------------------------+ //| Getting values of the RGB components | //+------------------------------------------------------------------+ void CColors::ColorToRGB(const color aColor,double &aR,double &aG,double &aB) { aR =GetR(aColor); aG =GetG(aColor); aB =GetB(aColor); } //+------------------------------------------------------------------+ //| Getting the R component value | //+------------------------------------------------------------------+ double CColors::GetR(const color aColor) { return(aColor&0xff); } //+------------------------------------------------------------------+ //| Getting the G component value | //+------------------------------------------------------------------+ double CColors::GetG(const color aColor) { return((aColor>>8)&0xff); } //+------------------------------------------------------------------+ //| Getting the B component value | //+------------------------------------------------------------------+ double CColors::GetB(const color aColor) { return((aColor>>16)&0xff); } //+------------------------------------------------------------------+ //| Getting the A component value | //+------------------------------------------------------------------+ double CColors::GetA(const color aColor) { return(double(uchar((aColor)>>24))); } //+------------------------------------------------------------------+ //| Conversion of RGB into const color | //+------------------------------------------------------------------+ color CColors::RGBToColor(const double aR,const double aG,const double aB) { int int_r =(int)::round(aR); int int_g =(int)::round(aG); int int_b =(int)::round(aB); int Color =0; //--- Color=int_b; Color<<=8; Color|=int_g; Color<<=8; Color|=int_r; //--- return((color)Color); } //+------------------------------------------------------------------+ //| Getting the value of the intermediary color between two colors | //+------------------------------------------------------------------+ color CColors::MixColors(const color aCol1,const color aCol2,const double aK) { //--- aK - from 0 to 1 double R1=0.0,G1=0.0,B1=0.0,R2=0.0,G2=0.0,B2=0.0; //--- ColorToRGB(aCol1,R1,G1,B1); ColorToRGB(aCol2,R2,G2,B2); //--- R1+=(int)::round(aK*(R2-R1)); G1+=(int)::round(aK*(G2-G1)); B1+=(int)::round(aK*(B2-B1)); //--- return(RGBToColor(R1,G1,B1)); } //+------------------------------------------------------------------+ //| Blending two colors considering the transparency of color on top | //+------------------------------------------------------------------+ color CColors::BlendColors(const uint lower_color,const uint upper_color) { double r1=0,g1=0,b1=0; double r2=0,g2=0,b2=0,alpha=0; double r3=0,g3=0,b3=0; //--- Convert the colors in ARGB format uint pixel_color=::ColorToARGB(upper_color); //--- Get the components of the lower and upper colors ColorToRGB(lower_color,r1,g1,b1); ColorToRGB(pixel_color,r2,g2,b2); //--- Get the transparency percentage from 0.00 to 1.00 alpha=GetA(upper_color)/255.0; //--- If there is transparency if(alpha<1.0) { //--- Blend the components taking the alpha channel into account r3=(r1*(1-alpha))+(r2*alpha); g3=(g1*(1-alpha))+(g2*alpha); b3=(b1*(1-alpha))+(b2*alpha); //--- Adjustment of the obtained values r3=(r3>255)? 255 : r3; g3=(g3>255)? 255 : g3; b3=(b3>255)? 255 : b3; } else { r3=r2; g3=g2; b3=b2; } //--- Combine the obtained components and return the color return(RGBToColor(r3,g3,b3)); } //+------------------------------------------------------------------+ //| Getting an array of the specified size with a color gradient | //+------------------------------------------------------------------+ void CColors::Gradient(color &aColors[], // List of colors color &aOut[], // Return array int aOutCount, // Set the size of the return array bool aCycle=false) // Closed-loop cycle. Return array ends with the same color as it starts with { ::ArrayResize(aOut,aOutCount); //--- int InCount =::ArraySize(aColors)+aCycle; int PrevJ =0; int nci =0; double K =0.0; //--- for(int i=1; i<InCount; i++) { int J=(aOutCount-1)*i/(InCount-1); //--- for(int j=PrevJ; j<=J; j++) { if(aCycle && i==InCount-1) { nci =0; K =1.0*(j-PrevJ)/(J-PrevJ+1); } else { nci =i; K =1.0*(j-PrevJ)/(J-PrevJ); } aOut[j]=MixColors(aColors[i-1],aColors[nci],K); } PrevJ=J; } } //+------------------------------------------------------------------+ //| One more variant of conversion of RGB into XYZ and | //| corresponding conversion of XYZ into RGB | //+------------------------------------------------------------------+ void CColors::RGBtoXYZsimple(double aR,double aG,double aB,double &oX,double &oY,double &oZ) { aR/=255; aG/=255; aB/=255; aR*=100; aG*=100; aB*=100; //--- oX=0.431*aR+0.342*aG+0.178*aB; oY=0.222*aR+0.707*aG+0.071*aB; oZ=0.020*aR+0.130*aG+0.939*aB; } //+------------------------------------------------------------------+ //| XYZtoRGBsimple | //+------------------------------------------------------------------+ void CColors::XYZtoRGBsimple(const double aX,const double aY,const double aZ,double &oR,double &oG,double &oB) { oR=3.063*aX-1.393*aY-0.476*aZ; oG=-0.969*aX+1.876*aY+0.042*aZ; oB=0.068*aX-0.229*aY+1.069*aZ; } //+------------------------------------------------------------------+ //| Negative color | //+------------------------------------------------------------------+ color CColors::Negative(const color aColor) { double R=0.0,G=0.0,B=0.0; ColorToRGB(aColor,R,G,B); //--- return(RGBToColor(255-R,255-G,255-B)); } //+------------------------------------------------------------------+ //| Search for the most similar color | //| in the set of standard colors of the terminal | //+------------------------------------------------------------------+ color CColors::StandardColor(const color aColor,int &aIndex) { color m_c[]= { clrBlack,clrDarkGreen,clrDarkSlateGray,clrOlive,clrGreen,clrTeal,clrNavy,clrPurple,clrMaroon,clrIndigo, clrMidnightBlue,clrDarkBlue,clrDarkOliveGreen,clrSaddleBrown,clrForestGreen,clrOliveDrab,clrSeaGreen, clrDarkGoldenrod,clrDarkSlateBlue,clrSienna,clrMediumBlue,clrBrown,clrDarkTurquoise,clrDimGray, clrLightSeaGreen,clrDarkViolet,clrFireBrick,clrMediumVioletRed,clrMediumSeaGreen,clrChocolate,clrCrimson, clrSteelBlue,clrGoldenrod,clrMediumSpringGreen,clrLawnGreen,clrCadetBlue,clrDarkOrchid,clrYellowGreen, clrLimeGreen,clrOrangeRed,clrDarkOrange,clrOrange,clrGold,clrYellow,clrChartreuse,clrLime,clrSpringGreen, clrAqua,clrDeepSkyBlue,clrBlue,clrFuchsia,clrRed,clrGray,clrSlateGray,clrPeru,clrBlueViolet,clrLightSlateGray, clrDeepPink,clrMediumTurquoise,clrDodgerBlue,clrTurquoise,clrRoyalBlue,clrSlateBlue,clrDarkKhaki,clrIndianRed, clrMediumOrchid,clrGreenYellow,clrMediumAquamarine,clrDarkSeaGreen,clrTomato,clrRosyBrown,clrOrchid, clrMediumPurple,clrPaleVioletRed,clrCoral,clrCornflowerBlue,clrDarkGray,clrSandyBrown,clrMediumSlateBlue, clrTan,clrDarkSalmon,clrBurlyWood,clrHotPink,clrSalmon,clrViolet,clrLightCoral,clrSkyBlue,clrLightSalmon, clrPlum,clrKhaki,clrLightGreen,clrAquamarine,clrSilver,clrLightSkyBlue,clrLightSteelBlue,clrLightBlue, clrPaleGreen,clrThistle,clrPowderBlue,clrPaleGoldenrod,clrPaleTurquoise,clrLightGray,clrWheat,clrNavajoWhite, clrMoccasin,clrLightPink,clrGainsboro,clrPeachPuff,clrPink,clrBisque,clrLightGoldenrod,clrBlanchedAlmond, clrLemonChiffon,clrBeige,clrAntiqueWhite,clrPapayaWhip,clrCornsilk,clrLightYellow,clrLightCyan,clrLinen, clrLavender,clrMistyRose,clrOldLace,clrWhiteSmoke,clrSeashell,clrIvory,clrHoneydew,clrAliceBlue,clrLavenderBlush, clrMintCream,clrSnow,clrWhite,clrDarkCyan,clrDarkRed,clrDarkMagenta,clrAzure,clrGhostWhite,clrFloralWhite }; //--- double m_rv=0.0,m_gv=0.0,m_bv=0.0; //--- ColorToRGB(aColor,m_rv,m_gv,m_bv); //--- double m_md=0.3*::pow(255,2)+0.59*::pow(255,2)+0.11*::pow(255,2)+1; aIndex=0; //--- for(int i=0; i<138; i++) { double m_d=0.3*::pow(GetR(m_c[i])-m_rv,2)+0.59*::pow(GetG(m_c[i])-m_gv,2)+0.11*::pow(GetB(m_c[i])-m_bv,2); //--- if(m_d<m_md) { m_md =m_d; aIndex =i; } } //--- return(m_c[aIndex]); } //+------------------------------------------------------------------+ //| Conversion into gray color | //+------------------------------------------------------------------+ double CColors::RGBtoGray(double aR,double aG,double aB) { aR/=255; aG/=255; aB/=255; //--- aR=::pow(aR,2.2); aG=::pow(aG,2.2); aB=::pow(aB,2.2); //--- double rY=0.21*aR+0.72*aG+0.07*aB; rY=::pow(rY,1.0/2.2); //--- return(rY); } //+------------------------------------------------------------------+ //| Simple conversion into gray color | //+------------------------------------------------------------------+ double CColors::RGBtoGraySimple(double aR,double aG,double aB) { aR/=255; aG/=255; aB/=255; double rY=0.3*aR+0.59*aG+0.11*aB; //--- return(rY); } //+------------------------------------------------------------------+
Todos los cambios que hemos realizado son la asignación a cada uno de los métodos del modificador static, y algunos cambios puramente formales (salvo el nombre de las variables en los argumentos de los métodos) que se adaptan mejor a nuestro estilo de programación. Además, hemos añadido el método RGBtoLab() para convertir el modelo de color RGB a Lab. El método simplemente convierte un modelo RGB a un modelo XYZ y, de ahí, a un modelo de color Lab. Sobre esto habló en su momento Anatoly Kazharsky en su artículo "Interfaces gráficas IX: Control "Paleta para seleccionar el color" (Capítulo 1)":
Simplemente, hemos seguido su consejo.
Para que la clase CColors pueda ser vista tanto por la biblioteca al completo como por los programas creados sobre su base, incluiremos un archicvo de clase en el archivo de funciones de servicio de la biblioteca, en el archivo \MQL5\Include\DoEasy\Services\DELib.mqh:
//+------------------------------------------------------------------+ //| DELib.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Defines.mqh" #include "Message.mqh" #include "TimerCounter.mqh" #include "Pause.mqh" #include "Colors.mqh" //+------------------------------------------------------------------+ //| Service functions | //+------------------------------------------------------------------+
En este artículo, no necesitaremos esta clase, pero comenzaremos a utilizarla más tarde, al crear clases herederas del objeto de elemento gráfico.
Cada objeto gráfico contiene (como mínimo) las coordenadas de su ubicación en el gráfico y sus dimensiones. Además, nuestros objetos están dotados de multitud de propiedades que se pueden cambiar mientras se ejecuta el programa. Pero si reiniciamos el programa o cambiamos el marco temporal, se descartarán todos los cambios realizados en los objetos gráficos mientras se ejecutaba el programa. Para que cada objeto recuerde el estado de sus propiedades, necesitaremos guardar estas propiedades desde el exterior. Luego, después de reiniciar el programa, todos los objetos gráficos construidos y modificados durante su funcionamiento leerán del archivo correspondiente las propiedades que les pertenecen y que están vigentes para ellos en el momento del reinicio, restaurando aquellas. Para hacer esto, tendremos que añadir dos métodos a la clase de objeto de elemento gráfico: para escribir las propiedades del objeto en un archivo y para leer las propiedades del objeto desde un archivo.
Para escribir y leer las propiedades del objeto, utilizaremos el guardado de las propiedades del objeto en una estructura que podremos guardar en un archivo y leer desde el mismo usando las funciones estándar StructToCharArray() y CharArrayToStruct().
Cada objeto gráfico contendrá los métodos necesarios para guardar las propiedades en un archivo y leer las propiedades de un archivo, ya que cada objeto gráfico basado en el lienzo será heredado del objeto de elemento gráfico en el que escribiremos estos métodos. Por consiguiente, si el objeto es compuesto, es decir, si contiene otros objetos basados en un elemento gráfico, podremos restaurar los estados de todos sus objetos subordinados uno por uno según el número de objeto en la lista de objetos subordinados (el número se guarda en la constante CANV_ELEMENT_PROP_NUM de la enumeración ENUM_CANV_ELEMENT_PROP_INTEGER de las propiedades del objeto de elemento).
Hoy no nos ocuparemos de guardar propiedades en un archivo y leerlas desde un archivo, ya que esto debe hacerse desde una clase de colección de objetos gráficos. Lo consideraremos más a fondo, después de crear un elemento gráfico. Pero hoy agregaremos métodos de escritura y lectura.
Como el elemento gráfico es heredero del objeto básico de todos los objetos gráficos de la biblioteca CGBaseObj, primero escribiremos en el archivo de clase de este objeto (\MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh) en la sección protegida el método virtual para crear la estructura a partir de las propiedades del objeto y el método virtual para restaurar las propiedades del objeto desde la estructura:
protected: string m_name_prefix; // Object name prefix string m_name; // Object name long m_chart_id; // Chart ID int m_subwindow; // Subwindow index int m_shift_y; // Subwindow Y coordinate shift int m_type; // Object type //--- Create (1) the object structure and (2) the object from the structure virtual bool ObjectToStruct(void) { return true; } virtual void StructToObject(void){;} public:
Estos métodos no hacen nada aquí: debemos redefinirlos en los herederos de la clase. El heredero más próximo de esta clase es la clase de objeto de elemento gráfico en el archivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh. Declaramos en su sección protegida los mismos métodos virtuales:
//+------------------------------------------------------------------+ //| Class of the base object of the library graphical objects | //+------------------------------------------------------------------+ class CGCnvElement : public CGBaseObj { protected: CCanvas m_canvas; // CCanvas class object CPause m_pause; // Pause class object //--- Return the cursor position relative to the (1) entire element and (2) the element's active area bool CursorInsideElement(const int x,const int y); bool CursorInsideActiveArea(const int x,const int y); //--- Create (1) the object structure and (2) the object from the structure virtual bool ObjectToStruct(void); virtual void StructToObject(void); private:
Y en la sección privada, declaramos la estructura para guardar todas las propiedades del objeto, el objeto con el tipo de esta estructura y la matriz de estructura del objeto:
private: struct SData { //--- Object integer properties int id; // Element ID int type; // Graphical element type int number; // Element index in the list long chart_id; // Chart ID int subwindow; // Chart subwindow index int coord_x; // Form's X coordinate on the chart int coord_y; // Form's Y coordinate on the chart int width; // Element width int height; // Element height int edge_right; // Element right border int edge_bottom; // Element bottom border int act_shift_left; // Active area offset from the left edge of the element int act_shift_top; // Active area offset from the top edge of the element int act_shift_right; // Active area offset from the right edge of the element int act_shift_bottom; // Active area offset from the bottom edge of the element uchar opacity; // Element opacity color color_bg; // Element background color bool movable; // Element moveability flag bool active; // Element activity flag int coord_act_x; // X coordinate of the element active area int coord_act_y; // Y coordinate of the element active area int coord_act_right; // Right border of the element active area int coord_act_bottom; // Bottom border of the element active area //--- Object real properties //--- Object string properties uchar name_obj[64]; // Graphical element object name uchar name_res[64]; // Graphical resource name }; SData m_struct_obj; // Object structure uchar m_uchar_array[]; // uchar array of the object structure long m_long_prop[ORDER_PROP_INTEGER_TOTAL]; // Integer properties double m_double_prop[ORDER_PROP_DOUBLE_TOTAL]; // Real properties string m_string_prop[ORDER_PROP_STRING_TOTAL]; // String properties
En la sección pública del objeto, declaramos los métodos para escribir y leer las propiedades del objeto desde un archivo:
public: //--- Set object's (1) integer, (2) real and (3) string properties void SetProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property,long value) { this.m_long_prop[property]=value; } void SetProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property,double value) { this.m_double_prop[this.IndexProp(property)]=value; } void SetProperty(ENUM_CANV_ELEMENT_PROP_STRING property,string value) { this.m_string_prop[this.IndexProp(property)]=value; } //--- Return object’s (1) integer, (2) real and (3) string property from the properties array long GetProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) const { return this.m_long_prop[property]; } double GetProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property) const { return this.m_double_prop[this.IndexProp(property)];} string GetProperty(ENUM_CANV_ELEMENT_PROP_STRING property) const { return this.m_string_prop[this.IndexProp(property)];} //--- Return the flag of the object supporting this property virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property) { return false;} virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property) { return true; } //--- Compare CGCnvElement objects with each other by all possible properties (for sorting the lists by a specified object property) virtual int Compare(const CObject *node,const int mode=0) const; //--- Compare CGCnvElement objects with each other by all properties (to search equal objects) bool IsEqual(CGCnvElement* compared_obj) const; //--- (1) Save the object to file and (2) upload the object from the file virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); //--- Create the element
Como el objeto no tiene propiedades reales, el método virtual que retorna la bandera de soporte de propiedades reales por parte del objeto debería retornar false.
Implementamos fuera del cuerpo de la clase los métodos declarados:
Método que crea la estructura del objeto a partir de sus propiedades:
//+------------------------------------------------------------------+ //| Create the object structure | //+------------------------------------------------------------------+ bool CGCnvElement::ObjectToStruct(void) { //--- Save integer properties this.m_struct_obj.id=(int)this.GetProperty(CANV_ELEMENT_PROP_ID); // Element ID this.m_struct_obj.type=(int)this.GetProperty(CANV_ELEMENT_PROP_TYPE); // Graphical element type this.m_struct_obj.number=(int)this.GetProperty(CANV_ELEMENT_PROP_NUM); // Eleemnt ID in the list this.m_struct_obj.chart_id=this.GetProperty(CANV_ELEMENT_PROP_CHART_ID); // Chart ID this.m_struct_obj.subwindow=(int)this.GetProperty(CANV_ELEMENT_PROP_WND_NUM); // Chart subwindow index this.m_struct_obj.coord_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_X); // Form's X coordinate on the chart this.m_struct_obj.coord_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_Y); // Form's Y coordinate on the chart this.m_struct_obj.width=(int)this.GetProperty(CANV_ELEMENT_PROP_WIDTH); // Element width this.m_struct_obj.height=(int)this.GetProperty(CANV_ELEMENT_PROP_HEIGHT); // Element height this.m_struct_obj.edge_right=(int)this.GetProperty(CANV_ELEMENT_PROP_RIGHT); // Element right edge this.m_struct_obj.edge_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_BOTTOM); // Element bottom edge this.m_struct_obj.act_shift_left=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT); // Active area offset from the left edge of the element this.m_struct_obj.act_shift_top=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP); // Active area offset from the top edge of the element this.m_struct_obj.act_shift_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT); // Active area offset from the right edge of the element this.m_struct_obj.act_shift_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM);// Active area offset from the bottom edge of the element this.m_struct_obj.opacity=(uchar)this.GetProperty(CANV_ELEMENT_PROP_OPACITY); // Element opacity this.m_struct_obj.color_bg=(color)this.GetProperty(CANV_ELEMENT_PROP_COLOR_BG); // Element background color this.m_struct_obj.movable=(bool)this.GetProperty(CANV_ELEMENT_PROP_MOVABLE); // Element moveability flag this.m_struct_obj.active=(bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE); // Element activity flag this.m_struct_obj.coord_act_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_X); // X coordinate of the element active area this.m_struct_obj.coord_act_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y); // Y coordinate of the element active area this.m_struct_obj.coord_act_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_RIGHT); // Right border of the element active area this.m_struct_obj.coord_act_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM); // Bottom border of the element active area //--- Save real properties //--- Save string properties ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj);// Graphical element object name ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res);// Graphical resource name //--- Save the structure to the uchar array ::ResetLastError(); if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY),(string)::GetLastError()); return false; } return true; } //+------------------------------------------------------------------+
Aquí todo es simple: en cada campo entero de la estructura, introducimos la propiedad correspondiente del objeto y guardamos las propiedades string del objeto en la matriz uchar correspondiente de la estructura. Luego, solo tenemos que guardar la estructura generada de las propiedades del objeto en una matriz uchar utilizando StructToCharArray().
Si no hemos logrado guardar la estructura en una matriz, notificamos sobre el error y retornamos false. Finalmente, retornamos true.
Método que restaura las propiedades del objeto a partir de la estructura:
//+------------------------------------------------------------------+ //| Create the object from the structure | //+------------------------------------------------------------------+ void CGCnvElement::StructToObject(void) { //--- Save integer properties this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id); // Element ID this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type); // Graphical element type this.SetProperty(CANV_ELEMENT_PROP_NUM,this.m_struct_obj.number); // Element index in the list this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,this.m_struct_obj.chart_id); // Chart ID this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,this.m_struct_obj.subwindow); // Chart subwindow index this.SetProperty(CANV_ELEMENT_PROP_COORD_X,this.m_struct_obj.coord_x); // Form's X coordinate on the chart this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,this.m_struct_obj.coord_y); // Form's Y coordinate on the chart this.SetProperty(CANV_ELEMENT_PROP_WIDTH,this.m_struct_obj.width); // Element width this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,this.m_struct_obj.height); // Element height this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.m_struct_obj.edge_right); // Element right edge this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.m_struct_obj.edge_bottom); // Element bottom edge this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,this.m_struct_obj.act_shift_left); // Active area offset from the left edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,this.m_struct_obj.act_shift_top); // Active area offset from the upper edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,this.m_struct_obj.act_shift_right); // Active area offset from the right edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,this.m_struct_obj.act_shift_bottom); // Active area offset from the bottom edge of the element this.SetProperty(CANV_ELEMENT_PROP_OPACITY,this.m_struct_obj.opacity); // Element opacity this.SetProperty(CANV_ELEMENT_PROP_COLOR_BG,this.m_struct_obj.color_bg); // Element background color this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,this.m_struct_obj.movable); // Element moveability flag this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,this.m_struct_obj.active); // Element activity flag this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.m_struct_obj.coord_act_x); // X coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.m_struct_obj.coord_act_y); // Y coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.m_struct_obj.coord_act_right); // Right border of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.m_struct_obj.coord_act_bottom); // Bottom border of the element active area //--- Save real properties //--- Save string properties this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj));// Graphical element object name this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res));// Graphical resource name } //+------------------------------------------------------------------+
Aquí, en cada propiedad entera del objeto, escribimos el valor del campo correspondiente de la estructura, y en las propiedades string del objeto, leemos el contenido de la matriz uchar correspondiente de la estructura usando CharArrayToString().
Método que guarda el objeto en un archivo:
//+------------------------------------------------------------------+ //| Save the object to the file | //+------------------------------------------------------------------+ bool CGCnvElement::Save(const int file_handle) { if(!this.ObjectToStruct()) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_OBJ_STRUCT)); return false; } if(::FileWriteArray(file_handle,this.m_uchar_array)==0) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_WRITE_UARRAY_TO_FILE)); return false; } return true; } //+------------------------------------------------------------------+
Transmitimos al método el manejador del archivo en el que se deben guardar las propiedades del objeto. A continuación, guardamos las propiedades del objeto en la estructura utilizando el método anterior, ObjectToStruct(). Luego, escribimos en el archivo la matriz uchar creada al generar la estructura, utilizando para ello FileWriteArray(), y retornamos true. En caso de fracaso, el método mostrará en el diario un mensaje sobre el error y retornará false.
Método que carga las propiedades del objeto desde un archivo:
//+------------------------------------------------------------------+ //| Upload the object from the file | //+------------------------------------------------------------------+ bool CGCnvElement::Load(const int file_handle) { if(::FileReadArray(file_handle,this.m_uchar_array)==0) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_LOAD_UARRAY_FROM_FILE)); return false; } if(!::CharArrayToStruct(this.m_struct_obj,this.m_uchar_array)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_OBJ_STRUCT_FROM_UARRAY)); return false; } this.StructToObject(); return true; } //+------------------------------------------------------------------+
El manejador del archivo se transmite al método en el que se guardan las propiedades del objeto. Luego, usando FileReadArray(), se cargan en una matriz uchar las propiedades del objeto desde el archivo, y las propiedades cargadas en la matriz se copian en la estructura con la ayuda de CharArrayToStruct(). Finalmente, usando el método StructToObject() que hemos analizado anteriormente, escribimos la estructura rellenada desde el archivo en las propiedades del objeto y retornamos true. Si hay errores al leer un archivo o copiar una matriz (obtenida desde un archivo) en una estructura, el método informará sobre el error y retornará false.
En el bloque de métodos para el acceso simplificado a las propiedades, añadimos los métodos para retornar el borde derecho e inferior del elemento, los métodos para establecer y retornar el color del fondo del elemento y los métodos para retornar el identificador del elemento y su número en la lista de elementos en un objeto compuesto:
//+------------------------------------------------------------------+ //| Methods of simplified access to object properties | //+------------------------------------------------------------------+ //--- Set the (1) X, (2) Y coordinates, (3) element width, (4) height, (5) right (6) and bottom edge, bool SetCoordX(const int coord_x); bool SetCoordY(const int coord_y); bool SetWidth(const int width); bool SetHeight(const int height); void SetRightEdge(void) { this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge()); } void SetBottomEdge(void) { this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.BottomEdge()); } //--- Set the shift of the (1) left, (2) top, (3) right, (4) bottom edge of the active area relative to the element, //--- (5) all shifts of the active area edges relative to the element, (6) the element background color and (7) the element opacity void SetActiveAreaLeftShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,fabs(value)); } void SetActiveAreaRightShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,fabs(value)); } void SetActiveAreaTopShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,fabs(value)); } void SetActiveAreaBottomShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,fabs(value)); } void SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift); void SetColorBG(const color colour) { this.SetProperty(CANV_ELEMENT_PROP_COLOR_BG,colour); } void SetOpacity(const uchar value,const bool redraw=false); //--- Return the shift (1) of the left, (2) right, (3) top and (4) bottom edge of the element active area int ActiveAreaLeftShift(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT); } int ActiveAreaRightShift(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT); } int ActiveAreaTopShift(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP); } int ActiveAreaBottomShift(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM); } //--- Return the coordinate (1) of the left, (2) right, (3) top and (4) bottom edge of the element active area int ActiveAreaLeft(void) const { return int(this.CoordX()+this.ActiveAreaLeftShift()); } int ActiveAreaRight(void) const { return int(this.RightEdge()-this.ActiveAreaRightShift()); } int ActiveAreaTop(void) const { return int(this.CoordY()+this.ActiveAreaTopShift()); } int ActiveAreaBottom(void) const { return int(this.BottomEdge()-this.ActiveAreaBottomShift()); } //--- Return (1) the background color, (2) the opacity, coordinate (3) of the right and (4) bottom element edge color ColorBG(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_COLOR_BG); } uchar Opacity(void) const { return (uchar)this.GetProperty(CANV_ELEMENT_PROP_OPACITY); } int RightEdge(void) const { return this.CoordX()+this.m_canvas.Width(); } int BottomEdge(void) const { return this.CoordY()+this.m_canvas.Height(); } //--- Return the (1) X, (2) Y coordinates, (3) element width and (4) height, int CoordX(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_COORD_X); } int CoordY(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_COORD_Y); } int Width(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_WIDTH); } int Height(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_HEIGHT); } //--- Return the element (1) moveability and (2) activity flag bool Movable(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_MOVABLE); } bool Active(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE); } //--- Return (1) the object name, (2) the graphical resource name, (3) the chart ID and (4) the chart subwindow index string NameObj(void) const { return this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ); } string NameRes(void) const { return this.GetProperty(CANV_ELEMENT_PROP_NAME_RES); } long ChartID(void) const { return this.GetProperty(CANV_ELEMENT_PROP_CHART_ID); } int WindowNum(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_WND_NUM); } //--- Return (1) the element ID and (2) index in the list int ID(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ID); } int Number(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_NUM); } //+------------------------------------------------------------------+
Todos estos métodos simplemente retornan una propiedad existente del objeto de elemento.
Métodos de trabajo con primitivas
La clase CCanvas ofrece amplias posibilidades para dibujar diferentes primitivas gráficas en el lienzo. Podemos leer el color de cada píxel y establecer el color y la transparencia necesarios. Además de establecer el color en un píxel, la clase ofrece herramientas para dibujar varias formas, píxel a píxel, sin suavizado o usando varios métodos de suavizado.
En nuestra clase de objeto de elemento gráfico, ofreceremos al usuario acceso a los métodos de dibujado de la clase CCanvas. Nuestros métodos solo simplificarán ligeramente la llamada a los métodos de la clase CCanvas. La simplificación consistirá en establecer el color de la forma habitual: especificando el color deseado en el formato color e indicando el grado de opacidad del color (0 — transparente, 255 — completamente opaco), mientras que los métodos de la clase CCanvas "piden" que se indique el color directamente en el formato uint ARGB, y esto es solo un número. No todo el mundo se siente cómodo indicando el color necesario en este formato (gris semitransparente: 0x7F7F7F7F). En las clases posteriores que heredarán del elemento gráfico, ampliaremos el rango de posibilidades de dibujado añadiendo a los métodos de dibujado la funcionalidad adecuada y propia de cada clase que creamos. En la misma clase, que supone la base para la creación de otros objetos gráficos, los métodos de dibujado deben ser simples y comprensibles.
Siguiendo el bloque de métodos para el acceso simplificado a las propiedades del objeto, empezaremos a escribir los nuevos bloques de código. Vamos a intentar distribuirlos según su finalidad.
Los métodos de recuperación de datos comienzan con el prefijo "Get", mientras que los métodos para establecer los datos comienzan con el prefijo "Set".
Método que obtiene el color del punto con las coordenadas especificadas:
//+------------------------------------------------------------------+ //| The methods of receiving raster data | //+------------------------------------------------------------------+ //--- Get a color of the dot with the specified coordinates uint GetPixel(const int x,const int y) const { return this.m_canvas.PixelGet(x,y); } //+------------------------------------------------------------------+
Aquí, simplemente se retorna el resultado de la llamada al método PixelGet() de la clase CCanvas. El método retorna el color en el formato ARGB.
Método para rellenar, limpiar y actualizar los datos ráster:
//+------------------------------------------------------------------+ //| The methods of filling, clearing and updating raster data | //+------------------------------------------------------------------+ //--- Clear the element filling it with color and opacity void Erase(const color colour,const uchar opacity,const bool redraw=false); //--- Clear the element completely void Erase(const bool redraw=false); //--- Update the element void Update(const bool redraw=false) { this.m_canvas.Update(redraw); } //+------------------------------------------------------------------+
El método Update() simplemente actualiza el objeto y el gráfico con la ayuda del método Update() de la clase CCanvas.
Los métodos Erase() los hemos implementado fuera del cuerpo de la clase:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CGCnvElement::Erase(const color colour,const uchar opacity,const bool redraw=false) { this.m_canvas.Erase(::ColorToARGB(colour,opacity)); if(redraw) ::ChartRedraw(this.m_chart_id); } //+------------------------------------------------------------------+ //| Clear the element completely | //+------------------------------------------------------------------+ void CGCnvElement::Erase(const bool redraw=false) { this.m_canvas.Erase(NULL_COLOR); if(redraw) ::ChartRedraw(this.m_chart_id); } //+------------------------------------------------------------------+
Son dos métodos sobrecargados.
En el primero, transmitimos el color y la opacidad necesarios con los que rellenaremos todo el elemento usando el método Erase() de la clase CCanvas. Debemos tener en cuenta que en nuestro método utilizamos el nivel de color y opacidad que transmitimos al método Erase() de la clase CCanvas convirtiendo sus valores al formato ARGB con la ayuda de la función ColorToARGB(). Esto es exactamente lo que haremos en todos nuestros métodos de dibujado.
En el segundo método, simplemente rellenaremos todo el fondo con color negro completamente transparente, cuyo valor hemos definido previamente con la macrosustitución NULL_COLOR.
Transmitimos la bandera que indica la necesidad de redibujar el gráfico a cada uno de los métodos y, si esta se ha establecido, el gráfico se redibujará.
A continuación, tenemos un bloque con métodos para dibujar primitivas sin suavizado. Todos los métodos son idénticos y llaman a los métodos correspondientes de la clase CCanvas a los que se transmiten los parámetros especificados en los argumentos del método, así como el color convertido a formato ARGB a partir de los valores de color y transparencia transmitidos a los métodos:
//+------------------------------------------------------------------+ //| Methods of drawing primitives without smoothing | //+------------------------------------------------------------------+ //--- Set the color of the dot with the specified coordinates void SetPixel(const int x,const int y,const color clr,const uchar opacity=255) { this.m_canvas.PixelSet(x,y,::ColorToARGB(clr,opacity)); } //--- Draw a segment of a vertical line void DrawLineVertical(const int x, // X coordinate of the segment const int y1, // Y coordinate of the segment's first point const int y2, // Y coordinate of the segment's second point const color clr, // Color const uchar opacity=255) // Opacity { this.m_canvas.LineVertical(x,y1,y2,::ColorToARGB(clr,opacity)); } //--- Draw a segment of a horizontal line void DrawLineHorizontal(const int x1, // X coordinate of the segment's first point const int x2, // X coordinate of the segment's second point const int y, // Segment's Y coordinate const color clr, // Color const uchar opacity=255) // Opacity { this.m_canvas.LineHorizontal(x1,x2,y,::ColorToARGB(clr,opacity)); } //--- Draw a segment of a freehand line void DrawLine(const int x1, // X coordinate of the segment's first point const int y1, // Y coordinate of the segment's first point const int x2, // X coordinate of the segment's second point const int y2, // Y coordinate of the segment's second point const color clr, // Color const uchar opacity=255) // Opacity { this.m_canvas.Line(x1,y1,x2,y2,::ColorToARGB(clr,opacity)); } //--- Draw a polyline void DrawPolyline(int &array_x[], // Array with the X coordinates of polyline points int & array_y[], // Array with the Y coordinates of polyline points const color clr, // Color const uchar opacity=255) // Opacity { this.m_canvas.Polyline(array_x,array_y,::ColorToARGB(clr,opacity)); } //--- Draw a polygon void DrawPolygon(int &array_x[], // Array with the X coordinates of polygon points int &array_y[], // Array with the Y coordinates of polygon points const color clr, // Color const uchar opacity=255) // Opacity { this.m_canvas.Polygon(array_x,array_y,::ColorToARGB(clr,opacity)); } //--- Draw a rectangle using two points void DrawRectangle(const int x1, // X coordinate of the first point defining the rectangle const int y1, // Y coordinate of the first point defining the rectangle const int x2, // X coordinate of the second point defining the rectangle const int y2, // Y coordinate of the second point defining the rectangle const color clr, // color const uchar opacity=255) // Opacity { this.m_canvas.Rectangle(x1,y1,x2,y2,::ColorToARGB(clr,opacity)); } //--- Draw a circle void DrawCircle(const int x, // X coordinate of the circle center const int y, // Y coordinate of the circle center const int r, // Circle radius const color clr, // Color const uchar opacity=255) // Opacity { this.m_canvas.Circle(x,y,r,::ColorToARGB(clr,opacity)); } //--- Draw a triangle void DrawTriangle(const int x1, // X coordinate of the triangle first vertex const int y1, // Y coordinate of the triangle first vertex const int x2, // X coordinate of the triangle second vertex const int y2, // Y coordinate of the triangle second vertex const int x3, // X coordinate of the triangle third vertex const int y3, // Y coordinate of the triangle third vertex const color clr, // Color const uchar opacity=255) // Opacity { m_canvas.Triangle(x1,y1,x2,y2,x3,y3,::ColorToARGB(clr,opacity)); } //--- Draw an ellipse using two points void DrawEllipse(const int x1, // X coordinate of the first point defining the ellipse const int y1, // Y coordinate of the first point defining the ellipse const int x2, // X coordinate of the second point defining the ellipse const int y2, // Y coordinate of the second point defining the ellipse const color clr, // Color const uchar opacity=255) // Opacity { this.m_canvas.Ellipse(x1,y1,x2,y2,::ColorToARGB(clr,opacity)); } //--- Draw an arc of an ellipse inscribed in a rectangle with corners at (x1,y1) and (x2,y2). //--- The arc boundaries are clipped by lines from the center of the ellipse, which extend to two points with coordinates (x3,y3) and (x4,y4) void DrawArc(const int x1, // X coordinate of the top left corner forming the rectangle const int y1, // Y coordinate of the top left corner forming the rectangle const int x2, // X coordinate of the bottom right corner forming the rectangle const int y2, // Y coordinate of the bottom right corner forming the rectangle const int x3, // X coordinate of the first point, to which a line from the rectangle center is drawn in order to obtain the arc boundary const int y3, // Y coordinate of the first point, to which a line from the rectangle center is drawn in order to obtain the arc boundary const int x4, // X coordinate of the second point, to which a line from the rectangle center is drawn in order to obtain the arc boundary const int y4, // Y coordinate of the second point, to which a line from the rectangle center is drawn in order to obtain the arc boundary const color clr, // Color const uchar opacity=255) // Opacity { m_canvas.Arc(x1,y1,x2,y2,x3,y3,x4,y4,::ColorToARGB(clr,opacity)); } //--- Draw a filled sector of an ellipse inscribed in a rectangle with corners at (x1,y1) and (x2,y2). //--- The sector boundaries are clipped by lines from the center of the ellipse, which extend to two points with coordinates (x3,y3) and (x4,y4) void DrawPie(const int x1, // X coordinate of the upper left corner of the rectangle const int y1, // Y coordinate of the upper left corner of the rectangle const int x2, // X coordinate of the bottom right corner of the rectangle const int y2, // Y coordinate of the bottom right corner of the rectangle const int x3, // X coordinate of the first point to find the arc boundaries const int y3, // Y coordinate of the first point to find the arc boundaries const int x4, // X coordinate of the second point to find the arc boundaries const int y4, // Y coordinate of the second point to find the arc boundaries const color clr, // Line color const color fill_clr, // Fill color const uchar opacity=255) // Opacity { this.m_canvas.Pie(x1,y1,x2,y2,x3,y3,x4,y4,::ColorToARGB(clr,opacity),ColorToARGB(fill_clr,opacity)); } //+------------------------------------------------------------------+
Bloque con los métodos de dibujado de primitivas coloreadas sin suavizado:
//+------------------------------------------------------------------+ //| Methods of drawing filled primitives without smoothing | //+------------------------------------------------------------------+ //--- Fill in the area void Fill(const int x, // X coordinate of the filling start point const int y, // Y coordinate of the filling start point const color clr, // Color const uchar opacity=255, // Opacity const uint threshould=0) // Threshold { this.m_canvas.Fill(x,y,::ColorToARGB(clr,opacity),threshould); } //--- Draw a filled rectangle void DrawRectangleFill(const int x1, // X coordinate of the first point defining the rectangle const int y1, // Y coordinate of the first point defining the rectangle const int x2, // X coordinate of the second point defining the rectangle const int y2, // Y coordinate of the second point defining the rectangle const color clr, // Color const uchar opacity=255) // Opacity { this.m_canvas.FillRectangle(x1,y1,x2,y2,::ColorToARGB(clr,opacity)); } //--- Draw a filled circle void DrawCircleFill(const int x, // X coordinate of the circle center const int y, // Y coordinate of the circle center const int r, // Circle radius const color clr, // Color const uchar opacity=255) // Opacity { this.m_canvas.FillCircle(x,y,r,::ColorToARGB(clr,opacity)); } //--- Draw a filled triangle void DrawTriangleFill(const int x1, // X coordinate of the triangle first vertex const int y1, // Y coordinate of the triangle first vertex const int x2, // X coordinate of the triangle second vertex const int y2, // Y coordinate of the triangle second vertex const int x3, // X coordinate of the triangle third vertex const int y3, // Y coordinate of the triangle third vertex const color clr, // Color const uchar opacity=255) // Opacity { this.m_canvas.FillTriangle(x1,y1,x2,y2,x3,y3,::ColorToARGB(clr,opacity)); } //--- Draw a filled polygon void DrawPolygonFill(int &array_x[], // Array with the X coordinates of polygon points int &array_y[], // Array with the Y coordinates of polygon points const color clr, // Color const uchar opacity=255) // Opacity { this.m_canvas.FillPolygon(array_x,array_y,::ColorToARGB(clr,opacity)); } //--- Draw a filled ellipse inscribed in a rectangle with the specified coordinates void DrawEllipseFill(const int x1, // X coordinate of the top left corner forming the rectangle const int y1, // Y coordinate of the top left corner forming the rectangle const int x2, // X coordinate of the bottom right corner forming the rectangle const int y2, // Y coordinate of the bottom right corner forming the rectangle const color clr, // Color const uchar opacity=255) // Opacity { this.m_canvas.FillEllipse(x1,y1,x2,y2,::ColorToARGB(clr,opacity)); } //+------------------------------------------------------------------+
Métodos de dibujado de primitivas con uso de suavizado:
//+------------------------------------------------------------------+ //| Methods of drawing primitives using smoothing | //+------------------------------------------------------------------+ //--- Draw a point using AntiAliasing algorithm void SetPixelAA(const double x, // Point X coordinate const double y, // Point Y coordinate const color clr, // Color const uchar opacity=255) // Opacity { this.m_canvas.PixelSetAA(x,y,::ColorToARGB(clr,opacity)); } //--- Draw a segment of a freehand line using AntiAliasing algorithm void DrawLineAA(const int x1, // X coordinate of the segment's first point const int y1, // Y coordinate of the segment's first point const int x2, // X coordinate of the segment's second point const int y2, // Y coordinate of the segment's second point const color clr, // Color const uchar opacity=255, // Opacity const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { this.m_canvas.LineAA(x1,y1,x2,y2,::ColorToARGB(clr,opacity),style); } //--- Draw a segment of a freehand line using Wu algorithm void DrawLineWu(const int x1, // X coordinate of the segment's first point const int y1, // Y coordinate of the segment's first point const int x2, // X coordinate of the segment's second point const int y2, // Y coordinate of the segment's second point const color clr, // Color const uchar opacity=255, // Opacity const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { this.m_canvas.LineWu(x1,y1,x2,y2,::ColorToARGB(clr,opacity),style); } //--- Draws a segment of a freehand line having a specified width using smoothing algorithm with the preliminary filtration void DrawLineThick(const int x1, // X coordinate of the segment's first point const int y1, // Y coordinate of the segment's first point const int x2, // X coordinate of the segment's second point const int y2, // Y coordinate of the segment's second point const int size, // Line width const color clr, // Color const uchar opacity=255, // Opacity const uint style=STYLE_SOLID, // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value ENUM_LINE_END end_style=LINE_END_ROUND) // Line style is one of the ENUM_LINE_END enumeration's values { this.m_canvas.LineThick(x1,y1,x2,y2,::ColorToARGB(clr,opacity),size,style,end_style); } //--- Draw a vertical segment of a freehand line having a specified width using smoothing algorithm with the preliminary filtration void DrawLineThickVertical(const int x, // X coordinate of the segment const int y1, // Y coordinate of the segment's first point const int y2, // Y coordinate of the segment's second point const int size, // Line width const color clr, // Color const uchar opacity=255,// Opacity const uint style=STYLE_SOLID, // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value const ENUM_LINE_END end_style=LINE_END_ROUND) // Line style is one of the ENUM_LINE_END enumeration's values { this.m_canvas.LineThickVertical(x,y1,y2,::ColorToARGB(clr,opacity),size,style,end_style); } //--- Draw a horizontal segment of a freehand line having a specified width using smoothing algorithm with the preliminary filtration void DrawLineThickHorizontal(const int x1, // X coordinate of the segment's first point const int x2, // X coordinate of the segment's second point const int y, // Segment's Y coordinate const int size, // Line width const color clr, // Color const uchar opacity=255,// Opacity const uint style=STYLE_SOLID, // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value const ENUM_LINE_END end_style=LINE_END_ROUND) // Line style is one of the ENUM_LINE_END enumeration's values { this.m_canvas.LineThickHorizontal(x1,x2,y,::ColorToARGB(clr,opacity),size,style,end_style); } //--- Draws a polyline using AntiAliasing algorithm void DrawPolylineAA(int &array_x[], // Array with the X coordinates of polyline points int &array_y[], // Array with the Y coordinates of polyline points const color clr, // Color const uchar opacity=255, // Opacity const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { this.m_canvas.PolylineAA(array_x,array_y,::ColorToARGB(clr,opacity),style); } //--- Draws a polyline using Wu algorithm void DrawPolylineWu(int &array_x[], // Array with the X coordinates of polyline points int &array_y[], // Array with the Y coordinates of polyline points const color clr, // Color const uchar opacity=255, // Opacity const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { this.m_canvas.PolylineWu(array_x,array_y,::ColorToARGB(clr,opacity),style); } //--- Draw a polyline with a specified width consecutively using two antialiasing algorithms. //--- First, individual line segments are smoothed based on Bezier curves. //--- Then, the raster antialiasing algorithm is applied to the polyline built from these segments to improve the rendering quality void DrawPolylineSmooth(const int &array_x[], // Array with the X coordinates of polyline points const int &array_y[], // Array with the Y coordinates of polyline points const int size, // Line width const color clr, // Color const uchar opacity=255, // Opacity const double tension=0.5, // Smoothing parameter value const double step=10, // Approximation step const ENUM_LINE_STYLE style=STYLE_SOLID,// Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value const ENUM_LINE_END end_style=LINE_END_ROUND)// Line style is one of the ENUM_LINE_END enumeration's values { this.m_canvas.PolylineSmooth(array_x,array_y,::ColorToARGB(clr,opacity),size,style,end_style,tension,step); } //--- Draw a polyline having a specified width using smoothing algorithm with the preliminary filtration void DrawPolylineThick(const int &array_x[], // Array with the X coordinates of polyline points const int &array_y[], // Array with the Y coordinates of polyline points const int size, // Line width const color clr, // Color const uchar opacity=255,// Opacity const uint style=STYLE_SOLID, // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value ENUM_LINE_END end_style=LINE_END_ROUND) // Line style is one of the ENUM_LINE_END enumeration's values { this.m_canvas.PolylineThick(array_x,array_y,::ColorToARGB(clr,opacity),size,style,end_style); } //--- Draw a polygon using AntiAliasing algorithm void DrawPolygonAA(int &array_x[], // Array with the X coordinates of polygon points int &array_y[], // Array with the Y coordinates of polygon points const color clr, // Color const uchar opacity=255, // Opacity const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { this.m_canvas.PolygonAA(array_x,array_y,::ColorToARGB(clr,opacity),style); } //--- Draw a polygon using Wu algorithm void DrawPolygonWu(int &array_x[], // Array with the X coordinates of polygon points int &array_y[], // Array with the Y coordinates of polygon points const color clr, // Color const uchar opacity=255, // Opacity const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { this.m_canvas.PolygonWu(array_x,array_y,::ColorToARGB(clr,opacity),style); } //--- Draw a polygon with a specified width consecutively using two smoothing algorithms. //--- First, individual segments are smoothed based on Bezier curves. //--- Then, the raster smoothing algorithm is applied to the polygon built from these segments to improve the rendering quality. void DrawPolygonSmooth(int &array_x[], // Array with the X coordinates of polyline points int &array_y[], // Array with the Y coordinates of polyline points const int size, // Line width const color clr, // Color const uchar opacity=255, // Opacity const double tension=0.5, // Smoothing parameter value const double step=10, // Approximation step const ENUM_LINE_STYLE style=STYLE_SOLID,// Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value const ENUM_LINE_END end_style=LINE_END_ROUND)// Line style is one of the ENUM_LINE_END enumeration's values { this.m_canvas.PolygonSmooth(array_x,array_y,::ColorToARGB(clr,opacity),size,style,end_style,tension,step); } //--- Draw a polygon having a specified width using smoothing algorithm with the preliminary filtration void DrawPolygonThick(const int &array_x[], // array with the X coordinates of polygon points const int &array_y[], // array with the Y coordinates of polygon points const int size, // line width const color clr, // Color const uchar opacity=255, // Opacity const uint style=STYLE_SOLID,// line style ENUM_LINE_END end_style=LINE_END_ROUND) // line ends style { this.m_canvas.PolygonThick(array_x,array_y,::ColorToARGB(clr,opacity),size,style,end_style); } //--- Draw a triangle using AntiAliasing algorithm void DrawTriangleAA(const int x1, // X coordinate of the triangle first vertex const int y1, // Y coordinate of the triangle first vertex const int x2, // X coordinate of the triangle second vertex const int y2, // Y coordinate of the triangle second vertex const int x3, // X coordinate of the triangle third vertex const int y3, // Y coordinate of the triangle third vertex const color clr, // Color const uchar opacity=255, // Opacity const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { this.m_canvas.TriangleAA(x1,y1,x2,y2,x3,y3,::ColorToARGB(clr,opacity),style); } //--- Draw a triangle using Wu algorithm void DrawTriangleWu(const int x1, // X coordinate of the triangle first vertex const int y1, // Y coordinate of the triangle first vertex const int x2, // X coordinate of the triangle second vertex const int y2, // Y coordinate of the triangle second vertex const int x3, // X coordinate of the triangle third vertex const int y3, // Y coordinate of the triangle third vertex const color clr, // Color const uchar opacity=255, // Opacity const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { this.m_canvas.TriangleWu(x1,y1,x2,y2,x3,y3,::ColorToARGB(clr,opacity),style); } //--- Draw a circle using AntiAliasing algorithm void DrawCircleAA(const int x, // X coordinate of the circle center const int y, // Y coordinate of the circle center const double r, // Circle radius const color clr, // Color const uchar opacity=255, // Opacity const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { this.m_canvas.CircleAA(x,y,r,::ColorToARGB(clr,opacity),style); } //--- Draw a circle using Wu algorithm void DrawCircleWu(const int x, // X coordinate of the circle center const int y, // Y coordinate of the circle center const double r, // Circle radius const color clr, // Color const uchar opacity=255, // Opacity const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { this.m_canvas.CircleWu(x,y,r,::ColorToARGB(clr,opacity),style); } //--- Draw an ellipse by two points using AntiAliasing algorithm void DrawEllipseAA(const double x1, // X coordinate of the first point defining the ellipse const double y1, // Y coordinate of the first point defining the ellipse const double x2, // X coordinate of the second point defining the ellipse const double y2, // Y coordinate of the second point defining the ellipse const color clr, // Color const uchar opacity=255, // Opacity const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { this.m_canvas.EllipseAA(x1,y1,x2,y2,::ColorToARGB(clr,opacity),style); } //--- Draw an ellipse by two points using Wu algorithm void DrawEllipseWu(const int x1, // X coordinate of the first point defining the ellipse const int y1, // Y coordinate of the first point defining the ellipse const int x2, // X coordinate of the second point defining the ellipse const int y2, // Y coordinate of the second point defining the ellipse const color clr, // Color const uchar opacity=255, // Opacity const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { this.m_canvas.EllipseWu(x1,y1,x2,y2,::ColorToARGB(clr,opacity),style); } //+------------------------------------------------------------------+
La lógica de los métodos añadidos está muy clara: todos los parámetros transmitidos a los métodos han sido descritos, el propósito de cada método está comentado y, esperamos, los códigos del método no provocan ninguna duda. En cualquier caso, el lector podrá plantear cualquier duda en los comentarios al artículo.
Métodos de trabajo con texto
La clase CCanvas está diseñada de tal forma que recuerda la configuración del último texto mostrado: los parámetros de su fuente, color, transparencia, etcétera. Para averiguar el tamaño del texto, podemos usar el método TextSize(), que utiliza la configuración de la fuente actual para medir la anchura y la altura del rectángulo delimitador del texto. ¿Por qué necesitamos esto? Bueno, aunque sea para sobrescribir un texto previamente dibujado en el lienzo con el color de fondo y dibujar el mismo texto con nuevas coordenadas: el desplazamiento del texto. Pero, en este caso, no solo las coordenadas del texto son importantes, sino también el punto de anclaje del texto (arriba a la izquierda, arriba hacia el centro, arriba a la derecha, etcétera). Necesitamos saber exactamente qué ángulo de anclaje del rectángulo delimitador se le ha dado al texto; de lo contrario, las coordenadas del rectángulo borrador se establecerán incorrectamente. Para conseguirlo, necesitaremos añadir una variable de miembro de clase que contendrá el último punto de anclaje establecido.
Declaramos esta variable en la sección privada de la clase:
long m_long_prop[ORDER_PROP_INTEGER_TOTAL]; // Integer properties double m_double_prop[ORDER_PROP_DOUBLE_TOTAL]; // Real properties string m_string_prop[ORDER_PROP_STRING_TOTAL]; // String properties ENUM_TEXT_ANCHOR m_text_anchor; // Current text alignment //--- Return the index of the array the order's (1) double and (2) string properties are located at int IndexProp(ENUM_CANV_ELEMENT_PROP_DOUBLE property) const { return(int)property-CANV_ELEMENT_PROP_INTEGER_TOTAL; } int IndexProp(ENUM_CANV_ELEMENT_PROP_STRING property) const { return(int)property-CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_DOUBLE_TOTAL; }
Al inicio del constructor paramétrico de la clase, inicializamos los valores del tipo del objeto y los puntos de anclaje del texto:
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int element_id, const int element_num, const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable=true, const bool activity=true, const bool redraw=false) { this.m_name=this.m_name_prefix+name; this.m_chart_id=chart_id; this.m_subwindow=wnd_num; this.m_type=element_type; this.m_text_anchor=0; if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,colour,opacity,redraw)) {
A continuación, añadimos a la variable de la clase padre m_type retornada por su método virtual Type() el tipo de objeto transmitido en los parámetros del constructor, e inicializamos la variable m_text_anchor con el valor por defecto: el ángulo superior izquierdo del rectángulo delimitador.
Al final del cuerpo de la clase, después del bloque de código para trabajar con primitivas, añadimos el bloque de código para trabajar con texto:
//+------------------------------------------------------------------+ //| Methods of working with text | //+------------------------------------------------------------------+ //--- Return text the alignment type (anchor method) ENUM_TEXT_ANCHOR TextAnchor(void) const { return this.m_text_anchor; } //--- Set the current font bool SetFont(const string name, // Font name. For example, "Arial" const int size, // Font size const uint flags=0, // Font creation flags const uint angle=0, // Font slope angle in tenths of a degree const bool relative=true) // Relative font size flag { return this.m_canvas.FontSet(name,(relative ? size*-10 : size),flags,angle); } //--- Set a font name bool SetFontName(const string name) // Font name. For example, "Arial" { return this.m_canvas.FontNameSet(name); } //--- Set a font size bool SetFontSize(const int size, // Font size const bool relative=true) // Relative font size flag { return this.m_canvas.FontSizeSet(relative ? size*-10 : size); } //--- Set font flags //--- FONT_ITALIC - Italic, FONT_UNDERLINE - Underline, FONT_STRIKEOUT - Strikeout bool SetFontFlags(const uint flags) // Font creation flags { return this.m_canvas.FontFlagsSet(flags); } //--- Set a font slope angle bool SetFontAngle(const float angle) // Font slope angle in tenths of a degree { return this.m_canvas.FontAngleSet(uint(angle*10)); } //--- Set the font anchor angle (alignment type) void SetTextAnchor(const uint flags=0) { this.m_text_anchor=(ENUM_TEXT_ANCHOR)flags; } //--- Gets the current font parameters and write them to variables void GetFont(string &name, // The reference to the variable for returning a font name int &size, // Reference to the variable for returning a font size uint &flags, // Reference to the variable for returning font flags uint &angle) // Reference to the variable for returning a font slope angle { this.m_canvas.FontGet(name,size,flags,angle); } //--- Return (1) the font name, (2) size, (3) flags and (4) slope angle string FontName(void) const { return this.m_canvas.FontNameGet(); } int FontSize(void) const { return this.m_canvas.FontSizeGet(); } int FontSizeRelative(void) const { return(this.FontSize()<0 ? -this.FontSize()/10 : this.FontSize()); } uint FontFlags(void) const { return this.m_canvas.FontFlagsGet(); } uint FontAngle(void) const { return this.m_canvas.FontAngleGet(); } //--- Return the text (1) width, (2) height and (3) all sizes (the current font is used to measure the text) int TextWidth(const string text) { return this.m_canvas.TextWidth(text); } int TextHeight(const string text) { return this.m_canvas.TextHeight(text); } void TextSize(const string text, // Text for measurement int &width, // Reference to the variable for returning a text width int &height) // Reference to the variable for returning a text height { this.m_canvas.TextSize(text,width,height); } //--- Display the text in the current font void Text(int x, // X coordinate of the text anchor point int y, // Y coordinate of the text anchor point string text, // Display text const color clr, // Color const uchar opacity=255, // Opacity uint alignment=0) // Text anchoring method { this.m_text_anchor=(ENUM_TEXT_ANCHOR)alignment; this.m_canvas.TextOut(x,y,text,::ColorToARGB(clr,opacity),alignment); } }; //+------------------------------------------------------------------+
Entonces, aquí todo es como en los métodos para trabajar con primitivas: todos los métodos, así como su propósito y sus variables de entrada y salida están comentados.
Ahora, querríamos comentar el método para configurar los parámetros de la fuente actual:
//--- Set the current font bool SetFont(const string name, // Font name. For example, "Arial" const int size, // Font size const uint flags=0, // Font creation flags const uint angle=0, // Font slope angle in tenths of a degree const bool relative=true) // Relative font size flag { return this.m_canvas.FontSet(name,(relative ? size*-10 : size),flags,angle);
Aquí, el parámetro size, que indica el tamaño de fuente, siempre debe establecerse según el tamaño de la fuente que habríamos establecido al mostrar un objeto de etiqueta de texto OBJ_LABEL normal: en él, los tamaños se establecen con valores enteros positivos, mientras que al dibujar el texto en el lienzo, los tamaños de fuente se indican de la misma forma que en la función TextSetFont():
El tamaño de fuente se establece con valores positivos o negativos. El signo determina la dependencia del tamaño del texto respecto a la configuración del sistema operativo (escala de la fuente).
- Si establecemos el tamaño en un número positivo, al mostrar una fuente lógica en una fuente física, el tamaño se convertirá a las unidades físicas del dispositivo (píxeles) y este tamaño se corresponderá con la altura de las celdas del símbolo de las fuentes disponibles. No resulta recomendable en los casos en los que se supone que debemos combinar los textos mostrados por la función TextOut() y los textos mostrados con el objeto gráfico OBJ_LABEL ("Etiqueta de texto").
- Si especificamos el tamaño con un número negativo, se supone que el tamaño indicado se determinará en décimas de un punto lógico (el valor -350 es igual a 35 puntos lógicos) y se dividirá por 10, y luego el valor resultante se convertirá en unidades físicas del dispositivo (píxeles) y se corresponderá con el valor absoluto de la altura del carácter de las fuentes disponibles. Para que el texto en la pantalla tenga el mismo tamaño que en el objeto OBJ_LABEL, tomaremos el tamaño de fuente indicado en las propiedades del objeto y lo multiplicaremos por -10.
El método verifica la bandera respecto al tamaño de fuente y, si ha sido establecida (por defecto), el tamaño especificado en el parámetro size se multiplicará por -10 para que la fuente se indique con el valor correcto al transmitirse al método FontSet() de la clase CCanvas.
En el constructor paramétrico de la clase, añadimos la inicialización de la fuente (establecemos su nombre y tamaño por defecto):
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int element_id, const int element_num, const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable=true, const bool activity=true, const bool redraw=false) { this.m_name=this.m_name_prefix+name; this.m_chart_id=chart_id; this.m_subwindow=wnd_num; this.m_type=element_type; this.SetFont("Calibri",8); this.m_text_anchor=0; if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,colour,opacity,redraw)) {
Estas son todas las mejoras necesarias por hoy. Vamos a poner a prueba lo que hemos obtenido.
Simulación
¿Qué vamos a probar y cómo vamos a hacerlo?
Tenemos el asesor del artículo anterior, que muestra en el gráfico dos objetos de elemento gráfico. Tomamos este asesor, lo guardamos en la nueva carpeta \MQL5\Experts\TestDoEasy\Part75\ con el nuevo nombre TestDoEasyPart75.mq5 y hacemos lo siguiente:
Al clicar en el primer objeto (superior), dibujaremos alternativamente un rectángulo y un círculo sobre él. Con cada nuevo clic en el objeto, el tamaño del rectángulo disminuirá en 2 píxeles a cada lado y el radio del círculo también disminuirá en 2 píxeles. A continuación, dibujamos un rectángulo de la forma habitual y dibujamos un círculo usando el suavizado. Además, con cada clic, la opacidad del objeto aumentará en un círculo de 0 a 255.
Al clicar en el segundo objeto (inferior), mostraremos el texto sobre él, cambiando alternativamente su punto de anclaje, mientras que escribiremos el nombre del punto de anclaje en el propio texto. No cambiaremos la transparencia del objeto.
Indicamos el número de elementos creados:
//+------------------------------------------------------------------+ //| TestDoEasyPart75.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 <Arrays\ArrayObj.mqh> #include <DoEasy\Services\Select.mqh> #include <DoEasy\Objects\Graph\GCnvElement.mqh> //--- defines #define ELEMENTS_TOTAL (2) // Number of created graphical elements //--- input parameters sinput bool InpMovable = true; // Movable flag //--- global variables CArrayObj list_elements; //+------------------------------------------------------------------+
Para que al cambiar de marco temporal no se creen cada vez objetos iguales e innecesarios, en el manejador OnInit(), limpiaremos la lista de objetos ya creados antes de crear nuevos objetos:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set the permissions to send cursor movement and mouse scroll events ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true); ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true); //--- Set EA global variables //--- Create the specified number of graphical elements on the canvas list_elements.Clear(); int total=ELEMENTS_TOTAL; for(int i=0;i<total;i++) { //--- When creating an object, pass all the required parameters to it CGCnvElement *element=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,i,0,ChartID(),0,"Element_0"+(string)(i+1),300,40+(i*80),100,70,clrSilver,200,InpMovable,true,true); if(element==NULL) continue; //--- Add objects to the list if(!list_elements.Add(element)) { delete element; continue; } } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Como aún no tenemos una clase de colección de objetos gráficos en la que verificar la necesidad de crear un nuevo objeto con el nombre indicado, aquí simplemente crearemos estos objetos de nuevo, borrando de antemano la lista de objetos creados previamente.
En el manejador OnChartEvent(), añadimos el procesamiento de los clics del ratón en los dos objetos creados:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- If clicking on an object if(id==CHARTEVENT_OBJECT_CLICK) { //--- In the new list, get the element object with the name corresponding to the sparam string parameter value of the OnChartEvent() handler CArrayObj *obj_list=CSelect::ByGraphCanvElementProperty(GetPointer(list_elements),CANV_ELEMENT_PROP_NAME_OBJ,sparam,EQUAL); //--- If the object is received from the list if(obj_list!=NULL && obj_list.Total()>0) { static uchar try0=0, try1=0; //--- Get the pointer to the object in the list CGCnvElement *obj=obj_list.At(0); //--- If this is the first graphical element if(obj.ID()==0) { //--- Set a new opacity level for the object uchar opasity=obj.Opacity(); if((opasity+5)>255) opasity=0; else opasity+=5; //--- Set a new opacity to the object obj.SetOpacity(opasity); //--- Set rectangle and circle coordinates int x1=2,x2=obj.Width()-3; int y1=2,y2=obj.Height()-3; int xC=(x1+x2)/2; int yC=(y1+y2)/2; int R=yC-y1; //--- Draw a rectangle at each first click if(try0%2==0) obj.DrawRectangle(x1+try0,y1+try0,x2-try0,y2-try0,clrDodgerBlue,obj.Opacity()); //--- Display the circle smoothed using AntiAliasing at each second click else obj.DrawCircleAA(xC,yC,R-try0,clrGreen,obj.Opacity()); //--- If the number of clicks on the object exceeds 30 if(try0>30) { //--- Clear the object setting its current color and transparency obj.Erase(obj.ColorBG(),obj.Opacity()); //--- Re-start the click number countdown try0=0; } //--- Update the chart and the object, and display the comment featuring color values and object opacity obj.Update(true); // 'true' is not needed here since the next Comment command redraws the chart anyway Comment("Object name: ",obj.NameObj(),", opasity=",obj.Opacity(),", Color BG: ",(string)obj.ColorBG()); //--- Increase the counter of mouse clicks by object try0++; } //--- If this is the second object else if(obj.ID()==1) { //--- Set the font parameters for it ("Calibri" size 8) obj.SetFont("Calibri",8); //--- Set the text anchor angle corresponding to the click counter by object obj.SetTextAnchor((ENUM_TEXT_ANCHOR)try1); //--- Create the text out of the anchor angle name string text=StringSubstr(EnumToString(obj.TextAnchor()),12); //--- Set the text coordinates relative to the upper left corner of the graphical element int xT=2,yT=2; //--- Depending on the anchor angle, set the new coordinates of the displayed text //--- LEFT_TOP if(try1==0) { xT=2; yT=2; } //--- CENTER_TOP else if(try1==1) { xT=obj.Width()/2; yT=2; } //--- RIGHT_TOP //--- since the ENUM_TEXT_ANCHOR enumeration features no 3, increase the counter of object clicks by 1 else if(try1==2) { xT=obj.Width()-2; yT=2; try1++; } //--- LEFT_CENTER else if(try1==4) { xT=2; yT=obj.Height()/2; } //--- CENTER else if(try1==5) { xT=obj.Width()/2; yT=obj.Height()/2; } //--- RIGHT_CENTER //--- since the ENUM_TEXT_ANCHOR enumeration features no 7, increase the counter of object clicks by 1 else if(try1==6) { xT=obj.Width()-2; yT=obj.Height()/2; try1++; } //--- LEFT_BOTTOM else if(try1==8) { xT=2; yT=obj.Height()-2; } //--- CENTER_BOTTOM else if(try1==9) { xT=obj.Width()/2; yT=obj.Height()-2; } //--- RIGHT_BOTTOM else if(try1==10) { xT=obj.Width()-2; yT=obj.Height()-2; } //--- Clear the graphical element filling it with the current color and transparency obj.Erase(obj.ColorBG(),obj.Opacity()); //--- Display the text with the calculated coordinates in the cleared element obj.Text(xT,yT,text,clrDodgerBlue,255,obj.TextAnchor()); //--- Update the object and chart obj.Update(true); // 'true' is not needed here since the next Comment command redraws the chart anyway Comment("Object name: ",obj.NameObj(),", opasity=",obj.Opacity(),", Color BG: ",(string)obj.ColorBG()); //--- Increase the counter of object clicks try1++; if(try1>10) try1=0; } } } } //+------------------------------------------------------------------+
El código del manejador se comenta con detalle. Esperamos que su lógica resulte clara. En cualquier caso, siempre podrá escribir cualquier duda referente al material en los comentarios al artículo.
Compilamos el asesor y lo ejecutamos en el gráfico. Clicamos en los objetos con el ratón:
Hmm... Como resultado, hemos obtenido accidentalmente en el objeto superior una imagen divertida semejante a un CD :)
¿Qué es lo próximo?
En el próximo artículo, comenzaremos a desarrollar los objetos herederos del objeto de elemento gráfico que hemos creado hoy.
Más abajo se adjuntan todos los archivos de la versión actual de la biblioteca y el archivo del asesor de prueba para MQL5. Puede descargarlo todo y ponerlo a prueba por sí mismo.
Si tiene preguntas, observaciones o sugerencias, podrá concretarlas en los comentarios al artículo.
*Artículos de esta serie:
Gráficos en la biblioteca DoEasy (Parte 73): Objeto de formulario del elemento gráfico
Gráficos en la biblioteca DoEasy (Parte 74): Elemento gráfico básico sobre la clase CCanvas
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/9515
- 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