0

I am currently building an application where I currently use MessageBox.Show() in the following way in the ConnectionHandler Model,

if (MessageBox.Show("Question", "Window Title", 
                    MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
  // User clicked yes
  // do stuff...
}
else
{
  // User clicked no
  // do other stuff..
}

This is to initialize a connection to a backend.

But my issue is that according to MVVM you cannot modify/take input from view in this way.

So what I did then is that I followed the answer here: how to show Messagebox in MVVM

which led me to this in my MainViewModel,

public event EventHandler<MvvmMessageBoxEventArgs> MessageBoxRequest;
protected void MessageBox_Show(Action<MessageBoxResult> resultAction, string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.None, MessageBoxOptions options = MessageBoxOptions.None) 
{
  if (this.MessageBoxRequest != null)
  {
    this.MessageBoxRequest(this, new MvvmMessageBoxEventArgs(resultAction, messageBoxText, caption, button, icon, defaultResult, options));
  }
}

And then I listen to the MessageBoxRequest in my View

Which kind of works, but I struggle to actually use it from my Model in the same way I did before, since this solution takes a function that gets triggered when the MessageBox is clicked away, instead of just locking, waiting for input then returning the data to be used in the if statement.

I am new to MVVM so please, if possible, explain it in more general programming terms, not MVVM specific ones.

Linus
  • 19
  • 1
  • 1
    Does this answer your question? [How have you successfully implemented MessageBox.Show() functionality in MVVM?](https://stackoverflow.com/questions/1098023/how-have-you-successfully-implemented-messagebox-show-functionality-in-mvvm) – Tarazed Dec 12 '22 at 16:56
  • this has another really good explanation. Using an OpenFileDialog, but similar concept https://stackoverflow.com/questions/1043918/open-file-dialog-mvvm – jgrmn Dec 12 '22 at 20:25
  • Your solution to raise an event which the View can observe, interpret to then decide how to handle it, for example by showing a dialog, is perfectly fine. BUT your view model should not generate messages as this is the responsibility of the View (user interaction). The View Model should also not expect and wait for a result (from the View). The event must be fire-and-forget. If the View Model can't complete an action e.g. because of invalid data, then this action must be aborted (this is also how data validation is designed) – BionicCode Dec 13 '22 at 11:55
  • Also the Model should have no responsibility to raise such an event. Usually you would use View Model data validation by implementing `INotifyDataErrorInfo` in order to ensure data integrity. In other words, if you manage to avoid passing invalid data to the Model, then there is no need for the Model to require user interaction. At least, related error events must be observed by the View Model which can pass it to the View by raising its own events. – BionicCode Dec 13 '22 at 11:59
  • But following the MVVM pattern properly would make such a requirement obsolete. This is where the strentgh off MVVM is. The decoupling of View and View Model is 100% unless you let the View bleed into your application. – BionicCode Dec 13 '22 at 11:59

1 Answers1

1

You need to abstract the service into an interface which you then inject to the ViewModel through the constructor. The ViewModel then simply calls a method in the interface without caring for the implementation (concrete) details.

See a simplified example below.

The interface:

public interface IMessageService
{
    bool Show(string title, string message);
} 

A concrete MessageBox implementation.

public sealed class MessageService : IMessageService
{
    public bool Show(string title, string message)
    {
        return MessageBox.Show(message, title, 
                    MessageBoxButton.YesNo) == MessageBoxResult.Yes;
    }
}

The ViewModel where you inject the service in the constructor.

public sealed class MainViewModel : INotifyPropertyChanged
{
    private readonly IMessageService _messenger;

    public MainViewModel(IMessageService messenger)
    {
        _messenger = messenger;
    }
}

Then, through a command in your ViewModel you call the service.

if (_messenger.Show("Title", "Message"))
{
    // do work
}

Lastly, you can change the implementation details of the IMessageService to load a custom form for example instead of the messagebox, or a hardcoded true/false for testing.

Kostas K.
  • 8,293
  • 2
  • 22
  • 28
  • What you suggest is called dependency inversion - a class design principle. MVVM is not about dependency inversion and not about class level dependencies. It's an *architectural* design pattern that is concerned with *application level* dependencies and responsibilities. I assume because developers tend to name their classes of the View Model component `...ViewModel` they are tricked to believe that MVVM deals with classes. But View, View Model and Model are application level components. The View Model actually contains classes that are not reasonable to name the with the `ViewModel` prefix. – BionicCode Dec 13 '22 at 12:20
  • The View Model is not responsible to control View related logic. It's the knowledge about the UI and user that violates MVVM not the access to a control (although the access requires this problematic knowledge). The problem is not that the View Model knows implementation details. The problem is the knowledge and responsibility that is required to participate in the user interaction. In order to decouple the View from the Model the View Model is introduced. – BionicCode Dec 13 '22 at 12:21
  • In order to keep the coupling between View Model and View and View Model and Model as loose as possible the dependency of those components is defined as unidirectional by the MVVM pattern. The View Model must not actively participate in the View in order to conform with the MVVM constraints. MVVM is only possible because of a technology like data binding which allows the View Model to passively send data to an unknown target with unknown responsibilities. This technology is mandatory because design principles like dependency inversion do not solve this problem. – BionicCode Dec 13 '22 at 12:21
  • Knowing that data binding is the essential ingredient to realize MVVM, we should use solutions that favor the design advantages of the existing binding infrastructure. Implementing `INotifyDataErrorInfo` is a very convenient way to use the binding engine in order to signal the View to display errors. A dialog is a tool to allow the application to communicate with the user. Allowing the View Model to create and show user messages implies that the View Model depends on the user (or user interaction). But user interaction is the responsibility of the View. – BionicCode Dec 13 '22 at 12:22
  • The "service" you have created should actually be used by the View and not by the View Model. It's recommended to change the class level design in order to fix the application level dependency violations. For example implementing data validation (`INotifyDataErrorInfo`) will eliminate 99% of the requirement of the View Model to depend on user input in order to complete an operation. The other 1% is proper exception handling (for example to test the pre-conditions of a critical operation like checking if a file exists). – BionicCode Dec 13 '22 at 12:23
  • If you design your View Model to only accept data following rules of data integrity and exception handling, you can design the flow to avoid starting operations using invalid data or abort such operations (instead of *Waiting*). Since `INotifayDataErrorInfo` is invoked by the framework (the binding engine) it is the best candidate in order to let the View Model dispatch data related errors efficiently without violating MVVM. – BionicCode Dec 13 '22 at 12:23
  • I mean, MVVM wouldn't be a deal if all it takes is the Dependency Inversion Principle. This makes MVVM so tricky to implement, because the language must support a technology in the likes of data binding to allow this special coupling. If MVVM was about knowledge of concrete types, WPF would have simply introduced interfaces and abstract types to access/reference the e.g. Button control from your View Model code. In this case dependency inversion would have been everything you need: a principle and not a design pattern. Data binding is more than just cosmetics. In terms of MVVM it is essential. – BionicCode Dec 13 '22 at 12:34
  • I made a mistake: *"The View Model actually contains classes that are not reasonable to name them with the ViewModel **prefix**."* - It must be "*[...] with the `ViewMode` __suffix__*. – BionicCode Dec 13 '22 at 12:38