图形界面 XI: 表格单元中的文本编辑框和组合框 (统合构建15)·综合运用
📘

图形界面 XI: 表格单元中的文本编辑框和组合框 (统合构建15)·综合运用

第 3/3 篇

把文本框和组合框塞进表格单元格

在 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 自动化涉及实时报价交互,属于高风险操作环境,参数调试请在模拟盘先验证。

MQL5 / C++
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 确实被置位;少这一步,控件指针虽能取到但内部事件不会分发。

MQL5 / C++
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 工具开发属高风险辅助环节,参数误写可能导致面板卡死;建议在策略测试器的可视化模式里单步验证这几个方法。

MQL5 / C++
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 派生类,双击任意单元格看编辑框是否精准落位;外汇与贵金属图表上挂此类控件需注意高频重绘带来的卡顿风险。

MQL5 / C++
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 前后图表是否同步。外汇与贵金属图表挂这类自定义控件,遇快速波动可能漏事件,属高风险调试场景,参数改动前先在模拟盘跑通。

MQL5 / C++
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();
    }
  }

「正文」

&nbsp;&nbsp;}; <span class="keyword">class</span> CTable : <span class="keyword">public</span> CElement &nbsp;&nbsp;{ <span class="keyword">public</span>: &nbsp;&nbsp; <span class="comment">//--- 将数值列表添加到组合框</span> &nbsp;&nbsp; <span class="keyword">void</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;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> &amp;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>); &nbsp;&nbsp;}; <span class="comment">//+------------------------------------------------------------------+</span>

<span class="comment">//将数值列表添加到组合框&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</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 头文件对照断点。

MQL5 / C++
  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 省事,再决定要不要进实盘。

常见问题

给该单元格指定控件类型为文本编辑框,绑定一个 CEdit 实例,点击单元格时把控件移到对应坐标并显示,失焦或回车后写回单元格文本。
在点击分发里按单元格的列/行索引判断类型:文本列挂编辑框,枚举列挂组合框,分别创建并显示对应控件即可。
可以。小布能按你的品种页配置自动生成带单元格编辑的看板,你只管调参数和看结论,不用手写控件布局代码。
用多行文本框类的 SetSelection 设起止字符,用 SetSize 按行数算高度,并在重绘前锁宽避免溢出单元格。
双击消息里置编辑标记并显控件;在全局鼠标松开和焦点丢失事件里检查标记,提交内容后隐藏控件并清标记。