Binding a Dictionary to a WinRT ListBox

2019-01-19 07:13发布

问题:

I've read a number of posts on binding Dictionary to WPF ListView and ListBox but I can't get equivalent code to work in WinRT.

<Grid Margin="10" Width="1000" VerticalAlignment="Stretch">
        <ListBox Name="StatListView" ItemsSource="{Binding FooDictionary}" >
            <ListBox.ItemTemplate>
                <DataTemplate >
                    <Grid Margin="6">
                        <StackPanel Orientation="Horizontal" >
                            <TextBlock Text="{Binding Key}" Margin="5" />
                            <TextBlock Text="{Binding Value}" Margin="5" />
                        </StackPanel>
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>


    public Dictionary<string, string> FooDictionary
    {
        get
        {
            Dictionary<string, string> temp = new Dictionary<string, string>();
            temp.Add("key1", "value1");
            temp.Add("key2", "value2");
            temp.Add("key3", "value3");
            temp.Add("key4", "value4");
            return temp;
        }
    }

What is the proper binding?

回答1:

The error in the output window is (trimmed to the most useful part):

Error: Cannot get 'Key' value (type 'String') from type 
'System.Runtime.InteropServices.WindowsRuntime.CLRIKeyValuePairImpl`2
[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, 
PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, 
Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, ....

Internally, WinRT is converting the type to:

System.Runtime.InteropServices.WindowsRuntime.CLRIKeyValuePairImpl<K, V>

If you add to your DataTemplate:

<TextBlock Text="{Binding}" Margin="5" />

You'll see that it emits the above type with String, String.

However, for some reason, it's not being properly handled as expected. If you search for that type on the Internet, you'll see that there's a documented bug for the issue on Connect.

A simple work around would be to place your data in a simple object that is not a KeyValuePair:

List<StringKeyValue> temp = new List<StringKeyValue>();
temp.Add(new StringKeyValue { Key = "key1", Value = "value1" } );
temp.Add(new StringKeyValue { Key = "key2", Value = "value2" });
temp.Add(new StringKeyValue { Key = "key3", Value = "value3" });
temp.Add(new StringKeyValue { Key = "key4", Value = "value4" });

this.DefaultViewModel["FooDictionary"] = temp;

public class StringKeyValue
{
    public string Key { get; set; }
    public string Value { get; set; }
}

As an aside, from a simple test at least, it's not the Dictionary that's causing the issue at all, it's the fact that it's a KeyValuePair object instance that's being converted to the CLRIKeyValuePairImpl type mentioned above. I tried just using a list and adding a KeyValuePair<string, string> instance to a List, and that failed as well.



回答2:

I've come up with a workaround, that involves generating your own Key Value pairs dynamically.

If you've specialized Dictionary, just add this:

    public IEnumerable<MyPair<K, V>> Collection
    {
        get {
            foreach (var v in this)
            {
                MyPair<K, V> p = new MyPair<K, V>() { Key = v.Key, Value = v.Value };
                yield return p;
            }
         }
    }

and define your Pair type:

public class MyPair<K, V>
{
    public K Key { get; set; }
    public V Value { get; set; }
}

Also, be careful that you create a new object each time. Some items walk across the object, and store the return, which can lead to everything looking like the last item, if you try to reuse the MyPair like I originally did.