现在的位置: 首页 > 综合 > 正文

CListCtrl控件详解

2014年09月05日 ⁄ 综合 ⁄ 共 20855字 ⁄ 字号 评论关闭

1、视图类别

列表视图控件可用四种不同方式显示其内容,称之为”视图“,有以下四个类别:

图标视图:每一项以全尺寸图标(32×32像素)出现,下面有一个标签。用户可在列表视图窗口拖动项到任意位置。
小图标:视图每一项以小图标(16×16像素)出现,右边有一个标签。用户可在列表视图窗口拖动项到任意位置。
列表视图:每一项以小图标出现,下面有一个标签。项按列排列,不能拖动到列表视图窗口的任何位置。

报表视图:每一项在本行上出现,右边有排列成列的附加信息。最左边的列包含小图标和标签,下一列包含应用指定的子项。

2、构造函数

CListCtrl::Create()创建列表控件并将其附加给CListCtrl对象,函数原型:

virtual BOOL Create(
   DWORD dwStyle,   //控件风格
   const RECT& rect,//位置和大小
   CWnd* pParentWnd,//父窗口
   UINT nID         //ID标识
);

dwStyle:LVS_ALIGNLEFT,指定条款左对齐且是”图标视图“或”小图标视图“。

                 LVS_ALIGNMASK决定控件当前靠齐方式。

                 LVS_ALIGNTOP指定条款顶端对齐且是”图标视图“或”小图标视图“。

                 LVS_AUTOARRANGE图标自动保持小图标和图标视图

                      LVS_EDITLABELSItem的文本可以编辑。父窗口处理通知消息: LVN_ENDLABELEDIT

                      LVS_ICON指定图标视图。

                 LVS_LIST,指定列表视图。

                 LVS_NOCOLUMNHEADER,在report类别的视图中不显示列头,report类别默认是显示列头的。

                      LVS_NOLABELWRAP在icon视图中,Item的文本在单行显示。Icon视图下可能多行。

                      LVS_NOSCROLL,屏蔽滚动条,所有item必须在客户区内。该风格与list和report视图不兼容。

                LVS_NOSORTHEADER,列头没有按钮功能。在report视图中,点击列头不执行操作时候(比如排序),可以使用该风格

                LVS_OWNERDATA,指定一个虚拟的ListView, 由用户自己管理Item数据。

                LVS_OWNERDRAWFIXED,允许自绘。

                LVS_REPORT,该风格决定一个report视图,当对ClistCtrl使用该风格时候,第一列总是左对齐。你不能使用LVCFMNT_RIGHT改变列的对齐方式。

                LVS_SHAREIMAGELISTS,在视图控件销毁时,不删除与之关联的imagelist控件。该风格可以使多个listctrl使用同样的imagelist。

                LVS_SHOWSELALWAYS,使listctrl控件一直处于选中状态,即使该控件没有焦点(鼠标移动到某项上自动变为选中)。

                LVS_SINGLESEL,在同一时间只有一个item可以被选择,默认情况下是可以有多个items被选择的。

                LVS_SMALLICON,指定小图标视图类别。

                LVS_SORTASCENDING,item索引基于item的文本升序排列。

                LVS_SORTDESCENDING,item索引基于item的文本降序排列。

                LVS_TYPEMASK,决定当前窗口风格(是否屏蔽设置的视图类别如:LVS_ICON)。可以确定当前视图的窗口风格。

               LVS_TYPESTYLEMASK,决定窗口风格,控制item对齐方式和头显示以及其它行为。

备注:由于LVS_LIST和LVS_REPORT风格的视图根据索引以相同的方式排列显示,索引这两个类别的视图的排序结果能立即体现给用户。关于LVS_ICON 和LVS_SMALLICON这两个视图不使用索引决定icons的位置,所以它们排序结果不能体现给用户。

你可以使用LVS_TYPEMASK风格从获得当前列表的视图类别。

你可以使用LVS_ALIGNMASK风格获得视图列表当前靠齐方式。

你可以使用LVS_TYPESTYLEMASK风格获得对齐方式(LVS_ALIGNTOP 、LVS_ALIGNLEFT)和头显示和行为(LVS_NOCOLUMNHEADER、LVS_NOSORTHEADER)的的风格。

eg:

	m_ListCtrl.Create(WS_BORDER|WS_DLGFRAME|WS_VISIBLE|LVS_REPORT/*|LVS_NOLABELWRAP*/|LVS_SHOWSELALWAYS,\
		CRect(16,90,780,580), this, 101);

	//设置扩展风格
	m_ListCtrl.SetExtendedStyle(LVS_SHOWSELALWAYS|LVS_EX_CHECKBOXES|LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES);
	
	//设置背景颜色
	m_ListCtrl.SetBkColor(RGB(255,255,255));


	//设置第一行为选中状态
	int nIndex = 0;
    m_ListCtrl.SetItemState(nIndex,LVIS_SELECTED|LVIS_FOCUSED,LVIS_SELECTED|LVIS_FOCUSED);

	//设置列表控件显示图标
	UINT nIconID[] = {IDI_ICON1,IDI_ICON2,IDI_ICON3};
	m_ImageList.Create(32, 32, /*ILC_COLORDDB|ILC_MASK*/ILC_COLOR24, sizeof(nIconID)/sizeof(UINT), 1); 
	m_ImageList.SetBkColor(RGB(255,255,255));
	for(int i = 0; i < sizeof(nIconID)/sizeof(UINT); i++)
	{
		m_ImageList.Add(AfxGetApp()->LoadIcon(nIconID[i]));//将图标添加到CImageList
	}
	m_ListCtrl.SetImageList(&m_ImageList,LVSIL_SMALL);

	//插入三列
	m_ListCtrl.InsertColumn(0, _T("姓名"), LVCFMT_LEFT, 255);
    m_ListCtrl.InsertColumn(1, _T("性别"), LVCFMT_LEFT, 255);
    m_ListCtrl.InsertColumn(2, _T("出生日期"), LVCFMT_LEFT, 270);
	
	//插入两行数据
	m_ListCtrl.InsertItem(0, L"王二小", 0);
	m_ListCtrl.SetItemText(0, 1, L"男");
	m_ListCtrl.SetItemText(0, 2, L"1992");
	m_ListCtrl.InsertItem(1, L"张三", 1);
	m_ListCtrl.SetItemText(1, 1, L"男");
	m_ListCtrl.SetItemText(1, 2, L"1987");
	m_ListCtrl.InsertItem(2, L"李四", 2);
	m_ListCtrl.SetItemText(2, 1, L"男");
	m_ListCtrl.SetItemText(2, 2, L"1980");

3、风格设置

ModifyStyle()用来设置”视图“类别,参数:LVS_ICON为图标视图

                                                                         LVS_SMALLICON为小图标视图

                                                                         LVS_LIST为列表视图

                                                                         LVS_REPORT为报表视图

为防止用户出错, 微软干脆把LVS_ICON, LVS_SMALLICON, LVS_LIST, LVS_REPORT这些都不设置成位标志,因此不能叠加.同时还设置了一个多余的掩码:LVS_TYPEMASK.它是用来屏蔽的。

例如,我们要判断一个CListCtrl的风格以下写法是错误的:

DWORD dwStyle = m_listctrl.GetStyle( );
// 判断是否大图标样式
if( dwStyle & LVS_ICON )
......

正确的方法为使用掩码:

DWORD dwStyle = m_listctrl.GetStyle( ) & LVS_TYPEMASK;
if( dwStyle = = LVS_ICON)
......

同理,我们在改变风格时应该这样写:

 ModifyStyle( LVS_TYPEMASK, LVS_ICON);

SetWindowLong()可以设置新的”视图“类别和控件风格:

    DWORD dwStyle = ::GetWindowLong(m_report.m_hWnd,  GWL_STYLE);

    dwStyle &= ~(LVS_TYPEMASK);

    SetWindowLong(m_report.m_hWnd,  GWL_STYLE, dwStyle|LVS_REPORT|LVS_NOCOLUMNHEADER

        | LVS_NOLABELWRAP| LVS_EDITLABELS);

4、扩展风格设置

SetExtendedStyle()/GetExtendedStyle()为设置/获得控件的扩展风格,重要参数:

                                                       LVS_EX_CHECKBOXES,带复选框

                                                       LVS_EX_FULLROWSELECT,选中某行后,整行高亮(只适用于REPORT风格)

                                                       LVS_EX_GRIDLINES,网状线(只适用于REPORT风格)

                                                       .......

举例:

          DWORD dwStyle = GetExtendedStyle();
          dwStyle |= LVS_EX_FULLROWSELECT;
          dwStyle |= LVS_EX_GRIDLINES;
          SetExtendedStyle(dwStyle);

5、其它风格设置

获取与设置列表控件的背景色:GetBkColor、SetBkColor
获取与设置文字背景色:GetTextBkColor、SetTextBkColor
获取与设置文字前景色:GetTextColor、SetTextColor

5、数据插入

插入一列标题:m_listCtrl.InsertColumn(0, L"项目一", LVCFMT_LEFT 80);

                            0是索引项,"项目一"为列标题,LVCFMT_LEFT为对齐方式,80为列宽。

获取与设置列宽:GetColumnWidth、SetColumnWidth

插入一行数据:m_ListCtrl.InsertItem(0, L"王二小");     //插入第一行,且将第一列的值设为"王二小"
                            m_ListCtrl.SetItemText(0, 1, L"男");     //将第一行第二列的值设为“男”
                            m_ListCtrl.SetItemText(0, 2, L"1987");//将第一行第二列的值设为“1987”

获取与设置行附加数据:GetItemData、SetItemData

6、选中和取消选中

选中第一行:int nIndex = 0;
                        m_ListCtrl.SetItemState(nIndex, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED);

取消选中第一行:m_ListCtrl.SetItemState(nIndex, 0, LVIS_SELECTED|LVIS_FOCUSED);

允许多行选中时,获取选中的行数:GetSelectedCount

单行选中时,获取选中的行:GetSelectionMark

获取某行的状态是否为选中:GetItemState(n,  LVIS_SELECTED) == LVIS_SELECTED

7、得到选中的所有行

  //方法一:
  CString str;
  for(int i=0; i<m_ListCtrl.GetItemCount(); i++)
  {
    if(m_ListCtrl.GetItemState(i, LVIS_SELECTED) == LVIS_SELECTED )
    {
      str.Format(_T("选中了第%d行"), i);
      AfxMessageBox(str);
     }
  }

  //方法二:
  POSITION pos=m_ListCtrl.GetFirstSelectedItemPosition();
  if(pos==NULL)
   TRACE0("No items were selected!\n");
  else
  {
   while(pos)
    {
      int nItem=m_ListCtrl.GetNextSelectedItem(pos);
      TRACE1("Item %d was selected!\n", nItem);
      //添加其他操作

     }
   }

8、得到所有行的checkbox的状态

获取与设置Check:GetCheck、SetCheck。使用前需要设置控件属性SetExtendedStyle(LVS_EX_CHECKBOXES);使控件有勾选功能

 CString str;
 for(int i=0; i<m_ListCtrl.GetItemCount(); i++)
 {
   if(m_ListCtrl.GetCheck(i))
   {
     str.Format(_T("第%d行的checkbox为选中状态"), i);
     AfxMessageBox(str);
    }
 }

9、获得item的信息

获得第一行第一列上的数据:CString str = m_ListCtrl.GetItemText(0, 0);

获得item的更多信息: TCHAR szBuf[1024];
                                         LVITEM lvi;
                                         lvi.iItem = 0;
                                         lvi.iSubItem = 1;
                                         lvi.mask = LVIF_TEXT;
                                         lvi.pszText = szBuf;
                                         lvi.cchTextMax = 1024;
                                         m_ListCtrl.GetItem(&lvi);

10、获得所有列的header字符串内容即标题

     LVCOLUMN lvcol;
     TCHAR  str[256];
     int   nColNum;
     CString strColumnName[3];//假如有3列

     nColNum = 0;
     lvcol.mask = LVCF_TEXT;
     lvcol.pszText = str;
     lvcol.cchTextMax = 256;
     while(m_ListCtrl.GetColumn(nColNum, &lvcol))
     {
       strColumnName[nColNum] = lvcol.pszText;
       nColNum++;
      }

11、使一项可见,即滚动滚动条

m_ListCtrl.EnsureVisible(i, FALSE);

12、获得所有行数、列数

获得所有行数:m_listctrl.GetItemCount( );

获得所有列数:int nHeadNum=m_ListCtrl.GetHeaderCtrl()->GetItemCount();

13.删除操作

删除指定列:DeleteColumn
删除指定行:DeleteItem

删除所有行:DeleteAllItems

删除所有列:
  方法一:
  while(m_ListCtrl.DeleteColumn(0));
  //因为你删除了第一列后,后面的列会依次向上移动。

  方法二:
  int nColumns = 3; //列数
  for(int i=nColumns-1; i>=0; i--)
     m_ListCtrl.DeleteColumn(i);

删除多行或者多列:

注意要从后面开始删起,比如,如果我想删除第一行和第二行,应该:
   m_listctrl.DeleteItem(1);
   m_listctrl.DeleteItem(0);
而不能:
   m_listctrl.DeleteItem(0);
   m_listctrl.DeleteItem(1);
因为每次删除一行或者一列,后面的索引号都要变化,所以从后面开始删除就没关系.
如果用for循环,应该递减:
   For(int k=m_listctrl.GetItemCount( );k>=-1;k++)
       m_listctrl.DeleteItem(k);
上面是删除所有行,当然也可以用CListCtrl::DeleteAllItems

14、得到单击选中的行列号

添加listctrl控件的NM_CLICK消息相应函数
void CMyList::OnNMClick(NMHDR *pNMHDR, LRESULT *pResult)
{
    // TODO: 在此添加控件通知处理程序代码
     NM_LISTVIEW* pNMListView=(NM_LISTVIEW*)pNMHDR;
     if(pNMListView->iItem!=-1)
     {
        CString strtemp;
        strtemp.Format(L"单击的是第%d行第%d列",pNMListView->iItem, pNMListView->iSubItem);
        AfxMessageBox(strtemp);
     }

    *pResult = 0;
}

15、得到checkbox选中的行号

有时需要在item前面添加一个CheckBox,供用户选择,然后对所有选中项进行处理。

       这里涉及到两个问题:第一个,如何添加CheckBox风格;第二个,如何判断某一行的CheckBox状态是否发生改变。

       对于第一个问题,在基本操作里已经有所阐述了,即通过SetExtendedStyle函数添加LVS_EX_CHECKBOXES扩展风格。

      这里重点探讨第二个问题,首先,操作复选框状态的有两个函数:

                      BOOL GetCheck(int nItem)-------获取复选框状态

                      BOOL SetCheck( int nItem, BOOL fCheck = TRUE )-------设置复选框状态

其次,我们要搞清楚以下四点:

当列表的项item改变时,控件会向父窗口发送LVN_ITEMCHANGED消息,因此可以在LVN_ITEMCHANGED消息的响应函数中对复选框的状态进行处理(查询或设置)。

鼠标点击CheckBox时,消息的顺序是 NM_CLICK —> LVN_ITEMCHANGED,即CheckBox的状态是在 NM_CLICK消息函数结束后才会发生变化,在NM_CLICK中使用GetCheck无效。

鼠标点击Item(非CheckBox区域)时,消息的顺序是 LVN_ITEMCHANGED —> NM_CLICK。

调用InsertItem 函数时,也会产生LVN_ITEMCHANGED消息。鉴于此,通常会自定义一个BOOL型变量m_bHit 来判断是点击操作还是插入操作,该变量初始赋FALSE,当有鼠标点击item时赋TRUE, 检测完是否有CheckBox被点击后重新复位为FALSE。

void CMyList::OnNMClick(NMHDR *pNMHDR, LRESULT *pResult)
{
    // TODO: 在此添加控件通知处理程序代码
     NM_LISTVIEW* pNMListView=(NM_LISTVIEW*)pNMHDR;
     if(pNMListView->iItem!=-1)
         g_iItemIndex = pNMListView->iItem;

    *pResult = 0;
}

void CMyList::OnLvnItemchanged(NMHDR *pNMHDR, LRESULT *pResult)
{
    LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
    // TODO: 在此添加控件通知处理程序代码
    if(GetCheck(g_iItemIndex))
    {
        CString str;
        str.Format(L"第%d个checkox被选择", g_iItemIndex);
        MessageBox(str);
    }
    
    *pResult = 0;
}

16、右键单击item弹出菜单

添加CListCtrl控件的NM_RCLICK消息相应函数

void CMyDlg::OnRclickList1(NMHDR* pNMHDR, LRESULT* pResult)
{
   NM_LISTVIEW* pNMListView=(NM_LISTVIEW*)pNMHDR;
   if(pNMListView->iItem!=-1)
   {
      DWORD dwPos = GetMessagePos();
      CPoint point( LOWORD(dwPos), HIWORD(dwPos) );

      CMenu menu;
      VERIFY( menu.LoadMenu( IDR_MENU1 ) );
      CMenu* popup = menu.GetSubMenu(0);
      ASSERT( popup != NULL );
      popup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,

      point.x, point.y, this );
    }

    *pResult = 0;
}

17、CListCtrl进行大数据量更新时,避免闪烁

m_ListCtrl.SetRedraw(FALSE);

//更新内容
m_ListCtrl.SetRedraw(TRUE);
m_ListCtrl.Invalidate();
m_ListCtrl.UpdateWindow();

注:应用程序调用SetRedraw以允许重画变化或防止变化被重画。

CWnd::SetRedraw( BOOL bRedraw = TRUE );

参数: bRedraw 指定了重画标志的状态。如果这个参数为TRUE,则重画标志被设置;如果为FALSE,则该标志被清除。

这个成员函数设置或清除重画标志。当重画标志被清除时,在每次变化以后,内容不会更新,直到重画标志被设置才会重新绘出。例如,如果一个应用程序需要在列表框中加入几个项,则可以清除重画标志,加入项,然后设置重画标志。最后,应用程序可以调用Invalidate或InvalidateRect成员函数以使列表框被重画.

18、让ListCtrl支持编辑的功能:
①、将属性中的 Edit Labels 设置为 True;
②、响应控件的 LVN_EDNLABELEDIT 消息,添加如下代码:
CString strText;
m_ListCtrl.GetEditControl()->GetWindowText(strText);
m_ListCtrl.SetItemText(pDispInfo->item.iItem, pDispInfo->item.iSubItem, strText);

19、多行可选时,跳到第一个被选中的行,即使第一个被选中的行可视。

  POSITION pos = pView->GetListCtrl().GetFirstSelectedItemPosition();
  int nFirstSel = pView->GetListCtrl().GetNextSelectedItem(pos);
  if (nFirstSel != -1)
  {
         pView->GetListCtrl().EnsureVisible(nFirstSel,FALSE);
  }

