利用标准库类创建您自己的市场报价·进阶篇
(2/3)·从 CChartObjectLabel 到自动命名,手把手把标准库类改造成可动态刷新的 EA 状态面板
接上篇,我们继续深挖标准库里几个文本控件的底层类。很多人直接调 Label 却卡在坐标错位和重名冲突,本质是没有吃透 CChartObject 的后代封装。
◍ 标签钉死在窗口还是跟着K线跑
MT5 里文本类对象有两条路线:OBJ_TEXT 绑价格/时间坐标,滚动时贴着 K 线走;OBJ_LABEL 绑窗口像素坐标,滚动时原地不动。做悬浮提示、固定角标就得用后者,否则一拖图表全乱。 CChartObjectLabel 继承自 CChartObjectText,但把定位逻辑换了。它用 X_Distance / Y_Distance 控制距锚点的像素偏移,Corner 决定锚点落在窗口四个角中的哪一个(ENUM_BASE_CORNER)。Time 和 Price 被直接返回 false——说明这东西根本不认价格坐标。 下面这段是类声明的原样,逐行看能少踩坑:X_Distance() const 只读当前 X 像素距;X_Distance(int X) 写距;Y 同理。X_Size / Y_Size 只提供只读,尺寸由系统按字体算。Corner() const 读锚角,Corner(ENUM_BASE_CORNER) 写锚角。Create 接收 chart_id、name、window、X、Y 真正生成对象。Type() 写死返回 OBJ_LABEL,Load/Save 管序列化。 外汇和贵金属波动剧烈、杠杆高风险大,用标签做价签前先在模拟盘验证锚角行为,避免实盘遮挡关键形态。
class CChartObjectLabel : class="kw">public CChartObjectText { class="kw">public: class="type">int X_Distance() class="kw">const; class="type">bool X_Distance(class="type">int X); class="type">int Y_Distance() class="kw">const; class="type">bool Y_Distance(class="type">int Y); class="type">int X_Size() class="kw">const; class="type">int Y_Size() class="kw">const; ENUM_BASE_CORNER Corner() class="kw">const; class="type">bool Corner(ENUM_BASE_CORNER corner); class="type">bool Time(class="type">class="kw">datetime time) { class="kw">return(class="kw">false); } class="type">bool Price(class="type">class="kw">double price) { class="kw">return(class="kw">false); } class="type">bool Create(class="type">long chart_id,class="type">class="kw">string name,class="type">int window,class="type">int X,class="type">int Y); class="kw">virtual class="type">int Type() class="kw">const { class="kw">return(OBJ_LABEL); } class="kw">virtual class="type">bool Save(class="type">int file_handle); class="kw">virtual class="type">bool Load(class="type">int file_handle); };
可编辑输入框对象的类封装与限制
在图表对象类层级里,CChartObjectEdit 承接自 CChartObjectLabel,专门负责生成 OBJ_EDIT 类型的输入字段。它和文本标签一样用像素坐标绑定到图表,但多了尺寸与交互控制,适合做参数录入或状态显示面板。 输入字段和标签的核心差异有三点:前者可用 X_Size / Y_Size 以屏幕像素硬性限定宽高,标签则自动撑开以显示完整文本;前者能用 ReadOnly 方法切换是否允许手动改字,后者无此概念;前者还能通过 BackColor 改背景色。 角度属性在这里被直接封死——Angle 方法写死返回 false,意味着输入框只能水平摆放,想做斜向输入区没必要折腾了。 下面这段是类的公开接口原貌,注意 Create 里 sizeX、sizeY 就是像素级宽高,调它之前得先想清楚输入框在你图表上的占位:
class CChartObjectEdit : class="kw">public CChartObjectLabel { class="kw">public: class="type">bool X_Size(class="type">int X); class="type">bool Y_Size(class="type">int Y); class="type">class="kw">color BackColor() class="kw">const; class="type">bool BackColor(class="type">class="kw">color new_color); class="type">bool ReadOnly() class="kw">const; class="type">bool ReadOnly(class="type">bool flag); class="type">bool Angle(class="type">class="kw">double angle) { class="kw">return(class="kw">false); } class="type">bool Create(class="type">long chart_id,class="type">class="kw">string name,class="type">int window,class="type">int X,class="type">int Y,class="type">int sizeX,class="type">int sizeY); class="kw">virtual class="type">int Type() class="kw">const { class="kw">return(OBJ_EDIT); } class="kw">virtual class="type">bool Save(class="type">int file_handle); class="kw">virtual class="type">bool Load(class="type">int file_handle); };
「在图表上造一个真按钮:CChartObjectButton 怎么用」
MT5 标准库里,CChartObjectButton 挂在 CChartObjectEdit 之下,专门用来在图表上画出一个 Windows 风格的下压按钮,而不是普通的编辑框。它和 OBJ_EDIT 输入控件最大的区别,是多了读取和修改「按下/弹起」状态的方法,并且生成的真实图形对象类型是 OBJ_BUTTON。 这个类对外暴露了两个 State() 重载:无参版本返回当前按钮状态(bool),带参版本直接写入新状态。Type() 被重写为固定返回 OBJ_BUTTON,所以你 new 出来的对象在图表上就是按钮而非文本域。 下面这段是类声明的核心骨架,注意它全是虚函数或 inline 实现,继承链上 CObject 才是所有标准库的基类: 别把继承当黑盒 CChartObjectButton 只管按钮自身,列表管理得靠上层的 CList 接 CObject。开 MT5 新建 EA,把按钮塞进图表后,用 ChartRedraw 配合 State() 轮询,就能做出可点击的面板控件,外汇和贵金属图表叠加这类 UI 时需注意点击事件可能受报价刷新干扰,属高风险操作环境。
class CChartObjectButton : class="kw">public CChartObjectEdit { class="kw">public: class="type">bool State() class="kw">const; class="type">bool State(class="type">bool state); class="kw">virtual class="type">int Type() class="kw">const { class="kw">return(OBJ_BUTTON); } class="kw">virtual class="type">bool Save(class="type">int file_handle); class="kw">virtual class="type">bool Load(class="type">int file_handle); };
◍ 给文本对象类加一层结构化输出
前面梳理过图表文本图形对象的标准类层级,现在直接在这一层上面派生新类,把‘标题 + 信息文本’的两段式结构固化下来。对多数轻量看盘需求来说,这种结构足够清晰,不需要更复杂的嵌套。 实际落点可以看一个交易品种参数指标:它用六个字段铺开信息,顶部平铺字段在示例里留空,其余字段都是‘标题 + 文本’的配对。源文件 PricelInfo.mq5 里能翻到完整实现,开 MT5 加载后图表右上角即出结构化块。 贵金属与外汇波动剧烈,这类只读信息面板仅作辅助,不构成任何方向暗示,实盘请自担高风险。
用字段坐标摆脱像素重算
在 MT5 图表上堆多个文本标签时,直接用屏幕像素定坐标是坑。窗口一缩放或换分辨率,所有对象的像素位置全乱,得逐个重算才能维持原相对排布。 更稳的做法是把图表窗口切成等尺寸矩形网格,每个格子给一个横向、纵向编号。这样坐标体系与屏幕物理分辨率脱钩,指定一次就固定了。 MQL5 里建图形文本对象时,可设网格的最大行列数,再把目标格子的行列号当参数传进去。类内部会在分辨率变动时自动换算像素,交易者不用再管重绘。外汇与贵金属波动剧烈、高风险,这类定位只解决布局麻烦,不预示任何价格走向。
「给图表对象造不撞名的流水号」
在 MT5 图表上批量画文本,最烦的不是画图本身,而是每次都要手写一个不重复的对象名。一旦重名,后创建的对象会顶掉前面的,定位和管理直接乱套。
| 比较稳的做法是用「GMT日期时间 + 毫秒计数」拼字符串:TimeToString(TimeGMT(), TIME_DATE | TIME_MINUTES | TIME_SECONDS) 给出到秒的时间段,DoubleToString(GetTickCount(),0) 给出系统启动后的毫秒数。理论上同一秒内能拉出极多不重复前缀。 |
|---|
但 GetTickCount() 在连续多次调用时极可能返回相同值——操作系统和 CPU 计时器不是连续刷新的。所以光靠时间不够,还得在生成逻辑里做自增兜底,并额外用 ObjectFind 查重。 下面这个函数把静态计数器和查重都封进去了。注意它的硬上限:每秒最多生成 UINT_MAX(4294967295)个独立名,实战里基本碰不到这堵墙。 别把毫秒当绝对时钟 GetTickCount 的分辨率受系统节拍限制,紧密循环里两次取值相等是常态。代码里用 prev_count 自增来消化冲突,而不是假设时间一定往前走,这才是能在实盘脚本里跑稳的关键。
class="type">class="kw">string GetUniqName() { class="kw">static class="type">uint prev_count = class="num">0; class="type">uint count = GetTickCount(); class="kw">while(class="num">1) { if(prev_count == UINT_MAX) { prev_count = class="num">0; } if(count <= prev_count) { prev_count++; count = prev_count; } else { prev_count = count; } class=class="str">"cmt">// 确认没有同样名称的对象: class="type">class="kw">string name = TimeToString(TimeGMT(), TIME_DATE|TIME_MINUTES|TIME_SECONDS)+" "+DoubleToString(count, class="num">0); if(ObjectFind(class="num">0, name) < class="num">0) { class="kw">return(name); } } class="kw">return(NULL); }