VCB listview 快速刷新大量数据

    本文地址:http://tongxinmao.com/Article/Detail/id/481

    Fast updates with virtual list views

    by Kent Reisdorph

    The TListView component is a powerful component. It is useful for situations where you want to present a list of items to the user, to allow the user to view those items in a number of ways, and to display icons next to each item. TListView provides three view styles; report (typically known as the details view), large icon, small icon, and list. TListView has one major shortcoming, though. If you have a lot of items to display then TListView can be very slow.

    This article will explain how to use TListView as a virtual list view. I will introduce you to the OwnerData property, and the OnData and OnDataFind events. I will also show you how to store your data separate from the list view. Virtual list view support appeared in the VCL in C++Builder 4. It is not available in C++Builder 1 and 3.

    Normal vs. virtual list view

    A virtual list view is much faster than a normal list view. It is faster because the list view does not actually store any data. To illustrate, consider the case where you have 1,000 items to present in a list view. (At first this might seem extreme, but it is actually quite common.) Let’s say, for example, that you are displaying the contents of a file folder in a list view. In this case you could easily have 1,000 items to display. Using a normal list view, you would have to add all of the items to the list view. Further, if you wanted to display the contents of a different directory, you would have to remove all of the existing items before adding the new items. This all takes a considerable amount of time. You can speed up the process by disabling screen updates while you are removing and adding items, but it still takes a long time.

    A virtual list view, on the other hand, doesn’t contain any items at all. You store your data in some external storage, such as a TList or STL vector. When the list view needs to paint items, it requests the information for each item from you through the OnData event. Depending on screen resolution, a list view can only display between 100 and 200 items on the screen at one time. This means that the list view is only requesting data for that number of items, rather than for the entire list. The disadvantage of a virtual list view is that you must maintain the list of items separate from the list view. Still, this is an acceptable tradeoff for those times where you need the speed of a virtual list view.

    So how much faster is a virtual list view? The example program for this article fills the list view with the contents of a directory. On my system, the WINNT\SYSTEM32 directory contains 1680 files and folders. Using a regular list view, adding all items takes about 550 milliseconds. Using a virtual list view, it only takes about 40ms. That’s an impressive difference, but it gets even better. Removing all items from the regular list view and adding them back in again takes over 1,000ms. With the virtual list view it takes about 50ms. That’s a speed increase of over 20 fold.

    Storing your data

    The first thing you need to do is to decide how you are going to store your data. For this article’s example program, I chose to use a TList to store the item data and created a structure called TFileItem for this purpose. Here’s the declaration:

    struct TFileItem {
      String FileName;
      String FullPath;
      int IconIndex;
      bool IsFolder;
    };

    This structure contains the basic information I will need in order to display a list view item, as well as some additional information that I will use to sort the items. I used a typical FindFirst()/FindNext() loop to iterate through the items in the folder. Here’s an abbreviated version of the code:

    TList* FileList = new TList;
    ListView1->OwnerData = true;
    TSearchRec SR;
    FindFirst(PathEdit->Text +
      "\\*.*", faAnyFile, SR);
    while (FindNext(SR) == 0) {
      TFileItem* fi = new TFileItem;
      fi->FileName = SR.FileName;
      fi->IsFolder = 
        (SR.Attr & faDirectory) != 0;
      FileList->Add(fi);
    }
    FindClose(SR);
    ListView1->Items->Count =FileList->Count;

    First I create a TList object to hold the items. Next, I set the OwnerData property to true. I could have set this property in the Object Inspector but I put it in the code to illustrate that OwnerData must be set to true for a virtual list view. Next I start the file search by calling FindFirst()PathEdit is a TEdit on my main form that is used to specify the starting path. You may have noticed that I don’t do anything with the original file found by FindFirst(). This is because the first item returned by FindFirst() is a single period representing the current directory.

    In the while loop I create an instance of the TFileItem structure, set a few of its members, and then add it to the TList. Note that I do not delete the pointer contained in the fi variable. I’ll do this later when I clear the TList.

    When the while loop ends, I call FindClose() to free the memory allocated for the file search operation. Finally, I set ListView1->Items->Count to the number of items in the file list. This is an important step because it forces the list view to updated. Before this line executes, the list view has no knowledge that it has items to display.

    I said earlier that this is an abbreviated version of the code. It is abbreviated because in the example program’s code I assign values to the remaining structure members. The example program includes an option to use the list view as a normal list view or as a virtual list view. I removed the code that adds the items to the normal list view because it is not pertinent to this part of the article. Naturally, I will need to run through the item list at some point and free the memory for the TFileItem objects in the list. Doing so is standard TList fare so I won’t elaborate further.

    Handling the OnData event

    The VCL will fire the OnData event whenever Windows needs to draw an item in the list view. OnData gives you a pointer to the list item that is about to be displayed. You use that pointer (a TListItem*) to assign the item’s text, its icon, and so on. Here is the example program’s OnData event handler:

    void __fastcall TForm1::ListView1Data(
      TObject *Sender, TListItem *Item)
    {
      TFileItem* fi = (TFileItem*)
        FileList->Items[Item->Index];
      Item->Caption = fi->FileName;
      Item->ImageIndex = fi->IconIndex;
    }

    The first line of code extracts the TFileItem object corresponding to the item that is about to be displayed. The Index property of the item corresponds to an item in the file list. Next I set the item’s Caption property to the FileName member of the TFileItem object, and the ImageIndex property of the item to the IconIndex member.

    In the previous code I didn’t show how I set the icon index so I’ll explain that now. The example program contains a TImageList component that has three images. The first image is an icon representing a file folder, the second image is the standard Windows file icon, and the third is the default Windows icon for an executable file. In the file search loop, I set the IconIndex member of the TFileItem structure to 01, or 2 depending on the file item attributes.

    That’s all there is to handling the OnData event. The VCL gives you the TListItem pointer representing the item about to be displayed, and you set any of the item’s properties as needed.

    More virtual list view events

    The OnData event is the only event that you are required to respond to if you are using a virtual list view. Other events that you can optionally respond to are OnDataFindOnDataHint, and OnDataStateChange.

    Create an event handler for the OnDataFind event if you plan to search for items in the list view using the FindCaption() or FindData() methods. The example program, for example, allows you to search for a file or folder by name. The code that finds the item looks like this:

    TListItem* item = 
      ListView1->FindCaption(0, 
       SearchEdit->Text, false, true, false);

    When this code executes, the OnDataFind event will fire. Here is the pertinent code in the example program’s OnDataFind handler:

    for (int i=0;i<FileList->Count;i++) {
      TFileItem* item =
        (TFileItem*)FileList->Items[i];
      if (UpperCase(item->FileName) ==
          UpperCase(FindString)) {
        Index = i;
        break;
      }
    }

    In this code, FindString is the search string passed in the event handler. Index is a parameter passed by reference that is used to return the index of the matching item. I simply run through the file list and, if a matching item is found, I set the Index parameter to the index of the item in the file list. See Listing B for the complete OnDataFind event handler.

    OnDataHint is used in special cases where you need even faster list view updates. The OnDataStateChange event is fired when the state of a range of items changes. The VCL help is a bit spotty on these two event handlers so some experimentation will be required in order to implement them. The good news is that neither event is specifically required for virtual list views.

    Sorting the virtual list view

    Sorting the items in a virtual list view is a matter of sorting the underlying list itself. After I fill the list of file items, I sort the list:

    FileList->Sort(ListSortFunc);

    Obviously, this code doesn’t tell you much. The real work is done in the ListSortFunc() function:

    int __fastcall
    ListSortFunc(void* Item1, void* Item2)
    {
     TFileItem* item1 = (TFileItem*)Item1;
     TFileItem* item2 = (TFileItem*)Item2;
     if (item1->IsFolder && !item2->IsFolder)
       return -1;
     if (item2->IsFolder && !item1->IsFolder)
       return 1;
     return CompareText(
       item1->FileName, item2->FileName);
    }

    This is a basic TList sort function, where you return -1 from the function if item one is less than item two, return 1 if item one is greater than item two, or 0 if the two items are the same. In this case I return –1 if the first item is a directory and the second item is not. Conversely, I return 1 if the second item is a folder and the first is not. Finally, if neither of these conditions is met, I return the result of a string compare between the two file (or folder) names. The result is that the list is sorted with the folders at the top and the individual files after the folders. Both the folders and the files are sorted by name.

    Sorting the list can be much more complex than the simple sort shown here. However complex your sort, remember to keep the code in the sort function as efficient as possible. This will drastically reduce the time required to perform the sort.

    Finally, consider using a profiler to improve the performance of your sort functions. I used TurboPower’s Sleuth QA Suite to profile my list sort function. I found out that to sort the 1680 items in my SYSTEM32 directory, the sort function was called 20,948 times. The total time spent in the function, however, was only 11.16 milliseconds. Certainly I can live with a sort that only takes a fraction of a second.

    Conclusion

    Virtual list views should be used any time you need fast updates for your list view controls. Managing the list of items takes a bit of work but it is well worth it in the long run. Listings A and B are the header and main unit for the example program. Examine them to get the full picture of how to use virtual list views. As an added bonus, the example program shows you how to sort a regular list view as well.

    Listing A: MAINU.H

    #ifndef MainUH
    #define MainUH
    
    #include <Classes.hpp>
    #include <Controls.hpp>
    #include <StdCtrls.hpp>
    #include <Forms.hpp>
    #include <ComCtrls.hpp>
    #include <ExtCtrls.hpp>
    #include <ImgList.hpp>
    
    // The structure that holds file items.
    struct TFileItem {
      String FileName;
      String FullPath;
      int IconIndex;
      bool IsFolder;
    };
    
    class TForm1 : public TForm
    {
    __published:    // IDE-managed Components
      TPanel *Panel1;
      TListView *ListView1;
      TLabel *Label1;
      TEdit *PathEdit;
      TButton *FillViewBtn;
      TImageList *LargeImages;
      TRadioButton *VirtualRb;
      TRadioButton *NormalRb;
      TLabel *Label2;
      TLabel *Label3;
      TButton *SearchBtn;
      TLabel *Label4;
      TEdit *SearchEdit;
      void __fastcall FillViewBtnClick(TObject *Sender);
      void __fastcall FormDestroy(TObject *Sender);
      void __fastcall ListView1Data(
        TObject *Sender, TListItem *Item);
      void __fastcall ListView1Click(TObject *Sender);
      void __fastcall SearchBtnClick(TObject *Sender);
      void __fastcall ListView1DataFind(TObject *Sender,
        TItemFind Find, const AnsiString FindString,
        const TPoint &FindPosition, Pointer FindData,
        int StartIndex, TSearchDirection Direction,
        bool Wrap, int &Index);
    private:    // User declarations
      TList* FileList;
      void ClearList();
    public:        // User declarations
      __fastcall TForm1(TComponent* Owner);
    };
    
    extern PACKAGE TForm1 *Form1;
    
    #endif

    Listing B: MAINU.CPP

    #include <vcl.h>
    #pragma hdrstop
    
    #include "MainU.h"
    
    #pragma package(smart_init)
    #pragma resource "*.dfm"
    TForm1 *Form1;
    
    int __fastcall
    ListSortFunc(void* Item1, void* Item2)
    {
      TFileItem* item1 = (TFileItem*)Item1;
      TFileItem* item2 = (TFileItem*)Item2;
      // Folder items come first so sort by folder.
      if (item1->IsFolder && !item2->IsFolder)
        return -1;
      if (item2->IsFolder && !item1->IsFolder)
        return 1;
      // Now sort by the display name of each item.
      return CompareText(
        item1->FileName, item2->FileName);
    }
    
    int __stdcall ListViewSortFunc(
      LPARAM Item1, LPARAM Item2, LPARAM Data)
    {
      // This is the sort function for the list view
      // in normal mode. It is essentially the same
      // as the sort function for the TList.
      TListItem* item1 = (TListItem*)Item1;
      TListItem* item2 = (TListItem*)Item2;
      if (!item1->ImageIndex && item2->ImageIndex)
        return -1;
      if (item1->ImageIndex && !item2->ImageIndex)
        return 1;
      return
        CompareText(item1->Caption, item2->Caption);
    }
    
    __fastcall TForm1::TForm1(TComponent* Owner)
      : TForm(Owner)
    {
      FileList = new TList;
      // Assign the form's TImageList to the
      // list view's LargeImages property.
      ListView1->LargeImages = LargeImages;
    }
    
    void __fastcall TForm1::FormDestroy(TObject *Sender)
    {
      ClearList();
      delete FileList;
    }
    
    void __fastcall
    TForm1::FillViewBtnClick(TObject *Sender)
    {
      int startTime;
      if (VirtualRb->Checked) {
        //OwnerData must be true for virtual list views
        ListView1->OwnerData = true;
        startTime = GetTickCount();
        // Clear out the items and the TList.
        ListView1->Items->Clear();
        ClearList();
      }
      else {
        // Disable updates for the normal list view.
        ListView1->Perform(WM_SETREDRAW, 0, 0);
        ListView1->OwnerData = false;
        startTime = GetTickCount();
        ListView1->Items->Clear();
      }
      TSearchRec SR;
      FindFirst(
        PathEdit->Text + "\\*.*", faAnyFile, SR);
      while (FindNext(SR) == 0) {
        if (SR.Name == "..")
          continue;
        // Get the next file in the folder.
        String FileName = ExtractFileName(SR.Name);
        int IconIndex;
        // Set the IconIndex property based on type.
        if ((SR.Attr & faDirectory) != 0)
          IconIndex = 0;
        else if (ExtractFileExt(SR.Name) == ".exe")
          IconIndex = 2;
        else
          IconIndex = 1;
        if (VirtualRb->Checked) {
          // Create a new TFileItem, set its properties
          TFileItem* fi = new TFileItem;
          fi->FileName = FileName;
          fi->FullPath =
            PathEdit->Text + "\\" + SR.Name;
          fi->IconIndex = IconIndex;
          fi->IsFolder = (SR.Attr & faDirectory) != 0;
          // Add the item to the list.
          FileList->Add(fi);
        }
        else {
          // Normal list view stuff.
          TListItem* item = ListView1->Items->Add();
          item->Caption = FileName;
          item->ImageIndex = IconIndex;
        }
      }
      FindClose(SR);
      if (VirtualRb->Checked) {
        // Sort the list.
        FileList->Sort(ListSortFunc);
        // This is the part that causes the virtual
        // list view to begin updating.
        ListView1->Items->Count = FileList->Count;
      }
      else {
        // Sort the normal list view.
        ListView1->CustomSort(ListViewSortFunc, 0);
        // Enable updates again.
        ListView1->Perform(WM_SETREDRAW, 1, 0);
      }
      Label2->Caption = "Elapsed time: " +
        String(GetTickCount() - startTime) + "ms for "+
        String(ListView1->Items->Count) + " items";
    }
    
    void TForm1::ClearList()
    {
      // Clear out the file list each time.
      for (int i=0;i<FileList->Count;i++)
        delete ((TFileItem*)FileList->Items[i]);
      FileList->Clear();
    }
    
    void __fastcall TForm1::ListView1Data(
      TObject *Sender, TListItem *Item)
    {
      // Get a TFileItem pointer from the file list.
      TFileItem* fi =
        (TFileItem*)FileList->Items[Item->Index];
      // Set the Caption and ImageIndex properties.
      Item->Caption = fi->FileName;
      Item->ImageIndex = fi->IconIndex;
    }
    
    void __fastcall
    TForm1::ListView1Click(TObject *Sender)
    {
      // A list view item may have been selected. If
      // so, update a label on the form with the path
      // of the item that was selected.
      if (VirtualRb->Checked && ListView1->Selected) {
        TFileItem* fi = (TFileItem*)
          FileList->Items[ListView1->Selected->Index];
        Label3->Caption = fi->FullPath;
      }
    }
    
    void __fastcall
    TForm1::SearchBtnClick(TObject *Sender)
    {
      // Finds an item and, if found, selects it.
      TListItem* item = ListView1->FindCaption(
        0, SearchEdit->Text, false, true, false);
      ListView1->Selected = item;
      ListView1->SetFocus();
      // Windows macro to make sure the found item
      // is visible.
      ListView_EnsureVisible(
        ListView1->Handle, item->Index, false);
    }
    
    void __fastcall TForm1::ListView1DataFind(
      TObject *Sender, TItemFind Find,
      const AnsiString FindString,
      const TPoint &FindPosition, Pointer FindData,
      int StartIndex, TSearchDirection Direction,
      bool Wrap, int &Index)
    {
      // This event handler is called when the
      // FindCaption method is called. Find the item.
      for (int i=0;i<FileList->Count;i++) {
        TFileItem* item =(TFileItem*)FileList->Items[i];
        if (UpperCase(item->FileName) ==
            UpperCase(FindString)) {
          Index = i;
          break;
        }
      }
    }


    上一篇:PHP hexstr2bytes bytes2hexstr 十六进制字符串和字节数组转换
    下一篇:MQTT知识点