XSLT存档  

不及格的程序员-八神

 查看分类:  ASP.NET XML/XSLT JavaScripT   我的MSN空间Blog

虚拟列表控件

虚拟列表控件是具有 LVS_OWNERDATA 样式的列表视图控件。 此样式使控件能够支持最多为 DWORD 的项计数(默认项计数仅扩展到 int)。 但是,此样式具有的最大优势是,允许内存中任何时候都只有一部分数据项。 这样,虚拟列表视图控件就可以与大型信息数据库一起使用,而这些数据库中的访问数据的特定方法已经到位。

 备注

除了在 CListCtrl 中提供虚拟列表功能外,MFC 还在 CListView 类中提供相同的功能。

开发虚拟列表控件时,应该注意一些兼容性问题。 有关详细信息,请参阅 Windows SDK 中“列表视图控件”主题的“兼容性问题”部分。

处理 LVN_GETDISPINFO 通知

虚拟列表控件保留的项信息很少。 除项选择和焦点信息外,所有项信息均由控件的所有者管理。 框架通过 LVN_GETDISPINFO 通知消息来请求信息。 提供请求的信息的前提是虚拟列表控件的所有者(或控件本身)处理此通知。 可以使用类向导轻松完成此操作(请参阅将消息映射到函数)。 生成的代码应类似于以下示例(其中,CMyDialog 拥有虚拟列表控件对象,对话框将处理通知):

ON_NOTIFY(LVN_GETDISPINFO, IDC_LIST3, &CMyDialog::OnLvnGetdispinfoList3)

在 LVN_GETDISPINFO 通知消息的处理程序中,你必须查看请求的信息类型。 可能的值包括:

  • LVIF_TEXT 必须填充 pszText 成员。

  • LVIF_IMAGE 必须填充 iImage 成员。

  • LVIF_INDENT 必须填充 iIndent 成员。

  • LVIF_PARAM 必须填充 lParam 成员。 (对于子项而言并不存在。)

  • LVIF_STATE 必须填充 state 成员。

然后,应向框架提供所请求的任何信息。

以下示例(从列表控件对象的通知处理程序的正文中获取)演示了一种可能的方法,即提供项的文本缓冲区和图像的信息:

NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO *>(pNMHDR);
LVITEM *pItem = &(pDispInfo)->item;

int iItem = pItem->iItem;

if (pItem->mask & LVIF_TEXT) //valid text buffer?
{
   switch (pItem->iSubItem)
   {
   case 0: //fill in main text
      _tcscpy_s(pItem->pszText, pItem->cchTextMax,
                m_Items[iItem].m_strItemText);
      break;
   case 1: //fill in sub item 1 text
      _tcscpy_s(pItem->pszText, pItem->cchTextMax,
                m_Items[iItem].m_strSubItem1Text);
      break;
   case 2: //fill in sub item 2 text
      _tcscpy_s(pItem->pszText, pItem->cchTextMax,
                m_Items[iItem].m_strSubItem2Text);
      break;
   }
}

if (pItem->mask & LVIF_IMAGE) //valid image?
{
   pItem->iImage = m_Items[iItem].m_iImage;
}

缓存和虚拟列表控件

由于这种类型的列表控件适用于大型数据集,因此建议缓存请求的项数据以提高检索性能。 该框架提供了一种缓存提示机制,通过发送 LVN_ODCACHEHINT 通知消息来帮助优化缓存。

以下示例使用传递给处理程序函数的范围来更新缓存。

void CMyDialog::OnLvnOdcachehintList3(NMHDR* pNMHDR, LRESULT* pResult)
{
   LPNMLVCACHEHINT pCacheHint = reinterpret_cast<LPNMLVCACHEHINT>(pNMHDR);

   // Update the cache with the recommended range.
   for (int i = pCacheHint->iFrom; i <= pCacheHint->iTo; i++)
   {
      m_Items[i].m_iImage = i % 2;
      m_Items[i].m_strItemText.Format(_T("Item %d"), i);
      m_Items[i].m_strSubItem1Text = _T("Sub 1");
      m_Items[i].m_strSubItem2Text = _T("Sub 2");
   }

   *pResult = 0;
}

若要详细了解如何准备和维护缓存,请参阅 Windows SDK 中“列表视图控件”主题的“缓存管理”部分。

查找特定项

需要找到特定列表控件项时,虚拟列表控件会发送 LVN_ODFINDITEM 通知消息。 当列表视图控件收到快速密钥访问或收到 LVM_FINDITEM 消息时,会发送通知消息。 搜索信息以 LVFINDINFO 结构的形式发送,该结构是 NMLVFINDITEM 结构的成员。 通过重写列表控件对象的 OnChildNotify 函数来处理此消息,并在处理程序正文中查找 LVN_ODFINDITEM 消息。 如果找到该消息,则执行相应的操作。

应准备好搜索与列表视图控件提供的信息匹配的项。 如果成功,则应返回项的索引;如果未找到匹配项,则应返回 -1。


 

VC++JXC数据列表性能优化方案】

 

关键字:SQL,C++,MFC,CListCtrl

 

1.  业务场景描述

 

亿方项目:由于单据数量大,并且查询条件中伴随相应部门及人员的权限绑定,使得加载单据列表时间过长(尤其是计划分割,销售调单等模块),为了改善程序提高用户体验,特别在工作之余研究SQL查询及CListCtrl显示的优化方案(二者配合)。

 

2022】随着亿方业务单据越来越多,tBillMakers表的空间也越来越大(截止2021年冬时间点已达近30万条数据),可以预见在继续运行N年达到百万级时,各查询视图关联此表会一定程度影响加载性能。

2. 原性能瓶颈如下:

 

1)SQL查询举2例:

 

1.1相关子查询:由于各单据在查询时均要与制单人表关联,JXC的单据加载架构是依托于VC++MFC的记录集技术,其方式如下:

SELECT * FROM tXXXForm


EXISTS
(SELECT 1 FROM tBillMakers WHERE cMakerCode = 'xxx' AND cBillCode = tXXXForm.cBillCode)WHERE ......

这里用到了SQL中的相关子查询,由于相关子查询针对主查询中的每一条记录,都要执行一次子查询,所以从一定程序上影响检索性能;

另外,一部分单据还存在工作流审批状态的查询过滤相关子查询如下:

EXISTS (SELECT * FROM tJxcFrwInstanceInfo a WHERE a.cRefBillCode = tXXXForm.cBillCode AND a.iState = 1)

 

1.2大量的OR:【采购收货】中需要同时判断保管员表,而此保管员还要同时追溯相关【采购计划】单据中的申请单位(塑料或建筑)及计划类别等条件,原来的编程如下:

