Click here to Skip to main content
15,892,072 members
Articles / Desktop Programming / WPF

WPF Persistency

Rate me:
Please Sign up or sign in to vote.
4.84/5 (19 votes)
3 Jan 2013CPOL2 min read 93.5K   784   60  
This article describes how to persist WPF dependency properties
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Data;
using System.ComponentModel;
using System.Configuration;
using System.Xml.Serialization;
using System.Diagnostics;

namespace WpfPersist
{
    /// <summary>
    /// provides storage for the UserSettingsExtension.
    /// </summary>
    public static class UserSettings
    {
		static UserSettings()
		{
			Application.Current.MainWindow.Closing += new CancelEventHandler(MainWindow_Closing);
		}

		static void MainWindow_Closing(object sender, CancelEventArgs e)
		{
			settings.Save();
		}

        private static Settings settings;

        public static StringDictionary Dictionary
        {
            get
            {
                if (settings == null)
                {
                    settings = new Settings("XamlPersist");
                }
                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.defaultValue = defaultValue;
        }

        #region private fields

        private string defaultValue;
        private string key;

        #endregion

        #region MarkupExtension overrides

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            IProvideValueTarget provideValue = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
            if (provideValue == null)
            {
                throw new NotSupportedException("The IProvideValueTarget is not supported");
            }

            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 (key == null)
			{
                IUriContext uriContext = (IUriContext)serviceProvider.GetService(typeof(IUriContext));

                // 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

                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);
                }
                else
                {
                    Debug.WriteLine(string.Format("key={0}", key));
                }
			}

            if (!UserSettings.Dictionary.ContainsKey(key))
            {
                UserSettings.Dictionary[key] = defaultValue;
            }

            object value = ConvertFromString(targetObject, targetProperty, UserSettings.Dictionary[key]);
   
            SetBinding(targetObject, targetProperty, key);

            return value;
        }

        #endregion

        #region static functions

        private static void SetBinding(DependencyObject targetObject, DependencyProperty targetProperty, string key)
        {
            Binding binding = new Binding();
            binding.Mode = BindingMode.OneWayToSource;
            binding.Path = new PropertyPath(string.Format("Dictionary[{0}]", key));
            binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
            binding.Source = new InternalBinder();
            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 { return key; }
            set { key = value; }
        }

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

        #endregion

        #region private types

        private class InternalBinder
        {
            public StringDictionary Dictionary
            {
                get
                {
                    return UserSettings.Dictionary;
                }
            }
        }

        #endregion
    }

	/// <summary>
	/// Implements a collection of strongly typed string keys and values with  
	/// additional XML serializability.
	/// </summary>
	[XmlRoot("dictionary")]
	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)


Written By
Switzerland Switzerland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions