海阔天空

海阔凭鱼跃 天高任鸟飞

 

转:Virtual List的使用方法

Virtual List(虚拟列表)是LVS_OWNERDATA 样式的List Ctrl.默认的List Ctrl在插入大量的数据时会变得很慢.在我的破机器上插入不到一万行的数据要几十秒,非常令人不爽.而用Virtual List可以大大加快速度。Virtual List不拥有数据,当需要显示一行时才发消息向父窗口查询显示内容。Virtual List的使用方法与普通List Ctrl稍微有点不同。它有三个重要的消息LVN_GETDISPINFO,LVN_ODCACHEHINT和 LVN_ODFINDITEM。响应这三个消息是关键。

1.创建Virtual List
只需添加LVS_OWNERDATA风格到List Ctrl就行了。
m_list.SetExtendedStyle(LVS_EX_FULLROWSELECT|LVS_OWNERDATA);
2.添加,删除和更改一行
添加:因为Virtual List不拥有数据,数据存在外部(如:数据库),所以你只需将数据添加到外部,然后m_list.SetItemCount(list_size+1) 告诉Virtual List行数加一。这样Virtual List会更改滚动条的样子,以便看上去添加了一行.
删除:先在外部数据中删除行,然后m_list.SetItemCount(list_size-1)更改滚动条.
更改:直接修改在外部数据中的行.

如果这些行是正在显示的行,则需要调用m_list.RedrawItems(nFirst,nLast)

3.响应LVN_GETDISPINFO消息
当Virtual List需要显示一行数据时,它发送这个消息给父窗口询问数据内容.父窗口收到消息后从传过来参数中知道Virtual List现在显示的是第几项,第几列,内容是什么类型.然后父窗口在外部数据中找到数据内容,将它返回给Virtual List.
先添加消息映射,这里用的是反射消息,可以减少控件于父窗口之间的耦合度
BEGIN_MESSAGE_MAP(CMyListCtrl, CListCtrl)
ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnGetdispinfo)
END_MESSAGE_MAP()
相应的响应函数
void CMyListCtrl::OnGetdispinfo(NMHDR* pNMHDR, LRESULT* pResult)
{
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:
{
memcpy(pItem->pszText,(find_index + iItemIndx)->code,8);
*(pItem->pszText+8) = 0;
}
break;
case 1:
{
memcpy(pItem->pszText,(find_index + iItemIndx)-

>description,INDEX_DESLEN);
*(pItem->pszText+INDEX_DESLEN) = 0;  
}
break;
}
}

*pResult = 0;
}

4.响应LVN_ODCACHEHINT消息
这个消息用来缓存请求项的数据.我没用到这个消息,因为我已一次将所有数据读入内存.

5.响应LVN_ODFINDITEM消息
在资源管理器中浏览文件时,让焦点list上,然后在键盘上输入文件名.资源管理器会自动找到并选中与之最相近的文件.LVN_ODFINDITEM就是实现这个功能的.当焦点在list上时,按键操作会使Virtual List发送LVN_ODFINDITEM给父窗口.父窗口找到相应项并将它选中.
BEGIN_MESSAGE_MAP(CMyListCtrl, CListCtrl)
ON_NOTIFY_REFLECT(LVN_ODFINDITEM, OnOdfinditemList)
END_MESSAGE_MAP()

void CMyListCtrl::OnOdfinditemList(NMHDR* pNMHDR, LRESULT* pResult)
{
// pNMHDR has information about the item we should find
// In pResult we should save which item that should be selected
NMLVFINDITEM* pFindInfo = (NMLVFINDITEM*)pNMHDR;

*pResult = -1; // *pResult = -1 表明没有找到

if( (pFindInfo->lvfi.flags & LVFI_STRING) == 0 )
return;

int nlen = _tcslen(pFindInfo->lvfi.psz);

int startPos = pFindInfo->iStart;
//Is startPos outside the list (happens if last item is selected)
if(startPos >= m_list.GetItemCount())
startPos = 0;

int currentPos=startPos;

do
{
if(memcmp((find_index + startPos ),pFindInfo->lvfi.psz,nlen) == 0)
{
*pResult = currentPos;
break;  
}
currentPos++;

if(currentPos >= m_list.GetItemCount())
currentPos = 0;
}while(currentPos != startPos);  

}

 

 

 

 

 

 

 

 

 

 

 

 、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

 

 Environment: VC6 SP3, IE 4.01 

