WPF View-ViewModel Loosely coupled communication

2k Views Asked by At

Using WPF my team is attempting to seperate the design and the code using the MVVM design patterns. To accomplish this, we are slowly moving away from the UserControl approach as this has a high ammount of coupling between design and code. We have been investigating using Control Templates, Styles and DataTemplates, in combination with a ViewModel class. We have gotten a large amount of this working to date.

The issues we are having are in relation to communications/notifications between View and ViewModel. At present, we have "solved" the View -> Viewmodel communication issues using ICommand. i.e. We create a button and bind its' "Command" parameter to the name of a RelayCommand defined in the ViewModel. By doing so, a button click or other Command event raised from the View calls a function defined in the ViewModel. This works.

Where we're having our main issue is in getting the notification to run the other way around: i.e. a change in data in the ViewModel needs to trigger an update in the View. We were doing this using NotifyPropertyChanged and DataTriggers, however this does not satisfy our needs. What we need is to be able to raise an event of some kind in the Viewmodel and have the View subscribe to said event. We have been searching for an answer to this and have found out about both RoutedEvents and AttachedBehaviors. RoutedEvents seemed like a winner of a solution, however from our research RoutedEvents can't be registered onto a ViewModel that doesn't extend from UIElement, and we are particularly trying to keep our code separated from the design.

Ultimately what we're trying to do is set up a ViewModel where a parameter can be set or a function can be called that will raise an event or behavior on the View, and subsequently run an animation. We did have this working with DataTriggers, however we are attempting to seperate our Animations out into the ControlTemplate and this raises issues as the DataTemplate that contains the DataTriggers has no access to the Storyboards defined in the ControlTemplate.

Can someone please point us in the right direction? Specifically the raising of an event in a ViewModel (that does not require extending UIElement) and subscribing to this event in the View and activating a Storyboard.

Thanks

6

There are 6 best solutions below

0
On

I have found that MVVM isn't a very good choice for loose coupling. By default Microsoft Model-View-Whatever solution templates throw everything into one project. To be truly loosely coupled, try moving everything but the GUI elements into their own libraries and avoid all references to a specific target. A loosely coupled system would have the System.Windows or System.Web references only on the target (client/console/web serving) platform and have reusable components in model and business layer libraries. You will probably end up with some facade and adapter classes that facilitate generic implementations. I suppose it depends what you are trying to decouple. Frameworks in general add more restrictions. I know that it's not a very popular viewpoint, but that's my 2 cents.

0
On

A more all purpose solution that allows even complex code to be decoupled is the View Services pattern. The long and short of it is, that similar to Views, View Services are platform dependent, but unlike Views, are kind of known to the ViewModel. A practical implementation is having the ViewModels Constructor expect an Interface of the desired ViewService, while the "View" side of the code handles the implementation of said interface with View specific code like Dialogs, Notifications, etc. This works very well with Inversion of Control too. Not something that helps if there is supposed to be no code behind on View at all though.

0
On

You could also use the mediator pattern to start an animation from the viewmodel. Just send a message from the viewmodel and subscribe to that kind of message on the view.

1
On

I use delegate to communicate from ViewModel to View. Let me explain. In my framework, I have this abstract class :

public abstract class NotificationObject : INotifyPropertyChanged
{
    // very usefull to order view (if exist) to close 
    public delegate void ActionClose(Boolean closeResult);

    public delegate void ActionUpdate();

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void RaisePropertyChanged(string propertyName)
    {
        // take a copy to prevent thread issues
        PropertyChangedEventHandler handler = this.PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    // other implementation for RaisePropertyChange (not the point here)
    // protected virtual void RaisePropertyChanged(params string[] propertyNames)
    // protected virtual void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression)
}

Now, all my ViewModel can inherit from NotificationObject

public class MyViewModel : NotificationObject
{
    public MyViewModel ()
    {
        // initialisation
    }

    // bindable action UI -> VM
    public ICommand Command1 { get; set; }
    public ICommand Command2 { get; set; }

    // VM -> View
    public ActionUpdate Action1 { get; set; }
    public ActionUpdate Action2 { get; set; }

    // in this method, we need to "notify" view that it must do something (refresh, close, modify UI ...)
    private void MyMethod()
    {
        // never forget that UI can not exist (testing for exemple), or not UI (command-line process), so Action1 can be null
        if (this.Action1 != null)
        {
            this.Action1 ();
        }        
    }
}

Now, in the View, we can declare the delegates (in Window_Loaded, the ViewModel is loaded) :

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    MyViewModel context = this.DataContext as MyViewModel ;

    if (context != null)
    {
        NotificationObject.ActionUpdate myAction1 = new NotificationObject.ActionUpdate(this.MyAction1);
        context.Action1 = myAction1 ;
        NotificationObject.ActionUpdate myAction2 = new NotificationObject.ActionUpdate(this.MyAction2);
        context.Action2 = myAction2 ;
    }
}

private void MyAction1()
{
    // do something
}

private void MyAction2()
{
    // do something
}

That's all. ^^

2
On

The best way is to create a base view that automatically subscribes to the INotifyPropertyChanged on the VM as soon as the property is set (don't forget to unsubscribe!). Keep away from RoutedEvents (or any UI-related events) on the view model. They should only implement INotifyPropertyChanged.

Catel provides an out-of-the-box solution for you for these kind of things:

https://catelproject.atlassian.net/wiki/display/CTL/Mapping+properties+from+view+to+view+model

If you can't use Catel, just copy/paste the parts you need into your own framework. However, don't waste your time reinventing the wheel over and over again ;-)

1
On

I use MVVM myself and if I want to change something on screen, I use INotifyPropertyChanged interface.

Whenever some UI element has to change, OnPropertyChanged event is called out and UI is updated.