
DoEasy 函数库中的图形(第七十五部分):处理基本图形元素图元和文本的方法
内容
概述
我继续开发基本图形元素对象类,用作创建更复杂函数库图形对象的基础。 在上一篇文章中,我提出了构建基准图形对象的概念,创建了图形元素,并已赋予它设置、修改和接收的基本属性。
由于 CCanvas 类意为在“在画布上”绘图,因此它提供了处理图形基元和文本的方法。 在本文中,我将创建元素对象方法,允许我们访问和处理绘图的 CCanvas 类方法。 这些方法很简单。 它们将用于在元素对象类的衍生后代对象中创建高级绘图方法。
除了创建操控基元的方法之外,我还将创建操控文件的方法。 我们的图形对象是自定义程序的 GUI 元素,理应“记住”它们在图表上的属性、状态和位置,例如在切换到另一个时间帧时。 为达此目的,我将对象属性保存到文件中。 而在构造对象时,会从文件中读取属性。 不过,由于文件应在图形对象集合类中处理,而我尚未开发它,我将简单地添加保存和上传图形对象属性的方法。 而在创建图形对象集合类时,我们将会用到此处为图形元素对象开发的保存和上传属性的方法。
此外,我将需要操控颜色的类。 我打算把它添加到函数库之中。
该类将取自 Dmitry Fedoseev 开发并提交至 MQL5.com 代码库社区的代码。
最终结果是图形元素会准备好,可作为将来利用函数库图形对象的基础。
改进库类
如果我们需要清除一个带有透明度的 CCanvas 类对象,我们应调用 Erase() 方法,默认接收零:
//--- clear/fill color void Erase(const uint clr=0);
在我们的例子中,这是一个不正确的解决方案。 在用零值清除画布时,我们看不到它的 alpha 通道(颜色透明度通道),这会最终导致以这种方式清除 alpha 通道的情况下,在画布上绘图时会出现残影。
为了清除画布的 alpha 通道,需用 0x00FFFFFF 替代零值。
这是 ARGB 格式的完全透明的黑色(Alpha = 0,Red = 255,Green = 255,Blue = 255)。
在 \MQL5\Include\DoEasy\Defines.mqh 里,添加指定此类颜色的宏替换:
//--- Canvas parameters #define PAUSE_FOR_CANV_UPDATE (16) // Canvas update frequency #define NULL_COLOR (0x00FFFFFF) // Zero for the canvas with the alpha channel //+------------------------------------------------------------------+
当利用 TextOut() 方法在画布上显示文本时,我们可以设置文本消息的锚点角度(文本对齐点)— 矩形边界,相对于消息的定位。 需要用六个标志来设置锚点 — 两两标志的组合,所有这些都如下所列:
水平文本对齐标志:
- TA_LEFT — 矩形边界左侧的锚点
- TA_CENTER — 矩形边界中间的水平锚点
- TA_RIGHT — 矩形边界右侧的锚点
垂直文本对齐标志:
- TA_TOP — 矩形边界顶部的锚点
- TA_VCENTER — 矩形边界中间的锚点
- TA_BOTTOM — 矩形边界底部的锚点
这些标志的可能组合,和设置锚点的方法如下所示:
为了避免混淆哪个标志先出现,只需设置一个自定义枚举,指定所有可能的标志组合,以及相对于其锚点的文本对齐:
//+------------------------------------------------------------------+ //| 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 | //+------------------------------------------------------------------+
在此我们为每个文本锚点级别设置了三个标志:
- 顶部垂直锚点 (TA_TOP) — 0:
- 左侧水平锚点 (TA_LEFT) — 0,
- 中间水平锚点 (TA_CENTER) — 1,
- 右侧水平锚点 (TA_RIGHT) — 2.
- 左侧水平锚点 (TA_LEFT) — 0,
- 中间垂直锚点 (TA_VCENTER) — 4:
- 左侧水平锚点 (TA_LEFT) — 0,
- 中间水平锚点 (TA_CENTER) — 1,
- 右侧水平锚点 (TA_RIGHT) — 2.
- 左侧水平锚点 (TA_LEFT) — 0,
- 底部垂直锚点 (TA_BOTTOM) — 8:
- 左侧水平锚点 (TA_LEFT) — 0,
- 中间水平锚点 (TA_CENTER) — 1,
- 右侧水平锚点 (TA_RIGHT) — 2.
- 左侧水平锚点 (TA_LEFT) — 0,
每个 ENUM_TEXT_ANCHOR 枚举值对应于上述设置标志的正确组合:
- 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.
我在将来会用此枚举来指定相对于其锚点的文本对齐方式。
由于我目前正在开发函数库的图形部分,因此我需要各种操控颜色的方法。
MQL5.com 源代码库提供了卓越的操控颜色的函数库,由 Dmitry Fedoseev 友情提供,供一般使用。
我们稍微改进 CColors 类 — 令其成为静态,从而可以不必设置类对象,而是直接用上下文解析运算符 (::) 访问其方法,例如:
class_name::variable
因此,函数库中包含的 CColors 类允许我们在代码中的任何位置(包括自定义程序)访问类方法,例如两种颜色混合 — 不透明度为 128 的蓝色和不透明度为 64 的红色:
CColors::BlendColors(ColorToARGB(clrBlue,128),ColorToARGB(clrRed,64));
将类文件保存在函数库目录 \MQL5\Include\DoEasy\Services\,文件是 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); } //+------------------------------------------------------------------+
所有进行的修改都归结到为每个方法设置'static' 修饰符,以及一些符合我的编码风格的纯粹外观更改(除了方法参数中的变量名称)。 此外,我还添加了 RGBtoLab() 方法,可将 RGB 颜色模型转换为 Lab。 该方法只是将 RGB 模型转换为 XYZ,然后将其转换为 Lab 颜色模型。 这个问题曾在 Anatoli Kazharski 的文章 "Graphical Interfaces IX: The Color Picker Control (Chapter 1)" 中进行过研究:
我简单地遵守作者的建议。
若要令 CColors 类在整个函数库和基于它的程序里可见,需将类文件包含到 \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 | //+------------------------------------------------------------------+
我在这里不需要这个类,但稍后我将在创建图形元素对象衍生后代类时开始用它。
至少为每个图形对象提供了尺寸和在图表上的位置坐标。 此外,我们的对象还拥有许多可在程序运行时更改的属性。 但如果我们重新启动程序或切换时间帧,程序运行时对图形对象所做的所有修改都会被重置。 为了让每个对象记住其属性的状态,我们需要将它们保存在外部。 在这种情况下,重新启动程序后,所有在其操作期间构造和更改的图形对象都会从文件中读取它们的相应属性(与重置时刻相关),并恢复它们。 为了达此目的,我们需要在图形元素对象类里添加两个方法 — 将对象属性写入文件的方法,和从文件读取对象属性的方法。
为了读写对象属性,我将对象属性保存到结构中,而利用 StructToCharArray() 和 CharArrayToStruct() 标准函数结构既可以保存到文件中,也可以从文件读取。
每个图形对象都提供了将属性保存到文件和从文件中读取它们的方法,因为基于画布的每个图形对象都是图形元素对象的后代,而在其中已经设置了方法。 因此,如果对象是复合的(即它由基于图形元素的其他对象组成),我们可以根据从属对象列表中的对象索引(索引存储在元素对象属性的 ENUM_CANV_ELEMENT_PROP_INTEGER 枚举的 CANV_ELEMENT_PROP_NUM 常量中),逐一恢复它的所有状态。
在本文中,我不会将属性保存到文件/从文件中读取它们,因为这应在图形对象集合类中完成。 我稍后会考虑 — 在创建图形元素之后。 无论如何,写和读的方法都会加在这里。
由于图形元素是所有 CGBaseObj 函数库图形对象的基准对象的衍生后代,首先我们在对象类文件 (\MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh) 里设置从对象属性中创建结构的虚拟方法,和从受保护部分中的结构恢复对象属性的虚拟方法:
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:
这些方法在这里什么都不做 — 它们应在类的衍生后代中重新定义。 该类的最接近的衍生后代是 \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh 中的图形元素对象类。 在其受保护部分声明相同的虚拟方法:
//+------------------------------------------------------------------+ //| 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 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:
在私密部分,声明存储所有对象属性的结构、含结构类型的对象和对象结构数组:
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
在类的公开部分,声明从文件中写入 和读取对象属性的方法:
public: //--- Set object (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 (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
由于对象没有实数型属性,故返回对象支持实数型属性标志的虚方法应返回 false。
在类主体之外实现所声明的方法。
在类主体之外实现所声明的方法。
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
此处的一切都很简单:每个整数型结构字段接收相应的对象属性,而对象字符串型属性保存在结构的相应 uchar 数组之中。 接下来,简单地调用 StructToCharArray() 将新创建的对象属性结构保存到 uchar 数组。 如果将结构保存到数组失败,则通知错误,并返回 false。 结果就是,返回 true。
从结构中恢复对象属性的方法:
//+------------------------------------------------------------------+ //| 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 } //+------------------------------------------------------------------+
此处,每个整数型对象属性都接收来自相应结构字段的值,同时调用 CharArrayToString() 将相应 uchar 数组结构的内容读取到对象字符串型属性之中。
该方法将对象保存到文件:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
该方法接收保存对象属性的文件句柄。 然后使用上面研讨过的 ObjectToStruct() 方法将对象属性保存在结构之中。 调用 FileWriteArray() 把构造结构时创建的 uchar 数组写入文件,并返回 true。 若失败,该方法会在日志中显示错误消息,并返回 false。
该方法从文件加载对象属性:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
该方法接收保存对象属性的文件句柄。 接下来,调用 FileReadArray() 将文件中的对象属性加载至 uchar 数组。 调用 CharArrayToStruct() 将加载的属性复制到结构之中。 调用上述 StructToObject() 方法把来自文件中的数据填充到设置对象属性的结构,并返回 true。 如果从文件读取,或将得到的数组复制到结构时以错误结束,则该方法会通知,并返回 false。
简化的访问对象属性的方法模块,接收方法返回的右侧和底部元素边缘的方法,设置和返回元素背景颜色的方法,返回元素 ID 的方法,以及返回其在复合对象元素列表中的索引的方法:
//+------------------------------------------------------------------+ //| The 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); } //+------------------------------------------------------------------+
所有这些方法只返回相应的元素对象属性。
操控基元的方法
CCanvas 类为在画布上绘制各种图形基元提供了充分的机会。 我们既能够读取每个像素的颜色,也可为其设置所需的颜色和透明度。 除了简单地为像素设置颜色之外,该类还提供了逐个像素(无需平滑)或借助各种平滑方法绘制不同图形的工具。
图形元素对象类可为用户提供访问 CCanvas 类的绘制方法。 我们的方法只会稍微简化 CCanvas 类方法的调用。 简化实际上在于颜色以通常的方式设置 — 通过在颜色格式中指定必要的颜色,并设置颜色不透明度(0 — 透明,255 — 完全不透明),而 CCanvas 类方法“要求”立即以 uint ARGB 格式指定颜色,这只是一个数字。 并非每个人都喜欢以这种格式指定所需的颜色(半透明灰色:0x7F7F7F7F)。 后续自图形元素对象继承的类中,我会在每个所创建的类中添加更便利的固有功能来扩展绘图功能的范围。 在同一个类中,这是创建其余图形对象的基础,故绘制方法应该简单明了。
简化访问对象属性的方法模块随后是新的代码模块。 我尝试根据它们的目的分派它们。 数据接收方法以 “Get” 开头,而数据设置方法以 “Set” 开头。
该方法接收指定坐标点的颜色:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
此处返回调用 CCanvas 类的 PixelGet() 方法的结果。 该方法返回 ARGB 格式颜色。
该方法填充、清除和更新栅格数据:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
Update() 方法调用 CCanvas 类的 Update() 方法来简单地更新对象和图表。
Erase() 方法则在类主体之外实现:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
这是两个重载的方法。
在第一个里,我们传递所需的颜色和不透明度,并在 CCanvas 类的 Erase() 方法的帮助下填充整个元素。 请注意,该方法调用 ColorToARGB() 函数将该值转换为 ARGB 格式,并把所应用的颜色和不透明度级别传递给 CCanvas 类的 Erase() 方法。 我会在所有绘图方法中做到这一点。 在第二个方法中,我们简单地用完全透明的黑色填充整个背景。 其值以前是用 NULL_COLOR 宏替换定义的。
每个方法都会收到一个标志,指示需要重新绘制图表。 如果设置了标志,则重新绘制图表。
接下来,在无需平滑的情况下绘制图元的方法模块。 所有方法都雷同,并调用 CCanvas 类的相应方法。 这些方法接收指定的参数,并把传递给方法的颜色和透明度值转换为 ARGB 格式:
//+------------------------------------------------------------------+ //| The 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 first point const int y2, // Y coordinate of the segment 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 first point const int x2, // X coordinate of the segment second point const int y, // Segment 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 first point const int y1, // Y coordinate of the segment first point const int x2, // X coordinate of the segment second point const int y2, // Y coordinate of the segment 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)); } //+------------------------------------------------------------------+
该方法模块绘制无需平滑的填充图元::
//+------------------------------------------------------------------+ //| The 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)); } //+------------------------------------------------------------------+
该方法采用平滑绘制图元:
//+------------------------------------------------------------------+ //| The 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 first point const int y1, // Y coordinate of the segment first point const int x2, // X coordinate of the segment second point const int y2, // Y coordinate of the segment 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 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 first point const int y1, // Y coordinate of the segment first point const int x2, // X coordinate of the segment second point const int y2, // Y coordinate of the segment 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 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 first point const int y1, // Y coordinate of the segment first point const int x2, // X coordinate of the segment second point const int y2, // Y coordinate of the segment 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 values or a custom value ENUM_LINE_END end_style=LINE_END_ROUND) // Line style is one of the ENUM_LINE_END enumeration 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 first point const int y2, // Y coordinate of the segment 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 values or a custom value const ENUM_LINE_END end_style=LINE_END_ROUND) // Line style is one of the ENUM_LINE_END enumeration 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 first point const int x2, // X coordinate of the segment second point const int y, // Segment 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 values or a custom value const ENUM_LINE_END end_style=LINE_END_ROUND) // Line style is one of the ENUM_LINE_END enumeration 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 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 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 values or a custom value const ENUM_LINE_END end_style=LINE_END_ROUND)// Line style is one of the ENUM_LINE_END enumeration 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 values or a custom value ENUM_LINE_END end_style=LINE_END_ROUND) // Line style is one of the ENUM_LINE_END enumeration 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 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 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 values or a custom value const ENUM_LINE_END end_style=LINE_END_ROUND)// Line style is one of the ENUM_LINE_END enumeration 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 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 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 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 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 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 values or a custom value { this.m_canvas.EllipseWu(x1,y1,x2,y2,::ColorToARGB(clr,opacity),style); } //+------------------------------------------------------------------+
所有添加方法的逻辑都是完全透明的。 所有传递给方法的参数都已签名。 每种方法的目的都已在注释中说明。 所以,我相信这里一切都很清晰明了。 无论如何,欢迎您参与评论部分。
操控文本的方法
CCanvas 类会记住上次显示文本的设置,包括其字体、颜色、透明度、等等。 为了查找文本大小,我们可以调用 TextSize() 方法应用当前字体设置来测量文本边界矩形的宽度和高度。 我们可能需要在多种情况下这样做,例如使用背景颜色覆盖之前的文本,并用新坐标重写 — 文本偏移。 在这种情况下,我们不仅需要文本坐标,还需要文本锚点(左上、中上、右上、等等)。 我们需要确切给出文本边界矩形的锚点角度,否则设置的擦除矩形坐标将不正确。 为达此目的,我们需要添加类成员变量存储最后指定锚点。
在类的私密部分声明变量:
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 (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; }
在参数型类构造函数的最开始,初始化对象类型和文本锚点:
//+------------------------------------------------------------------+ //| 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)) {
在由 Type() 虚方法返回的 m_type 父类变量里,添加构造函数参数中传入的对象类型,而 m_text_anchor 变量则采用默认值进行初始化 — 边界矩形的左上角。
在类主体的最后(即,在操控基元的代码块之后),添加操控文本的代码块:
//+------------------------------------------------------------------+ //| The 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); } }; //+------------------------------------------------------------------+
此处的一切都类似于处理基元。 所有方法都有注释,公告了其目的、输入和输出。
我想说明一下设置当前字体参数的方法:
//--- 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);
此处的 size 指定字体大小,并且总是根据我们要在普通 OBJ_LABEL 文本标签对象的帮助下显示文本时的字体大小来设置。 大小设置为正整数值。 与此对比,在画布上绘制文本时,字体大小的设置方式与 TextSetFont() 函数中的设置方式相同:
字体大小可用正值或负值进行设置。 符号定义文本大小是否取决于操作系统设置(字体比例)。
在方法中检查字体相对大小标志。 如果已设置(默认情况下),则将大小值乘以 -10,以便用正确的数值指定字体,并将其传递给 CCanvas 类的 FontSet() 方法。
在参数型类构造函数中,加入字体初始化(设置其默认名称和大小):
//+------------------------------------------------------------------+ //| 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)) {
这些就是我为本文计划的所有改进。 我们测试一下结果。
测试
我们有一个来自前一篇文章中的 EA,在图表上显示两个图形元素对象。 我们借用同一 EA,将其保存在新文件夹 \MQL5\Experts\TestDoEasy\Part75\ 中,命名为 TestDoEasyPart75.mq5,并执行以下操作:
单击第一个(顶部)对象时,我们将在其上交替绘制一个矩形和一个圆。 每次单击对象时,矩形的大小在每条边侧均减少 2 个像素,圆的半径也减少 2 个像素。 矩形以通常的方式绘制,而圆则运用平滑绘制。 每单击一次,对象的不透明度就会从 0 增加到 255 循环。
单击第二个(底部)对象时,在其锚点上显示交替更化的文本。 在文本中写入锚点名称。 对象透明度保持不变。
指定所创建元素的数量:
//+------------------------------------------------------------------+ //| 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; //+------------------------------------------------------------------+
为避免每次时间帧变更时都创建相同的对象,创建新对象之前清除在 OnInit() 处理程序中已创建的对象列表:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
由于我还没有图形对象集合类来检查是否需要创建具有指定名称的新对象,此处我将简单地重新创建这些对象,故需预先清除先前创建的对象列表。
OnChartEvent() 处理程序接收在两个已创建对象上的鼠标点击:
//+------------------------------------------------------------------+ //| 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; } } } } //+------------------------------------------------------------------+
处理程序代码具有详细的注释。 我相信,其逻辑是清晰的。 如果您对本文有任何疑问,请随时在下面的评论中提问。
编译 EA,并在图表上启动它。 单击对象:
结果就是,我不小心在上面的对象上得到了一个类似于 CD 的有趣图像 :)
下一步是什么?
在下一篇文章中,我将着手开发此处所创建图形元素对象的衍生后代对象。
以下是该函数库当前版本的所有文件,以及 MQL5 的测试 EA 文件,供您测试和下载。
请您在评论中留下问题和建议。
*该系列的前几篇文章:
DoEasy 函数库中的图形(第七十三部分):图形元素的交互窗对象
DoEasy 函数库中的图形(第七十四部分):由 CCanvas 类提供强力支持的基本图形元素
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/9515


