DoEasy. 控件 (第 3 部分): 创建绑定控件·进阶篇
(2/3)· 手动维护浮动面板的子控件坐标,是 MQL5 GUI 开发里最隐蔽的时间黑洞
「图形控件基类的指针与画布接口」
在 MT5 自定义图形库里,CGCnvElement 作为所有画布控件的父类,先要解决「谁是我的上级」和「去哪拿画布」这两个问题。下面这段声明给出了最基础的存取接口。 SetBase() 把传入的 CGCnvElement 指针存进 m_element_base,等于给控件挂上父节点;GetBase() 原样返回这个指针,方便在嵌套控件里向上回溯。GetCanvasObj() 直接返回内部 m_canvas 的地址,意味着子类绘图不用自己再建画布,复用同一块 CCanvas 能省掉重复申请资源。 DuplicateResArraySize() 调用 ArraySize 读 m_duplicate_res 数组长度,这个数组存的是被复制过的图形资源,返回 0 表示当前还没缓存任何副本。Move() 被标成 virtual,说明子类可以重写坐标偏移逻辑,x、y 是相对位移,redraw 默认 false 不立即重绘。 构造函数放在 protected 区,外部不能直接 new 这个基类,只能从其派生类实例化,这是典型的接口类写法。
class="kw">const class="type">bool redraw=class="kw">false); class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the pointer to the parent control class="type">void SetBase(CGCnvElement *element) { this.m_element_base=element; } CGCnvElement *GetBase(class="type">void) { class="kw">return this.m_element_base; } class=class="str">"cmt">//--- Return the pointer to a canvas object CCanvas *GetCanvasObj(class="type">void) { class="kw">return &this.m_canvas; } class=class="str">"cmt">//--- Return the size of the graphical resource copy array class="type">uint DuplicateResArraySize(class="type">void) { class="kw">return ::ArraySize(this.m_duplicate_res); } class=class="str">"cmt">//--- Update the coordinates(shift the canvas) class="kw">virtual class="type">bool Move(class="kw">const class="type">int x,class="kw">const class="type">int y,class="kw">const class="type">bool redraw=class="kw">false); class=class="str">"cmt">//--- Save an image to the array class="type">bool ImageCopy(class="kw">const class="type">class="kw">string source,class="type">uint &array[]); class="kw">protected: class=class="str">"cmt">//--- Protected constructor CGCnvElement(class="kw">const ENUM_GRAPH_ELEMENT_TYPE element_type, class="kw">const class="type">long chart_id, class="kw">const class="type">int wnd_num, class="kw">const class="type">class="kw">string name, class="kw">const class="type">int x, class="kw">const class="type">int y, class="kw">const class="type">int w,
画布元素的相对坐标与基对象绑定
在 MT5 自定义图形库里,每个画布元素都可以挂在一个基对象上,再用 X/Y 偏移量定位。CGCnvElement 类用两个私有成员 m_shift_coord_x 和 m_shift_coord_y 记录相对位移,默认构造时都初始化为 0,意味着不偏移、紧贴基对象左上角。 SetCoordXRelative / SetCoordYRelative 负责写偏移,CoordXRelative / CoordYRelative 负责读偏移。如果你在 EURUSD 的 H1 图上把基对象放在现价位,给 m_shift_coord_y 设 30,子元素就会在基对象下方 30 像素处绘制,具体视觉距离随图表缩放比例变化。 构造函数里还顺手抓了背景色:m_chart_color_bg 通过 ChartGetInteger(ChartID(), CHART_COLOR_BACKGROUND) 拿到当前图表背景,元素销毁时 m_canvas.Destroy() 释放资源。这套机制让你做复合指标时,不用每次重算绝对坐标,改一个基对象位置,整组图形跟着挪。 外汇与贵金属图表叠加自绘元素波动剧烈,高杠杆下误读坐标偏移可能造成错判,实盘前请在策略测试器里先验证偏移量表现。
class="kw">const class="type">int h); class="kw">public: class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the X coordinate shift relative to the base object class="type">void SetCoordXRelative(class="kw">const class="type">int value) { this.m_shift_coord_x=value; } class="type">int CoordXRelative(class="type">void) class="kw">const { class="kw">return this.m_shift_coord_x; } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the Y coordinate shift relative to the base object class="type">void SetCoordYRelative(class="kw">const class="type">int value) { this.m_shift_coord_y=value; } class="type">int CoordYRelative(class="type">void) class="kw">const { class="kw">return this.m_shift_coord_y; } class=class="str">"cmt">//--- Event handler class=class="str">"cmt">//--- Default constructor/Destructor CGCnvElement() : m_shadow(class="kw">false),m_chart_color_bg((class="type">class="kw">color)::ChartGetInteger(::ChartID(),CHART_COLOR_BACKGROUND)) { this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_base=NULL; this.m_shift_coord_x=class="num">0; this.m_shift_coord_y=class="num">0; } ~CGCnvElement() { this.m_canvas.Destroy(); } class=class="str">"cmt">//--- Return(class="num">1) the background class="type">class="kw">color, (class="num">2) the opacity, coordinate(class="num">3) of the right and(class="num">4) bottom element edge class="type">class="kw">color ColorBackground(class="type">void) class="kw">const { class="kw">return this.m_color_bg; }
◍ 画布元素的边界坐标与置顶控制
在 MT5 自定义图形库里,每个画布元素都得先算清自己占哪儿。右边缘等于元素 X 坐标加画布宽度,底边缘等于 Y 坐标加画布高度;相对坐标版本同理,只是换用 CoordXRelative / CoordYRelative,方便做子图嵌套布局。 Opacity() 直接返回 m_opacity 成员,透明度本身不参与边界运算,但会影响重叠时的视觉层次。RightEdge 与 BottomEdge 返回的是绝对像素,RightEdgeRelative 与 BottomEdgeRelative 返回相对父级坐标,两者差一个基准偏移。 BringToTop 的写法很取巧:先 SetVisible(false,false) 隐藏再 SetVisible(true,false) 显示,利用对象树的重绘顺序把元素顶到最上层。Show / Hide 只是单层显隐,不会改动层级。 外汇与贵金属图表叠加自定义元素时波动剧烈,建议先在 EURUSD 的 M1 周期跑一遍边界函数,确认 RightEdge 不超过图表可视区宽度(通常 0~ChartWidth 像素),否则元素会被裁切。
class="type">uchar Opacity(class="type">void) class="kw">const { class="kw">return this.m_opacity; } class="type">int RightEdge(class="type">void) class="kw">const { class="kw">return this.CoordX()+this.m_canvas.Width(); } class="type">int BottomEdge(class="type">void) class="kw">const { class="kw">return this.CoordY()+this.m_canvas.Height(); } class=class="str">"cmt">//--- Return the relative coordinate of the(class="num">1) right and(class="num">2) bottom element edge class="type">int RightEdgeRelative(class="type">void) class="kw">const { class="kw">return this.CoordXRelative()+this.m_canvas.Width(); } class="type">int BottomEdgeRelative(class="type">void) class="kw">const { class="kw">return this.CoordYRelative()+this.m_canvas.Height(); } class=class="str">"cmt">//--- Return the(class="num">1) X, (class="num">2) Y coordinates, (class="num">3) element width and(class="num">4) height, class=class="str">"cmt">//--- Set the object above all class="kw">virtual class="type">void BringToTop(class="type">void) { CGBaseObj::SetVisible(class="kw">false,class="kw">false); CGBaseObj::SetVisible(true,class="kw">false);} class=class="str">"cmt">//--- (class="num">1) Show and(class="num">2) hide the element class="kw">virtual class="type">void Show(class="type">void) { CGBaseObj::SetVisible(true,class="kw">false); } class="kw">virtual class="type">void Hide(class="type">void) { CGBaseObj::SetVisible(class="kw">false,class="kw">false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Parametric constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(class="kw">const ENUM_GRAPH_ELEMENT_TYPE element_type,
「图形基类的构造参数与坐标初始化」
在 MT5 自定义图形库里,图形元素基类靠一长串参数把位置、尺寸、颜色一次性锁死。上面这段构造函数签名里,element_id 与 element_num 用来区分同类型多个实例,chart_id 和 wnd_num 决定挂到哪张图、哪个子窗口,x/y/w/h 则是以像素为单位的左上角坐标和宽高。 构造函数体内先把 m_type 设为 OBJECT_DE_TYPE_GELEMENT,再把 m_element_base 置空,随后用 ChartGetInteger 取背景色存进 m_chart_color_bg——如果传进来的 chart_id 是 NULL 就回退到当前图 ChartID()。名字处理上有个细节:若传入 name 里没找到前缀 m_name_prefix,就自动拼上,避免和其他对象重名。 坐标偏移量 m_shift_coord_x 和 m_shift_coord_y 在初始化时都清零,这意味着对象刚建出来时不会带任何平移。最后调用 this.Create(...) 真正落盘到图表,redraw 默认 false 表示不立即重绘,由外部逻辑控制刷新节奏。 外汇与贵金属图表上叠加这类自绘元素,属于高风险环境下的视觉辅助,参数填错可能导致对象不可见或遮挡 K 线,建议开 MT5 用脚本单步验证坐标范围。
class="kw">const class="type">int element_id, class="kw">const class="type">int element_num, class="kw">const class="type">long chart_id, class="kw">const class="type">int wnd_num, class="kw">const class="type">class="kw">string name, class="kw">const class="type">int x, class="kw">const class="type">int y, class="kw">const class="type">int w, class="kw">const class="type">int h, class="kw">const class="type">class="kw">color colour, class="kw">const class="type">uchar opacity, class="kw">const class="type">bool movable=true, class="kw">const class="type">bool activity=true, class="kw">const class="type">bool redraw=class="kw">false) : m_shadow(class="kw">false) { this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_base=NULL; this.m_chart_color_bg=(class="type">class="kw">color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND); this.m_name=(::StringFind(name,this.m_name_prefix)<class="num">0 ? this.m_name_prefix : "")+name; this.m_chart_id=(chart_id==NULL || chart_id==class="num">0 ? ::ChartID() : chart_id); this.m_subwindow=wnd_num; this.m_type_element=element_type; this.SetFont(DEF_FONT,DEF_FONT_SIZE); this.m_text_anchor=class="num">0; this.m_text_x=class="num">0; this.m_text_y=class="num">0; this.m_color_bg=colour; this.m_opacity=opacity; this.m_shift_coord_x=class="num">0; this.m_shift_coord_y=class="num">0; if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,colour,opacity,redraw)) {
给画布元素加投影偏移量
在 MT5 的 Canvas 图形封装里,阴影不是后期滤镜,而是作为独立元素类挂到基类 CGCnvElement 下的。CShadowObj 这个类就专门管一个图形对象的投影,核心参数只有四个:颜色、不透明度、X 偏移、Y 偏移。 其中 m_offset_x 和 m_offset_y 是整型字段,决定阴影相对原图往右和下挪多少像素;OffsetX() 与 OffsetY() 是两个只读方法,直接回传这两个私有成员,外部绘制时拿它算投影坐标。 构造函数把 chart_id、subwindow、name 以及 x/y/w/h 全部透传给基类,元素类型写死为 GRAPH_ELEMENT_TYPE_SHADOW_OBJ。SupportProperty 对整型和字符串属性都返回 true,说明阴影对象在属性系统里不挑字段,便于运行时动态改色或改偏移。 实盘画 K 线标注时,若给信号箭头挂 2~3 像素的偏移阴影,在灰白背景上层次感会明显些;外汇与贵金属波动剧烈,这类纯视觉辅助不影响任何信号逻辑,但过度堆叠绘制调用可能拖慢老旧终端的刷新帧率。
class CShadowObj : class="kw">public CGCnvElement { class="kw">private: class="type">class="kw">color m_color_shadow; class=class="str">"cmt">// Shadow class="type">class="kw">color class="type">uchar m_opacity_shadow; class=class="str">"cmt">// Shadow opacity class="type">int m_offset_x; class=class="str">"cmt">// Shadow X axis shift class="type">int m_offset_y; class=class="str">"cmt">// Shadow Y axis shift class=class="str">"cmt">//--- Supported object properties(class="num">1) integer and(class="num">2) class="type">class="kw">string ones class="kw">virtual class="type">bool SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER class="kw">property) { class="kw">return true; } class="kw">virtual class="type">bool SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING class="kw">property) { class="kw">return true; } class=class="str">"cmt">//--- Return the shadow shift by(class="num">1) X and(class="num">2) Y class="type">int OffsetX(class="type">void) class="kw">const { class="kw">return this.m_offset_x; } class="type">int OffsetY(class="type">void) class="kw">const { class="kw">return this.m_offset_y; } class=class="str">"cmt">//--- Draw an object shadow CShadowObj::CShadowObj(class="kw">const class="type">long chart_id, class="kw">const class="type">int subwindow, class="kw">const class="type">class="kw">string name, class="kw">const class="type">int x, class="kw">const class="type">int y, class="kw">const class="type">int w, class="kw">const class="type">int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_SHADOW_OBJ,chart_id,subwindow,name,x,y,w,h) {
◍ 阴影偏移与模糊半径的落地细节
在 CShadowObj 的绘制流程里,DrawShadow 先把传入的 shift_x / shift_y 写进成员变量 m_offset_x 与 m_offset_y,这两个值直接决定阴影相对主体的像素位移。想验证偏移效果,把调用处的 shift_x 改成 10、shift_y 改成 5,MT5 画布上阴影就会向右下错开对应像素。 矩形尺寸用 Width() 和 Height() 减去两倍的 OUTER_AREA_SIZE 得出,意味着阴影图永远比外框内缩一圈。模糊半径被硬性夹在 OUTER_AREA_SIZE/4 以内——比如 OUTER_AREA_SIZE 为 40 时,blur_value 传 20 也只生效 10,超阈值的模糊参数会被静默截断。 最后 Move() 把坐标加上偏移量并 Update() 刷新画布;若 GaussianBlur() 返回失败则直接 return,不抛异常只会在日志报错。外汇与贵金属图表上叠加这类自定义阴影元素时,注意过度模糊会增加 CPU 占用,高波动时段可能拖累刷新帧率。
this.m_type=OBJECT_DE_TYPE_GSHADOW; CGCnvElement::SetColorBackground(clrNONE); CGCnvElement::SetOpacity(class="num">0); CGCnvElement::SetActive(class="kw">false); this.m_opacity_shadow=class="num">127; class="type">class="kw">color gray=CGCnvElement::ChangeColorSaturation(ChartColorBackground(),-class="num">100); this.m_color_shadow=CGCnvElement::ChangeColorLightness(gray,-class="num">50); this.m_shadow=class="kw">false; this.m_visible=true; this.m_offset_x=class="num">0; this.m_offset_y=class="num">0; CGCnvElement::Erase(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Draw the object shadow | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CShadowObj::DrawShadow(class="kw">const class="type">int shift_x,class="kw">const class="type">int shift_y,class="kw">const class="type">uchar blur_value) { class=class="str">"cmt">//--- Set the shadow shift values to the variables by X and Y axes this.m_offset_x=shift_x; this.m_offset_y=shift_y; class=class="str">"cmt">//--- Calculate the height and width of the drawn rectangle class="type">int w=this.Width()-OUTER_AREA_SIZE*class="num">2; class="type">int h=this.Height()-OUTER_AREA_SIZE*class="num">2; class=class="str">"cmt">//--- Draw a filled rectangle with calculated dimensions this.DrawShadowFigureRect(w,h); class=class="str">"cmt">//--- Calculate the blur radius, which cannot exceed a quarter of the OUTER_AREA_SIZE constant class="type">int radius=(blur_value>OUTER_AREA_SIZE/class="num">4 ? OUTER_AREA_SIZE/class="num">4 : blur_value); class=class="str">"cmt">//--- If failed to blur the shape, exit the method(GaussianBlur() displays the error on the journal) if(!this.GaussianBlur(radius)) class="kw">return; class=class="str">"cmt">//--- Shift the shadow object by X/Y offsets specified in the method arguments and update the canvas CGCnvElement::Move(this.CoordX()+this.m_offset_x,this.CoordY()+this.m_offset_y); CGCnvElement::Update(); }