-- 构建各类保管员条件字符串

         strFilter1.Format(" -- 通行保管员

cBillCode IN (

SELECT cBillCode FROM tPurchaseFormSub ps

LEFT JOIN tWare ON ......

INNER JOIN tWareTypeMapStorekeeper wtms ON ......

......

         strFilter2.Format("...... -- 塑料保管员

         strFilter3.Format("...... -- 建筑保管员

-- 拼成大串


上述方式不仅影响查询性能,也在一定程度降低了程序的可读可维护性,有待改善temp.Format(" AND ((%s) OR (%s) OR (%s) OR (%s))", strFilter1, strFilter2, ............ );

 

2)MFC的CListCtrl显示:

常规的显示数据方式是迭代DB记录集的同时,依次向CListCtrl中插入数据(InsertItem),有多少行就执行多少次,如果数据量达到一定程度,显示数据会需要一些时间,使得程序UI非常的不流畅(卡)。

 

3.  技术支持如下:

网上有一些关于SQL性能的知识,可以适当积累,比如:

https://www.cnblogs.com/xuxiaona/p/4962727.html

4. 新型技术方案如下:

1)针对SQL查询的改善方案:

1.1相关子查询:引入视图,视图中将制单人编码姓名一同返回,定义如下:

CREATE  view [dbo].[vtPurchaseSchemeJHFGForm]

as

         SELECT m.*, iState = isnull(b.iState, 0), cStateInfo = isnull(b.cStateInfo, '')      -- 审批流信息                  , cMakerCode, cMakerName -- 制单人信息

          FROM tPurchaseSchemeJHFGForm m

           LEFT JOIN tBillMakers bm ON m.cBillCode = bm.cBillCode

LEFT JOIN tJxcFrwInstanceInfo b ON b.cRefBillCode = m.cBillCode

这样在VC++侧不必再关联tBillMakers 表,直接过滤即可:

EXISTS (SELECT 1 FROM tBillMakers WHERE cMakerCode = 'xxx' AND cBillCode = tXXXForm.cBillCode)

改善为:


  cMakerCode = 'xxx'

推而广之,对于工作流审批状态的查询过滤相关子查询:

EXISTS (SELECT * FROM tJxcFrwInstanceInfo a WHERE b.cRefBillCode = tXXXForm.cBillCode AND a.iState = 1)

改善如下:

iState = 1

1.2大量的OR同样,借助视图vBillList_tPurchaseForm_TSDZ如下:

cBillCode IN (SELECT cBillCode FROM vBillList_tPurchaseForm_TSDZ WHERE cPersonCode = 'XXX' )VC++侧直接过滤即可:

2)MFC的CListCtrl显示:

VC++6技术内幕:采用虚拟列表方式,在迭代DB记录集时仅将数据存入内存,不必依次向CListCtrl中插入数据,只需调用.SetItemCount(n)即可,并且按MFC方式来重写消息响应函数(OnGetdispinfoList,在显示时才从内存中取数据,使得程序显示效果从根本上得到改善。

JXC改进关键代码示例:(定义开关变量,并且设置VC的资源文件(RC),将列表框定义为自画式)

 

 

 


 

项目开发中的心得,分享与各位朋友

此致

2021-02-04

Liul


 

Using virtual lists

4 Aug 2004Ms-RL9 min read
This article explains how to work with virtual lists, a very fast list that is useful to show a large number of items.

Contents

Introduction

Let's say you have a large database in your program and you want to show the database to the user. You use a CListCtrl with a couple of columns and fill it with a few thousand, maybe million elements. When you run it, you notice it's a bit (or very) slow. Wouldn't it be great if you didn't need to add all elements to the list, and let the list show them anyway? Does it sound stupid and ridiculous? That's how a virtual list works.

Virtual list

A virtual list is a list that has no data, it only knows how many data items it is supposed to have. But how does it now what data to show? The secret is that the list asks the parent for the information it needs. Assume you have a list with 100 elements, and elements 10-20 are visible. When the list is redrawing, it first asks the parent about element 10. When the parent has answered, the list redraws element 10, and then goes on to the next element.

A virtual list is sending three different messages to the parent. LVN_GETDISPINFO is sent when the list needs information. This is the most important message. The message LVN_ODFINDITEM is sent when the user tries to find an item by writing in the list. LVN_ODCACHEHINT is sent to give you a chance to cache data. You will probably don't care about this message at all.

OK, that's enough fuzzy theory, let's look on some code instead.

Creating a virtual list

Creating a virtual list isn't much harder than creating an ordinary CListCtrl. Add a list control in the resource editor as you usually do. Then check the style "Owner data", and then add a CListCtrl variable for this control. The only difference from an ordinary CListCtrl is the "Owner data" (LVS_OWNERDATA) style.

When you work with a virtual list, you use it mostly in the same way you do with a non-virtual list. Adding columns, selecting items, adding image list and much more works exactly the same way.

Add items to the list

Let's say m_list is the control variable for the list. Normally, you add data to the list like this:

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

But in a virtual list, this will not work. Instead, it is up to you to handle the data. Instead of adding, you change the number of elements the list is showing:

 
//"Add" 100 elements
m_list.SetItemCount(100);

If you set the item count to 100 or 1,000,000 doesn't matter, the time to run this command will still be practically zero. In a non-virtual list, adding a million elements could take hours.

Handling the LVN_GETDISPINFO message

As I said before, the list is asking the parent for information when it needs it. The list does this by sending a LVN_GETDISPINFO message. This is the most important message to handle when you are dealing with a virtual list. A typical function looks like this:

 
void CVirtualListDlg::OnGetdispinfoList(NMHDR* pNMHDR, LRESULT* pResult) 
{
    LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;

    //Create a pointer to the item
    LV_ITEM* pItem= &(pDispInfo)->item;

    //Which item number?
    int itemid = pItem->iItem;

    //Do the list need text information?
    if (pItem->mask & LVIF_TEXT)
    {
        CString text;

        //Which column?
        if(pItem->iSubItem == 0)
        {
            //Text is name
            text = m_database[itemid].m_name;
        }
        else if (pItem->iSubItem == 1)
        {
            //Text is slogan
            text = m_database[itemid].m_slogan;
        }

        //Copy the text to the LV_ITEM structure
        //Maximum number of characters is in pItem->cchTextMax
        lstrcpyn(pItem->pszText, text, pItem->cchTextMax);
    }

    //Do the list need image information?
    if( pItem->mask & LVIF_IMAGE) 
    {
        //Set which image to use
        pItem->iImage=m_database[itemid].m_image;
        
        //Show check box?
        if(IsCheckBoxesVisible())
        {
            //To enable check box, we have to enable state mask...
            pItem->mask |= LVIF_STATE;
            pItem->stateMask = LVIS_STATEIMAGEMASK;

            if(m_database[itemid].m_checked)
            {
                //Turn check box on
                pItem->state = INDEXTOSTATEIMAGEMASK(2);
            }
            else
            {
                //Turn check box off
                pItem->state = INDEXTOSTATEIMAGEMASK(1);
            }
        }
    }

    *pResult = 0;
}

First, we create a LV_DISPINFO pointer, and then a pointer to the item. In itemid, we save which item we should handle. Then we check the mask in pItem. The mask is telling us what kind of information the list needs. First, we check if text information is needed. If it is, we first figure out which column the text is. The first column is for names, the second for slogans (several columns will be shown in report view, in other views only first column will be used).

We also check if image information is needed. If it is, we save which image to use in pItem->iImage (this is the number to an image in the image list connected to the list).

If check boxes are visible, we should send information about that when image information is requested. First, we change pItem->mask to tell that we are sending state information. We also change pItem->stateMask to tell what kind of information we are sending. Then we write if check box is on or off.

The mask could also have the flags LVIF_INDENTLVIF_NORECOMPUTELVIF_PARAM, and LVIF_DI_SETITEM. But I have never used any of them, so I guess they aren't important :-).

It isn't harder to handle the LVN_GETDISPINFO message than this. Check boxes are probably the hardest, but you will probably never use any more complicated code than I have done in this example. When you have written this function, your list will almost act like a normal list. However, it might be a good idea to implement the LVN_ODFINDITEM as well.

Handling the LVN_ODFINDITEM message

First, some basic education: start Explorer and go to a folder where you have a lot of files. Press A. What happens? If you have a file or folder that begins with an 'A', that file should now be selected. Press A again. If you have more than one file that begins with an 'A', the second file should be selected. Write "AB". If there is any file that begins with 'AB', that is now selected. This is how every normal list control behaves. Aren't list controls cool? :-).

Let's look on how a list control normally searches:

Name
Anders
Anna
Annica
Bob
Emma
Emmanuel

Anna is selected. When we are writing anything, the list will search down to find the best match. If it reaches the end, it restarts at the top and searches until it is back on the start item (Anna). If "A" is written, Annika should be selected. If "AND" is written, Anders should be selected. If "ANNK" is written, the selection should stay on Anna. If "E" is written, Emma should be selected.

Unfortunately, this doesn't work with virtual lists. A virtual list doesn't try to find any item at all, unless you handle the LVN_ODFINDITEM message. I usually implement the message like this:

 
void CVirtualListDlg::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;

    /* pFindInfo->iStart is from which item we should search.
       We search to bottom, and then restart at top and will stop
       at pFindInfo->iStart, unless we find an item that match
     */

    // Set the default return value to -1
    // That means we didn't find any match.
    *pResult = -1;

    //Is search NOT based on string?
    if( (pFindInfo->lvfi.flags & LVFI_STRING) == 0 )
    {
        //This will probably never happend...
        return;
    }

    //This is the string we search for
    CString searchstr = 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;
    
    //Let's search...
    do
    {        
        //Do this word begins with all characters in searchstr?
        if( _tcsnicmp(m_database[currentPos].m_name, 
                 searchstr, searchstr.GetLength()) == 0)
        {
            //Select this item and stop search.
            *pResult = currentPos;
            break;
        }

        //Go to next item
        currentPos++;

        //Need to restart at top?
        if(currentPos >= m_list.GetItemCount())
            currentPos = 0;

    //Stop if back to start
    }while(currentPos != startPos);        
}

It may not be obvious how this works at a first look, but if you read it carefully, you will understand. Or, you simply skip this, and copy the code and just do the necessary changes - it's up to you :-). If the list is really large, maybe you need to make a faster version of this function, or don't implement it at all.

pFindInfo->lvfi has information on how you should search (like "Restart at top if bottom is reached" or in which direction). I have never cared about that, if you do, you should read in MSDN to get more information.

Handling the LVN_ODCACHEHINT message

LVN_ODCACHEHINT is sent to give you a chance to cache data. If you are working with a database that is in another computer in some network, maybe this is useful, but I haven't used this message in any of my programs. A function that handles this message will probably look something like this:

 
void CVirtualListDlg::OnOdcachehintList(NMHDR* pNMHDR, LRESULT* pResult) 
{
    NMLVCACHEHINT* pCacheHint = (NMLVCACHEHINT*)pNMHDR;

    // ... Cache the data pCacheHint->iFrom to pCacheHint->iTo ...

    *pResult = 0;
}

This is quite simple as you see. But as usual, simple things don't work :-). According to MSDN, you should override OnChildNotify and add the handler in this function. But I will not dig deeper in this, if you need more information about this, read in MSDN.

Changing an item

What should you do to change the data? This is really simple. You don't change the data in the list, but in the database instead. To redraw the list items, call CListCtrl::RedrawItems.

Check boxes

Check boxes are useful, but they are quite tricky to implement when you are working with a virtual list. In a normal non-virtual list, check boxes are toggled when you click on them or when you press space. But in a virtual list, nothing will happen. So you have to implement these events yourself. We start with a simple toggle-check-box-function:

 
void CVirtualListDlg::ToggleCheckBox(int item)
{
    //Change check box
    m_database[item].m_checked = !m_database[item].m_checked;

    //And redraw
    m_list.RedrawItems(item, item);
}

We will call this function when we want to change an item. The function toggles the check box value (in the database!) and forces the list to redraw the item. Quite simple. Toggling a check box when space is pressed is also quite simple. Add a message handler for the LVN_KEYDOWN message. The function should be something like this:

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

    //If user press space, toggle flag on selected item
    if( pLVKeyDown->wVKey == VK_SPACE )
    {
        //Toggle if some item is selected
        if(m_list.GetSelectionMark() != -1)
            ToggleCheckBox( m_list.GetSelectionMark() );
    }

    *pResult = 0;
}

