Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

WPF Persistency

, 3 Jan 2013 CPOL
This article describes how to persist WPF dependency properties
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Markup;
using System.Xml.Serialization;


// http://www.codeproject.com/Articles/19922/WPF-Persistency

//works only if defined in a different assembly
[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "WpfPersist")]

namespace WpfPersist
{
    /// <summary>
    /// provides storage for the UserSettingsExtension.
    /// </summary>
    public static class UserSettingsStorage
    {

        static UserSettingsStorage()
        {
            if (Application.Current != null && Application.Current.MainWindow != null)
            {
                Application.Current.MainWindow.Closed += (sender, e) => SaveSettings();
            }
            else
            {
                Debug.Fail("cannot hook main window, try something else or call SaveSettings where appropriate");
            }
        }

        /// <summary>
        /// explicit method because all the delegates above won't work in a win32 hosted application
        /// </summary>
        public static void SaveSettings()
        {
            if (Keyboard.Modifiers == ModifierKeys.Control)
            {
                Debug.WriteLine("user settings saving suppressed");
                return;
            }

            settings.Save();
            Debug.WriteLine("user settings saved");
        }

        private static Settings settings;

        public static StringDictionary Dictionary
        {
            get
            {
                if (settings == null)
                {
                    settings = new Settings("XamlPersist");

                    if (Keyboard.Modifiers == ModifierKeys.Control)
                    {
                        Debug.WriteLine("user settings loading suppressed, using defaults");
                        settings.Dictionary.Clear();
                    }
                    else
                    {
                        Debug.WriteLine("loading persisted values:\n" + string.Concat(settings.Dictionary.Select(a => string.Format(" '{0}' = '{1}'\n", a.Key, a.Value))));
                    }
                }
                return settings.Dictionary;
            }
        }

        #region private types

        [SettingsGroupName("AppPersist")]
        private sealed class Settings : ApplicationSettingsBase
        {
            internal Settings(string settingsKey)
                : base(settingsKey)
            {
            }

            [UserScopedSetting]
            public StringDictionary Dictionary
            {
                get
                {
                    if (this["Dictionary"] == null)
                    {
                        this["Dictionary"] = new StringDictionary();
                    }
                    return ((StringDictionary)(this["Dictionary"]));
                }
                set
                {
                    this["Dictionary"] = value;
                }
            }
        }


        #endregion
    }

    /// <summary>
    /// This class is a markup extension implementation. Markup extension classes exist mainly to provide
    /// infrastructure support for some aspect of the WPF XAML reader implementation, and the members exposed by
    /// a markup extension are not typically called from user code.
    /// This extension supports the x:UserSettings Markup Extension usage from XAML.
    ///
    /// example usage:
    ///   Width="{app:UserSettings Default=Auto,Key=MainWindow.Grid.Column1}"
    /// </summary>
    public class UserSettingsExtension : MarkupExtension
    {
        public UserSettingsExtension()
        {
        }
        public UserSettingsExtension(string defaultValue)
        {
            this.Default = defaultValue;
        }

        #region MarkupExtension overrides

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            IProvideValueTarget provideValue = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
            if (provideValue == null || provideValue.TargetObject == null)
            {
                return null;
            }

            DependencyObject targetObject = provideValue.TargetObject as DependencyObject;
            if (targetObject == null)
            {
                Debug.Fail(string.Format("can't persist type {0}, not a dependency object", provideValue.TargetObject));
                throw new NotSupportedException();
            }

            DependencyProperty targetProperty = provideValue.TargetProperty as DependencyProperty;
            if (targetProperty == null)
            {
                Debug.Fail(string.Format("can't persist type {0}, not a dependency property", provideValue.TargetProperty));
                throw new NotSupportedException();
            }

            if (DesignerProperties.GetIsInDesignMode(targetObject))
            {
                return ConvertFromString(targetObject, targetProperty, Default);
            }

