DoEasy 函数库中的图形(第七十四部分):由 CCanvas 类提供强力支持的基本图形元素·进阶篇
🎨

DoEasy 函数库中的图形(第七十四部分):由 CCanvas 类提供强力支持的基本图形元素·进阶篇

(2/3)· 当交互窗概念撞上库结构冲突,如何用画布元素对象重做图形基类

偏理论 第 2/3 篇
很多人在扩展 MQL5 图形库时直接修补现有基类,结果对象层级越叠越乱。把交互窗当成最低基准去承载所有属性,会和库原有概念打架,后续排序与渲染都会卡壳。

◍ 图形对象基类的私有骨架

在 MT5 里自建绘图库,第一步往往是抽一个基类把图表对象共有的属性收口。下面这段类声明把对象类型、名称前缀、所属图表 ID、子窗口索引和 Y 偏移全部塞进 protected/private 区,外部只能透过 Name()、ChartID()、SubWindow() 几个 const 取器读,不能直接改。 m_type 放在 private,却由派生类通过虚函数 Type() 向外暴露——这意味着基类只管存,具体是什么图形由子类在构造时填。m_name_prefix 与 m_name 分开,是为了批量生成对象时自动拼出唯一名,避免同图表上重名覆盖。 虚析构 ~CGBaseObj() 留空,但继承树里只要可能涉及 new/delete 的图形子类,就得靠它保证释放顺序。外汇与贵金属图表多品种同开时,chart_id 错绑会画到别的窗口上,属于高频踩坑点。

MQL5 / C++
class="macro">#include "..\..\Services\DELib.mqh"
class="macro">#include <Graphics\Graphic.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class of the base object of the library graphical objects        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CGBaseObj : class="kw">public CObject
  {
class="kw">private:
   class="type">int               m_type;             class=class="str">"cmt">// Object type
class="kw">protected:
   class="type">class="kw">string            m_name_prefix;      class=class="str">"cmt">// Object name prefix
   class="type">class="kw">string            m_name;             class=class="str">"cmt">// Object name
   class="type">long              m_chart_id;         class=class="str">"cmt">// Chart ID
   class="type">int               m_subwindow;        class=class="str">"cmt">// Subwindow index
   class="type">int               m_shift_y;          class=class="str">"cmt">// Subwindow Y coordinate shift

class="kw">public:
class=class="str">"cmt">//--- Return the values of class variables
   class="type">class="kw">string            Name(class="type">void)          class="kw">const { class="kw">return this.m_name;      }
   class="type">long              ChartID(class="type">void)       class="kw">const { class="kw">return this.m_chart_id;  }
   class="type">int               SubWindow(class="type">void)     class="kw">const { class="kw">return this.m_subwindow; }
class=class="str">"cmt">//--- The class="kw">virtual method returning the object type
   class="kw">virtual class="type">int       Type(class="type">void)          class="kw">const { class="kw">return this.m_type;      }
class=class="str">"cmt">//--- Constructor/destructor
                     CGBaseObj();
                    ~CGBaseObj();
  };

「图形对象基类的构造与取值接口」

在 MT5 自定义指标或 EA 里批量管理图表对象时,先立一个轻量基类能省掉大量重复代码。下面这段 CGBaseObj 的构造与存取设计,就是这类封装的骨架。 构造函数把三个成员直接初始化:m_shift_y 置 0 表示纵向偏移先不给,m_type 置 0 作为默认对象类型,m_name_prefix 则取当前程序名加下划线,保证同图表多实例的对象名不撞车。MQLInfoString(MQL_PROGRAM_NAME) 在实盘里返回的是 ex5 文件名,例如 "MyTrend_ " 前缀就自动带上了。 基类对外只暴露几个只读接口:Name() 回传完整对象名,ChartID() 回传绑定图表 ID(long 型),SubWindow() 回传子窗口序号(主图是 0)。Type() 写成虚函数,派生类覆盖后就能在运行时分辨是趋势线、矩形还是文字标签。 析构函数为空,说明资源释放交给具体子类或 MT5 终端的对象销毁机制。你在 MT5 里新建一个 include 头文件粘入下方代码,编译后就能以 CGBaseObj 为父级去写自己的 ObjLine、ObjRect,省去每类都重写一遍取值函数。外汇与贵金属图表对象受 Tick 刷新和品种点差影响,对象重绘逻辑须自行测试,存在滑点与渲染延迟风险。

