2

While learning how to program using MVVM pattern I've ran into a common problem - display various dialogs from ViewModels.

At first it looked simple for me. I created an IWindowService interface, implemented it in a WindowService class. I used this class to launch new View windows.

But then I needed MessageBox style dialogs. So I created an IDialogService and a DialogService class. I did same for Open/Save file dialogs.

After all that I've noticed that creating ViewModel instances became quite complicated:

ViewModel VM = new ViewModel(Data, AnotherData, MoreData, WindowService, DialogService, FileDialogService, AnotherService);

I tried to merge all services into a single FrontendService class but it just made it quite hard to maintain and the IFrontendService interface became really "bloated".

Now I'm looking for alternative ways. The best case for me would be something that won't require passing instances to the ViewModel constructors.

Technical
  • 145
  • 1
  • 11
  • Use a DependencyInjection container to construct the ViewModel – Sir Rufo Jul 21 '19 at 08:40
  • Do not show your dialogs from the VMs - that is a common misuse. Instead you should trigger it from your view because invariably showing that dialog is in response to a user interaction with the view. – slugster Jul 21 '19 at 11:33
  • Related: [*Creating an MVVM friendly dialog strategy*](https://stackoverflow.com/q/6595312/109702), [*Open a window from ViewModel*](https://stackoverflow.com/a/40777396/109702) – slugster Jul 21 '19 at 11:34

1 Answers1

4

Dialogs or Window in general are view related. If you want to implement MVVM then you have to separate View from your View Model. MVVM will help you to accomplish this.

MVVM dependency graph
MVVM dependency graph and responsibilities overview

The dependency graph shows that the View depends on the View Model. This dependency is unidirectional for the purpose of decoupling. This is possible only because of the data binding mechanism.

It is important to emphasize that MVVM is an application architectural design pattern. It views applications from a component perspoective and not class perspective. The wide spread practice to name the source class of a data binding "ViewModel" is quite misleading, obviously. In fact, as the View is composed of many classes (e.g. controls), so is the View Model. It's a component.

Since the dialog is part of the View, a Window.Show() invoked by the View Model would add an illegal arrow, that points from View Model to View. This would mean the View Model now depends on the View.
Now, that you have created an explicit dependency to the View, you will encounter new problems (that MVVM originally was trying to solve): if you decide to show a different window type or replace the dialog with a popup (or in other words anytime you modify the View), then you would have to modify the View Model. This is exactly what MVVM is designed for to avoid.

The solution is to let the View handle itself. When the View needs to show a dialog, it must do it on its own.

A dialog is a way to provide user interaction. User interaction is not business of the View Model.

If you need a dedicated service to handle displaying GUI, then it must operate entirely in the View - as such it can't be referenced by the View Model.
Since the View Model is data related (presentation of data) it could only flag data related states that the View can trigger on (e.g. data validation errors, where the recommended way is to implement INotifyDataErrorInfo on the View Model).

My recommendation is to keep your View Model free from View related responsibilities and keep its business focused on the model data presentation only - or let go MVVM and return to the initial decoupling problem.

Solution 1

The simplest way is to show the dialog from code-behind or a routed event handler. It can be triggered by an exception or event thrown or raised by the View Model. For example if writing to a file fails the View Model can raise as FileError event that the View listens to and reacts to e.g., by displaying a dialog to the user.
Then pass the collected data (if this is an input dialog) to the View Model (if this is required) by using an ICommand or by updating a data binding.

Code-behind does not violate MVVM as MVVM is component based, while code-behind is a C# language that is unknown to the concept of MVVM. A requirement of a design pattern is that it must be language independent. In the definition of MVVM code-behind does not play any role - its not mentioned.

Solution 2

Alternatively design your own dialog by implementing a ContentControl (or UserControl). Such a control blends perfectly into the WPF framework and allows to write MVVM compliant code. You can now make use of data binding and data triggers to show and hide the control/dialog.

Native Windows dialogs do not integrate well into the WPF framework. A Window can't be shown using a trigger. We must call the Show() or DialogShow() method. That's where the original problem "How to show a dialog in an MVVM compliant way" stems from.

This is an example of a modal dialog, that can be shown and hidden using XAML only. No C# involved. It uses Event Triggers to animate the Visibility of the dialog grid (or alternatively animate the Opacity). The event is triggered by a Button. For different scenarios the Button can be simply replaced with a DataTrigger or a binding using the BooloeanToVisibilityConverter:

<Window>
  <Window.Triggers>
    <EventTrigger RoutedEvent="Button.Click" SourceName="OpenDialogButton">
      <BeginStoryboard>
        <Storyboard>
          <ObjectAnimationUsingKeyFrames 
                          Storyboard.TargetName="ExampleDialog"
                          Storyboard.TargetProperty="Visibility"
                          Duration="0">
            <DiscreteObjectKeyFrame Value="{x:Static Visibility.Visible}"/>
          </ObjectAnimationUsingKeyFrames>
        </Storyboard>
      </BeginStoryboard>
    </EventTrigger>
  </Window.Triggers>

  <Grid SnapsToDevicePixels="True" x:Name="Root">

    <!-- The example dialog -->
    <Grid x:Name="ExampleDialog" Visibility="Hidden"  Panel.ZIndex="100" VerticalAlignment="Top">

      <!-- The Rectangle stretches over the whole window area --> 
      <!-- and covers all window child elements except the dialog -->
      <!-- This prevents user interaction with the covered elements -->
      <!-- and adds modal behavior to the dialog -->
      <Rectangle
        Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window}, Path=ActualWidth}"
        Height="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window}, Path=ActualHeight}"
        Fill="Gray" Opacity="0.7" />
      <Grid Width="400" Height="200" >
        <Grid.RowDefinitions>
          <RowDefinition Height="*"/>
          <RowDefinition Height="100"/>
        </Grid.RowDefinitions>
        <Border Grid.RowSpan="2" Background="LightGray" BorderBrush="Black" BorderThickness="1">
          <Border.Effect>
            <DropShadowEffect BlurRadius="5" Color="Black" Opacity="0.6" />
          </Border.Effect>
        </Border>
        <TextBlock Grid.Row="0" TextWrapping="Wrap"
                   Margin="30"
                   Text="I am a modal dialog and my Visibility or Opacity property can be easily modified by a trigger or a nice animation" />
        <StackPanel Orientation="Horizontal" Grid.Row="1" HorizontalAlignment="Right" Height="50" >
          <Button x:Name="OkButton"
                  Content="Ok" Width="80" />
          <Button x:Name="CancelButton" Margin="30,0,30,0"
                  Content="Cancel" Width="80" />
        </StackPanel>
      </Grid>

      <Grid.Triggers>
        <EventTrigger RoutedEvent="Button.Click">
          <BeginStoryboard>
            <Storyboard>
              <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExampleDialog"
                                             Storyboard.TargetProperty="Visibility"
                                             Duration="0">
                <DiscreteObjectKeyFrame Value="{x:Static Visibility.Hidden}" />
              </ObjectAnimationUsingKeyFrames>
            </Storyboard>
          </BeginStoryboard>
        </EventTrigger>
      </Grid.Triggers>
    </Grid>

    <! The actual control or page content -->
    <StackPanel>
      <TextBlock Text="This is some page content" />

      <!-- The button to open the dialog. This can be replaced by a DataTrigger -->
      <Button x:Name="OpenDialogButton" Content="ShowDialog" Width="100" Height="50" />
    </StackPanel>
  </Grid>