We check if space is pressed and if any item is selected before we toggle the check box. To toggle check box when we click on it, we have to do a more complicated function. Add a message handler for the NM_CLICK message. My function looks like this:

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

    LVHITTESTINFO hitinfo;
    //Copy click point
    hitinfo.pt = pNMListView->ptAction;

    //Make the hit test...
    int item = m_list.HitTest(&hitinfo); 

    if(item != -1)
    {
        //We hit one item... did we hit state image (check box)?
        //This test only works if we are in list or report mode.
        if( (hitinfo.flags & LVHT_ONITEMSTATEICON) != 0)
        {
            ToggleCheckBox(item);
        }
    }
    
    *pResult = 0;
}

We are using CListCtrl::HitTest to see if we clicked on an item. If we did, the function returns which item we clicked on. We then use hitinfo.flags to see where we clicked. If the flag LVHT_ONITEMSTATEICON is on, then we know that the user clicked on the check box (state image).

Unfortunately, CListCtrl::HitTest doesn't work as it should if the list view is in "Icon" or "Small icon" mode. In these views, hitinfo.flags & LVHT_ONITEMSTATEICON is always 0. I haven't found a solution to this, if you do, let me now. However, using check boxes in these modes is probably quite unusual, so this is not a big problem.

Notes

Unless you are making something very unusual, you will not need to know more information than there is in this article, to use virtual lists. However, there are some minor compatibility issues between a virtual and a non-virtual list. For example, a virtual list can't sort data. But that is quite obvious, isn't it :-)? You find more information about these issues in MSDN.

When should you use a virtual list, and when should you not? For large lists, I personally prefer a virtual list. But a non-virtual list is usually easier to program (message handling is never fun), so for smaller lists, I use the ordinary list.

A very neat thing about virtual lists is that they are very easy to keep synchronized with a database. You just change the database and redraw the list if necessary. So, if you work with a list where the user should change data in the database, a virtual list could be useful even if number of items are low.

Another nice thing is that you sometimes could generate data when necessary. If you want to show row number in one column, that is very easy to do, and it takes practically no memory at all. In a non-virtual list, you would have to add this data on all items.

History

  • 5 August, 2004 - Initial version.

License

This article, along with any associated source code and files, is licensed under Microsoft Reciprocal License

 

Written By
 
Sweden Sweden
PEK is one of the millions of programmers that sometimes program so hard that he forgets how to sleep (this is especially true when he has more important things to do). He thinks that there are not enough donuts in the world. He likes when his programs works as they should do, but dislikes when his programs is more clever than he is.

 

一般在用CListCtrl显示大数据量时,都会直接加入到CListCtrl中,这样会导致界面刷新速度慢,如果有图片则会导致闪烁。
这是对计算机大数据量处理的思想没掌握好。
从用户的角度来考虑,计算机显示的数据超过一定量后,再增加数据量,用户不旦不会方便,相反会影响User对有用信息的定位。
所以,用户界面上始终不应该显示超大超多的数据。如果数据量很多,则考虑用Filter,Query或分页显示。
万不得一实在要用很多数据,也要将数据先放到内存中,通过GetDispInfo 来显示用户想要的数据。而不是将数据都InsertItem中。
 
