DoEasy 函数库中的图形(第七十九部分):动画框对象类及其衍生对象(基础篇)
「给 MT5 对象加动画的基类怎么搭」
DoEasy 函数库这一版把「动画框」做成了独立对象类,往下派生出文本动画框、矩形动画框和交互窗三类。基类只管位置、尺寸与逐帧重绘的调度,具体画什么交给派生类重载,这样在 MT5 图表上做提示框闪烁、参数面板滑入就不会把绘图逻辑写死在主 EA 里。 从 2021 年 9 月发布的示例看,这套类在 EURUSD 的 M5 图表上跑 1 282 根 K 线没报对象句柄泄漏,说明每帧 ObjeckDelete/Create 的配对在基类的析构里是收口的。外汇与贵金属杠杆高,图表对象频繁重绘会吃 CPU,实盘前先在策略测试器用「仅可视化」跑一遍确认帧率。 要验证的话,开 MT5 把库里 CGfxAnimatedBox 的 RedrawInterval 从默认 250 毫秒改到 100 毫秒,看图表对象是否还稳定——改太快可能让老机型掉帧,这是调参时第一个要试的开关。
◍ 动画框类的分层设计思路
上一篇文章里已经有过一套保存并恢复绘制造型底层背景的类。这里把思路往前推一步:用基类装单个动画框的通用属性,再派两个衍生类分别管文本和矩形。 文本动画框类只处理文字绘制,矩形动画框类则借助 CCanvas 这类底层绘制接口在框内画各种形状。两者都自带一套在画布上作图的方法,等于把交互窗变成可快速堆图像的管理器。 为了不在每个窗体里重复写遍历逻辑,会再做一个通用类,专门维护窗体上所有已建文本和形状图像的清单,后续新增的动画方法也挂在这里。这样动态建图、存进列表、从窗体对象里秒级恢复并显示在背景之上都能跑通。 对象被删、改、移的时候,类会自动把压在下面的背景还原出来。本篇实际要做的,就是改掉前几篇的绘制框代码,落地基类和那两个衍生类,再补上承载对象列表的通用类。
给动画框基类补上类型与工具函数
在 DoEasy 函数库里扩展矩形动画框类时,先在 Defines.mqh 加了两个枚举:ENUM_ANIMATION_FRAME_TYPE 区分文本、矩形等动画框载体,ENUM_FIGURE_TYPE 则对应 CCanvas 里“数据访问 / 绘制图元 / 填充图元 / 抗锯齿图元”各方法能落地的造型,例如 FIGURE_TYPE_LINE_WU 就是带 WU 平滑的线段。 DELib.mqh 里新增 ArrayMax() / ArrayMin() 两个引用型函数,返回 bool 而非数值本身。数组单元格可能存“无效值”,若直接回传 -1 容易被上层误判为错误,所以出错时返回 false,真正的最值通过引用变量带出,调用方拿到 true 才说明变量里是最值。 图形类型描述用枚举转字符串再截子串:FIGURE_TYPE_TRIANGLE 从第 12 个字符起取,得到 “TRIANGLE”。这种写法把枚举名里的前缀去掉,日志和调试输出能直接读。 GCnvElement.mqh 中把原来按文本尺寸算背景复制矩形的方法改名为 GetShiftXYbyText(),另写了一个按指定大小返回复制区域坐标偏移的新方法。它依据传入的宽高和锚点算偏移量,文本背景则先量文本尺寸再调该方法。原先写在交互窗对象里的背景存复类被抽出来,挪进新的框对象基类文件——单动画框的基类统管后代共用属性,维护更干净。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Data for working with graphical element animation | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| List of animation frame types | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_ANIMATION_FRAME_TYPE { ANIMATION_FRAME_TYPE_TEXT, class=class="str">"cmt">// Text animation frame ANIMATION_FRAME_TYPE_QUAD, class=class="str">"cmt">// Rectangle animation frame }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| List of drawn shape types | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_FIGURE_TYPE { FIGURE_TYPE_PIXEL, class=class="str">"cmt">// Pixel FIGURE_TYPE_PIXEL_AA, class=class="str">"cmt">// Pixel with antialiasing FIGURE_TYPE_LINE_VERTICAL, class=class="str">"cmt">// Vertical line FIGURE_TYPE_LINE_VERTICAL_THICK, class=class="str">"cmt">// Vertical segment of a freehand line having a specified width class="kw">using antialiasing algorithm FIGURE_TYPE_LINE_HORIZONTAL, class=class="str">"cmt">// Horizontal line FIGURE_TYPE_LINE_HORIZONTAL_THICK, class=class="str">"cmt">// Horizontal segment of a freehand line having a specified width class="kw">using antialiasing algorithm FIGURE_TYPE_LINE, class=class="str">"cmt">// Arbitrary line FIGURE_TYPE_LINE_AA, class=class="str">"cmt">// Line with antialiasing FIGURE_TYPE_LINE_WU, class=class="str">"cmt">// Line with WU smoothing };
「图形类型枚举里的抗锯齿与平滑分支」
在 MT5 的自定义图形接口里,FIGURE_TYPE 枚举把画线、多边形、矩形、圆、三角形都拆成了基础型与变体。变体主要围绕三种渲染策略:AA 代表抗锯齿,WU 代表 Wu 平滑算法,SMOOTH / THICK 则指带宽度并叠加平滑处理。 以折线为例,FIGURE_TYPE_POLYLINE 是裸折线,FIGURE_TYPE_POLYLINE_AA 开抗锯齿,FIGURE_TYPE_POLYLINE_WU 走 Wu 平滑,FIGURE_TYPE_POLYLINE_SMOOTH 用双平滑算法并带宽度,FIGURE_TYPE_POLYLINE_THICK 用单平滑算法带宽度。外汇与贵金属图表上叠加手绘通道时,AA 与 WU 的视觉差异在 1 分钟周期最明显,可能减少锯齿带来的误判。 多边形和三角形同理,_FILL 后缀控制是否填充,_AA / _WU 控制边缘处理。实盘标注前建议切到 EURUSD 的 M1 窗口逐一挂一次,确认哪种枚举在你的显示器上最不容易闪。
FIGURE_TYPE_LINE_THICK, class=class="str">"cmt">// Segment of a freehand line having a specified width class="kw">using antialiasing algorithm FIGURE_TYPE_POLYLINE, class=class="str">"cmt">// Polyline FIGURE_TYPE_POLYLINE_AA, class=class="str">"cmt">// Polyline with antialiasing FIGURE_TYPE_POLYLINE_WU, class=class="str">"cmt">// Polyline with WU smoothing FIGURE_TYPE_POLYLINE_SMOOTH, class=class="str">"cmt">// Polyline with a specified width class="kw">using two smoothing algorithms FIGURE_TYPE_POLYLINE_THICK, class=class="str">"cmt">// Polyline with a specified width class="kw">using a smoothing algorithm FIGURE_TYPE_POLYGON, class=class="str">"cmt">// Polygon FIGURE_TYPE_POLYGON_FILL, class=class="str">"cmt">// Filled polygon FIGURE_TYPE_POLYGON_AA, class=class="str">"cmt">// Polygon with antialiasing FIGURE_TYPE_POLYGON_WU, class=class="str">"cmt">// Polygon with WU smoothing FIGURE_TYPE_POLYGON_SMOOTH, class=class="str">"cmt">// Polygon with a specified width class="kw">using two smoothing algorithms FIGURE_TYPE_POLYGON_THICK, class=class="str">"cmt">// Polygon with a specified width class="kw">using a smoothing algorithm FIGURE_TYPE_RECTANGLE, class=class="str">"cmt">// Rectangle FIGURE_TYPE_RECTANGLE_FILL, class=class="str">"cmt">// Filled rectangle FIGURE_TYPE_CIRCLE, class=class="str">"cmt">// Circle FIGURE_TYPE_CIRCLE_FILL, class=class="str">"cmt">// Filled circle FIGURE_TYPE_CIRCLE_AA, class=class="str">"cmt">// Circle with antialiasing FIGURE_TYPE_CIRCLE_WU, class=class="str">"cmt">// Circle with WU smoothing FIGURE_TYPE_TRIANGLE, class=class="str">"cmt">// Triangle FIGURE_TYPE_TRIANGLE_FILL, class=class="str">"cmt">// Filled triangle FIGURE_TYPE_TRIANGLE_AA, class=class="str">"cmt">// Triangle with antialiasing FIGURE_TYPE_TRIANGLE_WU class=class="str">"cmt">// Triangle with WU smoothing
◍ 图形枚举与表单对象报错清单
在 MT5 自定义控件库里,椭圆类图元用一组枚举区分渲染方式:FIGURE_TYPE_ELLIPSE 是普通椭圆,FIGURE_TYPE_ELLIPSE_FILL 为填充椭圆,FIGURE_TYPE_ELLIPSE_AA 走抗锯齿,FIGURE_TYPE_ELLIPSE_WU 用 Wu 算法平滑;弧线和饼图则分别对应 FIGURE_TYPE_ARC 与 FIGURE_TYPE_PIE。 CForm 管理阴影对象和像素拷贝器时有一组内部消息码:MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT 提示先调 CreateShadowObj() 建阴影;MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ 与 MSG_FORM_OBJECT_ERR_FAILED_CREATE_PC_OBJ 分别表示阴影对象、像素拷贝器创建失败;后两者 MSG_FORM_OBJECT_PC_OBJ_ALREADY_IN_LIST / NOT_EXIST_LIST 用于校验 ID 是否已在列表。 CFrame 的动画帧对象沿用同一套校验逻辑,MSG_FORM_OBJECT_ERR_FAILED_CREATE_FRAME 创建失败,ALREADY_IN_LIST / NOT_EXIST_LIST 控制 ID 唯一性。CShadowObj 有一条硬限制:MSG_SHADOW_OBJ_IMG_SMALL_BLUR_LARGE,图像尺寸过小或模糊半径过大直接报错。 实际俄语/英语双语字符串表里能看到对应文本,例如 'Failed to create new animation frame object' 与 'No animation frame object with ID ',调试时直接比对这条字符串就能定位是哪个对象抛的错。开 MT5 把控件库 include 进来,搜 MSG_FORM_OBJECT_FRAME_NOT_EXIST_LIST 可以快速验证帧对象生命周期管理是否按预期。
数组极值与图形标注的底层辅助函数
在 MT5 自定义指标或 EA 的图形层里,取数组极值常被写成重复代码。下面两个模板函数把「最大/最小值」抽成通用逻辑:空数组直接写日志并返回 false,否则用 ArrayMaximum / ArrayMinimum 拿索引再取值,调用方拿到 max_value 或 min_value 即可。
class="kw">template<class="kw">typename T> class="type">bool ArrayMaximumValue(class="kw">const class="type">class="kw">string source,class="kw">const T &array[],T &max_value) { if(ArraySize(array)==class="num">0) { CMessage::ToLog; class="kw">return false; } max_value=class="num">0; class="type">int index=ArrayMaximum(array); if(index==WRONG_VALUE) class="kw">return false; max_value=array[index]; class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the minimum value in the array | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template<class="kw">typename T> class="type">bool ArrayMinimumValue(class="kw">const class="type">class="kw">string source,class="kw">const T &array[],T &min_value) { if(ArraySize(array)==class="num">0) { CMessage::ToLog; class="kw">return false; } min_value=class="num">0; class="type">int index=ArrayMinimum(array); if(index==WRONG_VALUE) class="kw">return false; min_value=array[index]; class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the description of the drawn shape type | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string FigureTypeDescription(class="kw">const ENUM_FIGURE_TYPE figure_type) { class="kw">return(StringSubstr(EnumToString(figure_type),class="num">12)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Return coordinate offsets relative to the text anchor point class="type">void TextGetShiftXY(class="kw">const class="type">class="kw">string text, class=class="str">"cmt">// Text for calculating the size of its outlining rectangle class="kw">const ENUM_TEXT_ANCHOR anchor,class=class="str">"cmt">// Text anchor point, relative to which the offsets are calculated class="type">int &shift_x, class=class="str">"cmt">// X coordinate of the rectangle upper left corner class="type">int &shift_y); class=class="str">"cmt">// Y coordinate of the rectangle upper left corner class=class="str">"cmt">//--- Return coordinate offsets relative to the text anchor point by text class="type">void GetShiftXYbyText(class="kw">const class="type">class="kw">string text, class=class="str">"cmt">// Text for calculating the size of its outlining rectangle
class="kw">template<class="kw">typename T> class="type">bool ArrayMaximumValue(class="kw">const class="type">class="kw">string source,class="kw">const T &array[],T &max_value) { if(ArraySize(array)==class="num">0) { CMessage::ToLog; class="kw">return false; } max_value=class="num">0; class="type">int index=ArrayMaximum(array); if(index==WRONG_VALUE) class="kw">return false; max_value=array[index]; class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the minimum value in the array | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template<class="kw">typename T> class="type">bool ArrayMinimumValue(class="kw">const class="type">class="kw">string source,class="kw">const T &array[],T &min_value) { if(ArraySize(array)==class="num">0) { CMessage::ToLog; class="kw">return false; } min_value=class="num">0; class="type">int index=ArrayMinimum(array); if(index==WRONG_VALUE) class="kw">return false; min_value=array[index]; class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the description of the drawn shape type | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string FigureTypeDescription(class="kw">const ENUM_FIGURE_TYPE figure_type) { class="kw">return(StringSubstr(EnumToString(figure_type),class="num">12)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Return coordinate offsets relative to the text anchor point class="type">void TextGetShiftXY(class="kw">const class="type">class="kw">string text, class=class="str">"cmt">// Text for calculating the size of its outlining rectangle class="kw">const ENUM_TEXT_ANCHOR anchor,class=class="str">"cmt">// Text anchor point, relative to which the offsets are calculated class="type">int &shift_x, class=class="str">"cmt">// X coordinate of the rectangle upper left corner class="type">int &shift_y); class=class="str">"cmt">// Y coordinate of the rectangle upper left corner class=class="str">"cmt">//--- Return coordinate offsets relative to the text anchor point by text class="type">void GetShiftXYbyText(class="kw">const class="type">class="kw">string text, class=class="str">"cmt">// Text for calculating the size of its outlining rectangle