开发回放系统(第 75 部分):新 Chart Trade(二)·综合运用
◍ 浮动面板的状态恢复与析构清理
当 RestoreState() 返回 false 时,说明没有可复用的历史布局,代码会强制写入一套默认参数:杠杆取当前账户值,日交易标记置 true,财务止盈止损沿用传入量,窗口最大化开启,左上角坐标锁在 x=115、y=64。随后以 170×210 的尺寸创建 RAD 浮动窗,并套用模板调整。 析构函数 ~C_ChartFloatingRAD() 负责收尾:先 ChartRedraw() 刷新,再 ObjectsDeleteAll() 按终端 ID 和对象名清理图表附属物件;若本次运行未触发保存状态,就 FileDelete() 删掉临时模板文件,最后 delete 鼠标事件对象,避免 MT5 退出时残留句柄。 SaveState() 用了一个联合体技巧:uCast_Double 把 4 个 16 位坐标值(x/y/minx/miny)压进一个 double,通过 macro_GlobalVariable 写进终端全局变量,键名由 macro_NameGlobalVariable("POST") 生成。这样下次加载时 RestoreState() 读回该 double 就能还原窗口位置——外汇与贵金属品种的高杠杆下,面板坐标错乱可能让你误触重仓,建议在 MT5 里跑一遍并改 m_Info.minx 的 115 看吸附表现。
if (!RestoreState()) { m_Info.Leverage = Leverage; m_Info.IsDayTrade = true; m_Info.FinanceTake = FinanceTake; m_Info.FinanceStop = FinanceStop; m_Info.IsMaximized = true; m_Info.minx = m_Info.x = class="num">115; m_Info.miny = m_Info.y = class="num">64; } CreateWindowRAD(class="num">170, class="num">210); AdjustTemplate(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ ~C_ChartFloatingRAD() { ChartRedraw(); ObjectsDeleteAll(GetInfoTerminal().ID, m_Info.szObj_Chart); if (!m_Info.IsSaveState) FileDelete(m_Info.szFileNameTemplate); class="kw">delete m_Mouse; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void SaveState(class="type">void) { class="macro">#define macro_GlobalVariable(A, B) if (GlobalVariableTemp(A)) GlobalVariableSet(A, B); uCast_Double info; info._16b[class="num">0] = m_Info.x; info._16b[class="num">1] = m_Info.y; info._16b[class="num">2] = m_Info.minx; info._16b[class="num">3] = m_Info.miny; macro_GlobalVariable(macro_NameGlobalVariable("POST"), info.dValue); m_Info.IsSaveState = true; class="macro">#undef macro_GlobalVariable } class=class="str">"cmt">//+------------------------------------------------------------------+
卸载时的清理与状态保存分支
指标或 EA 被移除时,OnDeinit 拿到的 reason 参数决定了该做哪种收尾。上面这段 MT5 代码把初始化失败和图表属性变更分成了两个独立分支处理。 当 reason 等于 REASON_INITFAILED,直接调 ChartIndicatorDelete 按 def_ShortName 删掉当前图表零号子窗口里的指标实例,避免半初始化对象留在图上。若是 REASON_CHARTCHANGE,则触发 chart 指针指向对象的 SaveState 方法把界面状态落盘,再退出 switch。 两个 case 都 break 后,第 53 行无条件 delete chart 释放堆内存。注意宏 macro_NameGlobalVariable 与 macro_CloseIndicator 在文件尾用 #undef 取消定义,防止污染其他包含单元——这套写法在 260~392 行类定义收尾后紧接着出现,说明全局宏生命周期被严格框在单文件内。 DispatchMessage 函数体在原文里只留了「内部代码下篇见」的占位,说明消息分发逻辑尚未展开,当前节重点就是搞清卸载路径不漏资源。
class="type">void DispatchMessage(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam) { .... The internal code of this procedure will be seen in the next article } class=class="str">"cmt">//+------------------------------------------------------------------+ }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#undef macro_NameGlobalVariable class="macro">#undef macro_CloseIndicator class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { class="kw">switch (reason) { case REASON_INITFAILED: ChartIndicatorDelete(ChartID(), class="num">0, def_ShortName); class="kw">break; case REASON_CHARTCHANGE: (*chart).SaveState(); class="kw">break; } class="kw">delete chart; }
「先把能跑的类收进自己的工具箱」
第 260 行声明、第 386 行实现的 DispatchMessage 过程这次先不拆,它负责把用户和图表上浮动面板的交互事件转成开平仓指令,单独讲太占篇幅,留到和下一篇的另一个项目对照看更清楚。 眼下你能做的实事:把 C_ChartFloatingRAD 类的代码存成独立文件,直接挂进 MT5 图表跑一遍。按 CTRL+B 打开对象列表,只会看到 1 个对象,但图表上那些可点的按钮都是它隐性管理的——这就是 RAD 思路的妙处。 顺手把附件 Anexo.zip(163.48 KB)下下来,里面指标能直接加载交互,配合视频看一遍比读文档快。外汇和贵金属这类品种波动大、杠杆高,跑任何第三方面板前先在模拟账户验一遍事件响应,别拿真仓试未知逻辑。 下一篇会填第 260 到 386 行之间的坑,在那之前,把这套已经能跑的代码摸熟就够了。