Persisting WPF control state

I would like to be able to easily save/persist the state of an element in WPF. In my current project we have a “desktop” with gadgets that you can move around; you have the possibility to change width and position on ListView columns. But the problem is that this is only stored in memory as the application is running. I would like to be able to easily write in xaml what properties and elements that should have its state persisted.

The first thing that came to my mind was Attached Properties. I just love attached properties and what you can achieve with them. I found a blog post by Tomer Shamam where he describes his implementation of exactly my problem. I downloaded the sample application he had, plugged the implementation into my project and viola, I can now easily Persist property values when closing and restarting my application. I did however change his implementation a bit to fully satisfy my needs, and also how the states are persisted. We need to save them in our database since our WPF application is running through Citrix and the users are using our application at several machines/terminals and therefore we need to save the user profile settings in a central repository.

Check out this blog if you want to know how to persist WPF control state.

http://blogs.microsoft.co.il/blogs/tomershamam/archive/2007/06/08/wpf-control-state-persistency.aspx

But in his solution you cannot save the order of columns in a ListView. So how should I do that?

Persisting WPF ListView Column Order

Let’s start by investigating what is happening when you move a column in a ListView. You can add an event handler to the GridView.Columns.CollectionChanged  event and from there get indication of what is happening.


((GridView)listView1.View).Columns.CollectionChanged += Columns_CollectionChanged;
void Columns_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
    if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Move)   
    { … }
}

From NotifyCollectionChangedEventArgs you get the old index and new index for the column that you are moving.  We can then track all these changes to be able to later know exactly what moves that has been made on the columns collection and what order the columns has now.

The next problem is how do we easily move columns in a ListView? Fortunately there is a Move method on the columns collection


((GridView)listView1.View).Columns.Move(oldIndex, newIndex);

This method will move the specified item at the old index to a new index in the collection. So if we have done 10 column moves in the ListView and tracked all these in memory, we can then later reproduce the exact same moves using the Move method, this is what I would call a quick solution since we are probably doing a lot of moves that are unnecessary.

Instead let’s compare the original positions with the end result and from that calculate what minimal moves are needed to be able to achieve the same end result.

The implementation I ended up with removed and inserted the columns at their correct position during loading of the controls. But to be able to easily save the column order for each user I created a custom control that inherits ListView and provided a dependency property that is always up to date with the current column order as a string. For example, “3,0,1,2,4,5”, this means that column 3 in the original ListView is moved to be the first column in the end result.

I have a local field that holds the current column order


private List currentColumnOrder;

Event handler that updates the currentColumnOrder field to reflect moves made by the user

void Columns_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
    if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Move)
    {
        var item = currentColumnOrder [e.OldStartingIndex];
        currentColumnOrder.RemoveAt(e.OldStartingIndex);
        currentColumnOrder.Insert(e.NewStartingIndex, item);
    }
}

Save

var columnOrder = string.Join(",",currentColumnOrder.Select(o => o.ToString()).ToArray());
// Set the ColumnOrder property to hold the current column order so that we can later persist it
// using ElementState Attached Properties
ColumnOrder = columnOrder;

Load

var indices = ColumnOrder.Split(',');
var tempColumns = new List();
foreach (var col in ((GridView)this.View).Columns)
{
    tempColumns.Add(col);
}
// Move columns to their proper position
for (int i = 0; i < indices.Length - 1; i++)
{
    var index = int.Parse(indices[i]);
    var col = tempColumns[index];
    // Only move column if it not in default position
    if (view.Columns[i] != col)
    {
        view.Columns.Remove(col);
        view.Columns.Insert(i, col);
    }
}

Xaml

<ctrls:PersistingListView

x:Name="SearchResultListView"

ItemsSource="{Binding SearchResult}"

SelectedItem="{Binding SelectedItem}"

ElementState.Mode="Persist"

ElementState.UId="ArticleSearchResultListView"

ColumnOrder="{PropertyState Default=''}">

<ListView.View>

<GridView

ElementState.Mode="PersistOrder"

ElementState.UId="ArticleSearchResultGridView">

<!--RowNumber-->

<GridViewColumn

Width="{PropertyState Default=40}"

ElementState.Mode="Persist"

ElementState.UId="ArticleSearchResultRowNumberColumn">

. . .

I cannot give you the complete code, but send me an email if you want more detailed source code.

Advertisements
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: