Thomas Ayoub
Thomas Ayoub

Reputation: 29451

OrderedDictionary is not being saved when saving Settings

In my application I've implemented a few Controls to browse data in different ways. In every Control I display a TreeView to allow the user to go from a folder to another.

I would like my Controls to "remember" the last selected tree, in a generic way (I mean that if, in the future I add another Control I don't want to do a lot of adaptations). So I added an OrderedDictionary in the Settings. I use the Control's Type Name as a key and the Node's path as value.

As I was unable to set a default value for this dictionary I used this trick:

Settings.cs :

public OrderedDictionary Paths
{
    get
    {
        return LastsPaths ?? (LastsPaths = new OrderedDictionary());
    }
    set
    {
        this["LastsPaths"] = value;
    }
}

Settings.Designer.cs:

[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public global::System.Collections.Specialized.OrderedDictionary LastsPaths {
    get {
        return ((global::System.Collections.Specialized.OrderedDictionary)(this["LastsPaths"]));
    }
    set {
        this["LastsPaths"] = value;
    }
}

I do call Save each time I add/update a value, the user.config file timestamps change, but the content stays the same:

<setting name="LastsPaths" serializeAs="Xml">
    <value />
</setting>

It doesn't work with:

How can I fix this?

Upvotes: 3

Views: 214

Answers (2)

Cubicle.Jockey
Cubicle.Jockey

Reputation: 3328

This might be enough to get you started. This inherits IXMLSerializable which is needed during the Save call.

[XmlRoot("PreviouslyVisitedPaths")]
public class PreviouslySelectedPaths : OrderedDictionary, IXmlSerializable
{

    #region Implementation of IXmlSerializable

    /// <summary>
    /// This method is reserved and should not be used. When implementing the IXmlSerializable interface, you should return null (Nothing in Visual Basic) from this method, and instead, if specifying a custom schema is required, apply the <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/> to the class.
    /// </summary>
    /// <returns>
    /// An <see cref="T:System.Xml.Schema.XmlSchema"/> that describes the XML representation of the object that is produced by the <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/> method and consumed by the <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/> method.
    /// </returns>
    public XmlSchema GetSchema()
    {
        return null;
    }

    /// <summary>
    /// Generates an object from its XML representation.
    /// </summary>
    /// <param name="reader">The <see cref="T:System.Xml.XmlReader"/> stream from which the object is deserialized. </param>
    public void ReadXml(XmlReader reader)
    {
        var keySerializer = new XmlSerializer(typeof(object));
        var valueSerializer = new XmlSerializer(typeof(object));

        var wasEmpty = reader.IsEmptyElement;
        reader.Read();

        if(wasEmpty)
        {
            return;
        }

        while(reader.NodeType != XmlNodeType.EndElement)
        {
            reader.ReadStartElement("item");

            reader.ReadStartElement("key");
            var key = keySerializer.Deserialize(reader);
            reader.ReadEndElement();

            reader.ReadStartElement("value");
            var value = valueSerializer.Deserialize(reader);
            reader.ReadEndElement();

            Add(key, value);

            reader.ReadEndElement();
            reader.MoveToContent();
        }
        reader.ReadEndElement();
    }

    /// <summary>
    /// Converts an object into its XML representation.
    /// </summary>
    /// <param name="writer">The <see cref="T:System.Xml.XmlWriter"/> stream to which the object is serialized. </param>
    public void WriteXml(XmlWriter writer)
    {
        var keySerializer = new XmlSerializer(typeof(object));
        var valueSerializer = new XmlSerializer(typeof(object));

        foreach(var key in Keys)
        {
            writer.WriteStartElement("item");

            writer.WriteStartElement("key");
            keySerializer.Serialize(writer, key);
            writer.WriteEndElement();

            writer.WriteStartElement("value");
            var value = this[key];
            valueSerializer.Serialize(writer, value);
            writer.WriteEndElement();

            writer.WriteEndElement();
        }

        #endregion
    }
}

You would then change your code to this:

public PreviouslySelectedPaths Paths
{
    get
    {
        return LastsPaths ?? (LastsPaths = new PreviouslySelectedPaths());
    }
    set
    {
        this["LastsPaths"] = value;
    }
}

You would also need to make LastsPaths type PreviouslySelectedPaths.

This is just to get your started you may need to tweek the IXMLSerializable methods and fill in logic for GetSchema().

Upvotes: 0

Patrick Hofman
Patrick Hofman

Reputation: 157018

It seems OrderedDictionary (and generic dictionaries in general) are not XML serializable.

You can wrap it in another class that does the serialization manually. In that way you don't expose the dictionary directly to the XML serializer. You have to implement IXmlSerializable to achieve this.

Upvotes: 3

Related Questions