0

I am facing a problem when I try to use a ListView in virtual mode with LargeIcon images.

Let say that I want to show all the graphic files contained in a array of 2000 items (the average size of each file is 2MB): if the ListView is set to Details the display is immediate and the scroll goes up and down very smoothly; if, instead, the ListView is set to LargeIcon the display of the visible items takes a very long time and the scroll is virtually impossible. Waiting enough time (tens of seconds) the display is correct and the icons show the right image.

If the array contains e.g. thousands of .PDF files the display and the scroll are very quick (almost the same of the Details view) and the images show are the right PDF icons.

What I would like to get is something similar to what Windows Explorer does, i.e. show a generic icon while scrolling (fast) and then, show the right images only when the scroll stops (takes some time).

Here it is the most relevant (I think) part of the code that I am using and thanks for any help:

    // Based on the example given in: https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.listview.virtualmode?view=netframework-4.8

    // Notes:
    // The string[] array nameFoFi contains the list of the itens to be shown in ListView1 (drives or folders [and files]).
    // When I fill the array nameFoFi I also set listView1.VirtualListSize = allFoFi.Count();
    // If bViewDrives = True the array nameFoFi contains only drive names.

    // ...
    // ...

    private ListViewItem[] lwiCache; //array to cache items for the virtual list
    private int firstItem; //stores the index of the first item in the cache

    // ...
    // ...

    listView1.VirtualMode = true;

    //listView1.View = View.Details;
    listView1.View = View.LargeIcon;

    listView1.SmallImageList = new ImageList();
    listView1.SmallImageList.ColorDepth = ColorDepth.Depth32Bit;
    listView1.SmallImageList.ImageSize = new System.Drawing.Size(20, 20);

    listView1.LargeImageList = new ImageList();
    listView1.LargeImageList.ColorDepth = ColorDepth.Depth16Bit;
    listView1.LargeImageList.ImageSize = new System.Drawing.Size(50, 50);

    // ...
    // ...

    void listView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
    {
            //check to see if the requested item is currently in the cache
            if (lwiCache != null && e.ItemIndex >= firstItem && e.ItemIndex < firstItem + lwiCache.Length)
            {
                //A cache hit, so get the ListViewItem from the cache instead of making a new one.
                e.Item = lwiCache[e.ItemIndex - firstItem];
            }

            else
            {
                //A cache miss, so create a new ListViewItem and pass it back.
                string nameFoFi = allFoFi[e.ItemIndex];
                e.Item = ListViewNewItem(nameFoFi);
            }
    }

    void listView1_CacheVirtualItems(object sender, CacheVirtualItemsEventArgs e)
    {
        if (lwiCache != null && e.StartIndex >= firstItem && e.EndIndex <= firstItem + lwiCache.Length)
        {
            //If the newly requested cache is a subset of the old cache, 
            //no need to rebuild everything, so do nothing.
            return;
        }

        //Now we need to rebuild the cache.
        firstItem = e.StartIndex;
        int length = e.EndIndex - e.StartIndex + 1; //indexes are inclusive
        lwiCache = new ListViewItem[length];

        lblCacheTime.Text = firstItem.ToString() + " " + length.ToString();

        //Fill the cache with the appropriate ListViewItems.
        for (int i = 0; i < length; i++)
        {
            string nameFoFi = allFoFi[i + firstItem];
            lwiCache[i] = ListViewNewItem(nameFoFi);
        }
    }

    private ListViewItem ListViewNewItem(string itemName)
    {
        string name;
        ListViewItem lwItem;

        if (bViewDrives)
        {
            name = itemName;
        }
        else
        {
            name = Path.GetFileName(itemName);
        }

        lwItem = new ListViewItem(name, 0);
        lwItem.Tag = itemName;

        {
            if (listView1.View == View.Details)
            {
                lwItem.ImageIndex = GetListViewSmallIcons(itemName);

                GetDetails(lwItem, itemName);
            }
            else
                lwItem.ImageIndex = GetListViewUniIcons(itemName);
        }

        return lwItem;
    }

    // From: https://stackoverflow.com/questions/670546/determine-if-file-is-an-image
    public static readonly List<string> ImageExtensions = new List<string> { ".JPG", ".JPE", ".BMP", ".GIF", ".PNG" };

    private int GetListViewUniIcons(string nameFoFi)
    {
        int iconIndex = 0;

        if (bViewDrives)
        {
            // Get the icon of the drive nameFoFi:
            Icon iconFo = GetDriveIcon(nameFoFi, true);
            listView1.SmallImageList.Images.Add(nameFoFi, iconFo.ToBitmap());
            listView1.LargeImageList.Images.Add(nameFoFi, iconFo.ToBitmap());

            iconIndex = listView1.SmallImageList.Images.Count - 1;
        }

        else if (Directory.Exists(nameFoFi))
        {
            // Get the icon of the folder nameFoFi:
            Icon iconFo = GetFolderIcon(nameFoFi);
            listView1.SmallImageList.Images.Add(nameFoFi, iconFo.ToBitmap());
            listView1.LargeImageList.Images.Add(nameFoFi, iconFo.ToBitmap());

            iconIndex = listView1.SmallImageList.Images.Count - 1;
        }

        else // IsFile:
        {
            if (ImageExtensions.Contains(Path.GetExtension(nameFoFi).ToUpperInvariant()) && bEndScroll)
            {
                // Get the image of the file nameFoFi:
                Bitmap image = (Bitmap) Image.FromFile(nameFoFi); // <-- here is where most of the time is lost.

                listView1.SmallImageList.Images.Add(nameFoFi, image);
                listView1.LargeImageList.Images.Add(nameFoFi, image);

                iconIndex = listView1.LargeImageList.Images.Count - 1;
            }

            else
            {
                // Get the icon of the file nameFoFi:
                Icon iconFi = GetFileIcon(nameFoFi, Win32.IconSize.Large, false);
                listView1.SmallImageList.Images.Add(iconFi.ToBitmap());
                listView1.LargeImageList.Images.Add(nameFoFi, iconFi.ToBitmap());

                iconIndex = listView1.SmallImageList.Images.Count - 1;
            }
        }

        return iconIndex;
    }
  • Using [BeginUpdate](https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.listview.beginupdate?view=netframework-4.8) and [EndUpdate](https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.listview.endupdate?view=netframework-4.8#System_Windows_Forms_ListView_EndUpdate) can drastically increase performance when populating a `ListView` with lots of items. – itsme86 Aug 01 '19 at 16:21
  • Thank you for your answer but where is the best point in the code to use BeginUpdate and EndUpdate? I have already tried to use them in the listView1_CacheVirtualItems(...) event, like: ... listView1.BeginUpdate(); for (int i = 0; i < length; i++) { string nameFoFi = allFoFi[i + firstItem]; lwiCache[i] = ListViewNewItem(nameFoFi); } listView1.EndUpdate(); … with no good... – Franco Languasco Aug 01 '19 at 17:52

0 Answers0