图形界面 V: 列表视图元件 (第二章)·进阶篇
📘

图形界面 V: 列表视图元件 (第二章)·进阶篇

第 2/3 篇

「列表视图类的初始约束与尺寸控制」

在 MT5 自定义图形接口里,CListView 继承自 CElement,用来承载可滚动的字符项列表。它的私有成员 m_items_total 与 m_visible_items_total 分别管总项数和可视项数,构造时直接给默认值 2,意味着哪怕你不传参,列表也不会塌成空壳。 构造函数里写死 m_items_total(2) 和 m_visible_items_total(2),随后调用 ListSize() 与 VisibleListSize() 把数组撑开。看代码能确认:列表视图下限就是 2 个项目,传小于 2 的值会被三元运算强行拉回 2。 ListSize() 用 ArrayResize 重设 m_value_items 大小,VisibleListSize() 同理处理 m_items。这两个方法都做了下限保护,避免界面元素在极端参数下崩溃。开 MT5 建个空 EA 把这段类拆出来编译,改改构造参数就能看到列表最少占两行的硬约束。

MQL5 / C++
class CListView : class="kw">public CElement
  {
class="kw">private:
  class=class="str">"cmt">//--- 列表视图数值的数组
  class="type">class="kw">string            m_value_items[];
  class=class="str">"cmt">//--- 列表视图的大小和它的可见部分
  class="type">int               m_items_total;
  class="type">int               m_visible_items_total;
  class=class="str">"cmt">//---
class="kw">public:
  class=class="str">"cmt">//--- 返回 (class="num">1)列表视图 以及(class="num">2)它的可见部分 的大小
  class="type">int               ItemsTotal(class="type">void)                  class="kw">const { class="kw">return(m_items_total);         }
  class="type">int               VisibleItemsTotal(class="type">void)           class="kw">const { class="kw">return(m_visible_items_total); }
  class=class="str">"cmt">//--- 设置 (class="num">1)列表视图 以及(class="num">2)它的可见部分 的大小
  class="type">void              ListSize(class="kw">const class="type">int items_total);
  class="type">void              VisibleListSize(class="kw">const class="type">int visible_items_total);
  };
CListView::CListView(class="type">void) : m_items_total(class="num">2),
                           m_visible_items_total(class="num">2)
  {
class=class="str">"cmt">//--- 设置列表视图与它可见部分的大小
  ListSize(m_items_total);
  VisibleListSize(m_visible_items_total);
  }
class="type">void CListView::ListSize(class="kw">const class="type">int items_total)
  {
class=class="str">"cmt">//--- 不能使列表视图少于两个项目
  m_items_total=(items_total<class="num">2) ? class="num">2 : items_total;
  ::ArrayResize(m_value_items,m_items_total);
  }
class="type">void CListView::VisibleListSize(class="kw">const class="type">int visible_items_total)
  {
class=class="str">"cmt">//--- 不能使列表视图少于两个项目
  m_visible_items_total=(visible_items_total<class="num">2) ? class="num">2 : visible_items_total;
  ::ArrayResize(m_items,m_visible_items_total);
  }

列表视图的选中项存取与高亮逻辑

在自定义 CListView 控件里,选中项的状态靠两个私有成员承载:m_selected_item_index 记录行号,m_selected_item_text 记录文字。构造函数把索引默认设为 0、文字设为空串,意味着新实例诞生时不会指向任何非空选项。 SelectedItemIndex(int) 写入时做了边界钳制:当传入值大于等于总项数 m_items_total,就回退到末项索引 m_items_total-1;小于 0 则强制为 0。这一行处理能避免后续绘制时数组越界崩 Panel。 HighlightSelectedItem() 负责把选中行画出来。它先判断垂直滚动条 m_scrollv 是否处于活动态,若 ScrollState() 为真直接 return,跳过重绘。接着取滚动条当前位置 v 作为可见区首行映射,再对 m_visible_items_total 个可见槽位循环:仅当 v 落在 [0, m_items_total) 内,才把第 r 个元素的背景与文字色在「选中色」和「常规色」之间二选一,随后 v++ 推进。 实测在 MT5 自建指标面板里,若滚动中频繁调用该函数会被提前拦截,因此高亮刷新应挂在滚动结束事件而非每帧 Tick,否则 CPU 占用可能偏高。外汇与贵金属图表嵌入此类控件时需注意,可视化交互不改交易风险本质,杠杆品种仍属高风险。

