How to Save the State of C1DataGrid

The ComponentOne DataGrid for Silverlight and WPF has a rich collection of methods that allow you to easily track every movement the user makes. We can take advantage of this tracking to listen for layout changes made to the grid and then persist the layout between runs of the application. We’ve included a sample on how to save the state of C1DataGrid in Silverlight (C1DataGrid_SaveState). Today a user pointed out that we didn’t have the same sample in WPF. So I thought I would port the sample over and provide it to the community. The fact that our Silverlight and WPF controls share the same codebase can be a huge time saver in many cases. And this is one of them. The ONLY thing that is different between Silverlight and WPF for this sample is where we save the layout settings. In Silverlight we have the Isolated Storage as our best option. In WPF, however, we have many more options including application settings and user settings. You can read more about these settings in Microsoft’s documentation.

Settings in a Nutshell

I was able to create a setting and write against it in just a couple of minutes. First, you open your application’s properties page. On the Settings tab add a new entry and set the Scope. The scope determines if this is a user setting or application setting. The basic difference between the two is that user settings can be set by the user (at runtime), whereas application settings are typically set once per application (although there are ways to change application settings). Then in my code I can write to this setting very easily through the Properties.Settings.Default namespace. The data type of the setting is also determined on the Settings tab when it’s created.


Properties.Settings.Default.DataGridSettings = sw.ToString();  

And then the final and most important step (which I’ll be honest, I tend to forget everytime) you must call the Save method when you’re ready to persist the setting.


Properties.Settings.Default.Save();  

Next, I will explain the code necessary to save the state of C1DataGrid for WPF to a user setting. If you’re interested in Silverlight, you can download the same sample from the link above.

C1DataGrid Save State

To save the state of C1DataGrid we will update a user setting each time the user makes a change to the state at run-time. This includes changing the sort, group or filter state of any column, reordering columns, as well as freezing columns. C1DataGrid provides a rich collection of methods which we can listen to and catch the user making any of these changes.


c1DataGrid1.ColumnReordered += new EventHandler<C1.WPF.DataGrid.DataGridColumnEventArgs>(c1DataGrid1_ColumnReordered);  
c1DataGrid1.SortChanged += new EventHandler<DataGridSortChangedEventArgs>(c1DataGrid1_SortChanged);  
c1DataGrid1.GroupChanged += new EventHandler<DataGridGroupChangedEventArgs>(c1DataGrid1_GroupChanged);  
c1DataGrid1.FilterChanged += new EventHandler<DataGridFilterChangedEventArgs>(c1DataGrid1_FilterChanged);  
c1DataGrid1.FrozenColumnCountChanged += new EventHandler<C1.WPF.PropertyChangedEventArgs<int>>(c1DataGrid1_FrozenColumnCountChanged);  

Inside each of these events we will make a call to the SaveDataGridSettings method. This method will be responsible for gathering the current state of the grid and serializing itself to the user settings. Because it’s quite possible for multiple actions to occur in rapid succession, it is wise to use a timer and bool variable to prevent saving collisions.


bool doneLoading;  
private DispatcherTimer _saveSettingTimer;  

Then we initialize the timer.


doneLoading = true;  
_saveSettingTimer = new DispatcherTimer();  
_saveSettingTimer.Interval = TimeSpan.FromSeconds(0.5);  
_saveSettingTimer.Tick += SaveDataGridSettings;  

The timer will tick one time and call the SaveDataGridSettings class, where it will immediately stop (so the event only gets called once per start of the timer).


void SaveDataGridSettings(object sender, EventArgs e)  
{  
    _saveSettingTimer.Stop();  
    DataGridSettings settings = new DataGridSettings();  

    // get state from grid  
    settings.FrozenColumnsCount = c1DataGrid1.FrozenColumnCount;  
    foreach (var column in c1DataGrid1.Columns)  
    {  
        UserColumnSettings columnSetting = new UserColumnSettings();  
        columnSetting.ColumnName = column.Name;  
        columnSetting.DisplayIndex = column.DisplayIndex;  
        columnSetting.Sort = new UserColumnSettings.SortGroupState() { Direction = column.SortState.Direction, Index = column.SortState.Index };  
        columnSetting.Group = new UserColumnSettings.SortGroupState() { Direction = column.GroupState.Direction, Index = column.GroupState.Index };  
        columnSetting.Filter = column.FilterState;  
        columnSetting.Width = column.Width.IsAbsolute ? column.Width.Value : -1;  

        settings.ColumnSettings.Add(columnSetting);  
    }  

    // serialize DataGridSettings class to XML  
    XmlSerializer ser = CreateSerializer();  
    StringWriter sw = new StringWriter();  
    ser.Serialize(sw, settings);  

    // save to user settings  
    Properties.Settings.Default.DataGridSettings = sw.ToString();  

    // save all settings  
    Properties.Settings.Default.Save();  
}  

The SaveDataGridSettings method cycles through the grid columns and saves the group, filter and sort state into the DataGridSettings class. Finally, it serializes it to the user settings and saves the settings. Here is the static CreateSerializer referenced within the code above.


private static XmlSerializer CreateSerializer()  
{  
    // DataGridComboBoxFilter needs List<object>  
    return new XmlSerializer(typeof(DataGridSettings), new[] { typeof(List<object>) });  
}  

A separate SaveDataGridSettingsAsync method is also used to kick off the timer and ensure that there are no saving collisions.


private void SaveDataGridSettingsAsync()  
{  
    if (!doneLoading)  
        return;  
    _saveSettingTimer.Start();  
}  

We will then call the SaveDataGridSettingsAsync method inside each of the C1DataGrid event handlers attached above. For example:


void c1DataGrid1_FilterChanged(object sender, DataGridFilterChangedEventArgs e)  
{  
    SaveDataGridSettingsAsync();  
}  