MQL5 / C++
CGBaseObj::CGBaseObj() : m_shift_y(class="num">0), m_type(class="num">0), m_name_prefix(::MQLInfoString(MQL_PROGRAM_NAME)+"_")
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Destructor                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
CGBaseObj::~CGBaseObj()
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//--- method of identifying the object
  class="kw">virtual class="type">int         Type(class="type">void)                                      class="kw">const { class="kw">return(class="num">0);        }
class="kw">public:
class=class="str">"cmt">//--- Return the values of class variables
  class="type">class="kw">string              Name(class="type">void)                                      class="kw">const { class="kw">return this.m_name;      }
  class="type">long                ChartID(class="type">void)                                   class="kw">const { class="kw">return this.m_chart_id;  }
  class="type">int                 SubWindow(class="type">void)                                 class="kw">const { class="kw">return this.m_subwindow; }
class=class="str">"cmt">//--- The class="kw">virtual method returning the object type
  class="kw">virtual class="type">int         Type(class="type">void)                                      class="kw">const { class="kw">return this.m_type;      }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constructor                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
CGBaseObj::CGBaseObj() : m_shift_y(class="num">0), m_type(class="num">0), m_name_prefix(::MQLInfoString(MQL_PROGRAM_NAME)+"_")
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

画布图形元素的基类怎么搭

在 MT5 的 \MQL5\Include\DoEasy\Objects\Graph\ 目录下新建 GCnvElement.mqh,核心是把所有函数库图形元素统一挂到 CCanvas 上。类 CGCnvElement 公开继承自 CGBaseObj,受保护段里直接声明 CCanvas 与 CPause 对象,并留两个方法算光标相对元素本体和活动区域的坐标位置。 私密段放属性数组加索引定位方法,公开段则是标准库对象那套:写属性、读属性、比对对象。这些在更早的文章里已铺过,这里只是沿用。 真正的落点在构造与画布生命周期。参数化构造函数先把前缀和传入名拼成唯一对象名(形如 Prefix_Object_Name),设好图表 ID 与子窗口索引,再调 Create() 建 CCanvas;失败就写日志并返回。默认活动区域四边偏移为 0,即活动区等同元素尺寸,后续可单独改。 析构里销毁 CCanvas 对象。比较方法分两种:按指定属性比返回 1/-1/0,按全属性三重循环比,任一项不等就 false。 创建元素时调 CCanvas::CreateBitmapLabel() 第二种重载,资源建成就填色、调 Update() 上屏;m_shift 存子窗口 Y 偏移。注意 Resize() 改宽高会覆盖原画布内容,所以这两方法后面还得细化。 不透明度走 CCanvas::TransparentLevelSet(),0 全透 255 全不透,按重绘标志刷图。最后在 Select.mqh 里给 CSelect 补了按属性排图形元素的方法,测试即可见效果。外汇与贵金属自动化图形涉及高频重绘,实盘前务必在策略测试器验证资源泄漏风险。

MQL5 / C++
class="macro">#include "GBaseObj.mqh"
class CGCnvElement : class="kw">public CGBaseObj
  {
  };
class="kw">protected:
  CCanvas          m_canvas;          class=class="str">"cmt">// CCanvas class object

◍ 订单面板的属性存取与光标判定

在自定义图形面板里,每一笔挂单或持仓都被抽象成一个元素对象。它内部用三段数组分别装整数、双精度与字符串属性:m_long_prop 对应 ORDER_PROP_INTEGER_TOTAL 个整型字段,m_double_prop 与 m_string_prop 则承接剩下的浮点与文本类字段。 读写属性时靠重载的 SetProperty / GetProperty 完成。整型直接按下标存取;双精度与字符串要先经 IndexProp 换算偏移量——用枚举值减去 CANV_ELEMENT_PROP_INTEGER_TOTAL,字符串还要再扣掉 DOUBLE 总数。这样一套映射,能让面板在 MT5 里用连续内存管几百个订单元素而不散架。 光标交互另有两个布尔方法:CursorInsideElement 判断点是否落在整个元素框内,CursorInsideActiveArea 只看可点击热区。做贵金属或外汇面板时,热区通常比视觉框小 20%~30%,避免误触。这类品种波动快、杠杆高,鼠标事件若不加区域过滤,滑点概率会明显上升。 直接把下面这段声明贴进你的 EA 头文件,编译后打印 m_double_prop 长度,就能确认自己枚举总数对没对上。

MQL5 / C++
  CPause                m_pause;                                                                       class=class="str">"cmt">// Pause class object
class=class="str">"cmt">//--- Return the cursor position relative to the(class="num">1) entire element and(class="num">2) the element&class="macro">#x27;s active area
   class="type">bool                CursorInsideElement(class="kw">const class="type">int x,class="kw">const class="type">int y);
   class="type">bool                CursorInsideActiveArea(class="kw">const class="type">int x,class="kw">const class="type">int y);
class="kw">private:
class="kw">private:
   class="type">long                m_long_prop[ORDER_PROP_INTEGER_TOTAL];                                            class=class="str">"cmt">// Integer properties
   class="type">class="kw">double              m_double_prop[ORDER_PROP_DOUBLE_TOTAL];                                           class=class="str">"cmt">// Real properties
   class="type">class="kw">string              m_string_prop[ORDER_PROP_STRING_TOTAL];                                           class=class="str">"cmt">// String properties
class=class="str">"cmt">//--- Return the index of the array the order&class="macro">#x27;s(class="num">1) class="type">class="kw">double and(class="num">2) class="type">class="kw">string properties are located at
   class="type">int                 IndexProp(ENUM_CANV_ELEMENT_PROP_DOUBLE class="kw">property)  class="kw">const { class="kw">return(class="type">int)class="kw">property-CANV_ELEMENT_PROP_INTEGER_TOTAL;                    }
   class="type">int                 IndexProp(ENUM_CANV_ELEMENT_PROP_STRING class="kw">property)  class="kw">const { class="kw">return(class="type">int)class="kw">property-CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_DOUBLE_TOTAL;}
class="kw">public:
class="kw">public:
class=class="str">"cmt">//--- Set object&class="macro">#x27;s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties
   class="type">void                SetProperty(ENUM_CANV_ELEMENT_PROP_INTEGER class="kw">property,class="type">long value)   { this.m_long_prop[class="kw">property]=value;                    }
   class="type">void                SetProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE class="kw">property,class="type">class="kw">double value)  { this.m_double_prop[this.IndexProp(class="kw">property)]=value;    }
   class="type">void                SetProperty(ENUM_CANV_ELEMENT_PROP_STRING class="kw">property,class="type">class="kw">string value)  { this.m_string_prop[this.IndexProp(class="kw">property)]=value;    }
class=class="str">"cmt">//--- Return object’s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string class="kw">property from the properties array
   class="type">long                GetProperty(ENUM_CANV_ELEMENT_PROP_INTEGER class="kw">property)             class="kw">const { class="kw">return this.m_long_prop[class="kw">property];                    }
   class="type">class="kw">double              GetProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE class="kw">property)              class="kw">const { class="kw">return this.m_double_prop[this.IndexProp(class="kw">property)];  }
   class="type">class="kw">string              GetProperty(ENUM_CANV_ELEMENT_PROP_STRING class="kw">property)              class="kw">const { class="kw">return this.m_string_prop[this.IndexProp(class="kw">property)];  }
class=class="str">"cmt">//--- Return the flag of the object supporting this class="kw">property

「画布基类的属性支持与创建接口」

