DoEasy. 控件 (第 17 部分): 裁剪对象不可见部分、辅助箭头按钮 WinForms 对象(基础篇)
「给 WinForms 箭头按钮做不可见区域裁剪」
在 DoEasy 控件库的第 17 部分里,核心动作是给 WinForms 风格的辅助箭头按钮对象加上「裁剪不可见部分」的能力。也就是说,当按钮被其他图表对象遮挡,或自身只显示局部时,超出的绘制区域不再参与渲染,降低 Overlay 层的无效重绘开销。 原文给出的示例发布于 2023 年 1 月 9 日 17:29,截至统计时点页面显示阅读量 622、评论 8 条,说明这类底层控件改造在 MT5 量化社群里关注度不算低,但远没到热门指标那种体量。 实现上,作者在库类里新增了对对象可见矩形的判定逻辑,把落在裁剪区外的图元直接跳过。对做自定义面板的交易者来说,这意味着你可以在 MT5 上挂更多按钮而不明显拖慢终端——但外汇和贵金属交易本身杠杆高、滑点风险大,任何界面优化都不改变仓位风险。
◍ 容器裁剪与箭头按钮的硬约束
在 MT5 图形系统里,挂到容器上的子对象一旦越过容器边界,超出的部分必须裁掉,而不是把整个对象藏起来。边界通常就是容器边缘;若容器带边框,可视边界要退到边框内缘,不能和边框叠在一起,否则附着对象会糊掉。 MQL5 给 OBJ_BITMAP 和 OBJ_BITMAP_LABEL 提供了现成的位图裁剪:用 OBJPROP_XSIZE / OBJPROP_YSIZE 定矩形可视区大小,再用 OBJPROP_XOFFSET / OBJPROP_YOFFSET 在源图里平移这块窗口,其余图像直接不可见。但这套只对物理 bmp 文件类对象生效。 坑在 Canvas:它虽在内存里按位图建资源,却不吃上面那套偏移裁剪逻辑,等于“某些内容不允许”。所以只能自己写裁剪——初始矩形取容器宽高或边框内区,每个元素读自己相对容器的坐标,超出部分用全透明色补背景擦掉。 顺带要做的还有箭头按钮类。TabControl 一行标签放不下时,超出的标签隐藏,靠左右箭头滚标题栏找页;这类按钮后续还能复用到上下滚动。外汇/贵金属 EA 界面做密集控件时,这种手写裁剪+箭头导航能直接抄去用,但回测和实盘前请在 MT5 里验证边界计算,图形资源泄漏风险不低。
给 DoEasy 库补一套可视区域裁剪机制
在 DoEasy 函数库的 Defines.mqh 里加了一个宏 DEF_ARROW_BUTTON_SIZE,把箭头按钮两侧默认尺寸钉死在 15 像素。创建按钮时仍可手动改侧边大小,但默认走这个常量,避免每次实例化都重复传参。 图形对象类型列表新增了「辅助对象(WinForms 辅助类)」这一档。好处很直接:后续按类型筛选图形对象时,可以只捞出辅助对象做批量处理,不用遍历全部 OBJ_LABEL、OBJ_RECTANGLE 之类。 画布图形元素的整数型属性从 92 扩到 96,新增的 4 个属性专门存可视区域坐标和尺寸(左上角 X/Y 加宽高)。同期把它们塞进排序准则列表,意味着你能按可视区域边界对画布元素做 SELECT_BY_PROPERTY 式的排序和选取。 DELib.mqh 里改了自动命名逻辑。原函数在拼图形对象名时不允许两个连续大写字母,而新对象名必须带三个,所以直接把自动生成串替换成指定串,否则创建出来的对象名是错的、MT5 里根本认不出。 GBaseObj.mqh 加了设置/获取可视区域属性的虚方法,并重命名了原可视标志设置方法以免歧义。注意 ObjectSetXXX 是异步的——只返回命令入队成功标志,不保证属性真的写进图表。要确认就得回读属性值比对。 GCnvElement.mqh 里把 Erase() 拆成三步:EraseNoCrop() 先按色填充不裁剪,Crop() 再用透明色填掉容器外区域,Erase() 顺序调这两个并更新画布。对象构造时可视区域默认等于整个对象大小,所有属性设完才把可见性标成隐藏,这样 GUI 逐层构建过程中不会闪一堆半成品。 Label 和 Button 的重绘逻辑同步改了:画内容用 EraseNoCrop(),画完再调裁剪。实测下来文本和按钮贴边超出容器时,会被可视区域边界切干净,不再溢到父窗体外。外汇/贵金属 EA 做复杂面板时这种溢出很容易引发误点击,属于高危界面 bug。
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 DEF_CHECK_SIZE(class="num">12) class=class="str">"cmt">// Verification flag class="kw">default size class="macro">#define DEF_ARROW_BUTTON_SIZE(class="num">15) class=class="str">"cmt">// Default arrow button size
「GUI 库里的对象类型枚举怎么排」
在 MT5 自绘面板库里,先靠两个宏定死了外围留白和边框宽度:外圈预留 16 像素、默认框线 3 像素。这两个值直接决定表单在工作区周围的呼吸感,改小了控件容易贴边,改大了浪费屏幕。 对象类型用 ENUM_OBJECT_DE_TYPE 枚举收口,起点不是 0 而是 COLLECTION_ID_LIST_END+1,说明图形库和前面的集合 ID 做了隔离,避免撞号。里面分了 Graphics 和 WinForms 两条线:前者从 GBASE 基类一路到 GSHADOW 阴影对象,后者从 GWF_BASE 抽象基类到 CONTAINER、COMMON、HELPER 辅助控件。 图形元素本身还有第二层细分,ENUM_GRAPH_ELEMENT_TYPE 里列了 STANDARD、STANDARD_EXTENDED、SHADOW_OBJ、ELEMENT 四类。做自定义指标面板时,认准这些类型 ID 才能在运行时用 Type() 精准区分对象,不至于把阴影当实体去响应鼠标。 开 MT5 新建一个 include 头文件,把下面两段枚举原样贴进去编译,能立刻验证类型序号从集合末尾之后接续,方便你后续扩展自己的控件类型。
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">//+------------------------------------------------------------------+ class=class="str">"cmt">//| List of library object types | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_OBJECT_DE_TYPE { class=class="str">"cmt">//--- Graphics OBJECT_DE_TYPE_GBASE = COLLECTION_ID_LIST_END+class="num">1, class=class="str">"cmt">// "Base object of all library graphical objects" object type OBJECT_DE_TYPE_GELEMENT, class=class="str">"cmt">// "Graphical element" object type OBJECT_DE_TYPE_GFORM, class=class="str">"cmt">// Form object type OBJECT_DE_TYPE_GFORM_CONTROL, class=class="str">"cmt">// "Form for managing pivot points of graphical object" object type OBJECT_DE_TYPE_GSHADOW, class=class="str">"cmt">// Shadow object type class=class="str">"cmt">//--- WinForms OBJECT_DE_TYPE_GWF_BASE, class=class="str">"cmt">// WinForms Base object type(base abstract WinForms object) OBJECT_DE_TYPE_GWF_CONTAINER, class=class="str">"cmt">// WinForms container object type OBJECT_DE_TYPE_GWF_COMMON, class=class="str">"cmt">// WinForms standard control object type OBJECT_DE_TYPE_GWF_HELPER, class=class="str">"cmt">// WinForms auxiliary control object type class=class="str">"cmt">//--- Animation class=class="str">"cmt">//---... class=class="str">"cmt">//---... }; 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_SHADOW_OBJ, class=class="str">"cmt">// Shadow object GRAPH_ELEMENT_TYPE_ELEMENT, class=class="str">"cmt">// Element
◍ 正文
GRAPH_ELEMENT_TYPE_FORM, <span class="comment">// Form</span> GRAPH_ELEMENT_TYPE_WINDOW, <span class="comment">// Window</span> <span class="comment">//--- WinForms</span> GRAPH_ELEMENT_TYPE_WF_UNDERLAY, <span class="comment">// Panel object underlay</span> GRAPH_ELEMENT_TYPE_WF_BASE, <span class="comment">// Windows Forms Base</span> <span class="comment">//--- 'Container' object types are to be set below</span> GRAPH_ELEMENT_TYPE_WF_CONTAINER, <span class="comment">// Windows Forms container base object</span> GRAPH_ELEMENT_TYPE_WF_PANEL, <span class="comment">// Windows Forms Panel</s
画布图元的可视区与排序枚举
在 MT5 的画布(Canvas)体系里,每个图形元素都有一套整数属性枚举 ENUM_CANV_ELEMENT_PROP_INTEGER,用来描述 ID、类型、激活区边界以及可视区范围。其中 CANV_ELEMENT_PROP_VISIBLE_AREA_X / Y / WIDTH / HEIGHT 四个字段定义了元素在画布上的可见范围,做图层裁剪或只渲染视口内对象时会直接用上。 宏 CANV_ELEMENT_PROP_INTEGER_TOTAL 被定为 96,表示整数属性总数;CANV_ELEMENT_PROP_INTEGER_SKIP 为 0,即没有跳过不参与排序的属性。这两个值决定了后续双精度、字符串属性的起始偏移。 排序模式枚举 ENUM_SORT_CANV_ELEMENT_MODE 从 SORT_BY_CANV_ELEMENT_ID 起,到 SORT_BY_CANV_ELEMENT_VISIBLE_AREA_Y 止,都能作为图元排序键。若你写自定义控件库,按可视区坐标排序可实现“靠近视口者优先响应”的交互逻辑,外汇和贵金属图表叠加多图层时这种优先级管理能降低误触概率,但高频刷新仍会带来性能开销,属高风险环境下的工程权衡。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Integer properties of the graphical element on the canvas | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_PROP_INTEGER { CANV_ELEMENT_PROP_ID = class="num">0, class=class="str">"cmt">// Element ID CANV_ELEMENT_PROP_TYPE, class=class="str">"cmt">// Graphical element type class=class="str">"cmt">//---... class=class="str">"cmt">//---... CANV_ELEMENT_PROP_ACT_RIGHT, class=class="str">"cmt">// Right border of the element active area CANV_ELEMENT_PROP_ACT_BOTTOM, class=class="str">"cmt">// Bottom border of the element active area CANV_ELEMENT_PROP_VISIBLE_AREA_X, class=class="str">"cmt">// Visibility scope X coordinate CANV_ELEMENT_PROP_VISIBLE_AREA_Y, class=class="str">"cmt">// Visibility scope Y coordinate CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH, class=class="str">"cmt">// Visibility scope width CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT, class=class="str">"cmt">// Visibility scope height CANV_ELEMENT_PROP_GROUP, class=class="str">"cmt">// Group the graphical element belongs to CANV_ELEMENT_PROP_ZORDER, class=class="str">"cmt">// Priority of a graphical object for receiving the event of clicking on a chart class=class="str">"cmt">//---... class=class="str">"cmt">//---... }; class="macro">#define CANV_ELEMENT_PROP_INTEGER_TOTAL(class="num">96) class=class="str">"cmt">// Total number of integer properties class="macro">#define CANV_ELEMENT_PROP_INTEGER_SKIP(class="num">0) class=class="str">"cmt">// Number of integer properties not used in sorting class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Possible sorting criteria of graphical elements on the canvas | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define FIRST_CANV_ELEMENT_DBL_PROP(CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP) class="macro">#define FIRST_CANV_ELEMENT_STR_PROP(CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP) enum ENUM_SORT_CANV_ELEMENT_MODE { class=class="str">"cmt">//--- Sort by integer properties SORT_BY_CANV_ELEMENT_ID = class="num">0, class=class="str">"cmt">// Sort by element ID SORT_BY_CANV_ELEMENT_TYPE, class=class="str">"cmt">// Sort by graphical element type class=class="str">"cmt">//---... class=class="str">"cmt">//---... SORT_BY_CANV_ELEMENT_ACT_RIGHT, class=class="str">"cmt">// Sort by the right border of the element active area SORT_BY_CANV_ELEMENT_ACT_BOTTOM, class=class="str">"cmt">// Sort by the bottom border of the element active area SORT_BY_CANV_ELEMENT_VISIBLE_AREA_X, class=class="str">"cmt">// Sort by visibility scope X coordinate SORT_BY_CANV_ELEMENT_VISIBLE_AREA_Y, class=class="str">"cmt">// Sort by visibility scope Y coordinate