您的位置:首页 > 其它

CListCtrl控件详解(三)

2015-08-30 15:18 411 查看
17、右键单击item弹出菜单

例1:

添加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;

}

例2:

可以实现在整个系统中右键单击item弹出菜单:

void CEditableListCtrl::OnContextMenu(CWnd* pWnd, CPoint point)

{

// TODO: Add your message handler code here

CMenu menu;
VERIFY(menu.LoadMenu (IDR_MENUOUT));
CMenu* pPopup = menu.GetSubMenu(0);
ASSERT(pPopup != NULL);
pPopup->TrackPopupMenu(TPM_LEFTALIGN|TPM_LEFTBUTTON,point.x, point.y,this->GetParent());//this->GetParent()让其父窗口处理菜单


}

18、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成员函数以使列表框被重画.

19、让ListCtrl支持编辑的功能:

①、将属性中的 Edit Labels 设置为 True;

②、响应控件的 LVN_EDNLABELEDIT 消息,添加如下代码:

CString strText;

m_ListCtrl.GetEditControl()->GetWindowText(strText);

m_ListCtrl.SetItemText(pDispInfo->item.iItem, pDispInfo->item.iSubItem, strText);

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

POSITION pos = pView->GetListCtrl().GetFirstSelectedItemPosition();

int nFirstSel = pView->GetListCtrl().GetNextSelectedItem(pos);

if (nFirstSel != -1)

{

pView->GetListCtrl().EnsureVisible(nFirstSel,FALSE);

}

21、列表中添加图标:

获取与设置图标列表: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);//设置控件选中行的功能

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

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);

}

}

23、复制列表内容

v
f497
oid 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();


}

24、在非第一列中添加图标

如果想在子列插入图标,首先必须使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个图标

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

void CmylistCtrl::OnColumnclick(NMHDR* pNMHDR, LRESULT* pResult)

{

NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;

selectedField=pNMListView->iSubItem;//列头号

}

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

HDITEM hdi;

TCHAR lpBuffer[256];

hdi.mask = HDI_TEXT;

hdi.pszText = lpBuffer;

hdi.cchTextMax = 256;

plistctrl->GetHeaderCtrl()->GetItem(index, &hdi);

CString str=hdi.pszText;

27、虚拟列表技术

一、什么是虚拟列表控件

虚拟列表控件是指带有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、虚表的另一个优点是可以根据需要产生数据。比如在某一列加上行号

28、隐藏滚动条

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视图不兼容。

转载改编自:http://www.xuebuyuan.com/2122805.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: