DoEasy. 控件 (第 3 部分): 创建绑定控件(基础篇)
「给 DoEasy 库接上绑定控件」
在 DoEasy 控件系列的第 3 部分里,核心动作是把「绑定控件」塞进原有库结构,让界面元素能跟随父容器或数据源联动,而不是各自孤立。这类改造通常从扩展库基类开始,把绑定关系抽象成可复用的方法,避免每写一个面板都重写一遍事件转发逻辑。 原文给出的示例发布于 2022 年 6 月 27 日,截至归档时该篇在源站展示量为 981 次、评论数为 0,说明这类偏底层的库重构内容受众窄但复用价值高。做 MT5 自定义控件的交易者,直接拿这版绑定逻辑改自己的面板,比从零写 WinForms 式布局省掉至少一半调试时间。 下一步的落点是继续扩充控件类型并补测试覆盖;你这边若已在用 DoEasy,建议先 diff 第 2 部分与第 3 部分的基类头文件,确认绑定接口没破坏你现有的 OnEvent 分发。
把控件绑进面板容器
做 MT5 自定义面板时,最麻烦的不是画一个按钮,而是让一组控件跟着容器一起走。面板本身只是个能装其它图形对象的容器,移动面板,绑在上面的子控件要同步位移,这在终端没有可视化 GUI 构造器的情况下,只能程序员手写坐标逻辑。 库里现在已经有了“在别的元素里造元素”的方法工件,这节继续补的是:直接从面板对象里 new 出一个绑定控件,并让它作为独立 GUI 单元被处理。最小可用单元是窗体类对象,它自己也能再往里挂子图元,形成嵌套结构。 顺带改了图形阴影对象的逻辑。原实现里阴影只画在图表最顶层,实际应该压在所属对象投射的上方,否则带阴影的控件叠放时会穿帮。外汇和贵金属图表挂这类面板属高频操作,终端卡顿或对象层级错乱风险不低,建议先在模拟盘验证。
◍ 改进库类
\MQL5\Include\DoEasy\ Defines.mqh 拥有许多宏替换来指定某些函数库对象的默认值。 在画布参数模块中,将宏替换名称 CLR_FORE_COLOR 更改为 CLR_DEF_FORE_COLOR , 为图形元素对象的非透明度添加默认值 ,并 为阴影对象属性添加其它一些默认值 : 函数库将在创建图形元素的方法中用到这些值。 一旦创建完毕,默认值始终可以被更改。 常量在图形元素类型枚举中的逻辑位置不太正确: 阴影对象常量位于 图形元素对象常量之后 。 就某些限制对象类型的方法而言,这不是很实用。 例如,如果我们需要处理所有 GUI 元素对象,那么我们可以只指定需处理元素类型,或更高级别的对象 。 在这种情况下,阴影对象也由方法处理。 为了避免这种情况,并获得依据枚举常量值选择对象分组的能力,则交换指定的常量,如此 GUI 元素对象即可根据其继承层次结构进行排列 : 现在,我们可以快速选择要在函数库方法中处理的所需对象。 在 \MQL5\Include\DoEasy\ Data.mqh 中, 添加新的函数库消息索引 : 以及与新添加的索引相对应的 消息文本 : 所有创建的 GUI 元素都来自不同的函数库对象。 最小的是图形元素对象。 在继承层次结构中的每个后续对象中添加父对象中不存在的新功能。 因此,父对象里某些方法的功能在子对象当中并未完全实现。 我们需要把这些方法变为虚拟的,并在子对象中添加所需的功能。 GUI 元素造就的每个对象都应该“知道”它所附着的元素,如此它便可以利用基准对象的属性进行定位。 若要如此做,在元素对象类中引入一个指向基准对象的指针(正是该对象是所有 GUI 元素的父对象,因此在其中放置指针是合乎逻辑的)。 在此阶段,组成图形界面的所有对象都绑定到图表屏幕坐标 — 它们的坐标从图表的左上角开始计算。 但如果我们需要将一个对象放置在另一个对象内部,那么把其坐标系移到其基准对象是合乎逻辑的,其中原点将是基准图形元素的左上角,而非图表。 为此,我们引入相对坐标的概念,并添加新的方法来返回对象相对于基准对象的坐标 — 即它所附着的对象。 然后,为了定位对象,我们可以简单地指定相对于基准对象左上角的偏移量,而非当沿图表移动基准图形元素时持续不断计算新的定位坐标。 在 \MQL5\Include\DoEasy\Objects\Graph\ GCnvElement.mqh 的受保护部分, 声明指向图形元素所附着基准对象的指针 ;而在私密部分, 声明存储相对于基准对象坐标偏移的变量 : 在公开部分, 指定设置和返回指向基准对象指针的方法 : Move() 方法是 虚拟的 : 鉴于图形元素对象可能会附着于另一个对象,而本身不可附着对象,因此其重定位的处理方法 Move() 不需要修改。 与此同时,它的子对象可能已经附着于其它图形元素,而指向这些元素的指针亦被放置在列表之中,这意味着它的 Move() 方法也应该能处理绑定到它的整个对象列表,其中会为每个对象单独调用 Move() 方法。 出于这个原因,该方法是虚拟的,并且由每个继承的对象分别实现。 在公开部分中, 指定设置和返回对象相对坐标的方法 : 在默认的构造函数中, 为指向基准对象的指针设置初始化 , 以及相对对象坐标的偏移量 : 如果指向基准对象的指针为 NULL ,则表示该对象未附着于任何其它图形元素。 在简化对象属性访问的方法块中, 设置返回对象右边缘和下边缘相对坐标的方法 : The BringToTop() method is made virtual :
「画布与图元的基础宏定义」
在 MT5 用 Canvas 自绘面板时,先把刷新节奏和配色透明度钉死在宏里,比运行时反复传参更省事。下面这段把画布更新间隔设为 16 毫秒,意味着理论刷新上限约 62.5 帧/秒,实际受终端主循环限制往往到不了。 CLR_CANV_NULL 取 0x00FFFFFF,是利用 alpha 通道为 0 让画布底色全透明;前景色 0x2D,0x43,0x48 是一种偏暗青灰,适合做深色背景上的文字。默认不透明度 200(满值 255)、阴影不透明度 127,阴影模糊 4,这几个数直接决定界面观感,调小模糊会显得更硬朗。 图元类型枚举区分了标准对象、扩展标准对象、Element、Shadow Obj 和 Form。其中 Shadow Obj 专用于承载阴影层,和真实可交互 Element 分开,避免点击命中测试时误触阴影。 开 MT5 新建脚本,把下列宏和枚举贴进头文件,改 DEF_FONT_SIZE 从 8 到 10 看小窗文字是否更清晰,就能验证这套参数对你显示器的适配度。外汇与贵金属图表叠加自绘层存在重绘耗时风险,低配机建议把 PAUSE_FOR_CANV_UPDATE 调到 30 以上。
class="macro">#define PAUSE_FOR_CANV_UPDATE(class="num">16) class=class="str">"cmt">// Canvas update frequency class="macro">#define CLR_CANV_NULL(0x00FFFFFF) class=class="str">"cmt">// Zero for the canvas with the alpha channel class="macro">#define CLR_DEF_FORE_COLOR(C&class="macro">#x27;0x2D,0x43,0x48&class="macro">#x27;) class=class="str">"cmt">// Default class="type">class="kw">color for texts of objects on canvas class="macro">#define CLR_DEF_OPACITY(class="num">200) class=class="str">"cmt">// Default class="type">class="kw">color non-transparency for canvas objects class="macro">#define CLR_DEF_SHADOW_COLOR(C&class="macro">#x27;0x6B,0x6B,0x6B&class="macro">#x27;) class=class="str">"cmt">// Default class="type">class="kw">color for canvas object shadows class="macro">#define CLR_DEF_SHADOW_OPACITY(class="num">127) class=class="str">"cmt">// Default class="type">class="kw">color non-transparency for canvas objects class="macro">#define DEF_SHADOW_BLUR(class="num">4) class=class="str">"cmt">// Default blur for canvas object shadows class="macro">#define DEF_FONT("Calibri") class=class="str">"cmt">// Default font class="macro">#define DEF_FONT_SIZE(class="num">8) class=class="str">"cmt">// Default font size class="macro">#define OUTER_AREA_SIZE(class="num">16) class=class="str">"cmt">// Size of one side of the outer area around the form workspace class="macro">#define DEF_FRAME_WIDTH_SIZE(class="num">3) class=class="str">"cmt">// Default form/panel/window frame width class=class="str">"cmt">//--- Graphical object parameters class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| The list of graphical element types | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_GRAPH_ELEMENT_TYPE { GRAPH_ELEMENT_TYPE_STANDARD, class=class="str">"cmt">// Standard graphical object GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED, class=class="str">"cmt">// Extended standard graphical object GRAPH_ELEMENT_TYPE_ELEMENT, class=class="str">"cmt">// Element GRAPH_ELEMENT_TYPE_SHADOW_OBJ, class=class="str">"cmt">// Shadow object GRAPH_ELEMENT_TYPE_FORM class=class="str">"cmt">// Form };
图形元素类型的枚举与表单报错清单
在 MT5 自定义界面框架里,图形元素被统一收进 ENUM_GRAPH_ELEMENT_TYPE 枚举,便于运行时按类型分支处理。从标准对象、扩展标准对象,到影子对象、元素、表单、窗口乃至 WinForms 风格的面板,一共定义了 7 类,其中 GRAPH_ELEMENT_TYPE_SHADOW_OBJ 之后带高亮的几项(ELEMENT / FORM / WINDOW / PANEL)是界面库自行扩展的核心容器类型。 CForm 类维护了一套独立的消息枚举,专门描述表单对象构造期的异常。比如 MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT 提示必须先调用 CreateShadowObj() 才有影子对象;MSG_FORM_OBJECT_ERR_NOT_INTENDED 则明确某方法不该用来造此类对象,这类边界检查能省掉大量运行时诡异崩图。 配套的多语言文本表里,每条报错都存了俄文与英文双串。以 'Failed to create new pixel copier object' 为例,说明像素拷贝器(pixel copier)在表单内属于独立管理的子对象,ID 重复或缺失会分别触发 MSG_FORM_OBJECT_PC_OBJ_ALREADY_IN_LIST 与 MSG_FORM_OBJECT_PC_OBJ_NOT_EXIST_LIST。开 MT5 把这段枚举贴进 MQ5 头部,编译后就能在自定义面板里直接断点看类型分支是否走对。
enum ENUM_GRAPH_ELEMENT_TYPE { GRAPH_ELEMENT_TYPE_STANDARD, class=class="str">"cmt">// Standard graphical object GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED, class=class="str">"cmt">// Extended standard graphical object GRAPH_ELEMENT_TYPE_SHADOW_OBJ, class=class="str">"cmt">// Shadow object GRAPH_ELEMENT_TYPE_ELEMENT, class=class="str">"cmt">// Element GRAPH_ELEMENT_TYPE_FORM, class=class="str">"cmt">// Form GRAPH_ELEMENT_TYPE_WINDOW, class=class="str">"cmt">// Window class=class="str">"cmt">//--- WinForms GRAPH_ELEMENT_TYPE_PANEL, class=class="str">"cmt">// Windows Forms Panel };
◍ 图形元素的类骨架与坐标偏移
在 MT5 自定义图形界面里,所有可绘制的控件都建议从同一个基类派生。下面这段类声明展示了一个图形元素如何持有父对象指针、画布实例以及暂停控制对象,从而支持嵌套与动态隐藏。 m_element_base 指向父元素,使子对象能相对基准做坐标偏移;m_canvas 封装了像素级绘制,m_pause 负责交互冻结。m_shift_coord_x 与 m_shift_coord_y 是 private 成员,记录相对于基对象的 X/Y 像素位移,外部只能通过基类接口间接修改。 Create() 的参数表暴露了实盘可用的落点:chart_id 传 0 即当前图表,wnd_num 用 0 表示主窗口,x/y/w/h 以像素为单位,colour 与 opacity(0–255)决定填充与透明。外汇与贵金属图表上叠加此类控件,需注意高波动时段重绘可能拖慢 tick 响应,属于高风险界面实验。 直接把这段声明贴进 MQ5 编辑器,补完 SData 结构与两个虚函数,就能在策略面板里验证偏移量是否如预期叠加。
class CGCnvElement : class="kw">public CGBaseObj { class="kw">protected: CGCnvElement *m_element_base; class=class="str">"cmt">// Pointer to the parent element CCanvas m_canvas; class=class="str">"cmt">// CCanvas class object CPause m_pause; class=class="str">"cmt">// Pause class object class="type">bool m_shadow; class=class="str">"cmt">// Shadow presence class="type">class="kw">color m_chart_color_bg; class=class="str">"cmt">// Chart background class="type">class="kw">color class="type">uint m_duplicate_res[]; class=class="str">"cmt">// Array for storing resource data copy class=class="str">"cmt">//--- Create(class="num">1) the object structure and(class="num">2) the object from the structure class="kw">virtual class="type">bool ObjectToStruct(class="type">void); class="kw">virtual class="type">void StructToObject(class="type">void); class="kw">private: class="type">int m_shift_coord_x; class=class="str">"cmt">// Offset of the X coordinate relative to the base object class="type">int m_shift_coord_y; class=class="str">"cmt">// Offset of the Y coordinate relative to the base object class="kw">struct SData class=class="str">"cmt">//--- Create the element 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">class="kw">color colour, class="kw">const class="type">uchar opacity,