3

I've created my first MVVMLight project, and I've a question:

I've a button, on which is bound a command. When the command execute, in different use cases, I've to get/give information to the enduser, like:

  • Ask where the project should be saved if the project is new
  • Give a confirmation that everything has been correctly saved

I know that I can do a MessageBox.Show/... but where? Because in regards of the separation of concerns I guess it should be in the ViewModel? So what is the mecanism in place that I should use for this?

My ViewModel is basically like this:

public class MainViewModel : BaseViewModel
{
    private static readonly Logger m_logger = LoggerProvider.GetLogger("MyPath.MainViewModel");
    private ISerializationService m_serializationService;
    public ICommand TrySaveCommand { get; set; }

    //Lot of other fields here

    public MainViewModel()
    {
        m_serializationService = ServiceLocator.Current.GetInstance<ISerializationService>();
        TrySaveCommand = new RelayCommand(TrySave);
    }
    private void TrySave()
    {
        DispatcherHelper.RunAsync(() =>
        {           
            //Here I need to get the path where I save on some condition 
            m_serializationService.SaveProject(pathIGotFromTheUser);
            //Give a feedback that everything has been correctly saved(for test purpose, a MessageBox.Show() )
        });
    }
}

So how should I do to get information from the user on the file to save? (with SaveFileDialog ) and display that it has correctly been saved (with MessageBox.Show)

Thank you