</Window>

This is the dialog:

enter image description here

You can encapsulate the dialog and move the implementation into a dedicated Control e.g. DialogControl, which is easier to use throughout the application (no duplicate code, improved handling). You can add the common window chrome to the dialog like title bar, icon and the chrome buttons to control the state of the dialog.

Edit to show the wrong and why a "dialog service" violates/eliminates MVVM

Everything that the View Model component references is either part of the View Model too or part of the Model. The above MVVM dependency diagramm shows very well that the View component is completely unknown to the View Model component. Now, how can a dialog service, that is known by the View Model and that shows modules of the View like a dialog, not violate the MVVM pattern? Apparently, either the View Model component has knowledge of a View component's module or the View Model contains illegal responsibilities. Either way, a dialog service obviously does not solve the problem. Moving code from the class named ...ViewModel to a class named ...Service, where the original class still has a reference to the new class, is doing nothing in terms of architecture. The code is still in the same component, referenced by a View Model class. Nothing has changed except the name of the class that shows the dialog. And giving a class a name does not change its nature. For example naming my data binding source MainView instead of MainViewModel does not make MainView part of the View.
The class naming or naming conventions in general are absolutely irrelevant in terms of architecture, in terms of MVVM. The responsibilities and dependencies are the matter of interest.

Here are the dependencies introduced by a dialog service which is operated by the View Model:

enter image description here

As you can see we now have an arrow (dependency) that points from the View Model towards the View. Now changes in the View will reflect to the View Model and impact implementations. It is because the View Model is now involved in the View logic - in this special case the user interaction logic. User interaction logic is GUI, is View. Controlling this logic from the View Model screams "MVVM violation"...
It's fine if you can accept this violation. But it is a violation of the MVVM design pattern and can't be sold as a "MVVM way of showing dialogs". At least we should not buy it.

