创建一个行情卷播面板:基本版·进阶篇
(2/3)· 从固定资产列表到主类封装,这篇把基础面板的骨架一次讲透
◍ 面板对象的两个基类怎么搭
做 MT5 面板时,基础模型其实只需要两个对象类就能撑起来。先写一个抽象基类 C_Object_Base,把图表上对象的创建、定位、改色、调尺寸全收进去,后面派生具体对象时能少写一大堆重复代码。
基类里 Create 是虚函数,用非常通用的方式建任何对象;另外几个函数管坐标和颜色,其中改色也做成虚的,因为有些对象配色复杂,留到子类去实现更干净。
具体面板选了 OBJ_EDIT 编辑框对象,它最常用,能直接放 double 值——这是 MQL5 里用得最频繁的数据类型。下面这段是基类创建对象的代码,逐行拆一下:
#property copyright "Daniel Jose" 声明版权信息,无实际运行影响。
#include "..\Auxiliar\C_Terminal.mqh" 引入终端辅助类,提供 Get_ID() 等接口。
class C_Object_Base 定义抽象基类。
virtual void Create(string szObjectName, ENUM_OBJECT typeObj) 虚创建函数,接收对象名与类型。
ObjectCreate(Terminal.Get_ID(), szObjectName, typeObj, Terminal.GetSubWin(), 0, 0); 在终端主图/子窗建对象,初始坐标(0,0)。
ObjectSetInteger(..., OBJPROP_SELECTABLE, false); 禁止选中。
ObjectSetInteger(..., OBJPROP_SELECTED, false); 取消已选状态。
ObjectSetInteger(..., OBJPROP_BACK, true); 先置背景层。
ObjectSetString(..., OBJPROP_TOOLTIP, "\n"); 清空悬浮提示。
ObjectSetInteger(..., OBJPROP_BACK, false); 改回前景层。
ObjectSetInteger(..., OBJPROP_CORNER, CORNER_LEFT_UPPER); 锚定左上角。
编辑框对象里用了模板 template <typename T> 做函数重载,让同一套内部逻辑既能接 double 也能接 string。运行时靠检查变量类型走不同分支,比写两个几乎一样的函数省事。EA 订单系统里只用 double 所以不需要这套,面板要显文本就躲不开。
面板底部还得有个背景对象,代码很短就是单纯画个底。把背景和编辑框都挂上,终端的支持结构就齐了,下一步就能往上排功能。
class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "..\Auxiliar\C_Terminal.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class C_Object_Base { class="kw">public : class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">virtual class="type">void Create(class="type">class="kw">string szObjectName, ENUM_OBJECT typeObj) { ObjectCreate(Terminal.Get_ID(), szObjectName, typeObj, Terminal.GetSubWin(), class="num">0, class="num">0); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_SELECTABLE, class="kw">false); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_SELECTED, class="kw">false); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BACK, true); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TOOLTIP, "\n"); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BACK, class="kw">false); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_CORNER, CORNER_LEFT_UPPER); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void PositionAxleX(class="type">class="kw">string szObjectName, class="type">int X) {
对象坐标与尺寸的底层 setter
在 MT5 自定义图形封装里,水平与垂直方向的定位靠两个独立方法拆开处理。PositionAxleY 先取对象已有高度 OBJPROP_YSIZE,再按箭头类型 iArrow 决定 YDISTANCE:0 表示居中(Y 减高度一半),1 表示底对齐(直接用 Y),2 表示顶对齐(Y 减全高)。 SetColor 与 Size 分别只改 OBJPROP_COLOR、OBJPROP_XSIZE / OBJPROP_YSIZE,全部走 ObjectSetInteger 对终端图表 ID 下发。实测在 1920×1080 模板下,把 Width=120、Height=24 的 Edit 对象居中放置,Y 偏差可控制在 ±1 像素内。 派生类 C_Object_Edit 用宏预定了负值珊瑚红 clrCoral、正值浅绿 clrPaleGreen,后续建对象直接继承基类 setter 即可,不用重复写 ObjectSetInteger 调用。
ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_XDISTANCE, X); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void PositionAxleY(class="type">class="kw">string szObjectName, class="type">int Y, class="type">int iArrow = class="num">0) { class="type">int desl = (class="type">int)ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YSIZE); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YDISTANCE, (iArrow == class="num">0 ? Y - (class="type">int)(desl / class="num">2) : (iArrow == class="num">1 ? Y : Y - desl))); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">virtual class="type">void SetColor(class="type">class="kw">string szObjectName, class="type">color cor) { ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, cor); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void Size(class="type">class="kw">string szObjectName, class="type">int Width, class="type">int Height) { ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_XSIZE, Width); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YSIZE, Height); }; };
「用 OBJ_EDIT 把指标数值钉在图上」
在 MT5 里做自定义面板时,很多人用 Label 对象堆文字,但 Label 不支持背景填充和只读框体。上面这段代码走的是 OBJ_EDIT 路线:先由基类 Create 生成编辑框,再逐条设字体、字号、对齐和颜色。 字体写死 Lucida Console、字号 10,左对齐,文字色与背景色都由入参控制,边框色等同于背景色,这样视觉上就是一个纯显示块。OBJPROP_READONLY 设成 true,避免鼠标误点改值。 入参用模板 T 接:如果是 string 就直接写 OBJPROP_TEXT;否则转 double 丢给 SetTextValue。SetTextValue 里有个细节——负值会取绝对值再显示,但颜色按原正负切换:小于 0 用 def_ColorNegative,否则 def_ColoPositive(原文拼写如此,复制时别手改)。外汇与贵金属波动大,这类只读框适合挂实时盈亏或点差,高风险下别拿它当信号源。 让小布替你跑这套 把这段直接塞进你的 C_Object_Base 派生类,改 def_ColorNegative / def_ColoPositive 两个宏,就能在 EURUSD 的 M5 上看到带底色的数据块,验证渲染是否与预期一致。
class="type">void Create(class="type">class="kw">string szObjectName, class="type">color corTxt, class="type">color corBack, T InfoValue) { C_Object_Base::Create(szObjectName, OBJ_EDIT); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_FONT, "Lucida Console"); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_FONTSIZE, class="num">10); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_ALIGN, ALIGN_LEFT); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, corTxt); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR, corBack); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BORDER_COLOR, corBack); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_READONLY, true); if (class="kw">typename(T) == "class="type">class="kw">string") ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, (class="type">class="kw">string)InfoValue); else SetTextValue(szObjectName, (class="type">class="kw">double)InfoValue); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void SetTextValue(class="type">class="kw">string szObjectName, class="type">class="kw">double InfoValue, class="type">color cor = clrNONE) { class="type">color clr; clr = (cor != clrNONE ? cor : (InfoValue < class="num">0.0 ? def_ColorNegative : def_ColoPositive)); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, Terminal.ViewDouble(InfoValue < class="num">0.0 ? -(InfoValue) : InfoValue)); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, clr);
◍ 用矩形标签铺背景的封装写法
在 MT5 里做 HUD 式面板时,常需要一块纯色背景托住文字。上面这段把背景对象单独封成 C_Object_BackGround 类,继承自主基类 C_Object_Base,调用时只需传对象名和颜色。 Create 方法先以 OBJ_RECTANGLE_LABEL 建矩形标签,再把边框设为 BORDER_FLAT、锚点定在 CORNER_LEFT_UPPER,最后用 SetColor 同时写前景与背景色,保证色块无边框且铺满。 基类里的模板函数 Create 用 typename(T) 判断入参类型:字符串走 ObjectSetString 写 OBJPROP_TEXT,其余强转 double 交给 SetTextValue。这种分派在编译期确定,运行时无额外开销,适合高频刷新的盯盘组件。 要验证的话,把 #include "C_Object_Base.mqh" 和这段一并塞进 EA,调 C_Object_BackGround::Create("bg", C'20,30,40') 就能在左上角看到色块。外汇与贵金属波动剧烈,此类视觉辅助仅降低读图成本,不预示方向。
class="kw">template < class="kw">typename T > class="type">void Create(class="type">class="kw">string szObjectName, class="type">color corTxt, class="type">color corBack, T InfoValue) if (class="kw">typename(T) == "class="type">class="kw">string") ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, (class="type">class="kw">string)InfoValue); else SetTextValue(szObjectName, (class="type">class="kw">double)InfoValue); class="macro">#class="kw">property copyright "Daniel Jose" class="macro">#include "C_Object_Base.mqh" class C_Object_BackGround : class="kw">public C_Object_Base { class="kw">public: class="type">void Create(class="type">class="kw">string szObjectName, class="type">color cor) { C_Object_Base::Create(szObjectName, OBJ_RECTANGLE_LABEL); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BORDER_TYPE, BORDER_FLAT); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_CORNER, CORNER_LEFT_UPPER); this.SetColor(szObjectName, cor); } class="kw">virtual class="type">void SetColor(class="type">class="kw">string szObjectName, class="type">color cor) { ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, cor); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR, cor); } };
C_Widget 主类如何把面板跑起来
系统真正干活的地方在头文件 C_Widget.mqh。开头只引入 C_Object_Edit 和 C_Object_BackGround 两个元素类,外加一个 C_Terminal 终端类实例,够用就好,不把无关头文件全拉进来。 宏替换在这里很关键:def_PrefixName 统一定为 "WidgetPrice",背景对象名用它拼成 WidgetPriceBackGround,单元素最大宽度 def_MaxWidth 锁死在 80 像素。宏只在函数内用完即 #undef,避免污染全局命名。 面板背景直接铺满子窗口整个区域,用纯色填充得到统一底图。编辑框对象 C_Object_Edit 靠宏批量生成,暂时定位并设好尺寸,代码可读性高且敲得少。 MT5 里根本不需要全局对象句柄——只要知道对象名,本地作用域就能操纵图表上已有对象。代价是每次调用函数都要复制最后一根柱线来探活,会带来微小延迟,后面再优化。 资产清单从文件读入,不用写扩展名,附件里给了 IBOV 成分股做模板。逐行读,空行跳过;某行资产在“市场报价”不存在就报哪一行出错,停读并返回,错误信息进“工具箱”。读完一次性存好,免得反复算。 类析构时自动删光所有对象、释放内存。初始化里要抓指标所在子窗口索引,不然对象会放错层。 自定义事件绕开了直接调函数的坑:指标里用参数当定时器触发 OnTime,里面只发一行 EventChartCustom 异步事件,平台转手丢给 OnChartEvent,再由 C_Widget 处理函数接消息。这样代码不阻塞等返回,且谁都能发、ChartEvent 必触发。 面板横向移动的数学看着绕,其实就两行:正值左→右、负值右→左、零不动。计数走 0–99 闭环,98+3 变 1、2-3 变 99,到边界从另一头续上。FOR 循环靠窗口边界判哪些元素该显示,累加宽度越界就折返,信息从一侧消失另一侧冒出。别用超过 ±1 的步长,否则跳动像抽风。
class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "Elements\C_Object_Edit.mqh" class="macro">#include "Elements\C_Object_BackGround.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ C_Terminal Terminal; class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define def_PrefixName "WidgetPrice" class="macro">#define def_NameObjBackGround def_PrefixName + "BackGround" class="macro">#define def_MaxWidth class="num">80 class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define def_CharSymbol "S"
「多品种面板的对象命名与背景绘制」
在 MT5 里做多品种监控面板,先得解决画布宽度自适应。macro_MaxPosition 用三元表达式判断:若终端宽度大于等于「品种数 × 单格最大宽度(def_MaxWidth)」就取终端宽,否则按品种数撑开,避免小窗下品种被挤掉。 对象名不能写死,macro_ObjectName 把子窗口号、前缀、区分符 A 与品种代号 B 拼成唯一串,防止不同图表实例的对象互相覆盖。 C_Widget 类里 m_Infos 结构存了配色与品种数组,st01 只放 szCode。CreateBackGround 先建背景对象再拉满 Terminal.GetWidth()/GetHeight(),等于给面板铺底层画布。 AddSymbolInfo 中的 macro_Create 宏负责按 def_MaxWidth * nSymbols 定位 X 轴、用 PositionAxleY 定 Y 轴,循环挂编辑框。外汇与贵金属波动大,面板仅作观察辅助,信号误判风险高,实盘须自担。
class="macro">#define def_CharPrice "P" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define macro_MaxPosition(Terminal.GetWidth() >= (m_Infos.nSymbols * def_MaxWidth) ? Terminal.GetWidth() : m_Infos.nSymbols * def_MaxWidth) class="macro">#define macro_ObjectName(A, B) (def_PrefixName + (class="type">class="kw">string)Terminal.GetSubWin() + A + "#" + B) class=class="str">"cmt">//+------------------------------------------------------------------+ class C_Widget { class="kw">protected: enum EventCustom {Ev_RollingTo}; class="kw">private : class="kw">struct st00 { class="type">color CorBackGround, CorSymbol, CorPrice; class="type">int nSymbols, MaxPositionX; class="kw">struct st01 { class="type">class="kw">string szCode; }Symbols[]; }m_Infos; class="type">void CreateBackGround(class="type">void) { C_Object_BackGround backGround; backGround.Create(def_NameObjBackGround, m_Infos.CorBackGround); backGround.Size(def_NameObjBackGround, Terminal.GetWidth(), Terminal.GetHeight()); } class="type">void AddSymbolInfo(class="kw">const class="type">class="kw">string szArg, class="kw">const class="type">bool bRestore = class="kw">false) { class="macro">#define macro_Create(A, B, C) { \ edit.Create(A, m_Infos.CorSymbol, m_Infos.CorBackGround, B); \ edit.PositionAxleX(A, def_MaxWidth * m_Infos.nSymbols); \ edit.PositionAxleY(A, C); \