ListCtrl----虛擬列表的用法
一、什么是虛擬列表控件虛擬列表控件是指帶有LVS_OWNERDATA風(fēng)格的列表控件。。
二、為什么使用虛擬列表控件
我們知道,通常使用列表控件CListCtrl,需要調(diào)用InsertItem把要顯示的數(shù)據(jù)插入列表中,之后我們就不必關(guān)心數(shù)據(jù)在哪里了,這是因?yàn)榭丶约洪_(kāi)辟了內(nèi)存空間來(lái)保存這些數(shù)據(jù)?,F(xiàn)在假設(shè)我們要顯示一個(gè)數(shù)據(jù)庫(kù),里面的信息量很大,有幾十萬(wàn)條記錄。通常有兩種方法解決這個(gè)問(wèn)題:1是僅僅在ListCtrl中插入少量的數(shù)據(jù),比如100個(gè),然后通過(guò)[上一頁(yè)][下一頁(yè)]兩個(gè)按鈕進(jìn)行控制,某一時(shí)刻顯示的只是從xxx到xxx+100之間的記錄。2是把所有數(shù)據(jù)全部插入到ListCtrl中,然后讓用戶通過(guò)滾動(dòng)來(lái)查看數(shù)據(jù)。無(wú)疑,很多用戶喜歡采用第二種方式,特別是對(duì)于已經(jīng)排序的數(shù)據(jù),用戶只需用鍵盤(pán)輸入某行的開(kāi)頭字符,就可以快速定位到某一行。但是,如果這樣做,InsertItem插入數(shù)據(jù)的過(guò)程將是很漫長(zhǎng)的,而且用戶會(huì)看到ListCtrl刷新速度也很慢,而且所有數(shù)據(jù)都位于內(nèi)存中消耗了大量的內(nèi)存,當(dāng)數(shù)據(jù)多達(dá)上萬(wàn)以后幾乎是不能忍受的。
為此,mfc特別提供了虛擬列表的支持。一個(gè)虛擬列表看起來(lái)和普通的ListCtrl一樣,但是不用通過(guò)InsertItem來(lái)插入數(shù)據(jù),它僅僅知道自己應(yīng)該顯示多少數(shù)據(jù)。但是它如何知道要顯示什么數(shù)據(jù)呢?秘密就在于當(dāng)列表控件需要顯示某個(gè)數(shù)據(jù)的時(shí)候,它向父窗口要。假設(shè)這個(gè)列表控件包含100個(gè)元素,第10到20個(gè)元素(行)是可見(jiàn)的。當(dāng)列表控件重畫(huà)的時(shí)候 ,它首先請(qǐng)求父窗口給它第10個(gè)元素的數(shù)據(jù),父窗口收到請(qǐng)求以后,把數(shù)據(jù)信息填充到列表提供的一個(gè)結(jié)構(gòu)中,列表就可以用來(lái)顯示了,顯示第10個(gè)數(shù)據(jù)后,列表會(huì)繼續(xù)請(qǐng)求下一個(gè)數(shù)據(jù)。
在虛擬的樣式下,ListCtrl可以支持多達(dá)DWORD個(gè)數(shù)據(jù)項(xiàng)。(缺省的listctrl控件最多支持int個(gè)數(shù)據(jù)項(xiàng))。但是,虛擬列表的最大優(yōu)點(diǎn)不在于此,而是它僅僅需要在內(nèi)存中保持極少量的數(shù)據(jù),從而加快了顯示的速度。所以,在使用列表控件顯示一個(gè)很大的數(shù)據(jù)庫(kù)的情況下,采用虛擬列表最好不過(guò)了。
不僅CListCtrl提供虛擬列表的功能, MFC的CListView類(lèi)也有同樣的功能。
三、虛擬列表控件的消息
虛擬列表總共發(fā)送三個(gè)消息給父窗口:當(dāng)它需要數(shù)據(jù)的時(shí)候,它發(fā)送LVN_GETDISPINFO消息。這是最重要的消息。當(dāng)用戶試圖查找某個(gè)元素的時(shí)候,它發(fā)送LVN_ODFINDITEM消息;還有一個(gè)消息是LVN_ODCACHEHINT,用來(lái)緩沖數(shù)據(jù),基本上很少用到這個(gè)消息。
虛擬列表控件使用起來(lái)非常簡(jiǎn)單。它總共只有三個(gè)相關(guān)的消息,如果你直接使用CListCtrl,應(yīng)該在對(duì)話框中響應(yīng)這三個(gè)消息。如果你使用CListCtrl派生類(lèi),可以在派生類(lèi)中響應(yīng)這三個(gè)消息的反射消息。這三個(gè)消息分別是:
(1)LVN_GETDISPINFO 控件請(qǐng)求某個(gè)數(shù)據(jù)
(2)LVN_ODFINDITEM 查找某個(gè)數(shù)據(jù)
(3)LVN_ODCACHEHINT 緩沖某一部分?jǐn)?shù)據(jù)
我們必須響應(yīng)的消息是(1),多數(shù)情況下要響應(yīng)(2),極少數(shù)的情況下需要響應(yīng)(3)
四、如何使用虛擬列表控件
1、首先要?jiǎng)?chuàng)建控件,創(chuàng)建一個(gè)虛擬列表和創(chuàng)建一個(gè)正常的 CListCtrl差不多。先在資源編輯器里面添加一個(gè)list control資源。然后選中"Owner data"屬性,然后給它捆綁一個(gè)CListCtrl變量。添加列,添加imagelist等都和使用正常的listctrl一樣。
2、給虛擬列表添加元素。假設(shè) m_list 是這個(gè)列表的控制變量。通常的情況下這樣添加數(shù)據(jù):
m_list.InsertItem(0, _T("Hello world"));
但是對(duì)于虛擬列表,不能這么做。只需告訴列表有多少個(gè)數(shù)據(jù):
//總共顯示100行
m_list.SetItemCount(100);
3、處理它的通知消息。
五、如何響應(yīng)虛擬列表的消息(http://www.pudn.com/downloads72/sourcecode/windows/control/listview/detail261310.html#)
1、處理 LVN_GETDISPINFO 通知消息
當(dāng)虛擬列表控件需要某個(gè)數(shù)據(jù)的時(shí)候,它給父窗口發(fā)送一個(gè) LVN_GETDISPINFO通知消息,表示請(qǐng)求某個(gè)數(shù)據(jù)。因此列表的所有者窗口(或者它自己)必須處理這個(gè)消息。例如派生類(lèi)的情況 (CMyListCtrl是一個(gè)虛擬列表類(lèi)對(duì)象):
//這里處理的是反射消息
BEGIN_MESSAGE_MAP(CMyListCtrl, CListCtrl)
???//{{AFX_MSG_MAP(CMyListCtrl)
??? ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnGetdispinfo)
???//}}AFX_MSG_MAP
END_MESSAGE_MAP()
在LVN_GETDISPINFO的處理函數(shù)中,必須首先檢查列表請(qǐng)求的是什么數(shù)據(jù),可能的值包括:
(1)LVIF_TEXT??? 必須填充 pszText
(2)LVIF_IMAGE 必須填充 iImage?
(3)LVIF_INDENT 必須填充 iIndent
(4)LVIF_PARAM 必須填充 lParam?
(5)LVIF_STATE 必須填充 state
根據(jù)它的請(qǐng)求,填充所需的數(shù)據(jù)即可。
//================= 例子代碼=====================================
下面的給出一個(gè)例子,填充的是列表所需的某個(gè)數(shù)據(jù)項(xiàng)的文字以及圖像信息:
LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
LV_ITEM* pItem= &(pDispInfo)->item;
int?iItemIndx= pItem->iItem;
if?(pItem->mask & LVIF_TEXT)?//字符串緩沖區(qū)有效
{
????switch(pItem->iSubItem){
????????case?0:?//填充數(shù)據(jù)項(xiàng)的名字
???????????? lstrcpy(pItem->pszText,m_Items[iItemIndx].m_strItemText);
????????????break;
????????case?1:?//填充子項(xiàng)1
???????????? lstrcpy(pItem->pszText,m_Items[iItemIndx].m_strSubItem1Text);
????????????break;
????????case?2:?//填充子項(xiàng)2
???????????? lstrcpy(pItem->pszText,m_Items[iItemIndx].m_strSubItem2Text);
????????????break;
???? }
}
/*注意,多數(shù)情況下要使用lstrcpyn ,因?yàn)樽疃鄰?fù)制字符的個(gè)數(shù)由pItem->cchTextMax給出:
???????? lstrcpyn(pItem->pszText, text, pItem->cchTextMax);
*/
if?(pItem->mask & LVIF_IMAGE)?//是否請(qǐng)求圖像
???????? pItem->iImage= m_Items[iItemIndx].m_iImageIndex;
甚至連某行數(shù)據(jù)是否選中(當(dāng)有checkbox的情況下)的信息也需要由用戶自己來(lái)維護(hù),例如:
//是否顯示該行的選擇信息?
if(IsCheckBoxesVisible())?//自定義函數(shù)
{
???? 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 消息
在資源管理器里面,定位到某個(gè)文件夾,會(huì)顯示很多文件,如果按下鍵盤(pán)的'A',則資源管理器會(huì)自動(dòng)找到名字以 'A'打頭的文件夾或者文件, 并選擇該文件。繼續(xù)按 A,如果還有其它名字以'A'打頭的文件,則下一個(gè)文件被選中。如果輸入?"AB",則 'A''?頭的文件被選中。這就是列表控件的自動(dòng)查找功能。
當(dāng)虛擬列表收到一個(gè)LVM_FINDITEM消息,它也會(huì)發(fā)送這個(gè)消息通知父窗口查找目標(biāo)元素。要搜索的信息通過(guò) LVFINDINFO 結(jié)構(gòu)給出。它是 NMLVFINDITEM 結(jié)構(gòu)的一個(gè)成員。當(dāng)找到要搜索的數(shù)據(jù)后,應(yīng)該把該數(shù)據(jù)的索引(行號(hào))返回,如果沒(méi)有找到,則返回-1。
以對(duì)話框?yàn)槔憫?yīng)函數(shù)大致如下:
//================= 例子代碼=====================================
void?CVirtualListDlg::OnOdfinditemList(NMHDR* pNMHDR, LRESULT* pResult)?
{
????// pNMHDR 里面是要查找的元素的信息
????// 要選中的目標(biāo)元素的行號(hào)最后要保存在 pResult 中, 這是關(guān)鍵!
???? NMLVFINDITEM* pFindInfo = (NMLVFINDITEM*)pNMHDR;
????/* pFindInfo->iStart 是查找的起始位置,一直到最后,然后從頭開(kāi)始,如果沒(méi)有找到合適的,最終停留在iStart*/
???? *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;
????
????//開(kāi)始查找
????do
???? {????????
????????if( _tcsnicmp(m_database[currentPos].m_name,?
????????????????? searchstr, searchstr.GetLength()) == 0)
???????? {
????????????//選中這個(gè)元素,停止查找
???????????? *pResult = currentPos;
????????????break;
???????? }
???????? currentPos++;
????????//從頭開(kāi)始
????????if(currentPos >= m_list.GetItemCount())
???????????? currentPos = 0;
???? }while(currentPos != startPos);????????
}
顯然,如果數(shù)據(jù)很多,必須實(shí)現(xiàn)一個(gè)快速查找的方法。
關(guān)于pFindInfo->lvfi里面的信息的詳細(xì)說(shuō)明可以參考 MSDN。
3、處理 LVN_ODCACHEHINT 消息。
假如我們從數(shù)據(jù)庫(kù)或者其它地方讀取數(shù)據(jù)的速度比較慢,則可以利用這個(gè)消息,批量讀取一些數(shù)據(jù),然后根據(jù)請(qǐng)求,逐個(gè)提供給虛擬列表。LVN_ODCACHEHINT消息的用途就是給程序一個(gè)緩沖數(shù)據(jù)的機(jī)會(huì)。以提高程序的性能。
//================= 例子代碼=====================================
使用 ClassWizard 重載 OnChildNotify 函數(shù),檢查是否 LVN_ODCACHEHINT 消息,然后準(zhǔn)備緩沖數(shù)據(jù):
NMLVCACHEHINT* pcachehint=NULL;
NMHDR* phdr = (NMHDR*)lParam;
if(phdr->code == LVN_ODCACHEHINT)
{
????? pcachehint= (NMLVCACHEHINT*) phdr;
?????//自定義函數(shù),準(zhǔn)備指定范圍的數(shù)據(jù)到緩沖區(qū)
????? PrepCache(pcachehint->iFrom, pcachehint->iTo);
}
else?...
注意,如果消息不是 LVN_ODCACHEHINT,則要傳遞給基類(lèi)進(jìn)行處理。
五、如何修改ListCtrl顯示的數(shù)據(jù)。
由于是程序自己維護(hù)數(shù)據(jù),所以只需修改數(shù)據(jù)庫(kù)中的數(shù)據(jù),然后調(diào)用CListCtrl::RedrawItems函數(shù)進(jìn)行重畫(huà)即可。
六、數(shù)據(jù)的選擇狀態(tài)和選擇框
CListCtrl可以顯示checkbox選擇框。有些情況下是很有用的。對(duì)于正常的listctrl,用戶可以用鼠標(biāo)來(lái)修改某個(gè)元素的選擇狀態(tài),但是對(duì)于虛擬列表就不行了。必須自己處理一些消息,然后自己保存數(shù)據(jù)的選中狀態(tài):
void?CVirtualListDlg::ToggleCheckBox(int?item)
{
???? m_database[item].m_checked = !m_database[item].m_checked;
???? m_list.RedrawItems(item, item);
}
處理 LVN_KEYDOWN消息,添加對(duì)空格鍵 的響應(yīng),用于切換選擇狀態(tài):
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)
???? {
????????//看看鼠標(biāo)是否單擊在 check box上面了?
????????if( (hitinfo.flags & LVHT_ONITEMSTATEICON) != 0)
???????? {
???????????? ToggleCheckBox(item);
???????? }
???? }
????
???? *pResult = 0;
}
七、備注:
???? 1、虛擬列表無(wú)法進(jìn)行排序。
???? 2、虛表的一個(gè)優(yōu)點(diǎn)是容易保持和數(shù)據(jù)庫(kù)的同步。修改數(shù)據(jù)庫(kù)中的數(shù)據(jù),然后重畫(huà)list十分容易而且高效。
???? 3、虛表的另一個(gè)優(yōu)點(diǎn)是可以根據(jù)需要產(chǎn)生數(shù)據(jù)。比如在某一列加上行號(hào)。