首先說下虛擬列表出現的原因:
數據量比較小的時候,對於CListView控件可以直接使用InsertItem並配合SetItemText函數來插入並修改數據。這樣操作很直接。
但是,如果數據量比較大了,比如1w個數據,那么,根據插入的數據種類,長度,以及計算機性能,估計時間在10s中到1分鍾不等。如果你的用戶在使用的這樣的程序時肯定不會開心,初始化的時候插入則需要登上10s+后才能使用,如果外加一個線程來並行插入,倒也是一個方法(這個事情我做過),不過,很浪費CPU時間,以及內存。
如果是10w個,甚至100w個數據呢?那么至少會線性的增加時間了~
但是回頭想一想,一個列表,在你的計算機顯示器上,最多也就能看到50-80個。我的筆記本分辨率是1366*768的就按50個來算,768/50 約等於15吧,一行用15個像素寬度表示,已經有點小勉強了~~~
所以這個時候有個事情就非常明顯了:在大數據量的時候,根本沒必要在初始化時把全部的數據都插入到CListCtrl控件中。
要注意,這個問題是我在下自習回寢室的路上想明白的~~~
然后我就想了,既然這樣的話,如果自己動手做虛擬列表,也不是不能做。基本上需要准備好以下的東西:
1 右面的瀏覽滑塊,就是那個scroll,這個需要處理好,根據不同的位置,動態加載不同地方的數據。
2 鼠標的滾輪消息,上下移動,也要做好。
3 窗口的最大數據量,以及文字顯示之類的東西,都要做。
基本上就是意味着需要自己做一個控件,虛擬列表控件。這個確實是可行的。然后呢~我在百度查資料的時候,意外的看到了虛擬列表這個東西~~~
那時我才知道,原來microsoft的大神們已經想到了這個問題,而且在CListCtrl中已經整合好了。
在CListCtrl中使用虛擬列表
我之前的那篇關於CListCtrl控件使用方法的文章中說過,對於和控件綁定了的CListCtrl對象,主要需要做的工作,就是設置風格,並且插入列。
不過對於虛擬列表,與常規的列表相比,並沒有什么風格上的不同,所以風格還是依照自己的需求進行設置就可以了。
列的控制,按照前面那篇文章上來就好了~這里不是重點。
然后呢,很需要做的一點就是,設置最大條目的數量。這里要注意的是,所謂的最大條目的數量,就是和你的數據庫的數據量。這兩者一定是要匹配的。
展示一小段實例代碼:
1 else 2 { 3 CFileInfo cfi; 4 while(m_MyDataBase.ReadString(path)) 5 { 6 cfi.csFileRoot = path; 7 cfi.csFileName = path.Right(path.GetLength() - path.ReverseFind('\\') - 1); 8 cfi.csFilePath = path.Left(path.ReverseFind('\\')); 9 m_arrayFileInfo.Add(cfi); 10 nFileNum++; 11 } 12 m_MyDataBase.Close(); 13 } 14 15 m_LCTable.SetItemCountEx(nFileNum, LVSICF_NOSCROLL|LVSICF_NOINVALIDATEALL); 16 nInitialFlag = 1;
最上面的代碼是數據初始化的部分,存儲到CArray模板中。也算是個最簡單的數據庫吧。
初始化過后,我的數據就放到了容器中,上面的nFileNum變量就是跟蹤容量的數據,不過,其實也不需要,畢竟CArray是提供數據總量查詢的。
然后接下來調用我們的明星函數:SetItemCountEx
這個函數,第一個參數毫無疑問就是設置數量上限的,第二個參數又是一個什么風格設置,看看MSDN怎么說:
- LVSICF_NOINVALIDATEALL The list view control will not repaint unless affected items are currently in view. This is the default value.
- LVSICF_NOSCROLL The list view control will not change the scroll position when the item count changes.
大意我就不翻譯了,現在有個人正在和我聊天,翻譯了太費時間。這兩個的特性可以自己試一試。
然后在這算是第一步完成,主要還是SetItemCountEx函數。
接下來,需要做的事進行消息響應。
要知道,windows程序的運行是需要消息來推動的。當滑塊對拖動或者鼠標滾動的時候,都會有消息產生。
所以,CListCtrl也是采用了這種機制,消息響應的方式去填充虛擬列表。
我在這里只介紹我使用過的一個消息,就是LVN_GETDISPINFO

最上面的那個消息響應就是了。
然后我們看響應函數的代碼:
1 void CMyRisingDlg::OnLvnGetdispinfoList2(NMHDR *pNMHDR, LRESULT *pResult) 2 { 3 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR); 4 // TODO: Add your control notification handler code here 5 LV_DISPINFO * pLPD = (LV_DISPINFO *)pDispInfo; 6 LV_ITEM* pItem= &(pDispInfo)->item; 7 HICON hIconTmp; 8 int nItem = pItem->iItem; 9 if (pItem->mask & LVIF_TEXT) //valid text buffer? 10 { 11 switch(pItem->iSubItem) 12 { 13 case 0: //fill in main text首列添加圖像的工作肯定也要在這里完成 14 _tcscpy(pItem->pszText, m_arrayFileInfo[nItem].csFileName); 15 //ExtractIconEx(m_arrayFileInfo[nItem].csFileRoot, 0, NULL, &hIconTmp, 1); 16 //m_imagelist.Add(hIconTmp); 17 //Add Icon into the list 18 pItem->iImage = 0; 19 //m_imagelist.Remove(0); 20 break; 21 case 1: //fill in sub item 1 text 22 _tcscpy(pItem->pszText, m_arrayFileInfo[nItem].csFilePath); 23 break; 24 case 2: //fill in sub item 2 text 25 _tcscpy(pItem->pszText, m_arrayFileInfo[nItem].FileTime); 26 break; 27 } 28 } 29 30 *pResult = 0; 31 }
首先能看參數,其中一個是NMHDR的指針,這個類型,我還真的不是很清楚。
不過清楚的可以看到,經過兩次類型轉換,我們會得到一個LV_ITEM的指針。在這簡單的說一下,前面的那段類型轉換的代碼,是函數自動生成的時候就已經改出來的。
接着,在這里我們只要根據LV_ITEM中的消息進行相應來添加相應的數據就可以了。
所以接下來主要需要看的就是switch中的case選擇。
在上面代碼中的switchcase結構中,傳給switch的參數就是一個條目中的子序號。根據這個子序號,去添加對應位置的信息。
然后這里還有非常重要的一點,就是在上面的這段代碼:
8 int nItem = pItem->iItem;
這段代碼就是用來獲取當前位置對應數據的目錄號。只有根據這個目錄號,你才能找到在你的數據中應該添加的數據的位置。這個在我的代碼中你應該也能注意到的。
最主要的兩個數據在這里基本就介紹完了,具體的代碼也貼在上面。這樣就能進行最簡單的虛擬列表操作啦。
