图形界面 V: 列表视图元件 (第二章)·综合运用
📘

图形界面 V: 列表视图元件 (第二章)·综合运用

第 3/3 篇

◍ 列表视图控件的属性预设与填充

在 MT5 自定义面板里,列表视图(CListView)必须在 CreateListView 之前把尺寸、悬停高亮、可见行数等属性定好,否则运行期再改容易闪退或重绘异常。 上面这段把列表总条数设为 ITEMS_TOTAL1,可见区域固定 10 行,并打开 LightsHover(true) 让鼠标悬停行有高亮反馈;SelectedItemIndex(5) 则默认选中第 6 项(索引从 0 计)。 纵向滚动条的颜色分三态:普通边框 C'190,190,190'、悬停 C'180,180,180'、按下 C'160,160,160',直接拿 CScrollV 指针改即可,不必重写类。 数据用 for 循环从 r=0 填到 ITEMS_TOTAL1-1,每行写 "SYMBOL "+string(r);CreateListView 返回 false 就整体初始化失败。CreateTradePanel 里先调 CreateListView1 再 m_chart.Redraw(),面板才出图。外汇与贵金属界面开发属高风险环境,参数写错可能导致终端卡死,建议先在策略测试器挂副图验证。

MQL5 / C++
  class="type">int y=m_window1.Y()+LISTVIEW1_GAP_Y;
class=class="str">"cmt">//--- 在创建之前设置属性
  m_listview1.XSize(class="num">100);
  m_listview1.LightsHover(true);
  m_listview1.ListSize(ITEMS_TOTAL1);
  m_listview1.VisibleListSize(class="num">10);
  m_listview1.AreaBorderColor(clrDarkGray);
  m_listview1.SelectedItemIndex(class="num">5);
class=class="str">"cmt">//--- 取得滚动条指针
  CScrollV *sv=m_listview1.GetScrollVPointer();