在自定义图形控件体系里,CGCnvElement 作为画布元素基类,先用三个重载的 SupportProperty 虚函数统一放行整型、双精度、字符串三类属性,默认全部返回 true,意味着派生控件若不做特殊限制,就能被框架按任意属性排序或检索。 排序与判等由 Compare 和 IsEqual 承担:Compare 带 mode 参数支持按指定属性比对,用于链表排序;IsEqual 则逐属性比对两个 CGCnvElement 指针对象,用来在容器里查重。 实际建控件走 Create,参数覆盖了 chart_id、子窗口号、名称、左上角坐标 (x,y)、宽高 (w,h)、填充色与透明度 opacity,最后 redraw 默认 false,即创建后不立即重绘,需手动触发。 CanvasObj 直接返回内部 m_canvas 引用,外部借此拿到底层 CCanvas 去画像素;SetFrequency 把等待毫秒数塞给 m_pause,控制画布刷新节奏,值越小重绘越频繁,对 MT5 主线程占用也越高。

MQL5 / C++
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_DOUBLE 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">//--- Compare CGCnvElement objects with each other by all possible properties(for sorting the lists by a specified object class="kw">property)
class="kw">virtual class="type">int       Compare(class="kw">const CObject *node,class="kw">const class="type">int mode=class="num">0) class="kw">const;
class=class="str">"cmt">//--- Compare CGCnvElement objects with each other by all properties(to search equal objects)
class="type">bool              IsEqual(CGCnvElement* compared_obj) class="kw">const;
class=class="str">"cmt">//--- Creates the control
class=class="str">"cmt">//--- Creates the control
class="type">bool              Create(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">color colour,
                         class="kw">const class="type">uchar opacity,
                         class="kw">const class="type">bool redraw=class="kw">false);
class=class="str">"cmt">//--- Return the pointer to a canvas object
CCanvas           *CanvasObj(class="type">void)                                              { class="kw">return &this.m_canvas;     }
class=class="str">"cmt">//--- Set the canvas update frequency
class="type">void              SetFrequency(class="kw">const class="type">class="kw">ulong value)                               { this.m_pause.SetWaitingMSC(value);  }
class=class="str">"cmt">//--- Update the coordinates(shift the canvas)

图形元素的位移与构造接口

在 MT5 的 Canvas 图形封装里,CGCnvElement 基类用 Move() 把元素搬到指定像素坐标:x、y 是目标左上角位置,redraw 默认 false,即只改坐标不立即重绘,需手动触发才生效。 构造函数的参数表很长,但拆开看就是「身份 + 锚点 + 尺寸 + 外观 + 行为」五组:element_type / id / num 定身份,chart_id / wnd_num / name 绑图表与子窗口,x / y / w / h 是像素矩形,colour / opacity 控制绘制样式,movable / activity / redraw 三个 bool 决定交互与刷新策略,后两者默认 true / false。 这种封装意味着你新建一个按钮或标签时,若 opacity 给 255 则完全不透明,给 0 则整块隐身;movable 设 false 后用户拖不动它,适合做固定 HUD。外汇与贵金属图表叠加自绘元素时波动快,建议 redraw 按需开启,避免每 tick 重绘拖慢终端。

MQL5 / C++
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">//--- Constructors/Destructor
 CGCnvElement(class="kw">const ENUM_GRAPH_ELEMENT_TYPE element_type,
              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">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);
 CGCnvElement(){;}
~CGCnvElement();

class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Methods of simplified access to object properties                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Set the(class="num">1) X, (class="num">2) Y coordinates, (class="num">3) element width and(class="num">4) height,
 class="type">bool SetCoordX(class="kw">const class="type">int coord_x);
把对象层级梳理交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到画布对象继承关系的实时梳理,你专注决策逻辑而非底层结构。

常见问题

交互窗对象比基准对象复杂得多,其概念与构建函数库对象的概念不同,用作基准会导致后续对象层级混乱和管理困难。
元素对象属性均基于画布对象,并拥有实数型 stub 属性占位,支持按属性枚举排序,而终端原生对象仅有固有属性。
它存储任意图形对象的公共属性如类型、图表 ID、子窗口索引和名称前缀,所有库图形对象都从该类继承。
可以,小布盯盘的 AIGC 模块已内置画布对象层级与属性诊断,在对应品种页可直接观察元素对象行为。
基于画布的对象原本没有实数型属性,但构造函数库对象概念需要,故添加 stub 作为唯一实数型属性占位以便扩展。