DoEasy 函数库中的图形(第七十九部分):动画框对象类及其衍生对象(基础篇)
📘

DoEasy 函数库中的图形(第七十九部分):动画框对象类及其衍生对象(基础篇)

第 1/3 篇

「给 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(),另写了一个按指定大小返回复制区域坐标偏移的新方法。它依据传入的宽高和锚点算偏移量,文本背景则先量文本尺寸再调该方法。原先写在交互窗对象里的背景存复类被抽出来,挪进新的框对象基类文件——单动画框的基类统管后代共用属性,维护更干净。

MQL5 / C++
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 窗口逐一挂一次,确认哪种枚举在你的显示器上最不容易闪。

MQL5 / C++
  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 即可。

MQL5 / C++
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
逐行看这段:template<typename T> 让函数支持 double / int / 自定义结构等任意类型;ArraySize(array)==0 时走 CMessage::ToLog 打错误源,避免下游读到脏数据。FigureTypeDescription 里 StringSubstr(EnumToString(...),12) 是个取巧写法——枚举名前 12 个字符通常是前缀,截掉后拿到图形类型的纯描述串。 TextGetShiftXY 与 GetShiftXYbyText 只声明不实现,作用是根据文本锚点算轮廓矩形偏移。你在画标签时若想让文字框不挡 K 线,可参照这个接口自己补实现:用 ResourceReadString / 图文字宽估算填 shift_x、shift_y。外汇与贵金属波动剧烈,这类坐标计算误差可能在极端行情下导致标注错位,实盘前建议在 MTK 策略测试器里用 2023 年 XAUUSD 的 M5 数据跑一遍边界用例。

MQL5 / C++
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

常见问题

先把动画框拆成基类管通用属性、衍生类管具体动画,基类只留位置、尺寸、显隐和重绘接口,别把具体动画逻辑写死进去。
抗锯齿与平滑分支控制边缘渲染质量,不开启框线在缩放时会出现明显锯齿,小尺寸动画尤其扎眼,建议默认开。
可以,小布能直接读当前品种页的对象结构并标出表单对象报错点,你打开对应页就能看到哪类图形枚举或数组辅助函数出问题。
能,底层极值函数返回索引后可直接挂到框对象上画标注线,比每次遍历重算省事,适合做动态高低点框选。
多半是动画框基类缺了类型与工具函数声明,或图形枚举没覆盖平滑分支,补完类型映射和报错清单里的项即可消掉。