This is mainly for the list control's report view look & feel - you can use older controls, it just won't be so pretty - set in CDlgTest::OnInitDialog().

You may have noticed that the standard CListCtrl gets slow once you need to display lots of items. The time for both the fill and sort start to upset your users who have nothing but a flickering scroll bar to entertain them. Furthermore, if you've already got this data in an array, copying it into a list control is very wasteful. Microsoft's documentation says this can all be solved by virtual lists, but these are quite intimidating and most of the sample code is using classic 'Petzold' SDK style. This article gives some help on how to implement a virtual list control in an MFC project, and provides a demonstration application.

This dialog box MFC application contains both a virtual list control (IDC_LIST1) and a normal list control (IDC_LIST2) with 50,000 items so you can easily compare the differences. It also demonstrates how to:

  • make the list box display in a 'grid' style (CDlgTest::OnInitDialog)
  • use C++ classes with the qsort templates (CDlgTest::SortByCol and global CompareByLabelName / CompareByLabelAddress)
  • add icons to the list (CDlgTest::GetDispInfo)
  • override the standard sort algorithm (CDlgTest::OnColClick, CDlgTest::SortByCol)
  • change the header labels on the fly (CDlgTest::OnColClick)
  • support going to an item from a keystroke (CDlgTest::OnOdfinditem)

Virtual listing is enable by setting the 'Owner Data' property in the resources:
List Control Properties dialog box.

The dialog controls are then mapped via the DDX mechanism to data members in the dialog box class:

void CDlgTest::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CDlgTest)
DDX_Control(pDX, IDC_LIST2, m_List2);
DDX_Control(pDX, IDC_LIST1, m_List);
//}}AFX_DATA_MAP
}

It's essential to supply a message handler for the LVN_GETDISPINFO message, but you can also override the LVN_COLUMNCLICK if you want to do something when the user clicks on the header button (for example sort by column content), and the LVN_ODFINDITEM if you want to respond to a normal keypress (for example move to the next item starting with the keypress).

BEGIN_MESSAGE_MAP(CDlgTest, CDialog)
//{{AFX_MSG_MAP(CDlgTest)
ON_NOTIFY(LVN_GETDISPINFO, IDC_LIST1, GetDispInfo)
ON_NOTIFY(LVN_COLUMNCLICK, IDC_LIST1, OnColClick)
ON_NOTIFY(LVN_ODFINDITEM, IDC_LIST1, OnOdfinditem)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

You then need to initialise your report lists:

BOOL CDlgTest::OnInitDialog()
{
CDialog::OnInitDialog();

// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon

// Insert the columns.
CString Header;
int arColWidth[]={80,100};
int iNumCols = 2;
for(int i=0; i<iNumCols; i++)
{
Header.LoadString(IDS_LISTCOL+i);
m_List.InsertColumn(i,Header,LVCFMT_LEFT,arColWidth[i]);
m_List2.InsertColumn(i,Header,LVCFMT_LEFT,arColWidth[i]);
}

// Optional stuff from here:
// The icons are added to an image list and passed on to the
// virtual list (I haven't added them to the normal list)

// Configure the break icon array.
m_ImageList.Create(16, 16, ILC_COLOR4, 3, 1);
m_ImageList.Add(::AfxGetApp()->LoadIcon(IDI_BP_ENABLED));
m_ImageList.Add(::AfxGetApp()->LoadIcon(IDI_BP_DISABLED));
m_ImageList.Add(::AfxGetApp()->LoadIcon(IDI_BP_NONE));
m_List.SetImageList(&m_ImageList, LVSIL_SMALL);

// Here's the code for the grid style. Note that there's no
// definition for LVS_EX_LABELTIP in the MFC header files, so
// it's explicitely declared here - you could add it to your
// stdafx.h file if you intend to use it more than once.

// Configure the look & feel.
const int LVS_EX_LABELTIP = 0x00004000;
m_List.SetExtendedStyle(LVS_EX_FULLROWSELECT
| LVS_EX_GRIDLINES
| LVS_EX_LABELTIP);

m_List2.SetExtendedStyle(LVS_EX_FULLROWSELECT
| LVS_EX_GRIDLINES
| LVS_EX_LABELTIP);

return TRUE; // return TRUE unless you set the focus to a control
}

You'll need to add some items to your list. Here's the essential code for doing this - there's more in CDlgTest::OnAdd().
m_arLabels is the data array containing your data - note that you must maintain and clean up this array.
m_LabelCount is the number of items added to the array, this is the link between your data and the list box control via SetItemCountEx
You have to call Invalidate, otherwise the list box control won't know that the array contents have changed.

// Fill class data from dialog.
UpdateData(TRUE);

m_arLabels.SetAtGrow(...);
m_LabelCount=...;

// Tell the list box to update itself.
m_List.SetItemCountEx(m_LabelCount);
m_List.Invalidate();

When the list box control content has been marked as invalid, it will try to refresh it. However, the trick about virtual lists, is that only the visible data items are requested through the LVS_GETDISPINFO message for each 'cell' it is trying to display. It is absolutely essential that you handle these messages, otherwise the control will remain stoically blank.

void CDlgTest::GetDispInfo(NMHDR* pNMHDR, LRESULT* pResult) 
{
LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
LV_ITEM* pItem= &(pDispInfo)->item;
CLabelItem rLabel = m_arLabels.ElementAt(pItem->iItem);

if (pItem->mask & LVIF_TEXT) //valid text buffer?
{
// then display the appropriate column
switch(pItem->iSubItem)
{
case 0:
lstrcpy(pItem->pszText, rLabel.m_strText);
break;

case 1:
sprintf(pItem->pszText, "0x%08LX", rLabel.m_Addr);
break;

default:
ASSERT(0);
break;
}
}
.
*pResult = 0;
}
There are two parameters - NMHDR* pNMHDR and LRESULT* pResult - the first is a pointer to a LV_DISPINFO structure which in turn contains a pointer to an LV_ITEM structure. It's this item information you need, as it contains the information about what the virtual list control is trying to display. You need the item's index, subitem index, and the mask - using this you can fetch the necessary information out of your data array. Note that if the request is for text, you need to copy the string to the virtual list control to be displayed. I'm not sure what the result value is used for - 0 works - it's probably reserved for something...

Anyway, at this point you should have some working virtual list. You can add more functionality by handling the other LVN_... messages, and the source code contains handlers for LVN_COLUMNCLICK and LVN_ODFINDITEM. The LVN_COLUMNCLICK is worth looking at because it shows how to optimize the sorting - as far as I can tell, the list control always uses string comparison to do the sort, which is slow if your array contains numerical data - have a look at the time difference between sorting by address on the virtual and normal list controls. I've used the qsort algorithm as it was easy to implement - which is why the string comparison on the Label column is slower than the normal list - but there's many more efficient ones out there. I'd be interested if anyone gets Dan Kozub's HybridList to work with a virtual list control.

Happy coding!

Downloads

Download source - 14 Kb
Download demo project - 8 Kb

 

 

 

 

 

 

 

posted on 2010-07-29 13:27  liuym  阅读(3546)  评论(0编辑  收藏  举报

导航