CODEGATOR

.NET info worth sinking your teeth into!
Welcome to CODEGATOR Sign in | Join | Help
in Search

Martin Cook

Yet another C# developer with a blog.

My virtual ListView class.

While digging around in my old code folder I recently stumbled across a bunch of Windows controls that I wrote back in 2005. I thought I would share some of them on CODEGATOR, starting with my virtual ListView. Unlike other virtual ListView implementations I've seen on the Internet, this one caches list items correctly. Of course, I had to "tweak" my code a bit before I published it here.  (What is it about old source code that always makes one want to start rewriting for no apparent reason?). The code ran fine when I was building the sample code. 

I included a couple of nice bug fixes that I noticed on *** Grano Salis' website this week. I'm including his bug fixes as part of my code listing in the interest of completeness. A big toothy CODEGATOR grin goes out to him for the bug fixes.

 

Here is a listing for the code:

/// <summary>

/// A virtual <see cref="ListView"/> class.

/// </summary>

public partial class VirtualListView : ListView

{

 

    // ******************************************************************

    // Events.

    // ******************************************************************

 

    #region Events

 

    /// <summary>

    /// Fired whenever the list needs to create a new cache item.

    /// </summary>

    public event EventHandler<CreateVirtualListItemEventArgs> CreateVirtualListItem;

 

    /// <summary>

    /// Fired whenever the list has cleared the internal cache.

    /// </summary>

    public event EventHandler ClearVirtualCache;

 

    #endregion

 

    // ******************************************************************

    // Fields.

    // ******************************************************************

 

    #region Fields

 

    private Dictionary<int, ListViewItem> lviCache = new Dictionary<int, ListViewItem>();

 

    #endregion

 

    // ******************************************************************

    // Properties.

    // ******************************************************************

 

    #region Properties

 

    /// <summary>

    /// Gets or sets the virtual mode for the list.

    /// </summary>

    [Browsable(false)]

    public new bool VirtualMode

    {

        get { return base.VirtualMode; }

        set { base.VirtualMode = true; }

    }

 

    // ******************************************************************

 

    /// <summary>

    /// Gets and sets the size of the virtual list.

    /// </summary>

    public new int VirtualListSize

    {

        get { return base.VirtualListSize; }

        set

        {

            // Clear the cache.

            OnClearVirtualCache();

 

            // Save the new list size (which will rebuild the cache).

            base.VirtualListSize = value;

        }

    }

 

    #endregion

 

    // ******************************************************************

    // Constructors.

    // ******************************************************************

 

    #region Constructors

 

    /// <summary>

    /// Creates a new instzance of the <see cref="VirtualListView"/> class.

    /// </summary>

    public VirtualListView()

    {

        InitializeComponent();

    }

 

    #endregion

 

    // ******************************************************************

    // Public methods.

    // ******************************************************************

 

    #region Public methods

 

    /// <summary>

    /// Clears the internal cache of items.

    /// </summary>

    public void ClearItemCache()

    {

        OnClearVirtualCache();

    }

 

    #endregion

 

    // ******************************************************************

    // Overrides.

    // ******************************************************************

 

    #region Overrides

 

    /// <summary>

    /// Overridden in order to setup the virtual list properties.

    /// </summary>

    protected override void OnCreateControl()

    {

        // Give the base class a chance.

        base.OnCreateControl();

 

        // Setup the virtual list properties.

        base.VirtualMode = true;

    }

 

    // ******************************************************************

 

    /// <summary>

    /// Overridden in order to fetch virtual items from the cache

    /// </summary>

    /// <param name="e">The event arguments.</param>

    protected override void OnRetrieveVirtualItem(

        RetrieveVirtualItemEventArgs e

        )

    {

        // Give the base class a chance.

        base.OnRetrieveVirtualItem(e);

 

        ListViewItem lvi;

 

        // Check the cache first.

        if (lviCache.TryGetValue(e.ItemIndex, out lvi))

        {

            e.Item = lvi;

            return;

        }

 

        // Create an item and store it in the cache.

        e.Item = OnCreateVirtualListItem(e.ItemIndex);

        lviCache[e.ItemIndex] = e.Item;

    }

 

    #endregion

 

    // ******************************************************************

    // Protected methods.

    // ******************************************************************

 

    #region Protected methods

 

    /// <summary>

    /// Fires the <see cref="CreateVirtualListItem"/> event.

    /// <see cref="ListVireItem"/> for the internal cache.

    /// </summary>

    /// <param name="index">The index of the item.</param>

    /// <returns>A <see cref="ListViewItem"/> instance.</returns>

    protected virtual ListViewItem OnCreateVirtualListItem(

        int index

        )

    {

        // Should we fire the event?

        if (CreateVirtualListItem != null)

        {

            // Create the arguments.

            CreateVirtualListItemEventArgs e =

                new CreateVirtualListItemEventArgs(index);

 

            // Fire the event.

            CreateVirtualListItem(this, e);

 

            // Should we validate the item?

            if (e.Item != null)

            {

                // Loop and check each item in the list.

                foreach (ListViewItem.ListViewSubItem subItem in e.Item.SubItems)

                {

                    // If an items text is 260 characters long, add a space so it does

                    // not crash the program. Thanks to *** Grano Salis for the

                    // fix.

                    // https://blogs.msdn.com/cumgranosalis/archive/2006/03/18/ListViewVirtualModeBugs.aspx

                    if (subItem.Text.Length == 260)

                        subItem.Text = subItem.Text + " ";

                }

            }

            // Return the new item.

            return e.Item;

        }

 

        // Return something by default.

        return new ListViewItem();

    }

 

    // ******************************************************************

 

    /// <summary>

    /// Fires the <see cref="ClearVirtualCache"/> event.

    /// </summary>

    protected virtual void OnClearVirtualCache()

    {

        // Should we fire the event?

        if (ClearVirtualCache != null)

            ClearVirtualCache(this, EventArgs.Empty);

 

        // Clear the internal cache.

        lviCache.Clear();

    }

 

    #endregion

}

I handle the caching of items by using a Dictionary object, which is much easier than using an array of ListViewItem instances. I cache the items after I create them inside the OnRetriveVirtualItem method. Managing the ListView items in a single method is easier than trying to deal with the OnCacheVirtualItems method, which I think is completely useless since I have to cache the items as I create then inside my OnRetrieveVirtualItem override anyway.

The VirtualListView class exposes two public events:

  • CreateVirtualListItem - fired when the ListView needs a ListViewItem.
  • ClearVirtualCach - fired when the ListView clears it's internal cache.

 

Here is a listing for the CreateVirtualListItem event arguments class:

/// <summary>

/// The arguments for the CreateVirtualListItem event on the

/// <see cref="VirtualListView"/> class.

/// </summary>

public class CreateVirtualListItemEventArgs : EventArgs

{

 

    // ******************************************************************

    // Fields.

    // ******************************************************************

 

    #region Fields

 

    private int index;

    private ListViewItem lvi;

 

    #endregion

 

    // ******************************************************************

    // Properties.

    // ******************************************************************

 

    #region Properties

 

    /// <summary>

    /// Gets the index of the virtual list item.

    /// </summary>

    public int Index

    {

        get { return index; }

    }

 

    // ******************************************************************

 

    /// <summary>

    /// Gets and sets the list view item.

    /// </summary>

    public ListViewItem Item

    {

        get { return lvi; }

        set { lvi = value; }

    }

 

    #endregion

 

    // ******************************************************************

    // Constructors.

    // ******************************************************************

 

    #region Constructors

 

    /// <summary>

    /// Creates a new instance of the <see cref="CreateVirtualListItemEventArgs"/>

    /// class.

    /// </summary>

    /// <param name="index">The index of the item.</param>

    public CreateVirtualListItemEventArgs(int index)

    {

        this.index = index;

    }

 

    #endregion

 

}

 

Using this virtual ListView control is pretty easy, here is a quick example: Start by creating a form in Visual Studio, then drop my VirtualListView control on the form in the designer. It should look like this:

After that, wire up a handler for the CreateVirtualListItem event and set the size of the list in the form's OnLoad handler. Your code should look like this:

public partial class Form1 : Form

{

    public Form1()

    {

        InitializeComponent();

    }

 

    private void Form1_Load(object sender, EventArgs e)

    {

        listView1.VirtualListSize = 10000;

    }

 

    private void listView1_CreateVirtualListItem(

        object sender,

        CreateVirtualListItemEventArgs e

        )

    {

        e.Item = new ListViewItem("virtual item " + e.Index);

    }

}

Then run your code. The UI should look like this:

 

Pretty cool huh? (And easy). Of course, you can always intercept the ListView's virtual events and do the caching yourself, but this class makes that effort unnecessary. 

Have fun!

Comments

 

Matt G said:

I am very intersted in using this piece of code in a project I am working on.  I have attempted to add the code to a Class Library, Windows Control Library, and user control but haven't had any luck. I keep getting [The name 'InitializeComponent' does not exist in the current context.] as a complier error.  Any help with this would be greatly appriciated.

April 15, 2008 4:10 PM
 

Martin said:

Hello Matt,

There are a couple of easy ways to fix that problem but I wont go into either one since it's just easier to give you the actual source files. I added the code to my CG.Windows library, and placed the project into the download section under the "public | projects" folder.

I hope that helps. Thanks for reading CODEGATOR.

April 16, 2008 7:43 AM

Leave a Comment

(required) 
(optional)
(required) 
Submit

About Martin

I work as a software engineer specializing in designing and building object-oriented business solutions for Windows platforms using C#. I have been programming professionally for roughly 20 years.

This Blog

Syndication

Terms of Service | Privacy Statement