2

In WPF/MVVM, you sometimes need the ViewModel to trigger things at the View layer, such as displaying a MessageBox, opening a new window, closing the current window, or starting an animation based on some conditional state in the ViewModel.

MVVM purists seem to agree that the ViewModel should never know about the View. So to solve the above scenarios, other than a few hacks to address some of the simple scenarios, a common paradigm is to use message passing. Imagine using a message passing system just to display a messagebox - MVVM can make trivial stuff pretty complicated.

Let's consider a different approach. First, I make my View implement an interface:

public class MyWindow : IClosableView

Then, in the ViewModel's constructor, I make it take an instance of that interface as a parameter:

public class MyWindowViewModel(IClosableView view)

So when I set the DataContext in the View's constructor, I just pass in the View itself:

public MyWindow()
{
    InitializeComponents();

    this.DataContext = new MyWindowViewModel(this);
}

This makes it pretty simple for the ViewModel to do those things we mentioned above via the View:

public void Close()
{
    this.view.Close();
}

Now before all you MVVM purists start throwing random breakable things at me, let's take a look at what we're doing here. The ViewModel is taking an interface to a view, not a view itself. This means that although the ViewModel does know about a view,

  1. It only knows as much as it really needs in order to trigger the necessary view-side operations (which it needs to do anyway if using a message-passing approach).
  2. It does not rely on any specific view; it only requires that the view that uses it will provide certain facilities, in this case the ability to close that view.
  3. The ViewModel is still fully testable, since the view can be mocked by implementing the IClosableView interface in another class and passing that in unit tests.

Given this reasoning, I'm curious: why is message-passing better than this simple and effective approach?

Edit: just to make things clear, as I stated at the beginning of this question, I'm talking about cases where a View action depends on the state of the ViewModel. It's very easy to have a button close a window by just wiring it up in codebehind. But what about when that depends on some state in the ViewModel?

Community
  • 1
  • 1
Gigi
  • 28,163
  • 29
  • 106
  • 188
  • You are exaggerating the purism. A Caliburn ViewModel (Screen) derives from IViewAware for example. But this is not a good SO question. – H H Aug 10 '14 at 08:02
  • In MVVM the ViewModel is a model of view, nothing more. In your approach the ViewModel is a kind of manager of view. In MVVM you can have many Views binded to the ViewModel at the same time, in your approach it is not possible. Besides, your View is forced to implement an arbitrary interface without a good reason. – romanoza Aug 10 '14 at 10:54

2 Answers2

2

I think the main focus for MVVM purity is your point 2, it doesn't know the view but expects one which offers a defined set of features.

That in itself starts to build a dependency that the viewmodel can only be used with certain views.

That might work in your app, but it isn't the pattern. It's a different solution, which if you can make work then go for it.

kidshaw
  • 3,423
  • 2
  • 16
  • 28
  • But that's the whole point. Certain ViewModels need to perform those operations on views that support them, so an interface needs to be defined as a contract. This might arguably be better than message passing, where there is no compile-time enforcement of messages sent by the ViewModel being consumed by the View. – Gigi Aug 10 '14 at 07:35
  • But that is kinda the point with MVVM, sending a message to say notify the user means the viewmodel doesn't need to know about that implementation. It can be whatever the UI wants that to be. It is also reusable. What you're attempting is more like service locator pattern in my mind. You're passing an object to the viewmodel which provides a service, but in your case that object is also a view – kidshaw Aug 10 '14 at 07:41
  • I'm not suggesting there is anything wrong with that, I wonder how easy to scale that might be and if multiple interfaces would end up being needed. I think in a 50+ view app, this might become harder to work with then having separation through messaging. But it is an interesting solution to WPF over simplicity. – kidshaw Aug 10 '14 at 07:46
  • I see your point, I just fail to see the benefits of such an approach. A fire and forget mechanism feels pretty error-prone to me. Using interfaces the VM still doesn't need to know the implementation; only the contract. As for multiple interfaces, I think that's really the least of our concerns in MVVM where we end up with loads of views, viewmodels, interfaces, DTOs, and whatnot. – Gigi Aug 10 '14 at 07:48
  • It's fire and forget, but still unit testable. It does seem to put a lot of faith in a black box layer, which for devs is not comfortable. – kidshaw Aug 10 '14 at 07:51
1

Usually you invert dependencies. The ViewModel doesn't know anything about any view. For the ViewModel the View is replacable and even not neccessary to work. The ViewModel is therefore passed to the View or is instantiated here. The View communicates with ViewModel e.g. from code-behind (events), via commands, binding or triggers.