J4N
  • 19,480
  • 39
  • 187
  • 340
  • Have you had a look at the [Messnger](http://www.codeproject.com/Tips/696340/Thinking-in-MVVMLight-Messenger)? HTH – XAMlMAX Jan 23 '15 at 08:09
  • you can simply create and use a service http://stackoverflow.com/questions/3801681/good-or-bad-practice-for-dialogs-in-wpf-with-mvvm – blindmeis Jan 23 '15 at 08:13
  • @blindmeis I taught a service was more of a Business operation, but it makes totally sense. But typically, SHowing a windows would require to know the parent windows to prevent the user to just get the focus to the application – J4N Jan 23 '15 at 08:53
  • @XAMlMAX I'm not sure to understand how this should be used in my context? Can you propose a solution? Because Messages are used to communicate between ViewModels, right? So how does it solve the separation of concerns between the GUI Stuff(the fact that I use a `MessageBox` and not a custom usercontrol) and the view model? – J4N Jan 23 '15 at 08:56
  • Mediator pattern implies communication between actors in a particular system. Not `ViewModels` only, you can use it for your UI as well. besides if your `View` has a bit of code behind that will not affect `ViewModel` I wouldn't bother and just use the code-behind, that's just my personal opinion. HTH – XAMlMAX Jan 23 '15 at 09:07
  • Did i understood this correct. When we click on the button appears SaveDialog, where we get path to save. Then we are saving something by this path and show message to user? – RenDishen Jan 23 '15 at 09:15
  • It's the goal, but I feel a little dirty to put this in the ViewModel, since it's really a presentation matter(maybe one day I will use another UserControl than the ShowMessageBox) – J4N Jan 23 '15 at 13:38

3 Answers3

4

Laurent Bugnion in his mvvmlight library introduced a very handy helper class called Messenger, using it you can send and receive notifications and/or information between the viewmodels, views or viewmodel/view. here how it works

  • using the Messenger.Default.Send<..>(..) the viewmodel broadcast a message,
  • that message will be intercepted by the any view or viewmodel registered to it (using Messenger.Default.Register<>(..)) and based on that notification an appropriate logic is executed (showing message or a Dialog box for example ..)

to apply this in your case you must add some view related logic to your code behind, to show DialogueBox and confirmation messages. MainWndow.xaml.cs

 public partial class MainWindow : Window
{

    public MainWindow()
    {            
        Messenger.Default.Register<NotificationMessage>(this, (m) =>
        {
            switch (m.Notification)
            {
                case "SaveFile":
                    var dlg = new SaveFileDialog();
                    if (dlg.ShowDialog() == true)
                    {
                        var filename = dlg.FileName;
                        Messenger.Default.Send<String>( filename,"FileSaved");
                    }
                    break;
                case "WentWell":
                    MessageBox.Show("Everything went well Wohoo");
                    break;

            }
        });
    }
}

here the view will show a dialog box or a confirmation message box based on the broadcast-ed ViewModel notification
and in the MainWindowViewModel

 public class MainViewModel : ViewModelBase
{
    private static readonly Logger m_logger = LoggerProvider.GetLogger("MyPath.MainViewModel");
    private ISerializationService m_serializationService;

    private RelayCommand _trySaveCommand;
    public RelayCommand TrySaveCommand
    {
        get
        {
            return _trySaveCommand
                ?? (_trySaveCommand = new RelayCommand(
                () =>
                {                        
                    Messenger.Default.Send(new NotificationMessage("SaveFile"));    
                }));
        }
    }

    public MainViewModel()
    {
        m_serializationService = ServiceLocator.Current.GetInstance<ISerializationService>();
        Messenger.Default.Register<string>(this, "FileSaved", (pathIGotFromTheUser) =>
        {
            m_serializationService.SaveProject(pathIGotFromTheUser);
            //Give a feedback that everything has been correctly saved(for test purpose, a MessageBox.Show() )
            Messenger.Default.Send<NotificationMessage>(new NotificationMessage("WentWell"));
        });           
    }

the TrySaveCommand associated to a button or whatever will trigger the view.

**and before you said it, i don't believe that you are violating any mvvm rule by doing that, showing message boxes or dialogues is a presentation related logic and should be handled in the Views part of your solution; the view model by sending a message don't actually know any think about the view, he simply do some work and broadcast a status message, and finally here some in depth information about the Messenger Class HERE.

SamTh3D3v
  • 9,854
  • 3
  • 31
  • 47
0

Your question regarding SoC has essentially two aspects: 1. How do you trigger UI interactions (e.g. show a messagebox) from the view model (which is meant to be UI free). 2. How do you separate the different incarnations of your current demand, e.g. show a confirmation in one place, and not in another.

The first question boils down to "hide UI interactions behind an interface". It has been addressed by other commenters, so I won't go into details here.

Regarding the second question: You can of course put the actual calls to the messagebox (meaning an respective interface call, but let's keep it simple) in your command and use some condition to decide whether to call or not. In general this approach has two possible issues attached: - Your command "gains weight", as it has to function in every context, thus accumulating code and logical dependencies. - Your logic of "show confirmation" and other stuff may be usefull in other contexts as well, but you cannot reuse it.

A possible answer to these issues is "chaining of commands". I.e. have a "ConfirmationCommand" (object or method) that takes message and delegates or commands for follow-up activities. It shows a message box and calls one delegate or the other, depending on the button click. In one context you can simply use your SaveCommand, in another you use your ConfirmationComand and a SaveCommand attached to it. This way you build your acuall command logic from chains of smaller commands, which in turn become more generic, less dependent from context and thus more reusable. Of course there is more to it, such as the question of passing parameters, but this should be enough to give you a general idea of the approach,

HIH,

AJ.NET
  • 1
  • 1
-1

Create a "Dialog Service" class that has a public ShowMessage method which shows a MessageBox.

Then, extract an interface from this class and use it as a member in your viewmodel. Using dependency injection you can inject it, or even better, let an IOC container like Unity inject it into your viewmodel. This way the dialog service will be instantiating the message box. If you are create unit tests, you can mock the dialog service, by instantiating a new dialog service from the unit test.

EDIT

Example:

private RelayCommand<Window> doSomethingCommand;

    public RelayCommand<Window> DoSomethingCommand
    {
        get
        {
            return doSomethingCommand
                ?? (doSomethingCommand= new RelayCommand<Window>(
                                      window =>
                                      {
                                         dialogService.SaveFileDialog.ShowDialog(window);
                                         // save the file
                                      }));
        }
    }

The window parameter should be bound in the XAML as a commandParameter to the window Element like this:

 <Button Content="" Command="{Binding DoSomethingCommand, Mode=OneWay}" 
         CommandParameter="{Binding ElementName=window, Mode=OneWay}"/>

2nd EDIT

Here's how you can bind to the window from a UserControl:

CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, 
AncestorType={x:Type Window}}}"
Community
  • 1
  • 1
Igor
  • 1,532
  • 4
  • 23
  • 44
  • How do I get a reference on the current windows? Which is required for my usecase to call the `SaveFileDialog.ShowDialog(parentWindow)`. I'm using the default IOC provided with MVVMLight currently. – J4N Jan 28 '15 at 13:50
  • Pass it as a command parameter. You can use the `RelayCommand` with parameter in the `MVVMLight toolkit`. – Igor Jan 28 '15 at 16:12
  • Could you provide an example of that? I don't see how I can get the active windows in the `RelayCommand` – J4N Jan 29 '15 at 06:09
  • @J4N I've edited the answer. It has an example of a relay command with a parameter now. – Igor Jan 29 '15 at 20:49
  • Thank you! I'm in an usercontrol, how do I get from the XAML a reference to the windows? – J4N Jan 30 '15 at 06:46
  • @J4N You should do your own research instead of asking for code, but anyways, I've edited the answer one more time. – Igor Jan 30 '15 at 09:24