20、列表中添加图标:

获取与设置图标列表:GetImageList、SetImageList

添加图标效果代码:


CImageList m_ImageList;
......
UINT nIconID[] = {IDI_ICON1,IDI_ICON2,IDI_ICON3};//图标ID
m_ImageList.Create(32,32,/*ILC_COLOR24*/ILC_COLORDDB|ILC_MASK,sizeof(nIconID)/sizeof(UINT),0);//初始化CImageList类,sizeof(nIconID)/sizeof(UINT)为图标个数,后面的0表示CImageList在sizeof(nIconID)/sizeof(UINT)个图标个数的基础上可再添加多少个图标。
m_ImageList.SetBkColor(RGB(255,255,255));//设置CImageList背景色为白色
for(int i = 0; i < sizeof(nIconID)/sizeof(UINT); i++)
{
 m_ImageList.Add(AfxGetApp()->LoadIcon(nIconID[i]));//将图标添加到CImageList
}
m_ListCtrl.SetImageList(&m_ImageList,LVSIL_SMALL);//设置列表控件显示图标

m_ListCtrl.InsertColumn(0,_T("第1列"),0,100);
m_ListCtrl.InsertColumn(1,_T("第2列"),0,100);
m_ListCtrl.InsertColumn(2,_T("第3列"),0,100);

m_ListCtrl.InsertItem(0,_T("数据1"),0);//后面的0表示添加CImageList中的第0个图标
m_ListCtrl.InsertItem(1,_T("数据2"),1);//后面的1表示添加CImageList中的第1个图标
m_ListCtrl.InsertItem(2,_T("数据3"),2);//后面的2表示添加CImageList中的第2个图标
m_ListCtrl.SetItemText(0,1,_T("01"));
m_ListCtrl.SetExtendedStyle(LVS_EX_CHECKBOXES);//设置控件有勾选功能
DWORD dwStyle = m_ListCtrl.GetExtendedStyle();
m_ListCtrl.SetExtendedStyle(dwStyle|LVS_EX_FULLROWSELECT);//设置控件选中行的功能

21、遍历文件夹并显示文件名与图标:

CFileFind fileFind;
CString szDir("D:\\Tools");
BOOL bRet = fileFind.FindFile(szDir+"\\*.*");
while(bRet)
{
	bRet = fileFind.FindNextFile();
	if(fileFind.IsDots())
		continue;
	if(!fileFind.IsDirectory())
	{
		SHFILEINFO shfi;
		SHGetFileInfo(fileFind.GetFilePath(),FILE_ATTRIBUTE_NORMAL,&shfi,sizeof(shfi),SHGFI_ICON|SHGFI_USEFILEATTRIBUTES|SHGFI_TYPENAME);
		int nImageID = m_ImageList.Add(shfi.hIcon);
		m_ListCtrl.InsertItem(m_ListCtrl.GetItemCount(),fileFind.GetFileName(),nImageID);
	}
}

22、复制列表内容

void CXXXView::OnCopydataToClipboard()
{
	int nSelectCount = GetListCtrl().GetSelectedCount();
	int nSelect = -1;
	CString strData = _T("");
	for (int i = 0; i < nSelectCount; i++)
	{
		//找到选择的行号
		nSelect = GetListCtrl().GetNextItem(nSelect,LVNI_ALL | LVNI_SELECTED);
		//列数
		int nColumnCount = GetListCtrl().GetHeaderCtrl()->GetItemCount();
		for (int j = 0; j < nColumnCount; j++)
		{
			CString text;
			text = GetListCtrl().GetItemText(nSelect,j);
			if (j != 0)
				strData += "\t";
			strData += text;
		}
		//用\r\n来控制回车和换行
		if (i != nSelectCount - 1)
			strData += "\r\n";
	}

	//复制到剪贴板
	HGLOBAL clipbordBuf = GlobalAlloc(GMEM_DDESHARE,2*(strData.GetLength() + 1));
	TCHAR *pBuf = (TCHAR*)GlobalLock(clipbordBuf);
	lstrcpy((LPTSTR)pBuf,(LPCTSTR)strData);
	GlobalUnlock(clipbordBuf);

	OpenClipboard();
	EmptyClipboard();
	SetClipboardData(CF_TEXT,pBuf);
	CloseClipboard();
}

23、在非第一列中添加图标
如果想在子列插入图标,首先必须使ListCtrl控件具备LVS_EX_SUBITEMIMAGES风格,可以通过m_List.SetExtendedStyle(m_List.GetExtendedStyle()|LVS_EX_SUBITEMIMAGES);给控件附加该风格。

可以使用SetItem函数来添加。


m_List.SetExtendedStyle(LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES|LVS_EX_SUBITEMIMAGES);

m_List.SetItem(0,1,LVIF_TEXT|LVIF_IMAGE,“aaa”,0,0,0,0);//在第一行第二列加入CImageList中第1个图标

24、得到被单击的列头索引号

void CmylistCtrl::OnColumnclick(NMHDR* pNMHDR, LRESULT* pResult)
{
      NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
      selectedField=pNMListView->iSubItem;//列头号
}

25、根据索引号得到某一列的信息

HDITEM   hdi;   
TCHAR     lpBuffer[256];     
hdi.mask   =   HDI_TEXT;   
hdi.pszText   =   lpBuffer;   
hdi.cchTextMax   =   256;   
plistctrl->GetHeaderCtrl()->GetItem(index,   &hdi);
CString str=hdi.pszText;

26、虚拟列表技术

一、什么是虚拟列表控件

虚拟列表控件是指带有LVS_OWNERDATA风格的列表控件

 

二、为什么使用虚拟列表控件

我们知道,通常使用列表控件CListCtrl,需要调用InsertItem把要显示的数据插入列表中,之后我们就不必关心数据在哪里了,这是因为控件自己开辟了内存空间来保存这些数据。现在假设我们要显示一个数据库,里面的信息量很大,有几十万条记录。

通常有两种方法解决这个问题:

1是仅仅在ListCtrl中插入少量的数据,比如100个,然后通过[上一页][下一页]两个按钮进行控制,某一时刻显示的只是从xxx到xxx+100之间的记录。

2是把所有数据全部插入到ListCtrl中,然后让用户通过滚动来查看数据。无疑,很多用户喜欢采用第二种方式,特别是对于已经排序的数据,用户只需用键盘输入某行的开头字符,就可以快速定位到某一行。但是,如果这样做,InsertItem插入数据的过程将是很漫长的,而且用户会看到ListCtrl刷新速度也很慢,而且所有数据都位于内存中消耗了大量的内存,当数据多达上万以后几乎是不能忍受的。

为此,mfc特别提供了虚拟列表的支持。一个虚拟列表看起来和普通的ListCtrl一样,但是不用通过InsertItem来插入数据,它仅仅知道自己应该显示多少数据。但是它如何知道要显示什么数据呢?秘密就在于当列表控件需要显示某个数据的时候,它向父窗口要。假设这个列表控件包含100个元素,第10到20个元素(行)是可见的。当列表控件重画的时候,它首先请求父窗口给它第10个元素的数据,父窗口收到请求以后,把数据信息填充到列表提供的一个结构中,列表就可以用来显示了,显示第10个数据后,列表会继续请求下一个数据。

在虚拟的样式下,ListCtrl可以支持多达DWORD个数据项。(缺省的listctrl控件最多支持int个数据项)。但是,虚拟列表的最大优点不在于此,而是它仅仅需要在内存中保持极少量的数据,从而加快了显示的速度。所以,在使用列表控件显示一个很大的数据库的情况下,采用虚拟列表最好不过了。

不仅CListCtrl提供虚拟列表的功能, MFC的CListView类也有同样的功能。

 

三、虚拟列表控件的消息
虚拟列表总共发送三个消息给父窗口:当它需要数据的时候,它发送LVN_GETDISPINFO消息。这是最重要的消息。当用户试图查找某个元素的时候,它发送LVN_ODFINDITEM消息;还有一个消息是LVN_ODCACHEHINT,用来缓冲数据,基本上很少用到这个消息。

虚拟列表控件使用起来非常简单。它总共只有三个相关的消息,如果你直接使用CListCtrl,应该在对话框中响应这三个消息。如果你使用CListCtrl派生类,可以在派生类中响应这三个消息的反射消息。这三个消息分别是:

(1)LVN_GETDISPINFO 控件请求某个数据
(2)LVN_ODFINDITEM 查找某个数据
(3)LVN_ODCACHEHINT 缓冲某一部分数据

我们必须响应的消息是(1),多数情况下要响应(2),极少数的情况下需要响应(3)

 

四、如何使用虚拟列表控件

1、首先要创建控件,创建一个虚拟列表和创建一个正常的 CListCtrl差不多。先在资源编辑器里面添加一个list control资源。然后选中"Owner data"属性,然后给它捆绑一个CListCtrl变量。添加列,添加imagelist等都和使用正常的listctrl一样。

2、给虚拟列表添加元素。假设 m_list 是这个列表的控制变量。通常的情况下这样添加数据:

m_list.InsertItem(0, _T("Hello world"));

但是对于虚拟列表,不能这么做。只需告诉列表有多少个数据:

//总共显示100行
m_list.SetItemCount(100);

3、处理它的通知消息。

 

五、如何响应虚拟列表的消息

1、处理 LVN_GETDISPINFO 通知消息

当虚拟列表控件需要某个数据的时候,它给父窗口发送一个 LVN_GETDISPINFO通知消息,表示请求某个数据。因此列表的所有者窗口(或者它自己)必须处理这个消息。例如派生类的情况 (CMyListCtrl是一个虚拟列表类对象):

