0

I am making a custom control similar to an ItemsControl so it has an Items property I want to be bindable to but in order for my control to update, the property must implement INotifyCollectionChanged. I'd like the user to be able to bind any object so long as it implements both INotifyCollectionChanged and IList.

While the simple solution is to create a new interface list like so

public interface INotifyCollectionChangedAndList : INotifyCollectionChanged, IList { }

and require it be inherited on their custom collection objects.

However, instead of doing this they should also be able to use the standard ObservableCollection<T> as well which does inherit both INotifyCollectionChanged and IList but it does not inherit INotifyCollectionChangedAndList and I don't really have control over that. Likewise if they use a different library with a type that they can't control that does implement the two interfaces it should still work.

So my question is if there a way, easy or otherwise (i.e. reflection) where I can specify any type used must implement all the requirements of two (or more) interfaces without the explicit SomeClass : INotifyCollectionChangedAndList?

EDIT

I believe the answer is no. So I'm gonna mark @benjamin answer as correct because although it might not be the best for control authors, it will in other cases be the closest thing to what I would otherwise like to achieve.

Michael Wagner
  • 314
  • 1
  • 12
  • 2
    I think that when using binding, you don't need a strongtyped `ItemsSource` property. If you want to register the NotifyPropertyChanged event, just check (on demand) the type. Use Type testing with pattern matching. example: `if (ItemsSource is INotifyPropertyChanged propertyChangedVariable) { propertyChangedVariable.OnPropertyChanged += ..... }` – Jeroen van Langen Nov 23 '21 at 00:03
  • 1
    I also wouldn't force users to only bind to notifying collections in an ItemsControl-alike. At least if it's kinda all-purpuse control. Sometimes you have lists that don't have items added/removed and where an Array or List is enough. If you need to react in the UI on collection changes you can bind to ICollectionChanged if the bound collection supports it. I solved this problem in my own custom ItemsControl by letting the user bind anything that is IEnumerable but internally using my own ObservableCollection and keeping both collections in sync. – lidqy Nov 23 '21 at 02:19
  • @lidqy Did you use the generic IEnumerable or just IEnumerable? That was actually my first idea but I'm using IEnumerable and ObservableCollection because like you mentioned in your other comment, XAML doesn't play nice with generics. But I cant convert between the two without casting. – Michael Wagner Nov 23 '21 at 03:18

1 Answers1

1

Yes, there is. You don't need reflection. Just make your class generic and specify that whatever type parameter is passed must implement both interfaces:

    public class ItemsController<TModel, TItem> where TModel : INotifyPropertyChanged, IEnumerable<TItem>
    {
        public TModel Model { get; set; }
    }

TModel can be anything that implements both the interfaces you require, so you always know that your ItemsControllers will have a Model property of type TModel that implements INotifyPropertyChanged and also has an Items property of type IEnumerable<TItem>.

benjamin
  • 1,364
  • 1
  • 14
  • 26
  • 1
    That's the propery way of constrainting the type param. However you can't use such a class in standard XAML because of those type params... Type params should be avoided in control / fw element classes etc... – lidqy Nov 23 '21 at 02:03
  • I did forget about type constraints, but like lidqy mentions controls can't use generics easily and Microsoft discourages it in their guidelines. The end user shouldn't have to mess around with XAML and generics. – Michael Wagner Nov 23 '21 at 03:27
  • In .NET 6.0 some frameworks support generic controls. Blazor does, but I don't know about WPF. If you don't have support, maybe you could create a derived type that closes the generic base type. – benjamin Nov 23 '21 at 04:03