BionicCode
  • 1
  • 4
  • 28
  • 44
  • I don't see how I violated MVVM principles. I keep ViewModel free from user interaction by supplying it a Service which is doing exactly that. When I decide to unit test my ViewModel, I can just supply a dummy Service that does nothing. The ViewModel doesn't care what the Service does. It only call the Service's methods and expect the results. It doesn't care if the Service shows dialogs or not. – Technical Jul 21 '19 at 09:09
  • You are hiding it. Just because the view model isn't explicitly showing it doesn't remove the dependency. The service is _used_ by the view model. The graph would look like this: view model -> service -> control. Since service operates in the domain of the view the view model has a dependency on the view. You just extracted the dialog logic from the view model and put it into another class that the view model still must know. It should be: control -> service -> view model. Dialog logic (when, why, which and how to show) is not view model responsibility. It's view logic. – BionicCode Jul 21 '19 at 09:21
  • "The ViewModel doesn't care what the Service does. It only call the Service's methods and expect the results." This is the point: MVVM describes the dependency as follows: the view model only calls operations on the model. So the model is the only target to query data. Beside this, the view model is absolutely passive. It waits for bindings to update and for commands to be invoked nothing else. Imagine the view and view model would reside in a different DLL. Now when you need to show a different dialog you would have to modify the "ViewModel.dll". – BionicCode Jul 21 '19 at 09:22
  • In other words: the view model never handles view: it never changes visibility of controls because it should never know that there are controls at all. View model is not a class but an abstract domain of responsibilities. All classes that support the responsibilities of the view model (e.g. a data model converter) are part of the view model. No dependency arrow must ever point from the view model to the view. – BionicCode Jul 21 '19 at 09:24
  • 1
    Then why are there so many articles over the internet (including this site) suggesting this approach? – Technical Jul 21 '19 at 09:36
  • I think if you understand the dependencies and the goal of MVVM which is to eliminate dependencies between the view and the model and the view and the view model, then you can judge whats true. There are tons of wrong articles and wrong books out there. I have encountered so many school books that are wrong and full of mistakes. To find an article that matches your vision is not a proof of their correctness. – BionicCode Jul 21 '19 at 09:44
  • You should know the dependency graph of MVVM. I updated my answer to show the dependencies introduced by your service solution. This graphs speak the truth no matter what article you find that encourages your design style. (By the way, I think you missed to include the URL) – BionicCode Jul 21 '19 at 09:44
  • I didn't include any URL because you can just google "MVVM Dialog Service" and there will be more than enough articles. So I'm willing to try your approach. If I understand, you are telling me that ViewModel should communicate to a View and the View should open new Views, Dialogs, etc. And I have to questions: 1. How a ViewModel should communicate with a View? 2. This sounds like there will be some code in the Code Behind which is generally not a desirable thing in MVVM. – Technical Jul 21 '19 at 09:53
  • 2
    The need to use a dialog service is because when people are obsessed by the No-Code-Behind idiom. Their perception is that code-behind violates MVVM. When MVVM is about dependencies, then how can code-behind violate this pattern? When you take a look on how Microsoft implements their controls than you will se tons of code-behind. This is because XAML is powerful but doesn't cover all use cases. Sometimes you need to define dependency properties or routed events. Sometimes you have to implement complex logic and behavior that can't be done in XAML only. WPF by design makes XAML rely on C#. – BionicCode Jul 21 '19 at 09:57
  • I never said that the view model should communicate with the view. I am saying the OPPOSITE the whole time. I said view model only communicates with the model. View communicates with the view model. Only this direction. That's what I said. That's what the graphic is saying. – BionicCode Jul 21 '19 at 10:00
  • It is a clever thing to enforce the use of XAML and favor it over C# (when it comes to view). The XAML parser and the declarative markup are making implementations a lot easier and improve readability. This is it. E.g. setting up bindings in code-behind is not recommended since doing it in XAML is easier and more readable. You need to know the framework, then you know the limitations especially of XAML. Then you know when to switch to C# (or code-behind). The logic of handling dialogs is one scenario. – BionicCode Jul 21 '19 at 10:04
  • There are other techniques. You can use a `Popup` to show a dialog using a binding on `IsOpen` or use a `Collapsed` `Grid` and make it `Visible` using triggers. Add a `Grid` to the `Popup` that is transparent and stretches over your main window and it is modal too. You can handle results via bindings or commands. Most of the time this is sufficient. – BionicCode Jul 21 '19 at 10:10
  • @Technical I updated my answer and added an example of a dialog that can be shown and hidden using XAML only. The dialog result can be passed on using binding or commanding. It's just a raw example to show a way how you could handle dialogs without using any C#. Please feel free to test it yourself. – BionicCode Jul 21 '19 at 11:23
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/196764/discussion-between-technical-and-bioniccode). – Technical Jul 21 '19 at 11:27
  • Sorry to jump in at the very end of the discussion, it seems that you have worked out all the details. I'm happy over your entusiasm on the issue, and in my opinion, writing in code behind or using a dialog service are both acceptable solutions. The goal of MVVM is not to adhere to some set of rules, the goal of MVVM is to provide you with an architecture that doesn't prevent you from doing what you find valuable, whether that is view model unit tests or something else. – FantasticFiasco Jul 22 '19 at 20:59