//这里处理的是反射消息
BEGIN_MESSAGE_MAP(CMyListCtrl, CListCtrl)
  //{{AFX_MSG_MAP(CMyListCtrl)
  ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnGetdispinfo)
  //}}AFX_MSG_MAP
END_MESSAGE_MAP()

在LVN_GETDISPINFO的处理函数中,必须首先检查列表请求的是什么数据,可能的值包括:

(1)LVIF_TEXT   必须填充 pszText
(2)LVIF_IMAGE 必须填充 iImage
(3)LVIF_INDENT 必须填充 iIndent
(4)LVIF_PARAM 必须填充 lParam
(5)LVIF_STATE 必须填充 state

根据它的请求,填充所需的数据即可。

//================= 例子代码=====================================

下面的给出一个例子,填充的是列表所需的某个数据项的文字以及图像信息:

LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
LV_ITEM* pItem= &(pDispInfo)->item;

int iItemIndx= pItem->iItem;

if (pItem->mask & LVIF_TEXT) //字符串缓冲区有效
{
   switch(pItem->iSubItem){
       case 0: //填充数据项的名字
           lstrcpy(pItem->pszText,m_Items[iItemIndx].m_strItemText);
           break;
       case 1: //填充子项1
           lstrcpy(pItem->pszText,m_Items[iItemIndx].m_strSubItem1Text);
           break;
       case 2: //填充子项2
           lstrcpy(pItem->pszText,m_Items[iItemIndx].m_strSubItem2Text);
           break;
   }
}

if (pItem->mask & LVIF_IMAGE) //是否请求图像
       pItem->iImage= m_Items[iItemIndx].m_iImageIndex;

甚至连某行数据是否选中(当有checkbox的情况下)的信息也需要由用户自己来维护,例如:
//是否显示该行的选择信息?
if(IsCheckBoxesVisible()) //自定义函数
{
   pItem->mask |= LVIF_STATE;
   pItem->stateMask = LVIS_STATEIMAGEMASK;

   if(m_database[itemid].m_checked)
   {
        pItem->state = INDEXTOSTATEIMAGEMASK(2);
   }
   else
   {
        pItem->state = INDEXTOSTATEIMAGEMASK(1);
    }
}
2、处理 LVN_ODFINDITEM 消息

在资源管理器里面,定位到某个文件夹,会显示很多文件,如果按下键盘的‘A’,则资源管理器会自动找到名字以 'A'打头的文件夹或者文件, 并选择该文件。继续按 A,如果还有其它名字以'A'打头的文件,则下一个文件被选中。如果输入 "AB",则 'AB'打头的文件被选中。这就是列表控件的自动查找功能。

当虚拟列表收到一个LVM_FINDITEM消息,它也会发送这个消息通知父窗口查找目标元素。要搜索的信息通过 LVFINDINFO 结构给出。它是 NMLVFINDITEM 结构的一个成员。当找到要搜索的数据后,应该把该数据的索引(行号)返回,如果没有找到,则返回-1。

以对话框为例,响应函数大致如下:

//================= 例子代码=====================================
void CVirtualListDlg::OnOdfinditemList(NMHDR* pNMHDR, LRESULT* pResult)
{
   // pNMHDR 里面是要查找的元素的信息
   // 要选中的目标元素的行号最后要保存在 pResult 中, 这是关键!

   NMLVFINDITEM* pFindInfo = (NMLVFINDITEM*)pNMHDR;

   

   *pResult = -1;

   //是否按照文字查找?
   if( (pFindInfo->lvfi.flags & LVFI_STRING) == 0 )
   {
       return;
   }

   //这是我们要找的字符串
   CString searchstr = pFindInfo->lvfi.psz;

   int startPos = pFindInfo->iStart;//保存起始位置

   //判断是否最后一行
   if(startPos >= m_list.GetItemCount())
       startPos = 0;

   int currentPos=startPos;
   
   //开始查找
   do
   {       
       if( _tcsnicmp(m_database[currentPos].m_name,
                searchstr, searchstr.GetLength()) == 0)
       {
           //选中这个元素,停止查找
           *pResult = currentPos;
           break;
       }

       currentPos++;

       //从头开始
       if(currentPos >= m_list.GetItemCount())
           currentPos = 0;

   }while(currentPos != startPos);       
}

显然,如果数据很多,必须实现一个快速查找的方法。

关于pFindInfo->lvfi里面的信息的详细说明可以参考 MSDN。

3、处理 LVN_ODCACHEHINT 消息。

假如我们从数据库或者其它地方读取数据的速度比较慢,则可以利用这个消息,批量读取一些数据,然后根据请求,逐个提供给虚拟列表。LVN_ODCACHEHINT消息的用途就是给程序一个缓冲数据的机会。以提高程序的性能。

 