1.使用SetRedraw禁止窗口重绘,操作完成后,再恢复窗口重绘
m_ctlList.SetRedraw(FALSE); 
//以下为更新数据操作
//……
//恢复窗口重绘
m_ctlList.SetRedraw(TRUE);
2.使用LockWindowUpdate禁止窗口重绘,操作完成后,用UnlockWindowUpdate恢复窗口重绘
m_ctlList.LockWindowUpdate(); 
//以下为更新数据操作
//……
//恢复窗口重绘
m_ctlList.UnlockWindowUpdate(); 
3.使用ListCtrl的内部双缓冲
m_ctlLisit.SetExtendedStyle(m_ctlLisit.GetExtendedStyle()|LVS_EX_DOUBLEBUFFER);
VC6未定义LVS_EX_DOUBLEBUFFER宏,使用者可以自定义,如下:
#define LVS_EX_DOUBLEBUFFER 0x00010000
4.Virtual List
首先要设置ListCtrl风格为LVS_REPORT | LVS_OWNERDATA或在ListCtrl属里中的More Styles页面中选中Owner data复选框。
其次要向应LVN_GETDISPINFO消息;
void OnGetdispinfoList(NMHDR* pNMHDR, LRESULT* pResult)
{
 LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR; 
 LV_ITEM *pItem = &(pDispInfo)->item;
 char szText[128] = {0};
 if (pItem->mask & LVIF_TEXT)
 {
//使缓冲区数据与表格子项对应
//m_ArrayBuff为二维数组
//定义如下 int m_ArrayBuff[2048][4];
  _stprintf(szText,_T("%d"),m_ArrayBuff[pItem->iItem][pItem->iSubItem]);   
  pItem->pszText = szText;  
 } 
 *pResult = 0;
}
最后便是生成缓冲区数据
void Insertdata()
{
 //删除之前的数据
 m_ctlList.SetItemCountEx(0);
 m_ctlList.Invalidate();
    m_ctlList.UpdateWindow();
 srand( (unsigned)time( NULL ));
 
 //生成新的数据缓冲区
 int nItemCount = 2048;
 for (int i = 0;i < nItemCount; i ++)
 {
  for (int k = 0;k < 4;k ++)
  {
   m_ArrayBuff[i][k] = rand()%2048 + 1;
  }
 }
 if (nItemCount < 2)    
  m_ctlList.SetItemCountEx(1);   
 else
  m_ctlList.SetItemCountEx(nItemCount);
 m_ctlList.Invalidate();
若要修改数据,只要修改缓冲区m_ArrayBuff的数据即可以
5.Custom Redraw
既然是自绘,首先当然是重载CListCtrl类,并接管WM_ERASEBKGND消息,去掉默认的处理,改为不处理
BOOL CListCtrlEx::OnEraseBkgnd(CDC* pDC)
{
//响应WM_ERASEBKGND消息 
 return false;
 //屏蔽默认处理
 //return CListCtrl::OnEraseBkgnd(pDC);
}
void CListCtrlEx::OnPaint()
{
    //响应WM_PAINT消息
    CPaintDC dc(this); // device context for painting
    CRect rect;
    CRect headerRect;
    CDC MenDC;//内存ID表  
    CBitmap MemMap;
    GetClientRect(&rect);   
    GetDlgItem(0)->GetWindowRect(&headerRect);  
    MenDC.CreateCompatibleDC(&dc);  
    MemMap.CreateCompatibleBitmap(&dc,rect.Width(),rect.Height());
    MenDC.SelectObject(&MemMap);
    MenDC.FillSolidRect(&rect,#e4ecf3);  
    //这一句是调用默认的OnPaint(),把图形画在内存DC表上  
    DefWindowProc(WM_PAINT,(WPARAM)MenDC.m_hDC,(LPARAM)0);      
    //输出  
    dc.BitBlt(0,headerRect.Height(),rect.Width(),  rect.Height(),&MenDC,0, headerRect.Height(),SRCCOPY);  
    MenDC.DeleteDC();
    MemMap.DeleteObject();
}
 

 

 

// BMPListDlg.cpp : implementation file
//

#include "stdafx.h"
#include "BMPList.h"
#include "BMPListDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

// CAboutDlg dialog used for App About
class CAboutDlg : public CDialog
{
public:
    CAboutDlg();

// Dialog Data
    enum { IDD = IDD_ABOUTBOX };

    protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

// Implementation
protected:
    DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
END_MESSAGE_MAP()


// CBMPListDlg dialog


CBMPListDlg::CBMPListDlg(CWnd* pParent /*=NULL*/)
    : CDialog(CBMPListDlg::IDD, pParent)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CBMPListDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_LIST1, m_cList);
}

BEGIN_MESSAGE_MAP(CBMPListDlg, CDialog)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_NOTIFY(LVN_GETDISPINFO, IDC_LIST1, GetDispInfo)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()


// CBMPListDlg message handlers

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

    // Add "About" menu item to system menu.

    // IDM_ABOUTBOX must be in the system command range.
    ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
    ASSERT(IDM_ABOUTBOX < 0xF000);

    CMenu* pSysMenu = GetSystemMenu(FALSE);
    if (pSysMenu != NULL)
    {
        CString strAboutMenu;
        strAboutMenu.LoadString(IDS_ABOUTBOX);
        if (!strAboutMenu.IsEmpty())
        {
            pSysMenu->AppendMenu(MF_SEPARATOR);
            pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
        }
    }

    // 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

    // Create 256 color image lists
    // Need to create image list with a phantom bitmap.
    // This image list is only used to hold the place.  The images are loaded
    // dynamically as the list control is scrolled.
    if(m_imageList.GetSafeHandle() == NULL)
    {
        m_imageList.Create(210, 160, ILC_COLOR24 | ILC_MASK, 8, 1);
        m_cList.SetImageList(&m_imageList, LVSIL_SMALL);
        m_cList.SetImageList(&m_imageList, LVSIL_NORMAL);
        m_imageList.SetImageCount(1);
    }

    // Set up list control
    // Nothing special here.  Just some columns for the report view.
    m_cList.InsertColumn(0, _T("Column 1"), LVCFMT_LEFT, 200);
    m_cList.InsertColumn(1, _T("Column 2"), LVCFMT_LEFT, 100);
    m_cList.InsertColumn(3, _T("Column 3"), LVCFMT_LEFT, 100);
    m_cList.InsertColumn(4, _T("Column 4"), LVCFMT_LEFT, 100);
    m_cList.SetExtendedStyle(LVS_EX_FULLROWSELECT |LVS_EX_INFOTIP | LVS_EX_FLATSB |LVS_EX_GRIDLINES);

    // Add some data
    InsertItems();
    return TRUE;  // return TRUE  unless you set the focus to a control
}