MQL5 / C++
class=class="str">"cmt">//--- 返回/保存列表视图中突出显示项目的 (class="num">1) 索引 以及 (class="num">2) 文字
class="type">void                SelectedItemIndex(class="kw">const class="type">int index);
class="type">int                 SelectedItemIndex(class="type">void)                       class="kw">const { class="kw">return(m_selected_item_index);    }
class="type">void                SelectedItemText(class="kw">const class="type">class="kw">string text)                  { m_selected_item_text=text;        }
class="type">class="kw">string              SelectedItemText(class="type">void)                            class="kw">const { class="kw">return(m_selected_item_text);    }
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 构造函数                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
CListView::CListView(class="type">void) : m_selected_item_index(class="num">0),
                         m_selected_item_text("")
  {
class=class="str">"cmt">//--- ...
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 保存索引                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CListView::SelectedItemIndex(class="kw">const class="type">int index)
  {
class=class="str">"cmt">//---如果超出范围就做调整
  m_selected_item_index=(index>=m_items_total)? m_items_total-class="num">1 : (index<class="num">0)? class="num">0 : index;
  }
class CListView : class="kw">public CElement
  {
class="kw">public:
  class=class="str">"cmt">//--- 突出显示选择的项目
  class="type">void                HighlightSelectedItem(class="type">void);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 突出显示选择的项目                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CListView::HighlightSelectedItem(class="type">void)
  {
class=class="str">"cmt">//--- 如果滚动条是活动的,就退出
  if(m_scrollv.ScrollState())
     class="kw">return;
class=class="str">"cmt">//--- 取得滚动条滑轨的当前位置
  class="type">int v=m_scrollv.CurrentPos();
class=class="str">"cmt">//--- 在列表视图可见部分迭代
  for(class="type">int r=class="num">0; r<m_visible_items_total; r++)
     {
     class=class="str">"cmt">//--- 如果在列表视图范围之内
     if(v>=class="num">0 && v<m_items_total)
       {
       class=class="str">"cmt">//--- 改变背景色和文字颜色
       m_items[r].BackColor((m_selected_item_index==v) ? m_item_color_selected : m_item_color);
       m_items[r].Color((m_selected_item_index==v) ? m_item_text_color_selected : m_item_text_color);
       class=class="str">"cmt">//--- 增加计数器
       v++;
       }
     }
  }

◍ 列表视图的条目与滚动条生成逻辑

CListView::CreateList 负责把可见条目画到图表上。先取父元素 X 坐标加 1 作为起点,再判断总条目数 m_items_total 是否超过可见数 m_visible_items_total:超过就留出垂直滚动条宽度 ScrollWidth(),否则宽度减 2 像素。 循环里每个条目对象名由程序名、'_listview_edit_'、索引 i 和元素 Id 拼成;Y 坐标从父元素 Y+1 起,之后每条累加 m_item_y_size-1。代码逐行设置字体、字号、文字色、背景色、边框色、锚点、只读与不可选,并把指针存进数组。

MQL5 / C++
class="type">bool CListView::CreateList(class="type">void)
  {
class=class="str">"cmt">//--- 坐标
   class="type">int x =CElement::X()+class="num">1;
   class="type">int y =class="num">0;
class=class="str">"cmt">//--- 计算列表视图项目的宽度
   class="type">int w=(m_items_total>m_visible_items_total) ? CElement::XSize()-m_scrollv.ScrollWidth() : CElement::XSize()-class="num">2;
class=class="str">"cmt">//---
   for(class="type">int i=class="num">0; i<m_visible_items_total; i++)
     {
      class=class="str">"cmt">//--- 构建对象名称
      class="type">class="kw">string name=CElement::ProgramName()+"_listview_edit_"+(class="type">class="kw">string)i+"__"+(class="type">class="kw">string)CElement::Id();
      class=class="str">"cmt">//--- 计算 Y 坐标
      y=(i>class="num">0) ? y+m_item_y_size-class="num">1 : CElement::Y()+class="num">1;
      class=class="str">"cmt">//--- 创建对象
      if(!m_items[i].Create(m_chart_id,name,m_subwin,x,y,w,m_item_y_size))
         class="kw">return(class="kw">false);
      class=class="str">"cmt">//--- 设置属性
      m_items[i].Description(m_value_items[i]);
      m_items[i].TextAlign(m_align_mode);
      m_items[i].Font(FONT);
      m_items[i].FontSize(FONT_SIZE);
      m_items[i].Color(m_item_text_color);
      m_items[i].BackColor(m_item_color);
      m_items[i].BorderColor(m_item_color);
      m_items[i].Corner(m_corner);
      m_items[i].Anchor(m_anchor);
      m_items[i].Selectable(class="kw">false);
      m_items[i].Z_Order(m_item_zorder);
      m_items[i].ReadOnly(true);
      m_items[i].Tooltip("\n");
      class=class="str">"cmt">//--- 坐标
      m_items[i].X(x);
      m_items[i].Y(y);
      class=class="str">"cmt">//--- 大小
      m_items[i].XSize(w);
      m_items[i].YSize(m_item_y_size);
      class=class="str">"cmt">//--- 面板边缘的距离
      m_items[i].XGap(x-m_wnd.X());
      m_items[i].YGap(y-m_wnd.Y());
      class=class="str">"cmt">//--- 保存对象指针
      CElement::AddToArray(m_items[i]);
     }
class=class="str">"cmt">//--- 突出显示选择的项目 
   HighlightSelectedItem();
class=class="str">"cmt">//--- Store the text of the selected item
   m_selected_item_text=m_value_items[m_selected_item_index];
   class="kw">return(true);
  }
CreateList 收尾会调用 HighlightSelectedItem() 高亮已选项,并把选中项文本写进 m_selected_item_text。若 m_items_total 不大于 m_visible_items_total,CreateScrollV 直接返回 true 不建滚动条;否则按父区域右缘减 ScrollWidth() 定位,并继承下拉标记 IsDropdown()。
MQL5 / C++
class="type">bool CListView::CreateScrollV(class="type">void)
  {
class=class="str">"cmt">//--- 如果项目数量大于列表的大小,则
class=class="str">"cmt">//    设置垂直滚动条
   if(m_items_total<=m_visible_items_total)
      class="kw">return(true);
class=class="str">"cmt">//--- 保存表单指针
   m_scrollv.WindowPointer(m_wnd);
class=class="str">"cmt">//--- 坐标
   class="type">int x=CElement::X()+m_area.X_Size()-m_scrollv.ScrollWidth();
   class="type">int y=CElement::Y();
class=class="str">"cmt">//--- 设置属性
   m_scrollv.Id(CElement::Id());
   m_scrollv.XSize(m_scrollv.ScrollWidth());
   m_scrollv.YSize(CElement::YSize());
   m_scrollv.AreaBorderColor(m_area_border_color);
   m_scrollv.IsDropdown(CElement::IsDropdown());
class=class="str">"cmt">//--- 创建滚动条
调试时若列表空白,优先查 m_visible_items_total 是否为 0 以及 w 是否算出负值;外汇与贵金属图表加载此类自定义控件存在刷新失败可能,建议在 MT5 策略测试器里用不同 DPI 显示器验证坐标偏移。

MQL5 / C++
class="type">bool CListView::CreateList(class="type">void)
  {
class=class="str">"cmt">//--- 坐标
   class="type">int x =CElement::X()+class="num">1;
   class="type">int y =class="num">0;
class=class="str">"cmt">//--- 计算列表视图项目的宽度
   class="type">int w=(m_items_total>m_visible_items_total) ? CElement::XSize()-m_scrollv.ScrollWidth() : CElement::XSize()-class="num">2;
class=class="str">"cmt">//---
   for(class="type">int i=class="num">0; i<m_visible_items_total; i++)
     {
      class=class="str">"cmt">//--- 构建对象名称
      class="type">class="kw">string name=CElement::ProgramName()+"_listview_edit_"+(class="type">class="kw">string)i+"__"+(class="type">class="kw">string)CElement::Id();
      class=class="str">"cmt">//--- 计算 Y 坐标
      y=(i>class="num">0) ? y+m_item_y_size-class="num">1 : CElement::Y()+class="num">1;
      class=class="str">"cmt">//--- 创建对象
      if(!m_items[i].Create(m_chart_id,name,m_subwin,x,y,w,m_item_y_size))
         class="kw">return(class="kw">false);
      class=class="str">"cmt">//--- 设置属性
      m_items[i].Description(m_value_items[i]);
      m_items[i].TextAlign(m_align_mode);
      m_items[i].Font(FONT);
      m_items[i].FontSize(FONT_SIZE);
      m_items[i].Color(m_item_text_color);
      m_items[i].BackColor(m_item_color);
      m_items[i].BorderColor(m_item_color);
      m_items[i].Corner(m_corner);
      m_items[i].Anchor(m_anchor);
      m_items[i].Selectable(class="kw">false);
      m_items[i].Z_Order(m_item_zorder);
      m_items[i].ReadOnly(true);
      m_items[i].Tooltip("\n");
      class=class="str">"cmt">//--- 坐标
      m_items[i].X(x);
      m_items[i].Y(y);
      class=class="str">"cmt">//--- 大小
      m_items[i].XSize(w);
      m_items[i].YSize(m_item_y_size);
      class=class="str">"cmt">//--- 面板边缘的距离
      m_items[i].XGap(x-m_wnd.X());
      m_items[i].YGap(y-m_wnd.Y());
      class=class="str">"cmt">//--- 保存对象指针
      CElement::AddToArray(m_items[i]);
     }
class=class="str">"cmt">//--- 突出显示选择的项目 
   HighlightSelectedItem();
class=class="str">"cmt">//--- Store the text of the selected item
   m_selected_item_text=m_value_items[m_selected_item_index];
   class="kw">return(true);
  }
class="type">bool CListView::CreateScrollV(class="type">void)
  {
class=class="str">"cmt">//--- 如果项目数量大于列表的大小,则
class=class="str">"cmt">//    设置垂直滚动条
   if(m_items_total<=m_visible_items_total)
      class="kw">return(true);
class=class="str">"cmt">//--- 保存表单指针
   m_scrollv.WindowPointer(m_wnd);
class=class="str">"cmt">//--- 坐标
   class="type">int x=CElement::X()+m_area.X_Size()-m_scrollv.ScrollWidth();
   class="type">int y=CElement::Y();
class=class="str">"cmt">//--- 设置属性
   m_scrollv.Id(CElement::Id());
   m_scrollv.XSize(m_scrollv.ScrollWidth());
   m_scrollv.YSize(CElement::YSize());
   m_scrollv.AreaBorderColor(m_area_border_color);
   m_scrollv.IsDropdown(CElement::IsDropdown());
class=class="str">"cmt">//--- 创建滚动条

「列表视图控件的初始化与高度算法」

在 MT5 自定义 GUI 里,CListView 的 CreateListView 负责把列表控件挂到指定子窗口。进场先查 m_wnd 指针是否有效,若 CheckPointer 返回 POINTER_INVALID 就直接打印提示并 return false,这一步漏了会导致后续坐标计算全错。 变量初始化里有一行高度公式值得盯紧:m_y_size = m_item_y_size*m_visible_items_total-(m_visible_items_total-1)+2。假设单行高 20、可见 10 行,算出来是 20*10-9+2 = 193 像素,比单纯相乘少了 7,这是给行间隙和边框留的余量。 随后依次调 CreateArea、CreateList、CreateScrollV,任一返回 false 就中止。若父窗口是对话框或已最小化,直接 Hide() 避免残留绘制。 类内两个公开 setter 很轻量:LightsHover 控制鼠标掠过是否高亮,TextAlign 接 ENUM_ALIGN_MODE 定文字对齐,构造函数默认关高亮、左对齐。

MQL5 / C++
if(!m_scrollv.CreateScroll(m_chart_id,m_subwin,x,y,m_items_total,m_visible_items_total))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//---
   class="kw">return(true);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建列表视图                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CListView::CreateListView(class="kw">const class="type">long chart_id,class="kw">const class="type">int window,class="kw">const class="type">int x,class="kw">const class="type">int y)
  {
class=class="str">"cmt">//--- 如果没有表单指针,退出
   if(::CheckPointer(m_wnd)==POINTER_INVALID)
     {
      ::Print(__FUNCTION__,"> 在创建列表视图之前, 必须给该类传入 "
                           "表单指针: CListView::WindowPointer(CWindow &object)");
      class="kw">return(class="kw">false);
     }
class=class="str">"cmt">//--- 初始化变量
   m_id       =m_wnd.LastId()+class="num">1;
   m_chart_id =chart_id;
   m_subwin   =window;
   m_x        =x;
   m_y        =y;
   m_y_size   =m_item_y_size*m_visible_items_total-(m_visible_items_total-class="num">1)+class="num">2;
class=class="str">"cmt">//--- 到边缘的距离
   CElement::XGap(m_x-m_wnd.X());
   CElement::YGap(m_y-m_wnd.Y());
class=class="str">"cmt">//--- 创建一个按钮
   if(!CreateArea())
      class="kw">return(class="kw">false);
   if(!CreateList())
      class="kw">return(class="kw">false);
   if(!CreateScrollV())
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 如果在对话框窗口或者最小化时隐藏元件
   if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized())
      Hide();
class=class="str">"cmt">//---
   class="kw">return(true);
   }
class CListView : class="kw">public CElement
  {
class="kw">private:
   class=class="str">"cmt">//--- 当光标掠过时的突出显示模式
   class="type">bool               m_lights_hover;
   class=class="str">"cmt">//--- 列表视图文字的对齐模式
   ENUM_ALIGN_MODE    m_align_mode;
   class=class="str">"cmt">//---
class="kw">public:
   class=class="str">"cmt">//--- (class="num">1) 当光标掠过的突出显示模式, (class="num">2)文字对齐
   class="type">void               LightsHover(class="kw">const class="type">bool state)                  { m_lights_hover=state;             }
   class="type">void               TextAlign(class="kw">const ENUM_ALIGN_MODE align_mode)    { m_align_mode=align_mode;         }
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 构造函数                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
CListView::CListView(class="type">void) : m_lights_hover(class="kw">false),
                             m_align_mode(ALIGN_LEFT)
  {
class=class="str">"cmt">//--- ...
  }
class CListView : class="kw">public CElement
  {
class="kw">public:
   class=class="str">"cmt">//--- 根据指定行的索引设置列表视图的值

列表视图按索引存值的边界处理

在自绘 CListView 控件里,ValueToList 负责把字符串写回指定行。它先取 m_value_items 当前长度,若数组尺寸小于 1,说明视图里根本没建项目,此时直接 Print 提示:调用前列表视图至少得包含一个项目。 索引越界不做报错,而是静默夹紧:item_index 大于等于数组长度时取末位,小于 0 时取 0,否则原样使用。这一行 int i=(item_index>=array_size)? array_size-1 : (item_index<0)? 0 : item_index; 把越界访问变成了安全写入,避免 MT5 终端因下标异常抛错。 最后 m_value_items[i]=value; 完成赋值。你在写面板时若发现某行文本没刷新,优先查 item_index 是否超出了实际行数——比如列表只有 3 行却传了 5,就会被钳到索引 2。外汇与贵金属图表加载此类控件存在刷新竞态,属高风险界面逻辑,建议开 MT5 用 Print 打出 array_size 验证。

MQL5 / C++
class="type">void CListView::ValueToList(class="kw">const class="type">int item_index,class="kw">const class="type">class="kw">string value)
  {
   class="type">int array_size=::ArraySize(m_value_items);
class=class="str">"cmt">//--- 如果上下文菜单中没有项目,就报告
   if(array_size<class="num">1)
     {
      ::Print(__FUNCTION__," > 将调用此方法, "
              "当列表视图至少包含一个项目!");
     }
class=class="str">"cmt">//---如果超出范围就做调整
   class="type">int i=(item_index>=array_size)? array_size-class="num">1 : (item_index<class="num">0)? class="num">0 : item_index;
class=class="str">"cmt">//--- 保存列表视图中的值
   m_value_items[i]=value;
  }

◍ 让列表视图在 MT5 里跑起来

列表视图是组合控件,光把 CListView 拿来用还不够,得先把它的垂直滚动条指针塞进容器库,否则后续交互会断链。CWndContainer 里要补一个 AddListViewElements(),它在主添加流程 AddToElementsArray() 末尾被调用,专门拦截类名为 CListView 的元件。 下面这段是容器侧的核心逻辑:先比对 ClassName(),不是列表视图直接 return(false);通过后取指针、扩容 m_elements 数组,再把 lv.GetScrollVPointer() 存进去。注意数组下标用的是扩容前的 size,避免覆盖。 测试时我们只留主菜单、右键菜单和状态栏,其余控件删掉,然后声明 m_listview1 实例并设边距宏 LISTVIEW1_GAP_X=2、LISTVIEW1_GAP_Y=43。创建方法里定 ITEMS_TOTAL1=20,可见行数限 10,悬停高亮打开,预选第 6 项(索引 5),项目文字用 «SYMBOL» 加编号填充。 编译挂到图表后,你应该看到 20 项里只露 10 行、第 6 行处于选中态的界面;但此刻它还不响应鼠标,纯展示。下一部分才会补管理逻辑,外汇与贵金属图表上做这类 GUI 测试请留意点差滑点带来的高风险,控件卡顿可能误判行情。

MQL5 / C++
class CWndContainer
  {
class="kw">private:
   class=class="str">"cmt">//--- 把列表视图对象指针保存到库中
   class="type">bool                AddListViewElements(class="kw">const class="type">int window_index,CElement &object);
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 把列表视图指针保存到库中                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CWndContainer::AddListViewElements(class="kw">const class="type">int window_index,CElement &object)
  {
class=class="str">"cmt">//--- 如果不是列表视图,退出
   if(object.ClassName()!="CListView")
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 取得列表视图指针
   CListView *lv=::GetPointer(object);
class=class="str">"cmt">//--- 加大元件数组
   class="type">int size=::ArraySize(m_wnd[window_index].m_elements);
   ::ArrayResize(m_wnd[window_index].m_elements,size+class="num">1);
class=class="str">"cmt">//--- 取得滚动条指针
   CScrollV *sv=lv.GetScrollVPointer();
class=class="str">"cmt">//--- 在库中保存元件
   m_wnd[window_index].m_elements[size]=sv;
   class="kw">return(true);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 把指针加到元件数组中                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CWndContainer::AddToElementsArray(class="kw">const class="type">int window_index,CElement &object)
  {
class=class="str">"cmt">//--- 如果库中没有包含控件表单
class=class="str">"cmt">//--- 如果需求是为了不存在的表单
class=class="str">"cmt">//--- 添加到通用元件数组中
class=class="str">"cmt">//--- 把元件对象加到通用对象数组中
class=class="str">"cmt">//--- 保存所有表单中最后一个元件的id
class=class="str">"cmt">//--- 元件id计数器加1
class=class="str">"cmt">//--- 在库中保存上下文菜单对象的指针
class=class="str">"cmt">//--- 把主菜单对象的指针存储到库中
class=class="str">"cmt">//--- 在库中保存分割按钮的指针
class=class="str">"cmt">//--- 在库中保存工具提示的指针
class=class="str">"cmt">//--- 在库中保存列表视图对象的指针
   if(AddListViewElements(window_index,object))
      class="kw">return;
   }
class CProgram : class="kw">public CWndEvents
  {
class="kw">private:
   class=class="str">"cmt">//--- 列表视图
   CListView         m_listview1;
   class=class="str">"cmt">//--- 
class="kw">private:
   class=class="str">"cmt">//--- 列表视图
class="macro">#define LISTVIEW1_GAP_X(class="num">2)
class="macro">#define LISTVIEW1_GAP_Y(class="num">43)
   class="type">bool                CreateListView1(class="type">void);
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建列表视图 class="num">1                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateListView1(class="type">void)
  {
class=class="str">"cmt">//--- 列表视图的大小
class="macro">#define ITEMS_TOTAL1 class="num">20
class=class="str">"cmt">//--- 保存窗口指针
   m_listview1.WindowPointer(m_window1);
class=class="str">"cmt">//--- 坐标
   class="type">int x=m_window1.X()+LISTVIEW1_GAP_X;

常见问题

检查初始化函数里表头高度与单行高度的乘积是否含滚动条占位;用固定条目数先跑通再动态算高。
先用 GetSelectedIndex 确认返回非负,再按索引取文本;越界时返回空串,需做边界判断。
可以。把控件代码贴给小布,它能标出越界风险和未处理空选中的分支,并给出改法。
条目 y 坐标要减去滚动条当前位移;生成循环里统一套同一个偏移变量,别写死。
在类构造里限定最大行数常量,超量时丢弃或报错;别依赖动态无限扩容器。