WPF Control State Persistency

June 8, 2007

33 comments

One of my customers asked me if WPF provides an option for storing or serializing controls state. For example, having a ListView, is it possible to store the width of its columns after closing and opening the Window, or maybe after restarting the application?

I was thinking to myself, sure, you should use Data Binding. All you have to do is to bind the width or height, of any element to a back storage. For example you can create a State class for storing the data, and then you should bind it to your properties, using the Binding markup extension.

Thinking twice, I realized that it is much more complicated than it looks. Data Binding is a great tool but it should be customized to support this feature.

So the answer was no! but…

Then I developed a smart Markup Extension, backed up with Attached Properties and a smart Back Storage to provide an easy way to save controls properties state.

For example, the code snippet bellow stores the Window Size and Position, and the ListView, GridViewColumn’s width.

 

<Window x:Class="Tomers.WPF.State.Demo.DemoWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:data="clr-namespace:Tomers.WPF.State.Demo.Data"
 
    ElementState.Mode="Persist"
    ElementState.UId="DemoWindow"
    Height="{ElementState Default=300}"
    Width="{ElementState Default=300}"
    Left="{ElementState Default=100}"
    Top="{ElementState Default=100}"
 
    Title="Test">
 
    <Window.Resources>
        <ObjectDataProvider x:Key="cConsultants" ObjectType="{x:Type data:Consultants}" />
    </Window.Resources>
 
    <StackPanel>
      <ListView ItemsSource="{Binding Source={StaticResource cConsultants}}">
        <ListView.View>
            <GridView ColumnHeaderToolTip="Sela Consultants">
               <GridViewColumn
                  ElementState.Mode="Persist"
                  ElementState.UId="DemoWindow_GridViewColumn1"
                  DisplayMemberBinding="{Binding Path=FirstName}"
                  Header="First Name"
                  Width="{ElementState Default=100}" />
               <GridViewColumn
                  ElementState.Mode="Persist"
                  ElementState.UId="DemoWindow_GridViewColumn2"
                  DisplayMemberBinding="{Binding Path=LastName}"
                  Header="Last Name"
                  Width="{ElementState Default=100}" />
               <GridViewColumn
                  ElementState.Mode="Persist"
                  ElementState.UId="DemoWindow_GridViewColumn3"
                  DisplayMemberBinding="{Binding Path=Blog}"
                  Header="Blog"
                  Width="{ElementState Default=Auto}" />
            </GridView>
        </ListView.View>
      </ListView>
    </StackPanel>
</Window>

 

As you can see, I’m using my ElementState.Mode and ElementState.UId attached properties to tell the back storage to save the state for these element’s dependency properties. Then I’m using my ElementState Markup Extension to set each property and its default value.

The ElementState.Mode attached property can be one of: Persist or Memory values.

  • Persist is used to serialize the element state into an XML stream. Restarting the application will restore this state.
  • Memory is used to hold the state only in memory. Restarting the application will not restore this state.

The ElementState.UId attached property is used to uniquely identify the element (this must be a unique name among all elements of the application).

To Load state and Save state you should call ElementStateOperations.Load and ElementStateOperations.Save respectively. For example:

 

public partial class App : System.Windows.Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        using (Stream stream = File.Open("ElementStateDemo.xml",
           FileMode.OpenOrCreate, FileAccess.Read, FileShare.Read))
        {
            ElementStateOperations.Load(stream);
        }
        base.OnStartup(e);
    }
 
    protected override void OnExit(ExitEventArgs e)
    {
        using (Stream stream = File.Open("ElementStateDemo.xml",
           FileMode.Create, FileAccess.Write, FileShare.None))
        {
            ElementStateOperations.Save(stream);
        }
        base.OnExit(e);
    }
}

 

You can download ver 1.0 from here.

It is a first prototype, it took me several hours to write it down, so use it on your own risk.

You are free to write comments for any suggestion, bug or other.

 

Update on 12-Aug-2008

Since I have two classes ElementState and ElementStateExtension, when using ElementState from XAML it maps to the first class which is not a markup extension. I changed the ElementStateExtension to PropertyStateExtension.

You can download ver 1.1 from here.

 

Update on 16-Aug-2008

Thanks to Brandon comment, a weird bug was found after installing .NET 3.5 SP1. Somehow the ValueConverter.ConvertBack stop working after a success binding. I tried to figure out what happened in SP1 and got this:

Creating a property path with the “.” syntax: new ProeprtyPath(“.”) which means that the path is the source object itself works up until .NET 3.5 SP1. I think that changes for supporting formatting arguments in Data Binding are responsible for this.

 