The public DataGridSettings class is not built-into the C1DataGrid library. Add it to your solution from below.


public class UserColumnSettings  
{  
    public class SortGroupState  
    {  
        public DataGridSortDirection Direction { get; set; }  
        public int Index { get; set; }  
    }  
    public string ColumnName { get; set; }  
    public int DisplayIndex { get; set; }  
    public SortGroupState Sort { get; set; }  
    public SortGroupState Group { get; set; }  
    public DataGridFilterState Filter { get; set; }  
    public double Width { get; set; }  
}  

public class DataGridSettings  
{  
    public DataGridSettings()  
    {  
        ColumnSettings = new List<UserColumnSettings>();  
    }  
    public List<UserColumnSettings> ColumnSettings { get; set; }  
    public int FrozenColumnsCount { get; set; }  
}  

So far we have written all of the code that saves the grid state. Next we will look at the code required to load the saved settings. This might vary from implementation to implementation. In this sample we will begin the loading process from the C1DataGrid’s AutoGeneratedcolumns event. This fires after the columns have been auto-generated (if we have AutoGenerateColumns=True). If you’re grid is not auto-generated then you can run this code from some other on loaded event.


c1DataGrid1.AutoGeneratedColumns += new EventHandler(c1DataGrid1_AutoGeneratedColumns);  

In this event handler we will first load the user settings, and then cycle through the grid columns to apply the settings. It’s very handy that you can programmatically perform the same operations for C1DataGrid that the user did previously at run-time!


void c1DataGrid1_AutoGeneratedColumns(object sender, EventArgs e)  
{  
    if (HasDataGridSettings())  
    {  
        var dataGridSettings = GetDataGridSettings();  
        c1DataGrid1.FrozenColumnCount = dataGridSettings.FrozenColumnsCount;  
        var sortDict = new Dictionary<C1.WPF.DataGrid.DataGridColumn, UserColumnSettings.SortGroupState>();  
        var groupDict = new Dictionary<C1.WPF.DataGrid.DataGridColumn, UserColumnSettings.SortGroupState>();  
        var filterList = new List<KeyValuePair<C1.WPF.DataGrid.DataGridColumn, DataGridFilterState>>();  
        foreach (var columnSetting in dataGridSettings.ColumnSettings)  
        {  
            var column = c1DataGrid1.Columns[columnSetting.ColumnName];  
            if (column != null)  
            {  
                column.DisplayIndex = columnSetting.DisplayIndex;  
                sortDict[column] = columnSetting.Sort;  
                groupDict[column] = columnSetting.Group;  
                filterList.Add(new KeyValuePair<C1.WPF.DataGrid.DataGridColumn, C1.WPF.DataGrid.DataGridFilterState>(column, columnSetting.Filter));  
                if (columnSetting.Width > 0)  
                {  
                    column.Width = new C1.WPF.DataGrid.DataGridLength(columnSetting.Width);  
                }  
            }  
        }  
        if (filterList.Count > 0)  
        {  
            c1DataGrid1.FilterBy(filterList.ToArray());  
        }  
        if (sortDict.Count > 0)  
        {  
            var sortparam = sortDict.OrderBy(pair => pair.Value.Index).Select(pair => new KeyValuePair<C1.WPF.DataGrid.DataGridColumn, DataGridSortDirection>(pair.Key, pair.Value.Direction)).ToArray();  
            c1DataGrid1.SortBy(sortDict.OrderBy(pair => pair.Value.Index).Select(pair => new KeyValuePair<C1.WPF.DataGrid.DataGridColumn, DataGridSortDirection>(pair.Key, pair.Value.Direction)).ToArray());  
        }  
        if (groupDict.Count > 0)  
        {  
            var groupparam = groupDict.OrderBy(pair => pair.Value.Index).Select(pair => new KeyValuePair<C1.WPF.DataGrid.DataGridColumn, DataGridSortDirection>(pair.Key, pair.Value.Direction)).ToArray();  
            c1DataGrid1.GroupBy(groupDict.OrderBy(pair => pair.Value.Index).Select(pair => new KeyValuePair<C1.WPF.DataGrid.DataGridColumn, DataGridSortDirection>(pair.Key, pair.Value.Direction)).ToArray());  
        }  
    }  
    else  
    {  
        SaveDataGridSettingsAsync();  
    }  
    foreach (var column in c1DataGrid1.Columns)  
    {  
        column.WidthChanged += new EventHandler<C1.WPF.PropertyChangedEventArgs<C1.WPF.DataGrid.DataGridLength>>(column_WidthChanged);  
    }  
}  

void column_WidthChanged(object sender, C1.WPF.PropertyChangedEventArgs<C1.WPF.DataGrid.DataGridLength> e)  
{  
    SaveDataGridSettingsAsync();  
}  

private bool HasDataGridSettings()  
{  
    return !string.IsNullOrEmpty(Properties.Settings.Default.DataGridSettings);  
}  

In the GetDataGridSettings method we retrieve the user settings and deserialize the XML string into our custom DataGridSettings type.


private DataGridSettings GetDataGridSettings()  
{  
    StringReader sr = new StringReader(Properties.Settings.Default.DataGridSettings);  
    try  
    {  
        XmlSerializer ser = CreateSerializer();  
        return (DataGridSettings)ser.Deserialize(sr);  
    }  
    catch  
    {  

    }  

    return new DataGridSettings();  
}  

When you run the sample and change the grid state, the changes will be persisted the next time you run the application – even when debugging. Download Sample (WPF 4.0) One last tip – if you want to clear the settings you have created during testing you can do so by clicking the Synchronize button from the Properties.Settings tab.

Greg Lutz

comments powered by Disqus