Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding SortedList or Dictionary<int, string> to ResourceDictionary

Tags:

c#

wpf

xaml

Is there a way to add a SortedList or a Dictionary to a ResourceDictionary and use (and bind!) it to a control via XAML?

I've tried this, but I couldn't figure out how to do it:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    xmlns:coll="clr-namespace:System.Collections.Generic;assembly=mscorlib">

    <x:Array x:Key="test"
             Type="sys:Object">
        <coll:KeyValuePair>***</coll:KeyValuePair>
    </x:Array>
like image 595
SeToY Avatar asked Feb 25 '12 23:02

SeToY


1 Answers

SortedList is easy as it is not generic.

If a class implements IDictionary you can add values by defining them as the child nodes using x:Key to set the key by which they should be added to the dictionary.

xmlns:col="clr-namespace:System.Collections;assembly=mscorlib"
<col:SortedList x:Key="list">
    <sys:String x:Key="0">Lorem</sys:String>
    <sys:String x:Key="1">Ipsum</sys:String>
    <sys:String x:Key="2">Dolor</sys:String>
    <sys:String x:Key="3">Sit</sys:String>
</col:SortedList>
<!-- Usage: -->
<ContentControl Content="{Binding [0], Source={StaticResource list}}" />

The item keys are strings here, to get actual ints you could use a custom markup extension which parses the string to int, or by defining the keys as resource first:

<sys:Int32 x:Key="key1">0</sys:Int32>
<sys:Int32 x:Key="key2">1</sys:Int32>
<sys:Int32 x:Key="key3">2</sys:Int32>
<sys:Int32 x:Key="key4">3</sys:Int32>

<col:SortedList x:Key="list">
    <sys:String x:Key="{StaticResource key1}">Lorem</sys:String>
    <sys:String x:Key="{StaticResource key2}">Ipsum</sys:String>
    <sys:String x:Key="{StaticResource key3}">Dolor</sys:String>
    <sys:String x:Key="{StaticResource key4}">Sit</sys:String>
</col:SortedList>

The binding then becomes more complex as the indexer value needs to be cast to int explicitly as it otherwise would be interpreted as string.

<ContentControl Content="{Binding Path=[(sys:Int32)0],
                                  Source={StaticResource list}}"/>

You cannot omit the Path= because of an implementation detail.


Dictionaries are not so easy because they are generic and there (currently) is no simple built-in way to create generic objects in XAML. Using markup extensions however you can create generic objects via reflection.

Implementing IDictionary on such an extension also allows you to fill that newly created instance. Here is a very sketchy example:

public class DictionaryFactoryExtension : MarkupExtension, IDictionary
{
    public Type KeyType { get; set; }
    public Type ValueType { get; set; }

    private IDictionary _dictionary;
    private IDictionary Dictionary
    {
        get
        {
            if (_dictionary == null)
            {
                var type = typeof(Dictionary<,>);
                var dictType = type.MakeGenericType(KeyType, ValueType);
                _dictionary = (IDictionary)Activator.CreateInstance(dictType);
            }
            return _dictionary;
        }
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return Dictionary;
    }

    public void Add(object key, object value)
    {
        if (!KeyType.IsAssignableFrom(key.GetType()))
            key = TypeDescriptor.GetConverter(KeyType).ConvertFrom(key);
        Dictionary.Add(key, value);
    }

    #region Other Interface Members
    public void Clear()
    {
        throw new NotSupportedException();
    }
    public bool Contains(object key)
    {
        throw new NotSupportedException();
    }
    // <Many more members that do not matter one bit...>
    #endregion
}
<me:DictionaryFactory x:Key="dict" KeyType="sys:Int32" ValueType="sys:String">
    <sys:String x:Key="0">Lorem</sys:String>
    <sys:String x:Key="1">Ipsum</sys:String>
    <sys:String x:Key="2">Dolor</sys:String>
    <sys:String x:Key="3">Sit</sys:String>
</me:DictionaryFactory>

As passing in a typed instance as key is a bit of a pain i chose to do the conversion in IDictionary.Add before the value is added to the internal dictionary instead (this may cause problems with certain types).

Since the dictionary itself is typed the binding should not require a cast.

<ContentControl Content="{Binding [0], Source={StaticResource dict}}" />
like image 184
H.B. Avatar answered Oct 21 '22 02:10

H.B.