Persist ListView Settings with Serialization


Introduction

There are several limitations of the ListView control that comes with .NET. First, there is no way to query the order of the columns if you allow the user to reorder them ( set AllowColumnReorder=true ). Second, there are no events generated when the user moves a column or changes the width of a column. Somebody else might take the time to write a replacement for this control; I needed something quicker.

In this article, I'll show you how to persist the column order and width settings by using Serialization, binary serialization to be more specific. And you won't believe how easy it is.

You can use the demo source from this article to follow along. If you're using your own code, make sure the ListView control is in details mode (set View=Details) or you'll be confused all the way through. Also make sure you have AllowColumnReorder set to true.

The class ListViewSettings is where all the work is done and there is some funky code going on here so it's nice to encapsulate that in one place. Unfortunately, the ListView control provides no means of querying the column order once the user has reordered them. The columns are always returned in the order they were created. To get the column order, we have to send a message to the control and ask for them. And to set the order, we have to send another message.

bool ret = SendMessage(listView.Handle, LVM_GETCOLUMN, column.Index, ref pcol);
bool ret = SendMessage(listView.Handle, LVM_SETCOLUMN, column.Index, ref pcol);

We're also interested in saving and restoring the column widths so we set up a class that contains all the necessary items:

[Serializable]
public class ListViewColumn
{
public string header;
public int width;
public int order;
public ListViewColumn( string colHeader, int colWidth, int colOrder )
{
header = colHeader;
width = colWidth;
order = colOrder;
}
}

And we mark this class as Serializable with an attribute. That tells the compiler that we are interested in saving this object. You'll need to use the Serializable attribute on every class you plan on saving, even if objects from that class will only ever be created within another class marked as Serializable. In other words, the Serializable attribute is not inherited. So you'll notice that the class that contains all the ListViewColumns is also marked Serializable:

[Serializable]
public class ListViewSettings
{...}

When you serialize objects, you do have some say in how the objects get saved. And sometimes it is necessary to tell the compiler to save objects in a certain way. In this case, there is an ArrayList that contains all the ListViewColumn objects. We want those to be saved as elements so we can control that through additional attibute tags:

[XmlElement("ListViewColumns", typeof(ListViewColumn))]
public ArrayList listViewCols = new ArrayList();

Now for the fun and easy part

Since we set up our object to be serializable, with just a few lines of code we can save it in a binary format:

ListViewSettings listViewSettings = new ListViewSettings( listView );
try
{
IFormatter formatter =
new BinaryFormatter();
Stream stream =
new FileStream("ListViewSettings.config", FileMode.Create,FileAccess.Write, FileShare.None);
formatter.Serialize(stream, listViewSettings);
stream.Close();
}
catch {}

And reading it back in to reconstitute our objects is just as easy:

ListViewSettings listViewSettings = null;
try
{
IFormatter formatter =
new BinaryFormatter();
Stream stream =
new FileStream("ListViewSettings.config", FileMode.Open,FileAccess.Read, FileShare.Read);
listViewSettings = (ListViewSettings) formatter.Deserialize(stream);
stream.Close();
listViewSettings.RestoreFormat( listView );
}
catch {}

Now there is still another limitation of the ListView control...it doesn't generate an event when the user drags or resizes its columns. In the demo application, you control the saving and restoring by clicking buttons. In your own applications, you'll need to place calls to SaveColumnData() and RestoreColumnData() in appropriate places in your code. The form's closing event is one good place for saving the data:

private void MainForm_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
try
{
if( listView.View == View.Details )
SaveColumnData();
}
catch {}
}

Don't forget to set the connection string in the form_load event to point to a database on your network that has the Northwind database installed.

Serialization can be used for a lot of things, creating persistent objects is just one. But in this case, serialization is the perfect solution for saving and restoring columns settings in a ListView. I hope you can make good use of this code in your own projects.