            if (Key == null)
            {
                IUriContext uriContext = (IUriContext)serviceProvider.GetService(typeof(IUriContext));
                if (uriContext == null)
                {
                    // fallback to default value if no key available
                    DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor.FromProperty(targetProperty, targetObject.GetType());
                    return descriptor.GetValue(targetObject);
                }

                // UIElements have a 'PersistId' property that we can use to generate a unique key
                if (targetObject is UIElement)
                {
                    Key = string.Format("{0}.{1}[{2}].{3}",
                        uriContext.BaseUri.PathAndQuery,
                        targetObject.GetType().Name, ((UIElement)targetObject).PersistId,
                        targetProperty.Name);
                }
                // use parent-child relation to generate unique key
                else if (LogicalTreeHelper.GetParent(targetObject) is UIElement)
                {
                    UIElement parent = (UIElement)LogicalTreeHelper.GetParent(targetObject);
                    int i = 0;
                    foreach (object c in LogicalTreeHelper.GetChildren(parent))
                    {
                        if (c == targetObject)
                        {
                            Key = string.Format("{0}.{1}[{2}].{3}[{4}].{5}",
                                uriContext.BaseUri.PathAndQuery,
                                parent.GetType().Name, parent.PersistId,
                                targetObject.GetType().Name, i,
                                targetProperty.Name);
                            break;
                        }
                        i++;
                    }
                }
                //TODO:should do something clever here to get a good key for tags like GridViewColumn

                // x:Uid goes nowhere
                // see also: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/6c853a7a-15e9-4a94-b3a6-b13e6548008a


                if (Key == null)
                {
                    Debug.Fail(string.Format("don't know how to automatically get a key for objects of type {0}\n use Key='...' option", targetObject.GetType()));

                    // fallback to default value if no key available
                    DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor.FromProperty(targetProperty, targetObject.GetType());
                    return descriptor.GetValue(targetObject);
                }
            }

            if (!UserSettingsStorage.Dictionary.ContainsKey(Key))
            {
                UserSettingsStorage.Dictionary[Key] = Default;
            }

            object value = ConvertFromString(targetObject, targetProperty, UserSettingsStorage.Dictionary[Key]);

            SetBinding(targetObject, targetProperty, Key);

            Debug.WriteLine("Object='{0}.{1}' key='{2}' value='{3}'", targetObject, targetProperty, Key, value);

            return value;
        }

        #endregion

        #region static functions

        private static void SetBinding(DependencyObject targetObject, DependencyProperty targetProperty, string key)
        {
            Binding binding = new Binding
                {
                    Mode = BindingMode.OneWayToSource,
                    Path = new PropertyPath(string.Format("Dictionary[{0}]", key)),
                    UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
                    Source = new { Dictionary = UserSettingsStorage.Dictionary }
                };

            BindingOperations.SetBinding(targetObject, targetProperty, binding);
        }

        private static object ConvertFromString(DependencyObject targetObject, DependencyProperty targetProperty, string stringValue)
        {
            DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor.FromProperty(targetProperty, targetObject.GetType());
            return stringValue == null ? descriptor.GetValue(targetObject) : descriptor.Converter.ConvertFromInvariantString(stringValue);
        }

        #endregion

        #region public properties


        /// <summary>
        /// Gets or sets the key that is used for persistent storage.
        /// make sure that this key is unique for the application.
        /// </summary>
        public string Key  { get; set; }

        /// <summary>
        /// the default used when the value cannot be retrieved from persistent storage.
        /// </summary>
        public string Default { get; set; }

        #endregion
    }

    /// <summary>
    /// Implements a collection of strongly typed string keys and values with
    /// additional XML serializability.
    /// </summary>
    [XmlRoot("dictionary"), Serializable]
    public class StringDictionary : Dictionary<string, string>, IXmlSerializable
    {
        #region IXmlSerializable Members

        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(System.Xml.XmlReader reader)
        {
            bool wasEmpty = reader.IsEmptyElement;
            reader.Read();
            if (wasEmpty)
                return;

            while (reader.Name == "item")
            {
                this.Add(reader["key"], reader["value"]);
                reader.Read();
            }
            reader.ReadEndElement();
        }

        public void WriteXml(System.Xml.XmlWriter writer)
        {
            foreach (string key in this.Keys)
            {
                writer.WriteStartElement("item");
                writer.WriteAttributeString("key", key);
                writer.WriteAttributeString("value", this[key]);
                writer.WriteEndElement();
            }
        }
        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Reto Ravasio

Switzerland Switzerland
No Biography provided

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150129.1 | Last Updated 3 Jan 2013
Article Copyright 2007 by Reto Ravasio
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid