Angus Graham
Angus Graham

Reputation: 132

WPF XAML editor always says Dictionary with custom key type is not a collection

I'm trying to clear up some squigglies in our project's XAML. The issue I'm facing is that we use dictionaries that use a custom type as the key rather than string. This works fine at runtime, but the XAML editor squigglies the uses of them with the error:

Type 'Dictionary`2' is not a Collection

Example:

<Window x:Class="DictionaryCustomKey.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:DictionaryCustomKey"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="MainWindow"
        Width="800" Height="450"
        DataContext="{x:Static local:MainWindow.TheData}"
        mc:Ignorable="d">
    <StackPanel>
        <ListView ItemsSource="{Binding StringKeyDictionary}" />
        <TextBlock Text="String First is:" />
        <TextBlock Text="{Binding StringKeyDictionary[First]}" />
        <ListView ItemsSource="{Binding MyKeyDictionary}" />
        <TextBlock Text="MyKey First is:" />
        <TextBlock Text="{Binding MyKeyDictionary[First]}" />
    </StackPanel>
</Window>

The very last instance of "First" is squigglied.

Here's the codebehind where I try everything I can imagine to make the editor happy that the key type "MyKey" is suitable as a Dictionary key:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Windows;

namespace DictionaryCustomKey
{
    public class Data
    {
        public class MyKeyTypeConverter : TypeConverter
        {
            public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
            {
                return sourceType == typeof(string);
            }

            public override bool CanConvertTo(ITypeDescriptorContext context, Type sourceType)
            {
                return sourceType == typeof(string);
            }

            public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
            {
                if (value is string)
                {
                    return new MyKey((string)value);
                }
                return null;
            }

            public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
            {
                if (value == null)
                {
                    return null;
                }
                if (destinationType == typeof(string))
                {
                    return ((MyKey)value).String;
                }
                return null;
            }
        }

        [TypeConverter(typeof(MyKeyTypeConverter))]
        public class MyKey : IComparable, IComparable<MyKey>, IComparable<string>, IEquatable<MyKey>, IEquatable<string>, IConvertible
        {
            public MyKey(string stringKey)
            {
                m_String = stringKey;
            }

            public static implicit operator MyKey(string v)
            {
                return new MyKey(v);
            }

            public static implicit operator string(MyKey v)
            {
                return v.m_String;
            }

            public String String => m_String;

            private String m_String;

            public override string ToString()
            {
                return String;
            }

            public override bool Equals(object obj)
            {
                if (obj is MyKey)
                {
                    return ((MyKey)obj) == this;
                }
                if (obj is string)
                {
                    return ((string)obj) == this.String;
                }
                return false;
            }

            public bool Equals(MyKey other)
            {
                return this == other;
            }

            public override int GetHashCode()
            {
                return String.GetHashCode();
            }

            public int CompareTo(object obj)
            {
                return String.CompareTo(obj);
            }

            public int CompareTo(MyKey other)
            {
                return String.CompareTo(other);
            }

            public int CompareTo(string other)
            {
                return String.CompareTo(other);
            }

            public bool Equals(string other)
            {
                return String.Equals(other);
            }

            public TypeCode GetTypeCode()
            {
                return String.GetTypeCode();
            }

            public bool ToBoolean(IFormatProvider provider)
            {
                return ((IConvertible)String).ToBoolean(provider);
            }

            public char ToChar(IFormatProvider provider)
            {
                return ((IConvertible)String).ToChar(provider);
            }

            public sbyte ToSByte(IFormatProvider provider)
            {
                return ((IConvertible)String).ToSByte(provider);
            }

            public byte ToByte(IFormatProvider provider)
            {
                return ((IConvertible)String).ToByte(provider);
            }

            public short ToInt16(IFormatProvider provider)
            {
                return ((IConvertible)String).ToInt16(provider);
            }

            public ushort ToUInt16(IFormatProvider provider)
            {
                return ((IConvertible)String).ToUInt16(provider);
            }

            public int ToInt32(IFormatProvider provider)
            {
                return ((IConvertible)String).ToInt32(provider);
            }

            public uint ToUInt32(IFormatProvider provider)
            {
                return ((IConvertible)String).ToUInt32(provider);
            }

            public long ToInt64(IFormatProvider provider)
            {
                return ((IConvertible)String).ToInt64(provider);
            }

            public ulong ToUInt64(IFormatProvider provider)
            {
                return ((IConvertible)String).ToUInt64(provider);
            }

            public float ToSingle(IFormatProvider provider)
            {
                return ((IConvertible)String).ToSingle(provider);
            }

            public double ToDouble(IFormatProvider provider)
            {
                return ((IConvertible)String).ToDouble(provider);
            }

            public decimal ToDecimal(IFormatProvider provider)
            {
                return ((IConvertible)String).ToDecimal(provider);
            }

            public DateTime ToDateTime(IFormatProvider provider)
            {
                return ((IConvertible)String).ToDateTime(provider);
            }

            public string ToString(IFormatProvider provider)
            {
                return String.ToString(provider);
            }

            public object ToType(Type conversionType, IFormatProvider provider)
            {
                return ((IConvertible)String).ToType(conversionType, provider);
            }

            public static bool operator ==(MyKey lhs, MyKey rhs)
            {
                return lhs.String == rhs.String;
            }

            public static bool operator !=(MyKey lhs, MyKey rhs)
            {
                return !(lhs == rhs);
            }
        }

        public Dictionary<string, object> StringKeyDictionary { get; set; } = new Dictionary<string, object>
        {
            {"First", "zero"},
            {"Second", "one"}
        };
        public Dictionary<MyKey, object> MyKeyDictionary { get; set; } = new Dictionary<MyKey, object>
        {
            {new MyKey("First"), "zero"},
            {new MyKey("Second"), "one"}
        };
    }

    public partial class MainWindow : Window
    {
        static public Data TheData { get; set; } = new Data();

        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

But nothing seems to work.

Edit: An answer is given that makes the squiggly go away, but it reveals there is actually a worse problem: squiggly or not, the designer has trouble displaying the binding: https://developercommunity.visualstudio.com/content/problem/848331/visual-studio-must-be-restarted-after-every-build.html

Upvotes: 2

Views: 325

Answers (1)

Keithernet
Keithernet

Reputation: 2509

This appears to be a bug in the XAML editor. Using nested element syntax doesn't have the error:

<TextBlock>
    <TextBlock.Text>
        <Binding Path="MyKeyDictionary[First]" />
    </TextBlock.Text>
</TextBlock>

I would recommend reporting it through the Visual Studio feedback tool.

Upvotes: 2

Related Questions