图形界面 XI: 表格单元中的文本编辑框和组合框 (统合构建15)·综合运用
把文本框和组合框塞进表格单元格
在 MT5 的 MQL 应用里,表格控件一旦能承载不同子控件,数据交互的灵活度会明显提升。最直观的参照就是终端里 MQL 程序“输入参数”页和策略测试器“参数”页——它们早就用上了单元格内嵌编辑框与下拉框的做法。 要在自建表格里复现这种效果,先得在 ENUM_TYPE_CELL 枚举补两个类型:CELL_COMBOBOX(下拉框单元)和 CELL_EDIT(文本编辑单元),对应值分别是 3 和 4。这两个标识告诉 CTable 类,哪些格子该挂 CTextEdit、哪些该挂 CComboBox。 双击单元格时才动态生成控件,是这里的实现思路。类内部用 m_edit_state、m_combobox_state 两个私有布尔字段记录当前表是否含有这两类单元;建表时若没指定对应类型,控件根本不会实例化,省资源也避免空指针。 文本框有个易忽略的细节:单行文本框可开启自动全选模式,调用 CTextBox::AutoSelectionMode() 启用后,激活时原有文本被全选、光标跳到行尾,方便整体替换。整表共用一个文本框对象,所以每次双击不同宽度的格子都要用 ChangeSize() 重设尺寸,否则框会错位。 下拉框单元则要在 CTCell 结构里加一个字符串数组存列表项,以及一个整型字段存选中项索引。通过 CTable::AddValueList() 把列表和默认选中项(缺省索引 0)灌进去,双击时由 CheckPressedCombobox() 按单元格左上角坐标定位并重建列表。外汇与贵金属 GUI 自动化涉及实时报价交互,属于高风险操作环境,参数调试请在模拟盘先验证。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 枚举表格单元类型 | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_TYPE_CELL { CELL_SIMPLE =class="num">0, CELL_BUTTON =class="num">1, CELL_CHECKBOX =class="num">2, CELL_COMBOBOX =class="num">3, CELL_EDIT =class="num">4 }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 用来创建渲染表格的类 | class=class="str">"cmt">//+------------------------------------------------------------------+ class CTable : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 存在含有文本编辑框和组合框的表格单元 class="type">bool m_edit_state; class="type">bool m_combobox_state; class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- 设置/获取单元类型 class="type">void CellType(class="kw">const class="type">uint column_index,class="kw">const class="type">uint row_index,class="kw">const ENUM_TYPE_CELL type); };
◍ 表格单元格的控件类型与点击分发
CTable 里每个单元格都不是死文本,类型由 ENUM_TYPE_CELL 决定:CELL_SIMPLE 是纯显示,CELL_EDIT 挂文本编辑框,CELL_COMBOBOX 挂下拉框。CellType() 在赋类型时会顺手把 m_edit_state / m_combobox_state 置真,后续创建控件前靠这两个布尔避免重复 new。 [CODE] void CTable::CellType(const uint column_index,const uint row_index,const ENUM_TYPE_CELL type) { //--- 检查数组超界 if(!CheckOutOfRange(column_index,row_index)) return; //--- 设置单元类型 m_columns[column_index].m_rows[row_index].m_type=type; //--- 文本编辑框存在的标志 if(type==CELL_EDIT && !m_edit_state) m_edit_state=true; //--- 组合框存在的标志 else if(type==CELL_COMBOBOX && !m_combobox_state) m_combobox_state=true; } [/CODE] 逐行看:前两句做越界保护,越界直接 return 不写内存;中间把 type 写进对应行列的 m_type;后面两个 if/else if 只在对应控件状态为假时才翻转,意味着多次调用 CellType(CELL_EDIT) 不会重复触发创建标记。 类里 GetTextEditPointer() 和 GetComboboxPointer() 用 ::GetPointer 返回控件对象指针,外部就能直接操控单元格里的编辑框或下拉框内容。CheckCellElement() 则在鼠标点击时先判 m_type==CELL_SIMPLE 就直接 false 离开,否则进 switch 调 CheckPressedEdit / CheckPressedCombobox——也就是说简单单元格完全不消耗点击处理逻辑。 [CODE] bool CTable::CheckCellElement(const int column_index,const int row_index,const bool double_click=false) { //--- 如果单元格内没有控件, 离开 if(m_columns[column_index].m_rows[row_index].m_type==CELL_SIMPLE) return(false); //--- switch(m_columns[column_index].m_rows[row_index].m_type) { ... case CELL_EDIT : { if(!CheckPressedEdit(column_index,row_index,double_click)) return(false); break; } case CELL_COMBOBOX : { if(!CheckPressedCombobox(column_index,row_index,double_click)) return(false); break; } } return(true); } [/CODE] 在 MT5 自建面板时,若发现编辑框点了没反应,优先查 CellType 是否在 CreateEdit 之前调用、且 m_edit_state 确实被置位;少这一步,控件指针虽能取到但内部事件不会分发。
class="type">void CTable::CellType(class="kw">const class="type">uint column_index,class="kw">const class="type">uint row_index,class="kw">const ENUM_TYPE_CELL type) { class=class="str">"cmt">//--- 检查数组超界 if(!CheckOutOfRange(column_index,row_index)) class="kw">return; class=class="str">"cmt">//--- 设置单元类型 m_columns[column_index].m_rows[row_index].m_type=type; class=class="str">"cmt">//--- 文本编辑框存在的标志 if(type==CELL_EDIT && !m_edit_state) m_edit_state=true; class=class="str">"cmt">//--- 组合框存在的标志 else if(type==CELL_COMBOBOX && !m_combobox_state) m_combobox_state=true; } class CTable : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 用于创建表格的对象 CTextEdit m_edit; CComboBox m_combobox; class=class="str">"cmt">//--- class="kw">private: class="type">bool CreateEdit(class="type">void); class="type">bool CreateCombobox(class="type">void); class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- 返回控件指针 CTextEdit *GetTextEditPointer(class="type">void) { class="kw">return(::GetPointer(m_edit)); } CComboBox *GetComboboxPointer(class="type">void) { class="kw">return(::GetPointer(m_combobox)); } }; class="type">bool CTable::CheckCellElement(class="kw">const class="type">int column_index,class="kw">const class="type">int row_index,class="kw">const class="type">bool double_click=class="kw">false) { class=class="str">"cmt">//--- 如果单元格内没有控件, 离开 if(m_columns[column_index].m_rows[row_index].m_type==CELL_SIMPLE) class="kw">return(class="kw">false); class=class="str">"cmt">//--- class="kw">switch(m_columns[column_index].m_rows[row_index].m_type) { ... class=class="str">"cmt">//--- 如果这是一个含有文本编辑框的单元格 case CELL_EDIT : { if(!CheckPressedEdit(column_index,row_index,double_click)) class="kw">return(class="kw">false); class="kw">break; } class=class="str">"cmt">//--- 如果这是一个含有组合框的单元格 case CELL_COMBOBOX : { if(!CheckPressedCombobox(column_index,row_index,double_click)) class="kw">return(class="kw">false); class="kw">break; } } class="kw">return(true); }
「多行文本框类的选取与尺寸控制」
在 MT5 自定义指标面板里做可编辑多行文本框,核心类 CTextBox 继承自 CElement,私有成员 m_auto_selection_mode 决定鼠标聚焦时是否自动全选已有文本,对外用 AutoSelectionMode(bool) 一行赋值切换。 SelectAllText() 先取首行字符数 ArraySize(m_lines[0].m_symbol),把选中区间四个索引全部钉死:起止行都是 0,符号从 0 到 symbols_total,等于把第一行整行吞进选区。随后 HorizontalScrolling() 把水平滑块推到底,SetTextCursor(symbols_total,0) 把光标塞到行尾,用户点进来就能直接覆盖输入。 ActivateTextBox() 只做一件事:调 OnClickTextBox(m_textbox.Name()),用控件名触发点击回调,相当于用代码模拟一次鼠标点中。ChangeSize(uint,uint) 则分三步——改主区尺寸、重算文本框内部布局、再下发给底层 ChangeTextBoxSize(),x/y 以像素传入,调完界面不会闪但需配合重绘。 外汇与贵金属 GUI 工具开发属高风险辅助环节,参数误写可能导致面板卡死;建议在策略测试器的可视化模式里单步验证这几个方法。
class CTextBox : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 自动文本选择模式 class="type">bool m_auto_selection_mode; class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- 自动文本选择模式 class="type">void AutoSelectionMode(class="kw">const class="type">bool state) { m_auto_selection_mode=state; } }; class CTextBox : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 选择所有文本 class="type">void SelectAllText(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 选择所有文本 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CTextBox::SelectAllText(class="type">void) { class=class="str">"cmt">//--- 获取字符数组的大小 class="type">int symbols_total=::ArraySize(m_lines[class="num">0].m_symbol); class=class="str">"cmt">//--- 设置选择文本的索引 m_selected_line_from =class="num">0; m_selected_line_to =class="num">0; m_selected_symbol_from =class="num">0; m_selected_symbol_to =symbols_total; class=class="str">"cmt">//--- 将水平滚动条的滑块移动到最后的位置 HorizontalScrolling(); class=class="str">"cmt">//--- 将光标移动到行尾 SetTextCursor(symbols_total,class="num">0); } class CTextBox : class="kw">public CElement { class="kw">public: class=class="str">"cmt">//--- 激活文本框 class="type">void ActivateTextBox(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 激活文本框 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CTextBox::ActivateTextBox(class="type">void) { OnClickTextBox(m_textbox.Name()); } class CTextBox : class="kw">public CElement { class="kw">public: class=class="str">"cmt">//--- 调整大小 class="type">void ChangeSize(class="kw">const class="type">uint x_size,class="kw">const class="type">uint y_size); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 调整大小 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CTextBox::ChangeSize(class="kw">const class="type">uint x_size,class="kw">const class="type">uint y_size) { class=class="str">"cmt">//--- 设置新的大小 ChangeMainSize(x_size,y_size); class=class="str">"cmt">//--- 计算文本框的大小 CalculateTextBoxSize(); class=class="str">"cmt">//--- 为文本框设置新尺寸 ChangeTextBoxSize(); }
表格单元格双击编辑的实现细节
在 MT5 自定义表格控件里,单元格文本编辑不是单击触发,而是依赖双击事件。CTable 类用 m_last_edit_row_index 和 m_last_edit_column_index 两个私有整型记住最近被编辑的行列,避免后续写回数据时错位。 CheckPressedEdit 方法第一句就判断 double_click 参数,非双击直接 return(false),说明交互模型刻意排除误触。紧接着把行列索引存进成员变量,再根据表格对象的 XOFFSET / YOFFSET 算出编辑框相对图表的偏移量。 编辑框的定位用 XGap / YGap 设置,其中 Y 方向要叠加表头高度 m_header_y_size(若开启了 m_show_headers),否则编辑框会盖错行。单元格宽高由列边界差 +1 和 m_cell_y_size +1 得出,再调用 ChangeSize 同步文本框尺寸。 最后把原单元格完整文本 m_full_text 塞进编辑框,ActivateTextBox 激活、MouseFocus(true) 抢焦点、Reset 显示,Redraw 重绘图表。整套流程跑完才 return(true),用户随后输入结束会走 CHARTEVENT_CUSTOM+ON_END_EDIT 分支由 OnEndEditCell 收尾。 想验证这套逻辑,直接把下面代码贴进 MT5 的 CTable 派生类,双击任意单元格看编辑框是否精准落位;外汇与贵金属图表上挂此类控件需注意高频重绘带来的卡顿风险。
class CTable : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 最后编辑单元格的列和行的索引 class="type">int m_last_edit_row_index; class="type">int m_last_edit_column_index; class=class="str">"cmt">//--- class="kw">private: class=class="str">"cmt">//--- 检查是否在带有文本框的单元格上点击 class="type">bool CheckPressedEdit(class="kw">const class="type">int column_index,class="kw">const class="type">int row_index,class="kw">const class="type">bool double_click=class="kw">false); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 检查是否在单元格的文本框上单击 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CTable::CheckPressedEdit(class="kw">const class="type">int column_index,class="kw">const class="type">int row_index,class="kw">const class="type">bool double_click=class="kw">false) { class=class="str">"cmt">//--- 如果不是双击, 离开 if(!double_click) class="kw">return(class="kw">false); class=class="str">"cmt">//--- 保存索引 m_last_edit_row_index =row_index; m_last_edit_column_index =column_index; class=class="str">"cmt">//--- 沿两个坐标轴的偏移 class="type">int x_offset=(class="type">int)m_table.GetInteger(OBJPROP_XOFFSET); class="type">int y_offset=(class="type">int)m_table.GetInteger(OBJPROP_YOFFSET); class=class="str">"cmt">//--- 设置新坐标 m_edit.XGap(m_columns[column_index].m_x-x_offset); m_edit.YGap(m_rows[row_index].m_y+((m_show_headers)? m_header_y_size : class="num">0)-y_offset); class=class="str">"cmt">//--- 大小 class="type">int x_size =m_columns[column_index].m_x2-m_columns[column_index].m_x+class="num">1; class="type">int y_size =m_cell_y_size+class="num">1; class=class="str">"cmt">//--- 设置大小 m_edit.GetTextBoxPointer().ChangeSize(x_size,y_size); class=class="str">"cmt">//--- 设置表格单元中的值 m_edit.SetValue(m_columns[column_index].m_rows[row_index].m_full_text); class=class="str">"cmt">//--- 激活文本框 m_edit.GetTextBoxPointer().ActivateTextBox(); class=class="str">"cmt">//--- 设置焦点 m_edit.GetTextBoxPointer().MouseFocus(true); class=class="str">"cmt">//--- 显示文本框 m_edit.Reset(); class=class="str">"cmt">//--- 重绘图表 m_chart.Redraw(); class="kw">return(true); } class CTable : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 处理单元格中数值输入结束 class="type">bool OnEndEditCell(class="kw">const class="type">int id); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 事件处理器 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CTable::OnEvent(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) { ... class=class="str">"cmt">//--- 处理输入结束事件 if(id==CHARTEVENT_CUSTOM+ON_END_EDIT) { if(OnEndEditCell((class="type">int)lparam)) class="kw">return; class=class="str">"cmt">//---
◍ 表格单元格编辑与失焦收尾的逻辑
在 MT5 自定义表格控件里,单元格数值编辑的落账靠 OnEndEditCell 收口。它先比对控件 id 与 m_edit_state 标志,两者任一不对就直接返回 false,避免误写其他元素的事件。 确认无误后,用 SetValue(m_last_edit_column_index,m_last_edit_row_index,m_edit.GetValue(),0,true) 把文本框里的字符串写回对应行列,随后 Update() 刷新内部状态,再 DeactivateTextBox() 与 Hide() 收掉输入框,最后 m_chart.Redraw() 重绘图表。整套动作若成功返回 true。 左键状态切换走 CHARTEVENT_CUSTOM+ON_CHANGE_MOUSE_LEFT_BUTTON 分支,这里会顺手调 CheckAndHideEdit 与 CheckAndHideCombobox,把可能悬在表面的编辑控件藏掉。CheckAndHideEdit 自身先判断 m_edit_state 与 IsVisible,若文本框不可见直接 return;接着 CheckMouseFocus 测焦点,一旦发现失焦且左键按着,就 DeactivateTextBox + Hide + Redraw,等于点空白处自动退出编辑。 含组合框的单元格在 CTCell 结构里挂了 m_value_list[] 存选项、m_selected_item 记当前选中项。想验证就去 Standard Library 的 CTable 派生类里断点 OnEndEditCell,改一个单元格值看 Redraw 前后图表是否同步。外汇与贵金属图表挂这类自定义控件,遇快速波动可能漏事件,属高风险调试场景,参数改动前先在模拟盘跑通。
class="type">bool CTable::OnEndEditCell(class="kw">const class="type">int id) { if(id!=CElementBase::Id() || !m_edit_state) class="kw">return(class="kw">false); SetValue(m_last_edit_column_index,m_last_edit_row_index,m_edit.GetValue(),class="num">0,true); Update(); m_edit.GetTextBoxPointer().DeactivateTextBox(); m_edit.Hide(); m_chart.Redraw(); class="kw">return(true); } class="type">void CTable::CheckAndHideEdit(class="type">void) { if(!m_edit_state || !m_edit.IsVisible()) class="kw">return; m_edit.GetTextBoxPointer().CheckMouseFocus(); if(!m_edit.GetTextBoxPointer().MouseFocus() && m_mouse.LeftButtonState()) { m_edit.GetTextBoxPointer().DeactivateTextBox(); m_edit.Hide(); m_chart.Redraw(); } }
「正文」
}; <span class="keyword">class</span> CTable : <span class="keyword">public</span> CElement { <span class="keyword">public</span>: <span class="comment">//--- 将数值列表添加到组合框</span> <span class="keyword">void</span> AddValueList(<span class="keyword">const</span> <span class="keyword">uint</span> column_index,<span class="keyword">const</span> <span class="keyword">uint</span> row_index,<span class="keyword">const</span> <span class="keyword">string</span> &array[],<span style="background-color:rgb(255, 242, 153);"><span class="keyword">const</span> <span class="keyword">uint</span> selected_item=<span class="number">0</span></span>); }; <span class="comment">//+------------------------------------------------------------------+</span>
| <span class="comment">// | 将数值列表添加到组合框 | </span> |
|---|
<span class="comment">//+------------------------------------------------------------------+</span> <span class="keyword">void</span> CTable::AddValueList(<span class="keyword">const</span> <span class="keyword">uint</span> column_index,<span class="keyword">const</span> <span class="keyword">uint</span
表格单元格下拉项选中后的回写链路
在自定义表格控件里,单元格组合框的选项点击不是孤立事件,它会顺着 CTable::OnClickComboboxItem 把选中结果落回数据模型。先校验事件标识符是否匹配本元素且组合框处于激活态,否则直接返回 false,避免误写其他单元格。 回写时取 m_last_edit_column_index 与 m_last_edit_row_index 定位上次编辑的单元格,把列表视图的 SelectedItemIndex() 存进 m_columns[c].m_rows[r].m_selected_item,再用 SetValue(c,r,m_combobox.GetValue(),0,true) 把显示值刷进单元。注意第三个参数 0 是列偏移、true 表示立即重绘。 下拉展开前那一段也值得看:用 ArraySize 取 m_value_list 长度传给 Rebuilding(total),再 for 循环 SetValue 逐行灌入,最后 SelectItem(index) 还原原选中项。外汇与贵金属 GUI 面板高频交互下,这套同步逻辑若漏了 EventChartCustom 广播,子窗口状态可能和主表不一致,属高风险调试点。 下面这段是下拉填充与事件回写的直接代码,可粘进 MT5 头文件对照断点。
m_combobox.GetListViewPointer().ChangeSize(x_size,y_size); class=class="str">"cmt">//--- 设置单元列表大小 class="type">int total=::ArraySize(m_columns[column_index].m_rows[row_index].m_value_list); m_combobox.GetListViewPointer().Rebuilding(total); class=class="str">"cmt">//--- 设置来自单元格的列表 for(class="type">int i=class="num">0; i<total; i++) m_combobox.GetListViewPointer().SetValue(i,m_columns[column_index].m_rows[row_index].m_value_list[i]); class=class="str">"cmt">//--- 设置来自单元格的项目 class="type">int index=m_columns[column_index].m_rows[row_index].m_selected_item; m_combobox.SelectItem(index); class=class="str">"cmt">//--- 更新控件 m_combobox.GetButtonPointer().MouseFocus(true); m_combobox.GetButtonPointer().Update(true); m_combobox.GetListViewPointer().Update(true); class=class="str">"cmt">//--- 显示文本框 m_combobox.Reset(); class=class="str">"cmt">//--- 重绘图表 m_chart.Redraw(); class=class="str">"cmt">//--- 发送有关图形界面变化的消息 ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),class="num">0,""); class="kw">return(true); } class CTable : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 处理单元格下拉列表中项目的选择 class="type">bool OnClickComboboxItem(class="kw">const class="type">int id); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 事件处理器 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CTable::OnEvent(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) { ... class=class="str">"cmt">//--- 处理列表中选择项目的事件 if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM) { if(OnClickComboboxItem((class="type">int)lparam)) class="kw">return; class=class="str">"cmt">//--- class="kw">return; } ... } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 处理单元格组合框中项目的选择 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CTable::OnClickComboboxItem(class="kw">const class="type">int id) { class=class="str">"cmt">//--- 如果 (class="num">1) 标识符不匹配, 或 (class="num">2) 没有含组合框的单元格, 离开 if(id!=CElementBase::Id() || !m_combobox_state) class="kw">return(class="kw">false); class=class="str">"cmt">//--- 最后编辑的单元格索引 class="type">int c=m_last_edit_column_index; class="type">int r=m_last_edit_row_index; class=class="str">"cmt">//--- 存储单元格中选择项的索引 m_columns[c].m_rows[r].m_selected_item=m_combobox.GetListViewPointer().SelectedItemIndex(); class=class="str">"cmt">//--- 为表格单元设置数值 SetValue(c,r,m_combobox.GetValue(),class="num">0,true); Update(); class="kw">return(true); }
◍ 用表格与文本框搭一个事件回显样例
想直接验证控件事件机制,可建一个 MQL 程序把 CTable 和 CTextBox 拉起来:第一列全塞 CELL_CHECKBOX,第二列用 CELL_EDIT,第三列在 CELL_COMBOBOX 与 CELL_EDIT 之间交替,第四列放 CELL_BUTTON。 自定义类的事件处理接到消息后,把触发信息写进多行文本框,这样勾选、输入、下拉、点按钮的动作都能实时看到回显,省得猜底层有没有收到。 原文随附的归档里有完工样例,MT5 里加载后改一列单元格类型就能观察事件流差异,外汇与贵金属品种上跑这类界面工具同样面临点差滑点等高风险,仅作机制验证用途。
「把工具请下神坛」
这套 GUI 函数库眼下已支持在表格里直接放文本框与组合框,窗体还能拖边框拉满全屏或手动改尺寸,相当于把面板开发的体力活压到了最低。代码以 451.34 KB 的 zip 包免费放出,商用项目也能直接塞进去改。 有读者在 2021 年 3 月反馈,调 table.AddRow(1) 或 table.Clear() 时 Element.mqh 报“无效指针访问(561,23)”,说明在旧工程里接库得先核对指针生命周期。外汇与贵金属挂这种界面做单,杠杆风险高,崩一下界面可能就漏了报价。 真要落地,先开 MT5 把 zip 解到 Include 目录,建个 EA 拖个表格出来,看文本框回传是不是比手写 CForm 省事,再决定要不要进实盘。