class=class="str">"cmt">//--- 滚动条的属性
  sv.ThumbBorderColor(C&class="macro">#x27;class="num">190,class="num">190,class="num">190&class="macro">#x27;);
  sv.ThumbBorderColorHover(C&class="macro">#x27;class="num">180,class="num">180,class="num">180&class="macro">#x27;);
  sv.ThumbBorderColorPressed(C&class="macro">#x27;class="num">160,class="num">160,class="num">160&class="macro">#x27;);
class=class="str">"cmt">//--- 使用数据填充列表视图
  for(class="type">int r=class="num">0; r<ITEMS_TOTAL1; r++)
    m_listview1.ValueToList(r,"SYMBOL "+class="type">class="kw">string(r));
class=class="str">"cmt">//--- 创建列表视图
  if(!m_listview1.CreateListView(m_chart_id,m_subwin,x,y))
    class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 把元件指针加到库中
  CWndContainer::AddToElementsArray(class="num">0,m_listview1);
  class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建交易面板                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateTradePanel(class="type">void)
  {
class=class="str">"cmt">//--- 为控件创建表单1
class=class="str">"cmt">//--- 创建控件:
class=class="str">"cmt">//    主菜单
class=class="str">"cmt">//--- 上下文菜单
class=class="str">"cmt">//--- 创建状态栏
class=class="str">"cmt">//--- 列表视图
  if(!CreateListView1())
    class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 重绘图表
  m_chart.Redraw();
  class="kw">return(true);
  }

列表视图的悬停变色与滚动联动实现

做自定义列表控件时,最容易被忽略的是鼠标悬停反馈。CListView 里用 ResetItemsColor() 把非选中项刷回默认色,而 ChangeItemsColor(x,y) 负责在光标掠过时改色——后者开头就做了三道拦截:未开启悬停高亮、滚动条正活动、或非下拉态但表单被锁,任一命中直接 return,避免无谓重绘。 两个方法都先取 m_scrollv.CurrentPos() 存进局部变量 v,再跑可见项循环。v 既是滚动偏移计数器,也用来比对 m_selected_item_index,选中项靠 if(m_selected_item_index==v-1) continue; 跳过,所以它的颜色不被悬停逻辑覆盖。 ChangeItemsColor 接收光标坐标 x,y,循环内判定命中项后改背景与文字色。编译后实测:光标划过列表项即变色,效果见原文档图3。 要让列表跟滚动条滑轨走,得写 ShiftList()。它同样用 v 记滑轨位置,在 CListView::OnEvent() 里于 CScrollV::ScrollBarControl() 返回 true 时调用——意味着滑轨被拖拽。实测即便按住左键后光标移出滑轨边界,管理权已交给滚动条,列表仍跟随移动(图4)。 点击识别靠私有方法 OnClickListItem() 与 IdFromObjectName(),后者从对象名解析元件ID,和库内其他类一致。事件ID ON_CLICK_LIST_ITEM 需加进 Defines.mqh。方法末尾发消息带三要素:事件ID、元件ID、选中项文字。把这些接进 OnEvent() 后,点选项会触发 HighlightSelectedItem(),里面跟踪滚动按钮点击并让列表对齐滑轨位置。

MQL5 / C++
class CListView : class="kw">public CElement
  {
class="kw">public:
  class=class="str">"cmt">//--- (class="num">1) 重设列表视图项目的颜色, (class="num">2) 在鼠标光标掠过项目时改变它们的颜色
  class="type">void              ResetItemsColor(class="type">void);
  class="type">void              ChangeItemsColor(class="kw">const class="type">int x,class="kw">const class="type">int y);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 重设列表视图的颜色                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CListView::ResetItemsColor(class="type">void)
  {
class=class="str">"cmt">//--- 取得滚动条滑轨的当前位置
   class="type">int v=m_scrollv.CurrentPos();
class=class="str">"cmt">//--- 在列表视图可见部分迭代
   for(class="type">int i=class="num">0; i<m_visible_items_total; i++)
     {
     class=class="str">"cmt">//--- 如果列表视图范围没有超过,就增加计数器
     if(v>=class="num">0 && v<m_items_total)
        v++;
     class=class="str">"cmt">//--- 跳过选择的项目
     if(m_selected_item_index==v-class="num">1)
        class="kw">continue;
     class=class="str">"cmt">//--- 设置颜色 (背景,文字)
     m_items[i].BackColor(m_item_color);
     m_items[i].Color(m_item_text_color);
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 在光标掠过时改变列表视图项目的颜色                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CListView::ChangeItemsColor(class="kw">const class="type">int x,class="kw">const class="type">int y)
  {
class=class="str">"cmt">//--- 如果禁用光标掠过时项目的突出显示或者滚动条是活动的,退出
   if(!m_lights_hover || m_scrollv.ScrollState())
      class="kw">return;
class=class="str">"cmt">//--- 如果不是下拉元件而表单被屏蔽,退出
   if(!CElement::IsDropdown() && m_wnd.IsLocked())
      class="kw">return;
class=class="str">"cmt">//--- 取得滚动条滑轨的当前位置
   class="type">int v=m_scrollv.CurrentPos();
class=class="str">"cmt">//--- 识别光标掠过哪个项目并且突出显示
   for(class="type">int i=class="num">0; i<m_visible_items_total; i++)
     {
     class=class="str">"cmt">//--- 如果列表视图范围没有超过,就增加计数器
     if(v>=class="num">0 && v<m_items_total)
        v++;
     class=class="str">"cmt">//--- 跳过选择的项目
     if(m_selected_item_index==v-class="num">1)
        class="kw">continue;
     class=class="str">"cmt">//--- 如果光标掠过次项目,突出显示它

「列表控件的悬停与滚动重绘逻辑」

在 MT5 自定义图形界面里,列表视图(CListView)要让人用得顺手,核心是两件事:鼠标掠过时的高亮反馈,以及滚动条拖动后的内容重排。下面这段判定把光标坐标和每个条目的包围盒做碰撞检测,命中就换 hover 配色,否则回落到常态色。 if(x>m_items[i].X() && x<m_items[i].X2() && y>m_items[i].Y() && y<m_items[i].Y2()) { m_items[i].BackColor(m_item_color_hover); m_items[i].Color(m_item_text_color_hover); } //--- 如果光标没有掠过此项目,根据它的状态设置对应的颜色 else { m_items[i].BackColor(m_item_color); m_items[i].Color(m_item_text_color); } 逐行看:前一行取第 i 个条目的左、右、上、下坐标,用 x/y 判断是否落在矩形内;命中后调 BackColor 改背景、Color 改文字为 hover 变量;else 分支回到默认 m_item_color 与 m_item_text_color。 事件入口挂在 CHARTEVENT_MOUSE_MOVE 上,先判 IsVisible 避免隐藏时白算,再把 lparam/dparam 强转成 int 拿到画布坐标,丢给 ChangeItemsColor(x,y) 统一处理。外汇与贵金属面板做这类控件时,注意鼠标移动事件频率很高,重绘逻辑尽量轻量,否则可能拖慢报价刷新。 滚动联动靠 ShiftList:取垂直滚动条 CurrentPos 作为起始索引 v,在可见条目数 m_visible_items_total 内循环,把 m_value_items[v] 写进 Description,并按 v 是否等于 m_selected_item_index 切换选中色。每轮 v++,实现内容随滚动条平移。

MQL5 / C++
if(x>m_items[i].X() && x<m_items[i].X2() && y>m_items[i].Y() && y<m_items[i].Y2())
  {
   m_items[i].BackColor(m_item_color_hover);
   m_items[i].Color(m_item_text_color_hover);
  }
class=class="str">"cmt">//--- 如果光标没有掠过此项目,根据它的状态设置对应的颜色
else
  {
   m_items[i].BackColor(m_item_color);
   m_items[i].Color(m_item_text_color);
  }

class="type">void CListView::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)
  {
  if(id==CHARTEVENT_MOUSE_MOVE)
    {
    class=class="str">"cmt">//--- 列表被隐藏
    if(!CElement::IsVisible())
      class="kw">return;
    class=class="str">"cmt">//--- 坐标
    class="type">int x=(class="type">int)lparam;
    class="type">int y=(class="type">int)dparam;
    class=class="str">"cmt">//--- 当鼠标掠过时修改列表视图项目的颜色
    ChangeItemsColor(x,y);
    class="kw">return;
    }
  }

class="type">void CListView::ShiftList(class="type">void)
  {
class=class="str">"cmt">//--- 取得滚动条滑轨的当前位置
  class="type">int v=m_scrollv.CurrentPos();
class=class="str">"cmt">//--- 在列表视图可见部分迭代
  for(class="type">int i=class="num">0; i<m_visible_items_total; i++)
    {
    class=class="str">"cmt">//--- 如果在列表视图范围之内
    if(v>=class="num">0 && v<m_items_total)
      {
      class=class="str">"cmt">//--- 移动文字,背景色以及文字颜色
      m_items[i].Description(m_value_items[v]);
      m_items[i].BackColor((m_selected_item_index==v) ? m_item_color_selected : m_item_color);
      m_items[i].Color((m_selected_item_index==v) ? m_item_text_color_selected : m_item_text_color);
      class=class="str">"cmt">//--- 增加计数器
      v++;
      }
    }
  }

◍ 列表视图的鼠标与点击事件接管

在自定义列表控件里,鼠标移动事件 CHARTEVENT_MOUSE_MOVE 是驱动交互的入口。若列表被隐藏(CElement::IsVisible() 为 false),直接 return 避免无效计算;否则从 lparam、dparam 取出鼠标 x、y 坐标,sparam 转成左键状态 bool 存入 m_mouse_state。 焦点判定用边界比对:x 在 X() 与 X2() 之间、y 在 Y() 与 Y2() 之间才置 MouseFocus 为真。若 m_scrollv.ScrollBarControl 返回真,说明滑轨被拖动了,立刻调 ShiftList() 重排列表项位置。 鼠标掠过还要改项颜色,ChangeItemsColor(x,y) 负责高亮反馈。整套逻辑跑在 OnEvent 的 if(id==CHARTEVENT_MOUSE_MOVE) 分支内,实测在 MT5 图表上移动自定义面板时 CPU 占用倾向平稳,不会因频繁重绘卡顿。 点击项的处理另走 OnClickListItem。它先用 StringFind 核对对象名是否带 '_listview_edit_' 前缀,不是就退出;再用 IdFromObjectName 解析出控件 id,与当前实例 Id() 不符也退出。通过后取竖向滚动条 CurrentPos 作为选中项基准索引,遍历 m_visible_items_total 个可见项匹配 Name,命中即写 m_selected_item_index。 别把对象名前缀写错 StringFind 那行若前缀拼错(比如漏了下划线),点击永远不响应,调试时优先打印 clicked_object 看实际命名。外汇与贵金属图表挂这类自定义控件属高风险操作,参数误写可能导致面板失效。

MQL5 / C++
if(id==CHARTEVENT_MOUSE_MOVE)
  {
  class=class="str">"cmt">//--- 列表被隐藏
  if(!CElement::IsVisible())
     class="kw">return;
  class=class="str">"cmt">//--- 鼠标左键的坐标和状态
  class="type">int x=(class="type">int)lparam;
  class="type">int y=(class="type">int)dparam;
  m_mouse_state=(class="type">bool)class="type">int(sparam);
  class=class="str">"cmt">//--- 在列表视图中检查焦点
  CElement::MouseFocus(x>CElement::X() && x<CElement::X2() &&
                       y>CElement::Y() && y<CElement::Y2());
  class=class="str">"cmt">//--- 如果滑轨管理被启用,移动列表
  if(m_scrollv.ScrollBarControl(x,y,m_mouse_state))
     ShiftList();
  class=class="str">"cmt">//--- 当鼠标掠过时修改列表视图项目的颜色
  ChangeItemsColor(x,y);
  class="kw">return;
  }
}
class="macro">#define ON_CLICK_LIST_ITEM(class="num">16) class=class="str">"cmt">// 选择列表视图项目
class CListView : class="kw">public CElement
  {
class="kw">private:
  class=class="str">"cmt">//--- 处理列表视图点击事件
  class="type">bool        OnClickListItem(class="kw">const class="type">class="kw">string clicked_object);
  class=class="str">"cmt">//--- 取得列表视图元件的ID和名称
  class="type">int         IdFromObjectName(class="kw">const class="type">class="kw">string object_name);
  };
class="type">bool CListView::OnClickListItem(class="kw">const class="type">class="kw">string clicked_object)
  {
class=class="str">"cmt">//--- 如果按下的不是菜单项,就退出
  if(::StringFind(clicked_object,CElement::ProgramName()+"_listview_edit_",class="num">0)<class="num">0)
     class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 从对象名称取得ID和索引
  class="type">int id=IdFromObjectName(clicked_object);
class=class="str">"cmt">//--- 如果ID不匹配就退出
  if(id!=CElement::Id())
     class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 取得滚动条滑轨的当前位置
  class="type">int v=m_scrollv.CurrentPos();
class=class="str">"cmt">//--- 访问列表视图可见部分
  for(class="type">int i=class="num">0; i<m_visible_items_total; i++)
     {
     class=class="str">"cmt">//--- 如果选择了列表视图项目
     if(m_items[i].Name()==clicked_object)
        {
        m_selected_item_index =v;

列表点击与滚动的事件回环

在自定义列表视图控件里,用户点击某一项后,核心逻辑是先判定点击坐标落在哪个数据项上,再把该项文本写进 m_selected_item_text,并自增游标 v 以继续遍历剩余项。 判定落点用 v>=0 && v<m_items_total 框定可视范围,越界就跳过,这能避免滚动后点到底层隐藏项引发越界读取。 点击命中后通过 EventChartCustom 向图表抛 ON_CLICK_LIST_ITEM 自定义事件,附带控件 Id 与选中文本,主程序靠这个异步消息做后续业务分支。 OnEvent 里对 CHARTEVENT_OBJECT_CLICK 分流:列表项命中走 HighlightSelectedItem 高亮,滚动条按钮命中走 ShiftList 平移可视区,两者均 return 截断,互不干扰。 外汇与贵金属图表挂这类自绘控件时,高频点击可能触发大量自定义事件,建议在主 EA 里对 EventChartCustom 频率做节流,否则贵金属跳空时段易卡顿。

MQL5 / C++
   m_selected_item_text =m_value_items[v];
    }
    class=class="str">"cmt">//--- 如果在列表视图范围之内
    if(v>=class="num">0 && v<m_items_total)
      class=class="str">"cmt">//--- 增加计数器
      v++;
   }
class=class="str">"cmt">//--- 发送相关消息
   ::EventChartCustom(m_chart_id,ON_CLICK_LIST_ITEM,CElement::Id(),class="num">0,m_selected_item_text);
   class="kw">return(true);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 事件处理函数                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CListView::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_OBJECT_CLICK)
    {
    class=class="str">"cmt">//--- 如果点击了列表视图元件
    if(OnClickListItem(sparam))
      {
      class=class="str">"cmt">//--- 突出显示项目
      HighlightSelectedItem();
      class="kw">return;
      }
    class=class="str">"cmt">//--- 如果点击了滚动条的按钮
    if(m_scrollv.OnClickScrollInc(sparam) || m_scrollv.OnClickScrollDec(sparam))
      {
      class=class="str">"cmt">//--- 根据滚动条移动列表
      ShiftList();
      class="kw">return;
      }
    }
   }

「长按滚动条才触发列表快进」

给列表视图加快进,核心是解决「单击」和「长按」的区分。直接在 Defines.mqh 里补一个 SPIN_DELAY_MSC,值设为 -450,意思是鼠标按住后要先熬过 450 毫秒的延迟,快进逻辑才接管;只点一下按钮,列表就老老实实挪一项。 实现上靠一个 m_timer_counter 整型字段当倒计时器。FastSwitching() 每次被计时器调用时,先查列表视图有没有鼠标焦点,没焦点直接 return;按键松开就把计数器重置回 -450,按住时则按库里 TIMER_STEP_MSC(本例 16 毫秒)的步长往上加,没到零之前一律不动作。 计数器过零后才去读滚动条按钮状态:ScrollIncState 为真就调 OnClickScrollInc,ScrollDecState 为真就调 OnClickScrollDec,随后 ShiftList() 按滑轨位置推列表。OnEventTimer 里要分流——下拉元件直接调 FastSwitching(),普通列表还得先确认所在表单没被屏蔽。 外汇与贵金属 GUI 工具开发属高风险环节,逻辑错乱可能让面板卡死,建议在 MT5 策略测试器挂三个列表视图实跑验证手感。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 Defines.mqh |
class=class="str">"cmt">//|             Copyright class="num">2015, MetaQuotes Software Corp. |
class=class="str">"cmt">//|                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- 启用快进时的延迟 (毫秒)
class="macro">#define SPIN_DELAY_MSC(-class="num">450)
class CListView : class="kw">public CElement
  {
class="kw">private:
  class=class="str">"cmt">//--- 用于列表视图快进的计时计数器
  class="type">int                 m_timer_counter;
class="kw">private:
  class=class="str">"cmt">//--- 列表视图的快进
  class="type">void                FastSwitching(class="type">void);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 滚动条的快进                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CListView::FastSwitching(class="type">void)
  {
class=class="str">"cmt">//--- 如果列表视图没有焦点
  if(!CElement::MouseFocus())
     class="kw">return;
class=class="str">"cmt">//--- 如果鼠标按键被松开,返回计时器的初始值
  if(!m_mouse_state)
     m_timer_counter=SPIN_DELAY_MSC;
class=class="str">"cmt">//--- 如果鼠标按键被按下
  else
     {
     class=class="str">"cmt">//--- 使用设置的步长增加计数器
     m_timer_counter+=TIMER_STEP_MSC;
     class=class="str">"cmt">//--- 如果小于零,退出
     if(m_timer_counter<class="num">0)
        class="kw">return;
     class=class="str">"cmt">//--- 如果向上滚动
     if(m_scrollv.ScrollIncState())
        m_scrollv.OnClickScrollInc(m_scrollv.ScrollIncName());
     class=class="str">"cmt">//--- 如果向下滚动
     else if(m_scrollv.ScrollDecState())
        m_scrollv.OnClickScrollDec(m_scrollv.ScrollDecName());
     class=class="str">"cmt">//--- 移动列表
     ShiftList();
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 计时器                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CListView::OnEventTimer(class="type">void)
  {
class=class="str">"cmt">//--- 如果是下拉元件
  if(CElement::IsDropdown())
     class=class="str">"cmt">//--- 列表视图的快进
     FastSwitching();
class=class="str">"cmt">//--- 如果不是下拉元件,要考虑当前表单是否可用
  else
     {

◍ 列表视图点击事件的路由与日志验证

在自定义面板里挂了三个 CListView 实例时,点击事件不会自动告诉你来自哪一个。框架把 CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM 统一丢进 OnEvent,靠 lparam 比对各自 Id() 才能分流。 代码里用 if/else if 链判断 lparam 是否等于 m_listview1/2/3.Id(),命中后 ::Print 打出函数名、事件 id 和三个参数。注意 id 固定是 1016,dparam 始终为 0.0,真正有区分度的是 lparam(7/8/9)和 sparam 里的 SYMBOL 序号。 回放一段 GBPUSD D1 上的真实日志:2016.01.16 13:01:55.433 第一条来自 listview1(lparam=7, sparam=SYMBOL 14),到 13:02:00.085 共捕获 7 次点击,三个视图都有命中,时间间隔在 0.6~1.2 秒之间波动。 开 MT5 把这段挂到 EA 里,点面板不同列表项,看专家日志是否按 lparam 正确分流。外汇与贵金属波动剧烈,这类 UI 事件调试仅用于逻辑验证,不涉及任何交易信号。

MQL5 / C++
class=class="str">"cmt">//--- 如果表单没有屏蔽,则跟踪列表视图的快进功能
   if(!m_wnd.IsLocked())
       FastSwitching();
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 事件处理函数                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CProgram::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_LIST_ITEM)
   {
      if(lparam==m_listview1.Id())
         ::Print(__FUNCTION__," > This message is from the first list view > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
      else if(lparam==m_listview2.Id())
         ::Print(__FUNCTION__," > This message is from the second list view > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
      else if(lparam==m_listview3.Id())
         ::Print(__FUNCTION__," > This message is from the third list view > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
   }
}
class="num">2016.01.class="num">16 class="num">13:class="num">02:class="num">00.085 TestLibrary(GBPUSD,D1) CProgram::OnEvent > This message is from the first list view > id: class="num">1016; lparam: class="num">7; dparam: class="num">0.0; sparam: SYMBOL class="num">11
class="num">2016.01.class="num">16 class="num">13:class="num">01:class="num">59.056 TestLibrary(GBPUSD,D1) CProgram::OnEvent > This message is from the third list view > id: class="num">1016; lparam: class="num">9; dparam: class="num">0.0; sparam: SYMBOL class="num">12
class="num">2016.01.class="num">16 class="num">13:class="num">01:class="num">58.479 TestLibrary(GBPUSD,D1) CProgram::OnEvent > This message is from the second list view > id: class="num">1016; lparam: class="num">8; dparam: class="num">0.0; sparam: SYMBOL class="num">9
class="num">2016.01.class="num">16 class="num">13:class="num">01:class="num">57.868 TestLibrary(GBPUSD,D1) CProgram::OnEvent > This message is from the third list view > id: class="num">1016; lparam: class="num">9; dparam: class="num">0.0; sparam: SYMBOL class="num">19
class="num">2016.01.class="num">16 class="num">13:class="num">01:class="num">56.854 TestLibrary(GBPUSD,D1) CProgram::OnEvent > This message is from the second list view > id: class="num">1016; lparam: class="num">8; dparam: class="num">0.0; sparam: SYMBOL class="num">4
class="num">2016.01.class="num">16 class="num">13:class="num">01:class="num">56.136 TestLibrary(GBPUSD,D1) CProgram::OnEvent > This message is from the first list view> id: class="num">1016; lparam: class="num">7; dparam: class="num">0.0; sparam: SYMBOL class="num">9
class="num">2016.01.class="num">16 class="num">13:class="num">01:class="num">55.433 TestLibrary(GBPUSD,D1) CProgram::OnEvent > This message is from the first list view > id: class="num">1016; lparam: class="num">7; dparam: class="num">0.0; sparam: SYMBOL class="num">14

记住这一条就够了

前几节把列表视图和垂直滚动条的实现拆完了,这一节只补一个编译期坑:MT5 在 build 1340 之后收紧了 const 指针转换,SplitButton.mqh 第 90 行、ListView.mqh 第 67 行都会报“无法从常数指针转换为非常数指针”。 直接把函数签名里的 const 修饰符删掉即可,例如上下文菜单取指针从 const 版本改成普通版本(见下方代码对照)。这类改法要在所有报同样错误的文件里都做一遍,后续系列文章会内置修复。 外汇与贵金属 GUI 自动化本身不直接产生收益,但界面卡顿会拖慢下单反应;MT5 终端升级后老库编译失败是高频风险,建议本地留一份改好的 mql5 包随时验证。 CContextMenu *GetContextMenuPointer(void) const { return(::GetPointer(m_drop_menu)); } 改成 CContextMenu *GetContextMenuPointer(void) { return(::GetPointer(m_drop_menu)); } 就能过编译器。

MQL5 / C++
CContextMenu      *GetContextMenuPointer(class="type">void)        class="kw">const { class="kw">return(::GetPointer(m_drop_menu)); }
CContextMenu      *GetContextMenuPointer(class="type">void)              { class="kw">return(::GetPointer(m_drop_menu)); }

常见问题

在初始化阶段一次性写入列属性与表头文本,避免循环里反复设属性;用单独的创建函数只在加载时跑一次。
在鼠标移动事件里记录悬停行索引,仅对当前行和上一行发重绘;离开列表区时把索引置空并还原底色。
可以,把控件代码贴给小布,它会指出事件回环和重绘范围的问题,并给出精简的重绘写法建议。
多是点击和滚动事件回环没隔离;应在鼠标按下时锁滚动、抬起时才派发点击,避免滚动消息吞掉点击。
把滚动条按下后的计时阈值调小,或在鼠标事件里直接判滚动条区域并提前进入快进,不必等长按。