Reputation: 1822
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
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
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