Anyway, I changed the property path as follows: binding.Path = new PropertyPath(string.Format(“[{0}]“, uid));

Where string.Format(“[{0}]“, uid) means: Get the value from the source indexer using uid as the index.

Also I made a little refactoring to the code, especially in the markup extension and data binding. Now it is two-way.

You can download ver 1.2 from here.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

33 comments

  1. liorzJune 20, 2007 ב 12:41

    Hi,
    We are planning an event for bloggers and would like to send you an invitation.
    Can you please contact me with your email address at http://blogs.microsoft.co.il/blogs/liorz/contact.aspx
    Thanks,
    Lior Zoref
    Microsoft Israel

    Reply
  2. One ProblemJune 29, 2007 ב 07:37

    Hi,Tomer

    I got a puzzle about attach property after reading your article.
    About the ElementState.Mode and ElementState.UId why can be used in the xaml. I couldn’t find any namespace declaration, any idea ?
    Thanks!!!

    Reply
  3. senophenJune 29, 2007 ב 08:00

    Ah, I find the reason:

    // Maps Tomers.WPF.State namespace to WPF root namespace
    [assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "Tomers.WPF.State")]

    :(

    Reply
  4. Tomer ShamamJune 29, 2007 ב 12:25

    Hi,

    I’m glad that you found it :-)

    If you have any problem, just leave a message.

    Reply
  5. HorstAugust 28, 2007 ב 05:56

    Hi Tomer

    Your idea is great.
    I our project we use MS Expression Blend for GUI Design.
    Blend can’t handle you class:

    Two Errors are shown:
    1. The member “Default” is not recgnizing or is not accessible.
    2. The specified value cannot be assigned. The following type was expecting “Double”.

    What is to do that Blend can work with it?

    Best regards
    Horst

    Reply
  6. Tomer ShamamAugust 30, 2007 ב 21:34

    Hi Horst,
    I’m very happy that you find my solution useful.
    Unfortunately, Blend is not working well with custom markup-extensions.
    I suggest that you use my solution only after you finishing working on the UI with Blend.

    Reply
  7. Richard BothneMay 17, 2008 ב 10:00

    Hi Tomer,

    Thanks so much for this code/idea. I noticed on CodeProject that you have fixed a bug in VS 2008 designer support. Could you send me a link and or the updated zip?

    Thanks in advance,

    Richard Bothne
    Richard_AT_Bothne_DOT_COM

    Reply
  8. Tomer ShamamMay 19, 2008 ב 13:30

    Hi Richard,
    Please send me a private message with your email address, so I can send you the fixed version.

    Regards.

    Reply
  9. Sandeep (India)August 11, 2008 ב 12:54

    This is great!

    Can you help me for this..
    Error 307 Type ‘ElementState’ is used like a markup extension but does not derive from MarkupExtension. C:\Inventoty\frmStockTransfer.xaml 52 214 MyInventory

    Error 308 The property ‘Default’ was not found in type ‘ElementState’. C:\Inventoty\frmStockTransfer.xaml 52 227 MyInventory

    Regards,

    Reply
  10. Tomer ShamamAugust 11, 2008 ב 15:25

    Hi Sandeep,

    I’ll be glad to help, please send me a snippet of the XAML that throws this exception.

    Reply
  11. Sandeep (India)August 12, 2008 ב 03:45

    Hi Tomer,

    Thanks for your response.

    Following is my XAML:

    =====================

    <hw:ToolWindow x:Class=”HSDiamTrade.frmStockTransfer”
       xmlns=”schemas.microsoft.com/…/presentation”
       xmlns:x=”schemas.microsoft.com/…/xaml”
       xmlns:hw=”clr-namespace:Hotsoft.Windows”
       …>

          …

                   <Expander …
                      ElementState.Mode=”Persist”
                      ElementState.UId=”StockExpander”
                      IsExpanded=”{ElementState Default=True}”>

                       …

                   </Expander>

           …

    </hw:ToolWindow>

    Reply
  12. Tomer ShamamAugust 12, 2008 ב 06:38

    Hi Sandeep,

    I figured out what the problem is. Since I have two classes ElementState and ElementStateExtension, when using ElementState from XAML it maps to the first class which is not a markup extension. I have fixed this issue. You can download the fixed version from the bottom of the post.

    Cheers.

    Reply
  13. Sandeep (India)August 12, 2008 ב 08:05

    Hi Tomer,

    Thanks for you extended support.

    I downloaded fixed version, I tried to open your demo app. DemoWindow.xaml – I cannot see UI (I can see message on form: Problem Loading, The document contains errors that must be fixed before the designer can be loaded. Reload the designer after you have fixed errors.)

    When I Rebuild the app. I get following error after build is completed.

    Error 1 Object reference not set to an instance of an object. C:\WPFElementState_v1_1\ElementStateDemo\DemoWindow.xaml 8 5 ElementStateDemo

    Pl. advise.

    Regards,

    Reply
  14. Sandeep (India)August 12, 2008 ב 08:07

    In extended to previous request, I was not able to see UI when tried with previous version also.

    Regards,

    Reply
  15. Tomer ShamamAugust 12, 2008 ב 09:01

    Hi Sandeep,

    I have fixed the last bug you have reported.

    Download v1.1 again.

    Reply
  16. Sandeep (India)August 12, 2008 ב 10:22

    Hi Tomer!

    It works fine!

    Will this work with 3rd party controls (for e.g. i am using Xceed datagrid for wpf). I will test it..

    Thanks for all your help..

    Regards,

    Reply
  17. Tomer ShamamAugust 12, 2008 ב 10:30

    I believe that it will work just fine with 3rd party controls.

    Regards

    Reply
  18. BrandonAugust 15, 2008 ב 17:01

    I’ve been using this code with much success for a while now. But I’ve noticed a problem after updating to .NET 3.5 SP1. When running the demo, all of the values in the XML file are getting set to “Auto” regardless of how you resize the form.

    Digging into the code, it seems that the binding is no longer passing data from the target control through the ValueConverter as expected. If a breakpoint is set in the ConvertBack method on the ValueConverter, you can watch a NaN value get passed through once per property as the window is initialized, but after the app is running, the breakpoint never gets hit, and thus never updates the ElementState.

    I’ve experimented with a few things trying to fix the problem, but with no luck. I was wondering if you or anyone else might have some insight into what changed in SP1 to cause this? Any advice is much appreciated. Thanks!

    Reply
  19. Tomer ShamamAugust 16, 2008 ב 08:34

    Hi Brandon,

    I have fixed the “due to breaking changes” bug. See details in the bottom of this post.

    Thanks

    Reply
  20. BrandonAugust 18, 2008 ב 12:07

    Hi Tomer,

    Thanks for the update, and the insight into what went wrong. I had suspicions that it was related to the data binding changes, but wasn’t able to track down anything that pointed me to the answer. I’ve tried the updated code and everything works once again. Thanks again!

    Reply
  21. Ido RanOctober 5, 2008 ב 09:00

    Hi, I’m looking for way to persist the control state in my WPF application so that the application will look next run just as the user left it last run.
    I’ve found your blog post about the subject and I have some question:

    1. When binding a ListView column’s Width to persist ency you will never get the user’s width because this property is the ActualWidth. How do you solve it?
    2. I have some controls which have specific strategy for persisting all the control state. For example ListView with column order and width, also Xceed DataGridControl. Is it possible to implement a strategy class and to bind the root control element to the persist strategy?

    Thank you,Ido

    Reply
  22. Tomer ShamamOctober 5, 2008 ב 11:15

    Hi Ido,

    1. As you can see in the code snippet bellow, I’m using GridViewColumn.Width property, not ActualWidth. It is working just fine.

    ElementState.UId="DemoWindow_GridViewColumn1"
    DisplayMemberBinding="{Binding Path=FirstName}"
    Header="First Name"
    Width="{ElementState Default=100}">

    2. I didn’t designed Element State as an infrastructure or control, but feel free to make any changes. You should start by looking at the Persistency class. It uses simple XML to load and store data. Just turn it into an interface/abstract or change it to support strategies.

    Best Regards,
    Tomer

    Reply
  23. Sean INovember 13, 2008 ב 12:00

    Hi Tomer,

    Great work on the State Persistence, it’s a big help! I’m currently using it with the WPF toolkit on codeplex, more specifically the datagrid in it (http://windowsclient.net/wpf/wpf35/wpf-35sp1-toolkit-datagrid-feature-walkthrough.aspx). however i am getting a bit of weird behaviour.

    my xaml uses a wpf datagrid from codeplex and for each data column I am persisting 3 properties: displayindex, visibility and width. here is a sample column:
    Binding="{Binding Path=Cause, Mode=OneWay}" Visibility="{PropertyState Default=Visible}"
    Header="Cause" Width="{PropertyState Default=Auto}" IsReadOnly="True"
    DisplayIndex="{PropertyState Default=4}"
    CanUserSort="True" SortMemberPath="Cause">

    Now everything gets serialized fine when i save it upon closing. but the next time that i open my control with the grid, it seems as though inside InitializeComponent(), the CLR is resolving the databinding through the PropertyState binding and we end up inside of Persistency.Update (see the callstack at http://d.imagehost.org/view/0947/WPFStatePersistance_Callstack.png). As such, when I call ElementStateOperations.Load(layoutStream); later on, the _elementLookupTable inside Persistency is already populated with some dfault data and hence the call to InitializeLookupTables() gives an exception because we are trying to add elements to the already initialized elementLookupTable dictionary. am i doing something wrong or the grid? any input you have on this is greatly apperciated!

    so on my end i did a small workaround to fix that by modifying InitializeLookupTables() to:
    private void InitializeLookupTables()
    {
    foreach (Element element in _state.Elements)
    {
    if (_elementLookupTable.ContainsKey(element.UId) == false)
    _elementLookupTable.Add(element.UId, element);

    _elementLookupTable[element.UId] = element;
    }
    }

    it does the trick for now. but my problem (and i’m not sure if this is due to the workaround) is that after calling ElementStateOperations.Load(layoutStream); with a valid stream (i’ve verified, the _state object in persistency is recreated properly), none of the serialized properties in my grid have changed. i’ve tried debugging but feel like i’ve hit a wall in it. any insight? ideas?

    thanking you in advance for any help you can provide with this.
    cheers,
    sean

    Reply
  24. Sean INovember 13, 2008 ב 12:02

    hi again,

    the link for the wpf toolkit on codeplex is http://www.codeplex.com/wpf/Release/ProjectReleases.aspx?ReleaseId=15598 (in case you were unaware of the toolkit :) )

    cheers,
    sean

    Reply
  25. RussFebruary 8, 2009 ב 20:29

    Hi Tomer,

    Thanks for the excellent code.

    I’m trying to set the PropertyState binding in the code behind. I have been unable to figure it out. Would you be able to show the equivelent C# code for the following XAML lines?

    ElementState.Mode=”Persist”
    ElementState.UId=”DemoWindow”
    Height=”{PropertyState Default=300}”

    So far I have:

    using Tomers.WPF.State;

    ElementState.SetMode(this, ElementStateMode.Persist);
    ElementState.SetUId(this, “DemoWindow”);
    ???

    Thanks
    Russ.

    Reply
  26. Tomer ShamamFebruary 17, 2009 ב 21:40

    Hi Russ,

    The ElementState markup hasn’t been designed to be called from C#.

    But, you can easily change it by exposing a public method that accepts two parameters: DependencyObject and DependencyProperty. Update the ElementState markup to call this method from the ProvideValue method. Then you can call this method from C#.

    Reply
  27. JP ChowApril 13, 2009 ב 20:45

    Hi Tomer,

    This is a great bit of code. Thank you.

    A question though. Where would be the best place to modify the code such that it saves the element state to my Settings.settings file in Properties?

    Reply
  28. Tomer ShamamApril 13, 2009 ב 21:46

    Hi JP,

    I think that Persistency class would be the best place to put your logic.

    Cheers

    Reply
  29. JP ChowApril 14, 2009 ב 03:33

    I’ve added a string field ControlXML to my Settings.settings file.

    For whatever reason though, I cannot access Properties.Settings in your demo code. Any idea why?

    Reply
  30. JP ChowApril 14, 2009 ב 03:35

    Sorry Tomer. I can’t access Properties.Settings from the App.xaml.cs specificially when you load and save.

    Reply
  31. Tomer ShamamApril 14, 2009 ב 09:54

    Hi JP,

    Is it a syntax error or you just can’t load/save values?

    In case that it is a syntax error, try to use the full namespace.
    For example: YourProjectName.Properties.Settings.

    As for load/save of Properties.Settings, it’s a different mechanism. If you look at the code behind you’ll find that there is a auto-generated code that automatically loads the settings for you. For example referencing the Properties.Settings.Default static property, automatically create an instance which in turn load the settings.

    Also consider to use Application instead of user when editing values via the Settings designer, it will persist the values inside the application settings XML file instead of a per User profile.

    Reply
  32. UJanuary 5, 2011 ב 09:18

    Hello Tomer,
    this code is really great! I have an additional question for some modifications: The ElementStateOperations are static, so they can easily used from PropertyStateExtension. But I want to save the property settings per Dialog. Is there a comfortable way to get/access the Dialog/root in ProvideValue?

    Reply
  33. Tomer ShamamJanuary 7, 2011 ב 10:17

    Hello U!
    You can have an attached property inherited to all children, or you can use VisualTreeHelper to climb up the tree.
    Either way, you should be aware that the element may not be connected to the visual Tree yet, hence you should wait until it’s loaded either by registering the Loaded event or using Dispatcher.BeginInvoke.

    Reply