//================= 例子代码=====================================
使用 ClassWizard 重载 OnChildNotify 函数,检查是否 LVN_ODCACHEHINT 消息,然后准备缓冲数据:

NMLVCACHEHINT* pcachehint=NULL;

NMHDR* phdr = (NMHDR*)lParam;

if(phdr->code == LVN_ODCACHEHINT)
{
    pcachehint= (NMLVCACHEHINT*) phdr;
    //自定义函数,准备指定范围的数据到缓冲区
    PrepCache(pcachehint->iFrom, pcachehint->iTo);
}
else ...

注意,如果消息不是 LVN_ODCACHEHINT,则要传递给基类进行处理。

 

五、如何修改ListCtrl显示的数据。

由于是程序自己维护数据,所以只需修改数据库中的数据,然后调用CListCtrl::RedrawItems函数进行重画即可。

 

六、数据的选择状态和选择框

CListCtrl可以显示checkbox选择框。有些情况下是很有用的。对于正常的listctrl,用户可以用鼠标来修改某个元素的选择状态,但是对于虚拟列表就不行了。必须自己处理一些消息,然后自己保存数据的选中状态:

void CVirtualListDlg::ToggleCheckBox(int item)
{
   m_database[item].m_checked = !m_database[item].m_checked;
   m_list.RedrawItems(item, item);
}

处理 LVN_KEYDOWN消息,添加对空格键的响应,用于切换选择状态:

void CVirtualListDlg::OnKeydownList(NMHDR* pNMHDR, LRESULT* pResult)
{
   LV_KEYDOWN* pLVKeyDown = (LV_KEYDOWN*)pNMHDR;

   if( pLVKeyDown->wVKey == VK_SPACE )
   {
      int item = m_list.GetSelectionMark();
       if(item != -1)
           ToggleCheckBox(item);
   }

   *pResult = 0;
}

然后处理 NM_CLICK 消息:

void CVirtualListDlg::OnClickList(NMHDR* pNMHDR, LRESULT* pResult)
{
   NMLISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;

   LVHITTESTINFO hitinfo;
   hitinfo.pt = pNMListView->ptAction;

   int item = m_list.HitTest(&hitinfo);

   if(item != -1)
   {
       //看看鼠标是否单击在 check box上面了?
       if( (hitinfo.flags & LVHT_ONITEMSTATEICON) != 0)
       {
           ToggleCheckBox(item);
       }
   }
   
   *pResult = 0;
}

 

七、备注:

    1、虚拟列表无法进行排序。

    2、虚表的一个优点是容易保持和数据库的同步。修改数据库中的数据,然后重画list十分容易而且高效。

    3、虚表的另一个优点是可以根据需要产生数据。比如在某一列加上行号

27、隐藏滚动条

CWnd类有一系列的关于Scroll的函数,感觉好像有很多方法可以实现我们的目的。
    1、调用EnableScrollBarCtrl直接将不需要的类型的滚动条disable掉;
    2、调用GetScrollBarCtrl得到滚动条指针,然后用ShowWindow(SW_HIDE)将其隐藏掉;
    3、调用ShowScrollBar直接隐藏。
    遗憾的是,对于report类型的CListCtrl来说,上面的方法全部无效。(好像对于其他非report类型的CListCtrl控件或者CListView控件有些方法是有效的,没有进行具体验证。)其实用spy++查看一下就知道,CListCtrl中的滚动条根本不是窗口,也就是说滚动条不是scroll bar控件,只是CListCtrl自行绘制上去的假滚动条。因此上面这些对于scroll bar操作的函数都无效也就可以理解了,大家可以留意一下GetScrollBarCtrl函数返回的全是NULL。

 可以在创建列表时指定LVS_NOSCROLL风格来屏蔽滚动条,所有item必须在客户区内,可该风格与list和report视图不兼容。

28、CListCtr总结:http://blog.csdn.net/zwgdft/article/details/7560592

            CListCtrl改变行高:http://www.cnblogs.com/rainbowzc/archive/2009/08/30/1556904.html

         CListCtrl实现QQ风格的好友列表:http://www.cppblog.com/getborn/archive/2008/05/27/51268.html

            CListCtrl的Report风格自绘:http://jingyan.baidu.com/article/5bbb5a1b38af1113eaa17910.html

         CListCtrl装上皮肤:http://blog.csdn.net/coral9527/article/details/7982910

            CTreeCtrl和CListCtrl:http://www.cnblogs.com/kzang/archive/2012/11/23/2783305.html

转载出处http://yjlyp.blog.163.com/blog/static/802090112011327313166/

                http://hi.baidu.com/cannotforget/item/384de75e1aaa813f33e0a9c4

                http://hi.baidu.com/pearry/item/e4ad9e7baefc483f6f29f62b

              

抱歉!评论已关闭.