DoEasy. 控件 (第 2 部分): 操控 CPanel 类(基础篇)
◍ 从零接管 CPanel 的鼠标操控
在 DoEasy 库的第 2 部分里,CPanel 类不再只是静态容器,而是能响应鼠标拖拽与点击的可用控件。原示例于 2022 年 6 月 16 日发布,在 MetaTrader 5 官方代码库累计被查看 1556 次,说明这类轻量 GUI 封装确有实际需求。 要验证它,最直观的做法是打开 MT5 的 MetaEditor,把库里 CPanel 的 Demo 脚本挂到图表上:用鼠标左键按住面板标题栏移动,看坐标是否实时写回 OBJPROP_XDISTANCE/YDISTANCE。外汇与贵金属图表加载此类自定义控件存在平台兼容性风险,建议先在模拟账户跑通。 下一步应关注类内部对鼠标消息的过滤逻辑——它决定了面板会不会和指标窗体抢事件。这部分若改不好,多面板叠放时容易出现焦点漂移。
图形对象的历史包袱与本次修复落点
上一阶段按 Windows 窗体样式写控件时,留了一堆没验过的坑。最明显的是图形对象切时间帧就消失:日志说对象已创建,图表上却看不见,实际既没建也没渲染。这类问题从最早处理图形对象那会儿就埋下了。 另一个结构性矛盾是鼠标交互只挂在窗体对象上。图形元素本是窗体的父对象,本身没鼠标处理——作为最小函数库图形对象这合理,但一旦要交互,最小单元就该是窗体。面板控件作为窗体衍生后代,上篇刚起手,却同样不响应鼠标,这不该。 本篇先补这些缺陷,再给面板控件加功能。重点是实现 CPanel 类对所有面板文本对象的默认字体继承:附着在面板上的含文本控件,自动沿用面板字体;面板自身绘制的文本也走同一套默认参数。当然,显示前可单独覆盖参数,文本就按指定样式出。 另外,面板作为控件要能画边框并控框架参数。我们会实现用默认值或显式参数(也可不传)绘制边框的能力,开 MT5 把 CPanel 派生对象挂上图表就能验。
「重构 DoEasy 图形层的内存与字体管理」
在 \MQL5\Include\DoEasy\ 下几个头文件里做了针对性改动,主要解决窗体类继承时的内存泄漏,以及面板字体样式、边框宽度的可控性。先说最痛的点:CPanel 继承自 CForm,而 CForm 内部用 new 创建了画布动画对象和阴影对象,原以为析构能回收,实际从图表移除 EA 时日志报丢失 4 个对象、512 字节内存泄漏。 修复办法是在 Form.mqh 里声明一个 CArrayObj 列表,把动画和阴影对象都加进去,并设排序标志。这样终端子系统负责清空,不再依赖派生类析构时序。侧边框宽度宏替换 DEFAULT_FORM_BORDER_WIDTH 也挪到受保护变量,方便面板类直接读。 字体这块,Defines.mqh 新增字体样式与宽度类型枚举,普通字体给个零值常量重置其它标志;Panel.mqh 里用 m_bold_type 存宽度类型,GetFontFlags() 私有方法只回传 CCanvas 字体标志,避免每个公开方法都声明局部字体变量。设置粗体时若入参带标志,m_bold_type 写 Bold 且加 FW_BOLD,否则回退默认。 GraphElementsCollection.mqh 加了返回码枚举:成功添加、已存在、添加失败。创建元素时先查重,存在就回传已有对象指针而非报错,这对动态 GUI 重建很实用。搜索方法会自动补程序名前缀,防止按裸名找不到对象。 鼠标事件处理也有坑:原 GetFormUnderCursor() 用 == 严格比类型,导致面板等派生类收不到事件。改成 >= 后,因类型枚举升序,所有继承窗体的对象都能进处理分支。SetZOrderMAX() 里同步修了阻止交互的判断。 最后在 Engine.mqh 的 OnDeinit() 调集合类逆初始化,清空图形元素列表——切换周期重绘 GUI 就不会叠旧对象。外汇与贵金属 GUI 自动化高风险,改完请开 MT5 挂前一篇 EA 验证泄漏是否消失。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| List of form style parameter indices | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_FORM_STYLE_PARAMS {
◍ 面板样式的枚举与初始化数组
在 MT5 自定义控件库里,表单与面板的视觉参数被拆成了 9 个整型字段,用枚举集中管理。前 5 个归属 CForm:阴影透明度、模糊半径、颜色压暗程度、X/Y 偏移;后 4 个归属 CPanel:左、右、上、下边框宽度。 宏 TOTAL_FORM_STYLE_PARAMS 直接写死为 9,意味着后续遍历参数时循环上界固定,改样式结构必须同步改这个宏,否则数组越界。 二维数组 array_form_style 的第一维是样式种类(如「Flat form」),第二维就是上面那 9 个参数。以 Flat 为例:阴影透明度 80、模糊 4、压暗 80、偏移各 2,面板四边边框统一 3 像素——这些值直接决定 GUI 在图表上的观感,开 MT5 把数字改小就能看到边框变细、阴影变淡。
class=class="str">"cmt">//--- CForm FORM_STYLE_FRAME_SHADOW_OPACITY, class=class="str">"cmt">// Shadow opacity FORM_STYLE_FRAME_SHADOW_BLUR, class=class="str">"cmt">// Shadow blur FORM_STYLE_DARKENING_COLOR_FOR_SHADOW, class=class="str">"cmt">// Form shadow class="type">class="kw">color darkening FORM_STYLE_FRAME_SHADOW_X_SHIFT, class=class="str">"cmt">// Shadow X axis shift FORM_STYLE_FRAME_SHADOW_Y_SHIFT, class=class="str">"cmt">// Shadow Y axis shift class=class="str">"cmt">//--- CPanel FORM_STYLE_FRAME_WIDTH_LEFT, class=class="str">"cmt">// Panel frame width to the left FORM_STYLE_FRAME_WIDTH_RIGHT, class=class="str">"cmt">// Panel frame width to the right FORM_STYLE_FRAME_WIDTH_TOP, class=class="str">"cmt">// Panel frame width on top FORM_STYLE_FRAME_WIDTH_BOTTOM, class=class="str">"cmt">// Panel frame width below }; class="macro">#define TOTAL_FORM_STYLE_PARAMS(class="num">9) class=class="str">"cmt">// Number of form style parameters class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Array containing form style parameters | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int array_form_style[TOTAL_FORM_STYLES][TOTAL_FORM_STYLE_PARAMS]= { class=class="str">"cmt">//--- "Flat form" style parameters { class=class="str">"cmt">//--- CForm class="num">80, class=class="str">"cmt">// Shadow opacity class="num">4, class=class="str">"cmt">// Shadow blur class="num">80, class=class="str">"cmt">// Form shadow class="type">class="kw">color darkening class="num">2, class=class="str">"cmt">// Shadow X axis shift class="num">2, class=class="str">"cmt">// Shadow Y axis shift class=class="str">"cmt">//--- CPanel class="num">3, class=class="str">"cmt">// Panel frame width to the left class="num">3, class=class="str">"cmt">// Panel frame width to the right
浮雕样式的边框与阴影参数落地
在自定义面板库里,「浮雕形式」并非只靠一张图搞定,而是把 CForm 与 CPanel 的描边、阴影拆成独立数值写进结构体数组。上面这段就是两套风格切换时的实参:第一套用 3 像素四边边框,第二套「Embossed form」同样保留 3 像素左/右/上/下边框,但给 CForm 加了阴影控制。 CForm 的阴影部分五个值依次对应:80 为阴影不透明度(百分比),4 为阴影模糊半径,80 为表单阴影颜色压暗程度,2 与 2 分别是阴影在 X、Y 轴的偏移量。也就是说,浮雕感主要来自「不透明 80 + 偏移 2,2」这组小位移,而不是大模糊。 CPanel 的四条边框宽度在第一、第二套里都写死为 3,意味着面板外框粗细不随风格切换变化,变的只是背后那层阴影。你在 MT5 里改这两组数,能直接看到控件从「平贴」变「浮起」。 下面那段 CGraphElementsCollection 的枚举,是图表控件集合出错时的消息ID:重复创建、获取失败、空对象都在列。其中「Error! Empty object」和「Failed to get a graphical element from the list」是高亮项,说明集合在取元素时最容易踩空。
{
class="num">3, class=class="str">"cmt">// Panel frame width on top
class="num">3, class=class="str">"cmt">// Panel frame width below
},
class=class="str">"cmt">//--- "Embossed form" style parameters
{
class=class="str">"cmt">//--- CForm
class="num">80, class=class="str">"cmt">// Shadow opacity
class="num">4, class=class="str">"cmt">// Shadow blur
class="num">80, class=class="str">"cmt">// Form shadow class="type">class="kw">color darkening
class="num">2, class=class="str">"cmt">// Shadow X axis shift
class="num">2, class=class="str">"cmt">// Shadow Y axis shift
class=class="str">"cmt">//--- CPanel
class="num">3, class=class="str">"cmt">// Panel frame width to the left
class="num">3, class=class="str">"cmt">// Panel frame width to the right
class="num">3, class=class="str">"cmt">// Panel frame width on top
class="num">3, class=class="str">"cmt">// Panel frame width below
},
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- CGraphElementsCollection
MSG_GRAPH_ELM_COLLECTION_ERR_OBJ_ALREADY_EXISTS, class=class="str">"cmt">// Error. A chart control object already exists with chart id
MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_CREATE_CTRL_OBJ,class=class="str">"cmt">// Failed to create chart control object with chart ID
MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_GET_CTRL_OBJ, class=class="str">"cmt">// Failed to get chart control object with chart ID
MSG_GRAPH_ELM_COLLECTION_ERR_GR_OBJ_ALREADY_EXISTS, class=class="str">"cmt">// Such graphical object already exists:
MSG_GRAPH_ELM_COLLECTION_ERR_EMPTY_OBJECT, class=class="str">"cmt">// Error! Empty object
MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_GET_ELEMENT, class=class="str">"cmt">// Failed to get a graphical element from the list
class=class="str">"cmt">//--- CGraphElementsCollection「Canvas 控件的错误码与默认皮肤参数」
在自绘图表控件库里,对象重复创建和空指针是最常踩的两个坑。源码用俄英双语文组做了提示:同图表 ID 已存在控制对象时会报 'A chart control object already exists',而从列表取图形元素失败则对应 'Failed to get graphic element from list',调试时直接比对这两类字符串就能定位。 画布自身的刷新与配色走了一组宏定义。PAUSE_FOR_CANV_UPDATE 设为 16 毫秒,意味着 canvas 重绘节拍约 62.5 FPS;CLR_CANV_NULL 用 0x00FFFFFF 把 alpha 通道置零,避免透明底污染图层;前景文字默认 C'0x2D,0x43,0x48'(深青灰),字体 Calibri、字号 8,外边距 OUTER_AREA_SIZE=16 像素,窗体边框 DEF_FRAME_WIDTH_SIZE=3 像素。 字体样式被收进 ENUM_FONT_STYLE 枚举:0 为常规,其余映射系统 FONT_ITALIC / FONT_UNDERLINE / FONT_STRIKEOUT。要在 MT5 里改面板观感,调 DEF_FONT_SIZE 和 DEF_FRAME_WIDTH_SIZE 两个宏最直接,外汇与贵金属图表叠加自绘控件时波动大,建议先在模拟盘验证重绘开销。
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_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 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 enum ENUM_FONT_STYLE { FONT_STYLE_NORMAL=class="num">0, class=class="str">"cmt">// Normal FONT_STYLE_ITALIC=FONT_ITALIC, class=class="str">"cmt">// Italic FONT_STYLE_UNDERLINE=FONT_UNDERLINE, class=class="str">"cmt">// Underline FONT_STYLE_STRIKEOUT=FONT_STRIKEOUT class=class="str">"cmt">// Strikeout };