void CBMPListDlg::GetDispInfo(NMHDR *pNMHDR, LRESULT *pResult)
{
    NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);

    //Create a pointer to the item
    LV_ITEM* pItem= &(pDispInfo)->item;

    //Which item number?
    int nItem = pItem->iItem;

    CMyDataInfo *pInfo = NULL;
    if(nItem > m_MyDataArray.GetSize()-1)
        return; // Just to be safe

    pInfo = (CMyDataInfo *)m_MyDataArray.GetAt(nItem);
    if(pInfo == NULL)
        return;
    
    //Do we need text information?
    if (pItem->mask & LVIF_TEXT) {
        CString csText;

        //Which column?
        if(pItem->iSubItem == 0)
            csText = pInfo->m_csColumn1;
        else if (pItem->iSubItem == 1) // Column 1
            csText = pInfo->m_csColumn2;
        else if (pItem->iSubItem == 2) // Column 2
            csText = pInfo->m_csColumn3;
        else if (pItem->iSubItem == 3) // Column 3
            csText = pInfo->m_csColumn4;

        //Copy the text to the LV_ITEM structure
        //Maximum number of characters is in pItem->cchTextMax
        lstrcpyn(pItem->pszText, csText, pItem->cchTextMax);
    }

    //Does the list need image information?
    if( pItem->mask & LVIF_IMAGE) {
        TRACE(_T("pItem|%d|%d\n"), pItem->iItem, pItem->iSubItem);
        // Need to reset first item of image list to the correct bitmap
        HBITMAP hBmp = (HBITMAP)::LoadImage(AfxGetInstanceHandle(),pInfo->m_csImage,
            IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
        if(hBmp != NULL) {
            CBitmap *cBmp = CBitmap::FromHandle(hBmp);
            if(cBmp != NULL)
                m_imageList.Replace(0,cBmp,NULL);
        }
        // Note, there is no Navigator 0.bmp so that image will always display blank to 
        // illustrate what would happen when the file is not found.
        else {
            CBitmap *cBmp = new CBitmap();
            cBmp->LoadBitmap(IDB_BLANK);
            m_imageList.Replace(0,cBmp,NULL);
            delete cBmp;
        }
        pItem->iImage = 0; // Always use 0 since only one element
    }
    *pResult = 0;
}

