Virtual ListView via API

The ListView control is ideal for displaying large amounts of data.  Unfortunately, when the number of tiems gets extremely large, the ListView becomes unbearably slow.  It's hard to imagine that a ListView would be slow, as Explorer itself is a fancy ListView.  With the release of IE3 (comctrl32 version 4.70 MS introduced the LVS_OWNERDATA style -- the virtual list view control intended for raw speed. 

At this point, Windows does not support dynamically changing the ListView to virtual mode.  What this means is, you can't use an existing TListView, then call GetWindowLong()/SetWindowLong().  This imposes additionaly work since you have to create and manage everything dynamically.  So, the first task is to use the CreateWindowEx() function to create a ListView -- not a TListView, but a WC_LISTVIEW (window control ListView).  Then, tell the ListView how many items it is to contain by using the ListView_SetItemCountEx() macro.  This is not implemented in some versions, so I defined it below.  Next, you'll need to attach an ImageList to the ListView using the ListView_SetImageList() macro.  Finally, you need to map the WM_NOTIFY message and examine it for the LVN_GETDISPINFO code.  The basic idea is this: The ListView knows how many items it has (specified by the ListView_SetItemCount() call), and whenever it needs to display an item, it sends its Parent (your Form), a WM_NOTIFY message.  Within this message is the information it's requesting (i.g., item caption, image index, etc.).  In turn, you respond by telling the ListView what to display, in your case, strings from your StringList and an index from your ImageList.  Windows does the rest.  All the memory management for the items is handled by you (in your StringList).  That's why it's called a virtual ListView -- it's nothing more than a container for you to display items in.  The ListView itself has no idea what it contains, it just knows how many, and what style to display
the data in.
 

KEYWORDS:  LVS_OWNERDATA, CreateWindowEx



 
 
//in header...
//define the ListView constants...
#define LVS_OWNERDATA           0x1000
#define LVSICF_NOINVALIDATEALL  0x00000001
#define LVSICF_NOSCROLL         0x00000002
#define LVM_SETITEMCOUNT        (LVM_FIRST + 47)

#define ListView_SetItemCountEx(hwndLV, cItems, dwFlags) \
  SNDMSG((hwndLV), LVM_SETITEMCOUNT, (WPARAM)cItems, (LPARAM)dwFlags)

//functions for creation and management...
    HWND HListView;
    HWND CreateVirtListView(DWORD style, int x, int y, int width, int height);
    void __fastcall WMNotify(TMessage &Msg);

BEGIN_MESSAGE_MAP
    MESSAGE_HANDLER(WM_NOTIFY, TMessage, WMNotify)
END_MESSAGE_MAP(TForm)
 

 


 

//---------------------------------------------------------------------------
const int num_items = 25000;

__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
    // create the virtual ListView
    HListView = CreateVirtListView(LVS_LIST, 10, 10, 200, 250);
    if (HListView)
    {
        // specify the number of items     
        ListView_SetItemCountEx(HListView, num_items, LVSICF_NOINVALIDATEALL);

        // bind an ImageList to your ListView
        ListView_SetImageList(HListView, ImageList1->Handle, LVSIL_SMALL);
    }
}
 

void __fastcall TForm1::WMNotify(TMessage &Msg)
{
    LPNMHDR lpnmhdr = (NMHDR *)Msg.LParam;
    if (lpnmhdr->code == LVN_GETDISPINFO)
    {
        LV_DISPINFO *lplvdi = (LV_DISPINFO *) Msg.LParam;

        // index of item to display
        int index = lplvdi->item.iItem;

        if (lplvdi->item.mask & LVIF_IMAGE)
        {
            // decide what image to display, for example
            // I made all even items have image index zero
            // and all odd items have image index of one.
            if (index % 2 == 0) lplvdi->item.iImage = 0;
            else lplvdi->item.iImage = 1;
        }
        if (lplvdi->item.mask & LVIF_TEXT)
        {
            // fill in the pszText member with data from your StringList
            lstrcpy(lplvdi->item.pszText, SL->Strings[index].c_str());
        }

        Msg.Result = false;
    }
    else TForm::Dispatch(&Msg);
}

// function to create the virtual list view
HWND TForm1::CreateVirtListView(DWORD style, int x, int y, 
    int width, int height)
{
    DWORD dwStyle = WS_TABSTOP | WS_CHILD | WS_BORDER | WS_VISIBLE |
                    LVS_AUTOARRANGE | style | LVS_OWNERDATA;

    HWND HListView = CreateWindowEx(WS_EX_CLIENTEDGE, WC_LISTVIEW, NULL,
                                    dwStyle, 0, 0, 0, 0, Handle, (HMENU)0, 
                                    HInstance, NULL);
    if (!HListView) return NULL;

    ::MoveWindow(HListView, x, y, width, height, true);
    return HListView;