Francesco B. Francesco B. - 1 month ago 5
C# Question

How to make a user control that binds different types?

I would like to make a UserControl in which a user could bind more than one type of data. Usually I define a dependency property, for example:

public ObservableCollection<MyDataType1> Values
{
get { return (ObservableCollection<MyDataType1>)GetValue(ValuesProperty); }
set { SetValue(ValuesProperty, value); }
}

public static readonly DependencyProperty ValuesProperty =
DependencyProperty.Register(
"Values", typeof(ObservableCollection<MyDataType1>),
typeof(MyUserControl),
new PropertyMetadata(
null));


How can I define a cast from some type to MyDataType1?

Example:

public class MyDataType1
{
string ID { get; set; }
string Title { get; set; }
SolidColorBrush Color { get; set; }
}


I'd like that is possible to bind also:

ObservableCollection<int> TestCollection { get; set; }


I tried to define in MyDataType1 an implicit cast:

static public implicit operator MyDataType1(int value)
{
return new MyDataType1()
{
ID = value,
Color = Colors.Black,
Title = "Not defined"
};
}


However, when I bind the collection with ints, my control doesn't show anything.

<ItemsControl ItemsSource="{Binding Values}" ItemTemplate="{StaticResource MyTemplate}" />


To achieve this I don't want to define a
Converter
.

Answer

Bindings to object properties get resolved at Runtime, this means you can pretty much dump any binding in the XAML and if the binding doesn't work, it'll just ignore it.

Using this, it's possible to effectively pick and choose what data type you're going to use. I would say the best method of achieving this is to make use of an interface. Here's an example:

public interface IAwesome
{
    int ID { get; }
}

public class MegaClass : IAwesome
{
    public int ID { get; set; }
    ...
}

public class SuperClass : IAwesome
{
    public int ID { get; set; }
    ...
}

Using this method, your dependency property will look something like this:

public ObservableCollection<IAwesome> Values
{
    get { return (ObservableCollection<IAwesome>)GetValue(ValuesProperty); }
    set { SetValue(ValuesProperty, value); }
}

public static readonly DependencyProperty ValuesProperty =
    DependencyProperty.Register(
        "Values", typeof(ObservableCollection<IAwesome>),
        typeof(MyUserControl),
        new PropertyMetadata(
            null));

Another thing to mention is that you can also make use of a typed DataTemplate, see here:

<DataTemplate DataType="{x:Type MyNamespace:SomethingOrOther}">

This will allow you to define a DataTemplate for a specific Type, so even though your collection is a list of IAwesome, they may be displayed differently in the container based on their assigned DataTemplate.

The beauty of this is that your strongly typed C# will still be valid, however in your scenario, we need to make this even more generic.

The method of using an interface doesn't really solve your problem, because int can't inherit from IAwesome, so you're going to get compiler errors if you try to assign any int values to this property. A way to get around this is to instead use object.

public ObservableCollection<object> Values
{
    get { return (ObservableCollection<object>)GetValue(ValuesProperty); }
    set { SetValue(ValuesProperty, value); }
}

public static readonly DependencyProperty ValuesProperty =
    DependencyProperty.Register(
        "Values", typeof(ObservableCollection<object>),
        typeof(MyUserControl),
        new PropertyMetadata(
            null));

Because everything inherits from object, you can add any value to this collection.

This is all fine and dandy until you actually need to make use of this property in C#. You're going to have lots of headaches because you will need to cast these objects to their desired types.

In the scenario where you don't need to use these values, then this should suffice, otherwise, perhaps you need to re-think your control, perhaps it's trying to do too many things.

Comments