Joe
Joe

Reputation: 6816

WPF: Using interface as DataContext instead of object implementing INotifyPropertyChanged

I've refactored my WPF application so that the DataContexts are no longer instances of some class that implements INotifyPropertyChanged but rather some custom interfaces of mine. The object that implements each custom interface is required to also implement INotifyPropertyChanged. But I can no longer enforce this requirement at build time. I'm not sure if this is a good idea.

For example, consider the following simple class hierarchy

// Base view model class has stock implementation of INotifyPropertyChanged.

public BaseViewModel : INotifyPropertyChanged
{
   // Assume INotifyPropertyChanged implementation with this utility to raise the event

   void RaisePropertyChanged(string property) { // ...blah blah blah ... }
}

// Interface for nameable object

public interface INameable
{
    string Name { get; set; }
}

// View model for nameable object

public class NameableViewModel : BaseViewModel, INameable
{
    private string _name;
    public string Name
    {
        get => _name;
        set
        {
            if (value == _name) return;
            _name = value
            RaisePropertyChanged();
    }
}

// Main view model exposes named object by interface, NOT by view-model

public class MainViewModel : BaseViewModel
{
    public INameable NameableObject { get; set; } 
}

Here's a view binding to INameable instead of NameableViewModel

<UserControl x:Class="MyView" 
             (... blah blah blah... namespace declarations...)
             d:DataContext="{d:DesignInstance MainViewModel}">

    <TextBox DataContext={Binding NameableObject.Name, Mode=TwoWay}"/>
</UserControl>

This works fine in WPF because it queries any data context for INotifyPropertyChanged. It receives the PropertyChanged notifications even though all it got was INameable. But if I forget to set up my classes right, WPF will just silently never get property changed notifications. I might end up having errors that take a while to notice or track (testing resources are limited here).

Furthermore, in my code-behind, I sometimes manually monitor some objects via INotifyPropertyChanged using Josh Smith's excellent PropertyObserver.

public class PropertyObserver<TPropertySource> 
    : IWeakEventListener
    where TPropertySource : INotifyPropertyChanged
{
   ... blah blah blah..
}

This does require INotifyPropertyChanged at build time, and that was great when I was using instances of my BaseViewModel. But now that I merely have an interface, I must query it myself and rely on a run-time error to let me know I've screwed up.

Which leads me to my questions. One general and one very specific:

  1. Is this design reasonable? It it something done commonly in practice by people working in WPF? Or is it generally always better to be sure that whatever type I use as a data context is something already declared to implement INotifyPropertyChanged? (I realize I might be thinking too much like a C++ programmer with that question)
  2. Suppose in my code, I cast an object and it does NOT support an interface I require and I want to throw an exception. What specific .NET exception should I throw? What's most appropriate? NotSupportedException? Something else?

Upvotes: 0

Views: 1076

Answers (1)

redcurry
redcurry

Reputation: 2497

I'm not entirely sure what the "design" or "practice" that you refer to is. I don't see a general pattern, only a specific use of an interface, for which its purpose is not clear to me.

In my experience, it's best to keep the view models as simple as possible. In some cases, they don't even need to implement INotifyPropertyChanged (think of a read-only view model). Generally, I use an MVVM framework (such as MVVM Light Toolkit) that provides a ViewModelBase. Almost every view model I make derives from ViewModelBase, and the view model simply contains setters, getters, and commands.

For your second question, I think the specific exception depends on the intent for the use of the interface. But perhaps a general exception could be InvalidOperationException.

Upvotes: 1

Related Questions