DoEasy 函数库中的图形(第七十六部分):会话窗对象和预定义的颜色主题·进阶篇
🎨

DoEasy 函数库中的图形(第七十六部分):会话窗对象和预定义的颜色主题·进阶篇

(2/3)· 从基类派生独立会话窗,用两套预置主题把 GUI 换肤成本压到改一个文件

实战向进阶 第 2/3 篇
不少人在 MQL5 里手写控件配色,每加一个面板就复制一遍 RGB,改主色要翻十几个文件。把外观硬编码进对象,后期换肤基本等于重写。本篇用主题文件隔离配色,避免这种自缚。

◍ 图形对象基类的字段与可见性开关

在 MT5 自定义指标或 EA 里封装图形对象,第一步往往是先定义一组私有成员变量来承载对象的身份与位置信息。下面这段类内声明给出了最精简的骨架:对象名前缀、完整对象名、图表 ID、子窗口索引、Y 轴偏移、对象类型,以及一个可见性布尔量。 [CODE] string m_name_prefix; // 对象名前缀 string m_name; // 对象名 long m_chart_id; // 图表 ID int m_subwindow; // 子窗口索引 int m_shift_y; // 子窗口 Y 坐标偏移 int m_type; // 对象类型 bool m_visible; // 对象可见性 [/CODE] 两个虚函数 ObjectToStruct 与 StructToObject 留作子类钩子:前者默认返回 true 表示结构已同步,后者为空实现,具体图形(如趋势线、矩形)再各自填充坐标转换逻辑。 对外暴露的只读访问器 Name()、ChartID()、SubWindow() 直接返回对应成员变量,调用成本为零,适合在高频 OnCalculate 里反复取对象上下文。 可见性不是简单赋值,而是通过 OBJPROP_TIMEFRAMES 属性切换。SetVisible 里把 flag 映射成 OBJ_ALL_PERIODS 或 0:传 true 时对象在所有周期显示,传 false 时 value=0 即仅当前周期隐藏。外汇与贵金属波动剧烈,多周期对象堆积可能误导判断,用这个开关按周期收起图形能降低图表噪音。 [CODE] void SetVisible(const bool flag) { long value=(flag ? OBJ_ALL_PERIODS : 0); if(::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,value)) this.m_visible=flag; } [/CODE] 打开 MT5 新建一个空指标,把上面两组代码粘进类定义,编译后挂到 XAUUSD 的 M15 图,调用 SetVisible(false) 观察对象是否从其他周期消失,即可验证这套封装是否生效。

MQL5 / C++
class="type">class="kw">string m_name_prefix;      class=class="str">"cmt">// Object name prefix
class="type">class="kw">string m_name;            class=class="str">"cmt">// Object name
class="type">long   m_chart_id;        class=class="str">"cmt">// Chart ID
class="type">int    m_subwindow;       class=class="str">"cmt">// Subwindow index
class="type">int    m_shift_y;         class=class="str">"cmt">// Subwindow Y coordinate shift
class="type">int    m_type;            class=class="str">"cmt">// Object type
class="type">bool   m_visible;         class=class="str">"cmt">// Object visibility

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">return true; }
class="kw">virtual class="type">void   StructToObject(class="type">void){;}
class="kw">public:
class="kw">public:
class=class="str">"cmt">//--- Return the values of class variables
class="type">class="kw">string Name(class="type">void) class="kw">const { class="kw">return this.m_name; }
class="type">long   ChartID(class="type">void) class="kw">const { class="kw">return this.m_chart_id; }
class="type">int    SubWindow(class="type">void) class="kw">const { class="kw">return this.m_subwindow; }
class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the object visibility
class="type">void SetVisible(class="kw">const class="type">bool flag)
  {
   class="type">long value=(flag ? OBJ_ALL_PERIODS : class="num">0);
   if(::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,value))
      this.m_visible=flag;

「图形基类的可见性与画布封装」

在自建图形库里,CGBaseObj 作为所有元素的父类,用 m_visible 这个布尔成员控制显隐。它的 IsVisible() 方法直接返回 this.m_visible,构造函数里默认赋值为 false,意味着新对象生成时处于隐藏状态,必须显式开启才会绘制到图表上。 派生类 CGCnvElement 在继承基础上挂了 CCanvas 和 CPause 两个成员:前者负责离屏绘制,后者处理交互暂停逻辑。同时它增加了 m_shadow(是否带阴影)和 m_chart_color_bg(图表背景色)两个保护变量,背景色需要在对象初始化时从当前图表抓取,否则半透明层会偏色。 CursorInsideElement 与 CursorInsideActiveArea 这两个私有判断函数,分别接收鼠标坐标 x、y,用来区分「光标是否在整块元素内」和「是否落在可点击热区」。实际调试时若发现按钮点不动,优先打印这两个函数的返回值,八成是热区矩形算错了。 私有区还预开了 m_long_prop[ORDER_PROP_INTEGER_TOTAL] 和 m_double_prop[ORDER_PROP_DOUBLE_TOTAL] 两个数组,分别存整数与实数属性。数组长度由宏定死,改结构体前先确认宏值,不然越界会让 MT5 终端直接崩溃。

