Carlos
Carlos

Reputation: 1822

C# DependencyProperty of generic class in UserControl

I have read lots of questions and answers to solve this problem, but haven't found anything that helps with this problem. I am trying to expose a minimal example, so this is not exactly my real code (if there is any mistake in it, please, advice me).

What do I want to do? I am just trying to create a UserControl that uses a generic class. I want to use it like the WPF frameworks works, I mean, I want to be able to bind a property to a generic object of any class (Itemssource in the WPF framework case, you can bind it to ObservableCollection<AnyClass>).

First of all I define my class:

public class MyGenericClass<T> where T : IMyItemInterface
{ 
    public ObservableCollection<T> MyOriginalList { get; set; }
    public T MySelectedItem { get; set; }
    ... more code here ...
}

Then I create a UserControl (MyUserControl) with this DependencyProperty in it:

public partial class MyUserControl: UserControl
{
... code here ...

public static readonly DependencyProperty ItemsListProperty =
    DependencyProperty.Register("ItemsList", typeof(MyGenericClass<IMyItemInterface>),
    typeof(MyUserControl),
    new FrameworkPropertyMetadata(new PropertyChangedCallback(OnItemsListChanged)));

private static void OnItemsListChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    MyUserControl myControl = (MyUserControl)d; // just for debugging
}

public MyGenericClass<IMyItemInterface> ItemsList
{
    get => (MyGenericClass<IMyItemInterface>)GetValue(ItemsListProperty); 
    set => SetValue(ItemsListProperty, value);
}

... more code here ...
}

This code compiles, but it does not work. If I set a breakpoint in OnItemsListChanged, it never fires.

I can change typeof(MyGenericClass<IMyItemInterface>) to typeof(object) this way:

public static readonly DependencyProperty ItemsListProperty =
    DependencyProperty.Register("ItemsList", typeof(object),
    typeof(MyUserControl),
    new FrameworkPropertyMetadata(new PropertyChangedCallback(OnListaItemsChanged)));

Now it does fire OnItemsListChanged but now I have a casting excepcion in get => (MyGenericClass<IMyItemInterface>)GetValue(ItemsListProperty);, and it is logical because MyGenericClass derives from object and not object from MyGenericClass. So I have also tried to change the property to:

public object ItemsList
{
    get => GetValue(ItemsListProperty); 
    set => SetValue(ItemsListProperty, value);
}

Now it compiles again and remains firing OnItemsListChanged but now I cannot use ItemList for anything because I cannot refer to ItemLists.MySelectedItem (for example), because MySelectedItem does not exist in object class.

The question is: what is the right approach for it? what am I doing wrong?

UPDATE:

What I bind to ItemsList is an instance of MyGenericClass. For example, I have a class that implements IMyItemInterface (MyClassItem), and after doing in code-behind MyGenericClass<MyClassItem> myItem = new();, I bind in XAML this way:

<MyCustomControl ItemsList={Binding myItem, Mode=TwoWay} />

Upvotes: 1

Views: 858

Answers (2)

Clemens
Clemens

Reputation: 128097

It may be sufficient to declare the dependency property type like this:

public class MyItemsList
{
    public ObservableCollection<IMyItemInterface> MyOriginalList { get; set; }
    public IMyItemInterface MySelectedItem { get; set; }
}

and the dependency property like this:

public MyItemsList ItemsList
{
    get { return (MyItemsList)GetValue(ItemsListProperty); }
    set { SetValue(ItemsListProperty, value); }
}

public static readonly DependencyProperty ItemsListProperty =
    DependencyProperty.Register(
        nameof(ItemsList), typeof(MyItemsList), typeof(MyUserControl));

Assignments like these (and equivalent Bindings) would now work:

c.ItemsList = new MyItemsList { MySelectedItem = new MyClassItem() };

Upvotes: 1

mm8
mm8

Reputation: 169360

You can't set a property of type MyGenericClass<IMyItemInterface> to an MyGenericClass<MyClassItem> because a MyGenericClass<MyClassItem> is not a MyGenericClass<IMyItemInterface> just because MyClassItem implements IMyItemInterface.

This is called variance and C# restricts variant type parameters to generic interfaces and generic delegate types.

For example, you can set an IEnumerable<object> field to a List<string> but you cannot set a List<object> field to a List<string> despite the fact that List<T> implements IEnumerable<T>.

That's your issue and why your dependency property isn't set and your callback isn't invoked.

Please refer to this question and answers for more information:

C# variance problem: Assigning List<Derived> as List<Base>

The docs on covariance and contravariance should also be helpful.

Upvotes: 2

Related Questions