Wednesday, December 10, 2008

Storing Silverlight DataGrid settings

Don't you find it annoying whenever you use an application with some kind of datagrid, that you can resize en reorder your columns in and the next time you use your application, the changes you made are lost? I know I hate it and I know my users are annoyed by it. This made me decide to find a way to store this information for my users and restore the settings whenever the user comes back.

As a start point I used the example code from an earlier article you can find here. I'm keen to store at least the width of each column and the order of the columns within the grid. To start this off I build a custom class to hold this information:

public class ColumnSetting
{
public int DisplayIndex { get; set; }
public int Index { get; set; }
public double Width { get; set; }

public ColumnSetting()
{
}

public ColumnSetting(int displayIndex, int index, double width)
{
DisplayIndex = displayIndex;
Index = index;
Width = width;
}
}


The next step is to have a collection of these and fill them. This begs the question, when? The moment of storing your information is key in this process. Obviously this is very dependent on your requirements. To keep it simple I used only the ColumnReorderd event on the grid. Later on I might expand this to more appropriate events.

Next is the building of code that creates a collection of the ColumnSetting class I've created, so the information is stored. Here is the first part of the method that takes care of saving the settings:

private void SaveGridLayout()
{
List<ColumnSetting> settings = new List<ColumnSetting>();
for (int index = 0; index < CarGrid.Columns.Count; index++)
{
DataGridColumn column = CarGrid.Columns[index];
ColumnSetting setting = new ColumnSetting(column.DisplayIndex, index, column.ActualWidth);
settings.Add(setting);
}


As you can see I use a generic list of ColumnSetting objects to collect the information. The columns DisplayIndex gives me the relative location in the grid, which is separate from the location in the Columns collection. The ActualWidth obviously gives me the width of the column.

Next question is, where should we store this information? Again this really depends on your requirements. Do users log on to your system and do you want to have these settings based on the user? Or do you want these settings based on the client as well? In this case I decided to go with the last, because this would allow me to play with Isolated Storage, which I haven't used before.
Some info on how this will work. Every user has his or her own Isolated Storage on the client PC, so depending on what user account is used to log on to the PC and depending on which PC there are settings. If users moves from one PC to another they will not have there settings available.

So how do we store the info then? Here is the code:


IsolatedStorageSettings appSettings = IsolatedStorageSettings.ApplicationSettings;

if (!appSettings.Contains("carGrid"))
{
appSettings.Add("carGrid", null);
}
appSettings["carGrid"] = settings;

As you can see, my first action is to get a shorter reference to the ApplicationSettings object. Each Silverlight 2 application has one ApplicationSettings object. Based on domain there can also be shared settings in the SiteSettings object, but in this case ApplicationSettings is the obvious choice.
As someone might very well be working with your application on this client for the very first time, we need to first create a settings entry in the appSettings collection, but only if it doesn't already exist. Next we can simply set our settings into the entry that was created earlier.

So now our settings are stored and we want to load these settings into the grid at load time. The first thing I tried was to do this in the Loaded event of the user control... and it failed. This is because you can not set the DisplayIndex as a result of a change to the DisplayIndex, which happens when you use the loaded event. As is documented by Microsoft this will result in an InvalidOperationException.
I decided that a good event would be the datagrids LayoutUpdated event. To prevent the settings being loaded every time this event is triggered, I simply keep track of this with a boolean field.

The loading of the actual settings is done like this:


private void LoadGridSettings()
{
IsolatedStorageSettings appSettings = IsolatedStorageSettings.ApplicationSettings;
if (appSettings.Contains("carGrid"))
{
List<ColumnSetting> settings = appSettings["carGrid"] as List<ColumnSetting>;
if (settings != null)
{
foreach (ColumnSetting setting in settings)
{
DataGridColumn column = CarGrid.Columns[setting.Index];
column.DisplayIndex = setting.DisplayIndex;
column.Width = new DataGridLength(setting.Width);
}
}
}
}

As you can see, if any settings are available then they are loaded in the datagrid. Note that you obviously can't set the ActualWidth property (as it is read only), so you simply set the Width property.

I hope this article was useful to you. Please leave me a comment to tell my what you think about it, ask me any questions, or suggest any topics and I will be grateful.

8 comments:

  1. How can we save ColumnWidth Changes ?

    ReplyDelete
  2. Hi Sandeep,
    I'm not sure what you mean by that. If you mean you want to save only the difference of the width between when the grid was loaded and whatever changes the user made to it, then you could calculate the changes as you save them.

    If you mean you want to save the new column widths right after they change, you could try to bind to each of the ActualWidth properties of the columns as they are loaded. Make it set a value in some class, so you can catch the updated value and save it. If you need more help on this, please let us know.

    Greets,
    Jonathan

    ReplyDelete
    Replies
    1. Hi Jonathan can you elaborate on this please. I am looking for a similar solution.

      Delete
    2. @Goks: sure, what is it you want to do? I've described two possible aproaches.

      Delete
  3. Your code produces some unexpected results. You need to sort the List by DisplayIndex before you load the settings e.g:

    foreach (ColumnSetting setting in settings.OrderBy(i=>i.DisplayIndex))
    {
    var column = allRequestsDataGrid.Columns[setting.Index];
    column.DisplayIndex = setting.DisplayIndex;
    column.Width = new DataGridLength(setting.Width);
    }

    ReplyDelete
  4. Thanks, Myles. That's a very useful addition.

    Greets,
    Jonathan

    ReplyDelete
  5. Do you have any suggestion how to get a event when the user changes the Width on a column.
    //lg

    ReplyDelete
  6. Hi lg,

    Thanks for your question. I looked into that. Unfortunately you can't get an event for that by default.
    You may have some luck with deriving from either the DataGrid class or the DataGridColumn class. That last one seems like a problematic approach as you would end up rendering the standard derived classes from DataGridColumn useless for your application.
    Another approach may be to look into using a third party data grid (there is a free one from DevExpress), but I'm not sure if any of them supports this feature.

    Greets,
    Jonathan

    ReplyDelete