MQL5 / C++
  class="type">bool             IsVisible(class="type">void)                class="kw">const { class="kw">return this.m_visible;  }
class=class="str">"cmt">//--- The class="kw">virtual method returning the object type
  class="kw">virtual class="type">int       Type(class="type">void)                              class="kw">const { class="kw">return this.m_type;      }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constructor                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
CGBaseObj::CGBaseObj() : m_shift_y(class="num">0), m_type(class="num">0),m_visible(class="kw">false), m_name_prefix(::MQLInfoString(MQL_PROGRAM_NAME)+"_")
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class of the base object of the library graphical objects         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CGCnvElement : class="kw">public CGBaseObj
  {
class="kw">protected:
  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=class="str">"cmt">//--- Return the cursor position relative to the(class="num">1) entire element and(class="num">2) the element&class="macro">#x27;s active area
  class="type">bool               CursorInsideElement(class="kw">const class="type">int x,class="kw">const class="type">int y);
  class="type">bool               CursorInsideActiveArea(class="kw">const class="type">int x,class="kw">const class="type">int y);
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">long               m_long_prop[ORDER_PROP_INTEGER_TOTAL];     class=class="str">"cmt">// Integer properties
  class="type">class="kw">double             m_double_prop[ORDER_PROP_DOUBLE_TOTAL];    class=class="str">"cmt">// Real properties

图形元素基类的成员与构造封装

在 MT5 自定义图形画布里,所有图元都从 CGCnvElement 这一基类派生。它内部用数组缓存订单对象的字符串属性,例如 m_string_prop[ORDER_PROP_STRING_TOTAL] 就专门存文本类字段,便于批量读写而不必反复调 API。 文本锚点、背景色与透明度是高频改动的视觉参数。m_text_anchor 控制对齐方式,m_color_bg 记录背景色,m_opacity 用 uchar 表达 0~255 的透明度区间——实测把 opacity 设到 120 左右,悬浮标签在黄金 1 分钟图上的遮挡感会明显降低。 Move() 负责按 x、y 重定位画布元素,redraw 默认 false,意味着你连续拖多个部件时可最后统一重绘以省开销。ChangeColorLightness() 接收 uint 颜色值与 double 明暗增量,返回调整后的颜色,适合做鼠标悬停高亮。 构造函数分两层:protected 版绑定图表 ID、子窗口号、名称与坐标宽高,public 版改用 element_id 与 element_num 做索引。这种拆分让派生类(如按钮、标签)只能走受控初始化,避免运行时乱 new 出游离图元。

MQL5 / C++
class="type">class="kw">string m_string_prop[ORDER_PROP_STRING_TOTAL]; class=class="str">"cmt">// String properties
ENUM_TEXT_ANCHOR m_text_anchor; class=class="str">"cmt">// Current text alignment
class="type">class="kw">color m_color_bg; class=class="str">"cmt">// Element background class="type">class="kw">color
class="type">uchar m_opacity; class=class="str">"cmt">// Element opacity
class=class="str">"cmt">//--- Return the index of the array the order&class="macro">#x27;s(class="num">1) class="type">class="kw">double and(class="num">2) class="type">class="kw">string properties are located at
class=class="str">"cmt">//--- Update the coordinates(shift the canvas)
class="type">bool Move(class="kw">const class="type">int x,class="kw">const class="type">int y,class="kw">const class="type">bool redraw=class="kw">false);
class=class="str">"cmt">//--- Change the class="type">class="kw">color lightness by the specified amount
class="type">uint ChangeColorLightness(class="kw">const class="type">uint clr,class="kw">const class="type">class="kw">double change_value);
class="kw">protected:
class=class="str">"cmt">//--- Protected constructor
CGCnvElement(class="kw">const ENUM_GRAPH_ELEMENT_TYPE element_type,
             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">public:
class=class="str">"cmt">//--- Parametric constructor
CGCnvElement(class="kw">const ENUM_GRAPH_ELEMENT_TYPE element_type,
             class="kw">const class="type">int    element_id,
             class="kw">const class="type">int    element_num,

◍ 图形元素基类的坐标与构造参数

在 MT5 自定义指标或 EA 的图形层封装里,CGCanvas 体系通常会抽出一个 CGCnvElement 基类,用来统一管理画布上每个子元素的几何与交互属性。下面这段声明展示了它的构造函数签名,参数覆盖了图表 ID、子窗口序号、元素名、左上角坐标 (x,y)、宽高 (w,h)、颜色、透明度,以及是否可拖动、是否激活、是否重绘。 构造函数默认把 movable 设为 true、activity 设为 true、redraw 设为 false,意味着元素默认可被鼠标拖动且处于激活态,但不会在每次 tick 强制重绘,这有助于降低 CPU 占用。 默认构造实现里初始化了两个成员:m_shadow 置为 false(不画阴影),m_chart_color_bg 通过 ChartGetInteger(ChartID(), CHART_COLOR_BACKGROUND) 实时读取当前图表背景色。这个背景色缓存很关键,后续绘制半透明矩形或文本时直接复用,避免每帧查询。 基类还提供了一组简化访问器:SetCoordX / SetCoordY 改左上角坐标,SetWidth / SetHeight 改尺寸。调用这些方法会返回 bool,便于在布局逻辑里判断坐标越界或参数非法。开 MT5 新建个脚本,include 你自己的 canvas 头文件,打印 SetWidth(-5) 的返回值,就能验证负宽高是否被拒。

MQL5 / C++
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,
class="kw">const class="type">bool    movable=true,
class="kw">const class="type">bool    activity=true,
class="kw">const class="type">bool    redraw=class="kw">false);
class=class="str">"cmt">//--- Default constructor/Destructor
      CGCnvElement() : m_shadow(class="kw">false),m_chart_color_bg((class="type">class="kw">color)::ChartGetInteger(::ChartID(),CHART_COLOR_BACKGROUND)) {;}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Methods of simplified access to object properties                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Set the(class="num">1) X, (class="num">2) Y coordinates, (class="num">3) element width, (class="num">4) height, (class="num">5) right(class="num">6) and bottom edge,
   class="type">bool            SetCoordX(class="kw">const class="type">int coord_x);
   class="type">bool            SetCoordY(class="kw">const class="type">int coord_y);
   class="type">bool            SetWidth(class="kw">const class="type">int width);
   class="type">bool            SetHeight(class="kw">const class="type">int height);

「画布元素的边界与可交互属性怎么设」

在 MT5 的 Canvas 自定义控件里,元素的活动区域(active area)并不等于它画出来的外框。用 SetRightEdge / SetBottomEdge 可以把元素右、下边界直接对齐到当前 RightEdge()、BottomEdge() 返回值,常用于图表尺寸变化后重算贴边位置。 活动区相对元素的四个方向偏移都走 fabs(value) 强制取绝对值,也就是说 SetActiveAreaLeftShift(-5) 和传 5 的效果一样,负值不会把活动区往反方向缩。这四个方法只改单边,若要一次设全用 SetActiveAreaShift(left, bottom, right, top) 更省事。 背景色和透明度是两套独立入口:SetColorBackground 只写成员变量 m_color_bg,不触发重绘;SetOpacity 多带一个 redraw 开关,默认 false,传 true 才会马上刷新。 交互开关里,SetMovable 控制用户能否拖拽,SetActive 控制元素是否接收事件。两者都走 SetProperty 改 CANV_ELEMENT_PROP_MOVABLE / CANV_ELEMENT_PROP_ACTIVE,在 EA 里动态禁掉 movable 可以防止误拖关键按钮。

MQL5 / C++
class="type">void SetRightEdge(class="type">void) { this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge()); }
class="type">void SetBottomEdge(class="type">void) { this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.BottomEdge()); }
class=class="str">"cmt">//--- Set the shift of the(class="num">1) left, (class="num">2) top, (class="num">3) right, (class="num">4) bottom edge of the active area relative to the element,
class=class="str">"cmt">//--- (class="num">5) all shifts of the active area edges relative to the element, (class="num">6) the element background class="type">class="kw">color and(class="num">7) the element opacity
class="type">void SetActiveAreaLeftShift(class="kw">const class="type">int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,fabs(value)); }
class="type">void SetActiveAreaRightShift(class="kw">const class="type">int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,fabs(value)); }
class="type">void SetActiveAreaTopShift(class="kw">const class="type">int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,fabs(value)); }
class="type">void SetActiveAreaBottomShift(class="kw">const class="type">int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,fabs(value)); }
class="type">void SetActiveAreaShift(class="kw">const class="type">int left_shift,class="kw">const class="type">int bottom_shift,class="kw">const class="type">int right_shift,class="kw">const class="type">int top_shift);
class="type">void SetColorBackground(class="kw">const class="type">class="kw">color colour) { this.m_color_bg=colour; }
class="type">void SetOpacity(class="kw">const class="type">uchar value,class="kw">const class="type">bool redraw=class="kw">false);
class=class="str">"cmt">//--- Set the flag of(class="num">1) object moveability, (class="num">2) activity, (class="num">3) element ID, (class="num">4) element index in the list and(class="num">5) shadow presence
class="type">void SetMovable(class="kw">const class="type">bool flag) { this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,flag); }
class="type">void SetActive(class="kw">const class="type">bool flag) { this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,flag); }

给画布元素补上存取接口

在自定义图形面板的基类里,元素身份与几何边界要靠一组轻量方法托管。下面这段接口把 ID、序号、阴影开关以及背景色、透明度、右/下边缘坐标全部封装成成员函数,调用方不用碰底层属性枚举。 SetID 与 SetNumber 直接写 CANV_ELEMENT_PROP_ID、CANV_ELEMENT_PROP_NUM,前者是面板内唯一定位元素的句柄,后者常用来记录同类型控件的排列序号;SetShadow 留了声明没给实现,说明阴影绘制逻辑可能在别处按图表主题动态切换。 读接口里 ColorBackground 返回 m_color_bg(私有成员缓存,避免每次查属性),Opacity 返回 uchar 类型的 m_opacity,值域 0–255,0 为全透明、255 为不透明。RightEdge 与 BottomEdge 用 CoordX()+m_canvas.Width() 算出元素在画布上的绝对右、下边界,做碰撞检测或自动布局时这两个值比存宽高更省事。 取回身份的 ID()、Number() 走 GetProperty 并强转 int,和前面的 Set 成配对。外汇与贵金属图表叠加这类自绘 UI 时,注意透明度误设成 255 会挡住价格动作,属于高风险定制项,参数改完建议在 MT5 策略测试器里用 XAUUSD 的 M15 跑一遍看遮挡。

MQL5 / C++
class="type">void SetID(class="kw">const class="type">int id) { this.SetProperty(CANV_ELEMENT_PROP_ID,id); }
class="type">void SetNumber(class="kw">const class="type">int number) { this.SetProperty(CANV_ELEMENT_PROP_NUM,number); }
class="type">void SetShadow(class="kw">const class="type">bool flag);
class=class="str">"cmt">//--- Return the shift(class="num">1) of the left, (class="num">2) right, (class="num">3) top and(class="num">4) bottom edge of the element active area
class=class="str">"cmt">//--- Return(class="num">1) the background class="type">class="kw">color, (class="num">2) the opacity, coordinate(class="num">3) of the right and(class="num">4) bottom element edge
class="type">class="kw">color ColorBackground(class="type">void) class="kw">const { class="kw">return this.m_color_bg; }
class="type">uchar Opacity(class="type">void) class="kw">const { class="kw">return this.m_opacity; }
class="type">int RightEdge(class="type">void) class="kw">const { class="kw">return this.CoordX()+this.m_canvas.Width(); }
class="type">int BottomEdge(class="type">void) class="kw">const { class="kw">return this.CoordY()+this.m_canvas.Height(); }
class=class="str">"cmt">//--- Return the(class="num">1) X, (class="num">2) Y coordinates, (class="num">3) element width and(class="num">4) height,
class=class="str">"cmt">//--- Return(class="num">1) the element ID, (class="num">2) element index in the list, (class="num">3) flag of the form shadow presence and(class="num">4) the chart background class="type">class="kw">color
class="type">int ID(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_ID); }
class="type">int Number(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_NUM); }
把换肤验证交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到主题参数是否生效,你只管调宏替换和枚举。

常见问题

基于图形元素对象派生,而图形元素又继承自函数库基准对象与 CObject,本篇实现的会话窗已可作为独立单元呈现控件与可视化。
参数写在单独的函数库文件里,程序员改该文件即可批量调整外观与配色,无需动各对象代码。
它们不参与对象搜索与排序,保留只增开销,移入类受保护成员变量后枚举总数由 23 减到 21。
可以,小布盯盘对应品种页内置了图形参数诊断,切换主题后能看到配色与偏移是否按预期加载。
留白用于绘制会话窗投影阴影,宏替换定义单侧像素,设 5 像素则上下左右各留 5 像素空白。