While the ViewModel contains the business logic the view only implements presentation logic. So displaying a dialog is not the job of the ViewModel. Rather would the View itself display a dialog for example via triggers, binding or events (e.g. Clicked). For the purpose of binding the ViewModel usually implements the INotifyPropertyChanged or is a decendant of DependencyObject.

Say you click on the exit button to close the app then the View would subscribe to the UIElements (button's) Clicked event and call this.Close() to shutdown (or launch a dialog). The ViewModel is not involved in this in an active way because it don't knows any View.

<!--  View.xaml  -->
<Window.Resources>
     <viewModel:MainViewModel x:Key="MyViewModel">
</Window.Resources>
...
<Button x:Name="ExitButton" Clicked="CloseApp_OnClicked">

// View.xaml.cs (code-behind)
public void CloseApp_OnClicked(object sender, MouseEventArgs e)
{
    // Check if the ViewModel's data is saved before closing the app (state check)
    var theViewModel = this.Resources["MyViewModel"] as MainViewModel;
    if ( (theViewModel != null) && (theViewModel.DataIsSaved) )
        this.Close();
}

Requested example: An animation is triggered depending on a ViewModels property value using a trigger. When value is true the animation gets kicked. In this example the opacity of an image is animated. The trigger uses binding to observe the source and triggers on a specified value and is attached to the element you like to animate in this example the image:

// ViewModel
class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private bool credentialsAreValid;

    public bool CredentialsAreValid 
    { 
        get { return this.credentialsAreValid; }
        set 
        {
             this.credentialsAreValid = value; 
             OnPropertyChanged(); // Not implemented.
        }
    }
}

XAML:

<!-- View -->
<Window.Resources>
    <viewModel:ViewModel x:Key="MyViewModel">
</Window.Resource>
<Window.DataContext>
    <Binding Source="{staticResource MyViewmodel}">
</Window.DataContext>

<Image x:Name="AnimatedImage">
    <Image.Style>
        <Style x:Name="ToggleAnimationStyle" TargetType=Image>
            <Style.Triggers>
                <DataTrigger x:Name="ValidCredentialsTrigger Binding={Binding CredentialsAreValid} Value="True">
                    <DataTrigger.EnterActions>
                         <BeginStoryboard>
                              <Storyboard x:Name="FadeInStoryBoard">
                                     <DoubleAnimation Storyboard.TargetProperty="Opacity" From="1.0" To="0.0" FillBehavior="HoldEnd" BeginTime="0:0:0" Duration="0:0:3"/>
                               </Storyboard>
                          </BeginStoryboard>
                      </DataTrigger.EnterActions>
                      <DataTrigger.ExitActions>
                        <BeginStoryboard>
                            <Storyboard x:Name="FadeOutStoryBoard">
                                  <DoubleAnimation Storyboard.TargetProperty="Opacity" From="0.0" To="1.0" FillBehavior="HoldEnd" BeginTime="0:0:0" Duration="0:0:10">
                                         <DoubleAnimation.EasingFunction>
                                              <ExponentialEase EasingMode="EaseIn"/>
                                         </DoubleAnimation.EasingFunction>
                                   </DoubleAnimation>
                             </Storyboard>
                         </BeginStoryboard>
                      </DataTrigger.ExitActions>
                 </DataTrigger>
          </Style.Triggers>
        </Style>
    </Image.Style>
</Image>
BionicCode
  • 1
  • 4
  • 28
  • 44
  • When the View can take care of all the logic that's possible, but I'm talking about cases where View actions depend on ViewModel state. For example, clicking on a Login button will only start an animation if the login is successful. – Gigi Aug 10 '14 at 08:00
  • 2
    This is where triggers come in. You can bind to a certain ViewModel property representing a state and trigger the animation if state (e.g. a boolean) is valid. – BionicCode Aug 10 '14 at 08:02
  • 2
    In your case your ViewModel would have a property of type bool 'loginSuccessfull'. You would bind to it and trigger depending on the value and the display the dialog or trigger the animation. – BionicCode Aug 10 '14 at 08:04
  • That's interesting. Could you enhance your answer by providing a code example? – Gigi Aug 10 '14 at 08:04
  • @BionicCode Not to be mr picky pants but you have an error in yur code. public bool CredentialsAreValid { get; set; } // Where is the NotifyPropertyChanged invoking in the setter? :) – Stígandr Aug 10 '14 at 08:33