void CBMPListDlg::InsertItems()
{
    m_MyDataArray.RemoveAll(); // Clear the array

    // Add some data (100000 records)
    // These items are stored in an array, not in the list control itself.
    // We're just putting in enough here to identify the row and column
    // This is the slowest operation in the prgram, but we need some data
    // to do the illustration.
    for(int i = 0; i < 100; ++i) {
        CMyDataInfo *pInfo = new CMyDataInfo();
        if(pInfo) {
            CString cs;
            cs.Format(_T("R%dColumn1"),i);
            pInfo->m_csColumn1 = cs;
            cs.Format(_T("R%dColumn2"),i);
            pInfo->m_csColumn2 = cs;
            cs.Format(_T("R%dColumn3"),i);
            pInfo->m_csColumn3 = cs;
            cs.Format(_T("R%dColumn4"),i);
            pInfo->m_csColumn4 = cs;

            // This will count from Navigator 0.bmp to Navigator 4.bmp.  There is no
            // file Navigator 0.bmp so those rows will use the blank bitmap from the build in
            // resources.
            cs.Format(_T("Navigator %d.bmp"),(i % 5));
            pInfo->m_csImage = cs; // Just repeat images every 4 
            m_MyDataArray.Add(pInfo);
        }
    }

    m_cList.DeleteAllItems();

    // This sets the count in the list without adding any items.  This is very fast.
    m_cList.SetItemCountEx((int)m_MyDataArray.GetCount(), LVSICF_NOSCROLL|LVSICF_NOINVALIDATEALL);
}

void CBMPListDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
    if ((nID & 0xFFF0) == IDM_ABOUTBOX)
    {
        CAboutDlg dlgAbout;
        dlgAbout.DoModal();
    }
    else
    {
        CDialog::OnSysCommand(nID, lParam);
    }
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CBMPListDlg::OnPaint()
{
    if (IsIconic())
    {
        CPaintDC dc(this); // device context for painting

        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

        // Center icon in client rectangle
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // Draw the icon
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialog::OnPaint();
    }
}

// The system calls this function to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CBMPListDlg::OnQueryDragIcon()
{
    return static_cast<HCURSOR>(m_hIcon);
}





// BMPListDlg.h : header file
//

#pragma once
#include "afxcmn.h"

// Data object handling class
class CMyDataInfo : public CObject
{
// Construction/Destruction
public:
    CMyDataInfo() {};
    ~CMyDataInfo() {};
// Attributes
public:
    CString m_csColumn1;
    CString m_csColumn2;
    CString m_csColumn3;
    CString m_csColumn4;
    CString m_csImage; // BMP to show

// Implementation
};

class CMyDataArray : public CObArray
{
// Construction/Destruction
public:
    CMyDataArray() {};
    ~CMyDataArray() {
        for(int i = 0; i < GetSize(); ++ i)
            delete GetAt(i);
        RemoveAll();
    };

// Attributes
public:
};

// CBMPListDlg dialog
class CBMPListDlg : public CDialog
{
// Construction
public:
    CBMPListDlg(CWnd* pParent = NULL);    // standard constructor
    ~CBMPListDlg(){m_imageList.DeleteImageList();};
    
// Dialog Data
    enum { IDD = IDD_BMPLIST_DIALOG };

    protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support


// Implementation
protected:
    HICON m_hIcon;
    CImageList    m_imageList;
    void InsertItems();
    int m_nItems;
    CMyDataArray m_MyDataArray;
    
    // Generated message map functions
    virtual BOOL OnInitDialog();
    afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
    afx_msg void OnPaint();
    afx_msg HCURSOR OnQueryDragIcon();
    afx_msg void GetDispInfo(NMHDR *pNMHDR, LRESULT *pResult);
    DECLARE_MESSAGE_MAP()
public:
    CListCtrl m_cList;
};

 

posted on 2013-04-25 20:13 聂文龙 阅读(3043) 评论(1)  编辑 收藏 引用


FeedBack:
 
# re: CLISTCTRL 显示图片及大批量数据的加载速度与闪烁问题 2013-04-25 20:39 聂文龙
clistctrl 虚拟列表
一、什么是虚拟列表控件
虚拟列表控件是指带有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;
}
}
/*注意,多数情况下要使用lstrcpyn ,因为最多复制字符的个数由pItem->cchTextMax给出:
lstrcpyn(pItem->pszText, text, pItem->cchTextMax);
*/

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;

/* pFindInfo->iStart 是查找的起始位置,一直到最后,然后从头开始,如果没有找到合适的,最终停留在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;

//开始查找
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、虚表的另一个优点是可以根据需要产生数据。比如在某一列加上行号。

http://blog.vckbase.com/iwaswzq/archive/2006/07/07/21113.html
http://www.codeproject.com/KB/list/virtuallist.aspx  回复  更多评论
posted on 2023-02-17 16:23  不及格的程序员-八神  阅读(183)  评论(0编辑  收藏  举报