3

Hi i want to show confirmation window on button click. i am trying to develop using MVVM design pattern i have achieved it but i dont think Calling view in viewModel
is proper way of doing this. I have attached code with this please go through it is it correct way or not

<Window x:Class="MessegeBox_Demo_2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
        <Grid>
            <Button Content="Ckick ME" HorizontalAlignment="Left" 
                    Command="{Binding GetMessegeboxCommand}"
                    Margin="200,131,0,0" VerticalAlignment="Top" Width="75"/>

        </Grid>
    </Window>

public class MainWindowViewModel : ViewModelBaseClass
{
   private ICommand _getMessegeboxCommand;
   public ICommand GetMessegeboxCommand
   {
      get
      {
          return _getMessegeboxCommand ?? (_getMessegeboxCommand = new MessegeBox_Demo_2.Command.realyCommand(() => ShowUsercontrol(), true));
      }
   }
   private void ShowUsercontrol()
   {
      MessegeBox_Demo_2.View.Window1 mbox = new View.Window1();
      mbox.ShowDialog();
   }
}
Artem Kulikov
  • 2,250
  • 19
  • 32
Rahul Rathod
  • 115
  • 3
  • 12
  • 2
    You can either implement a [dialog service](http://stackoverflow.com/a/28710441/220636) to open the dialog or use [messages](http://stackoverflow.com/a/31161068/220636) to achieve a clean separation of concerns in a MVVM way. – nabulke Aug 06 '15 at 08:29
  • There are several question here about how to display a new view (e.g. a dialog window) in response to a ViewModel event. What you've done already is fine, providing it works. From a purist view, you could take a look at using DependencyProperties to provide the View with a mechanism for detecting that the ViewModel wants a confirmation window without the VeiwModel having to call into teh View directly. – Evil Dog Pie Aug 06 '15 at 08:31

5 Answers5

10

The simplest way is to implement a dialogservice and use dependency injection to inject the service into to the viewmodel. It is ok to have a dependency to an interface but not to have a dependency to a concrete implementation.
Following is the interface I use:

namespace DialogServiceInterfaceLibrary
{
    public enum MessageBoxButton  
    {
    // Summary:
    //     The message box displays an OK button.
    OK = 0,
    //
    // Summary:
    //     The message box displays OK and Cancel buttons.
    OKCancel = 1,
    //
    // Summary:
    //     The message box displays Yes, No, and Cancel buttons.
    YesNoCancel = 3,
    //
    // Summary:
    //     The message box displays Yes and No buttons.
    YesNo = 4,
    }

    public enum MessageBoxResult
    {
    // Summary:
    //     The message box returns no result.
    None = 0,
    //
    // Summary:
    //     The result value of the message box is OK.
    OK = 1,
    //
    // Summary:
    //     The result value of the message box is Cancel.
    Cancel = 2,
    //
    // Summary:
    //     The result value of the message box is Yes.
    Yes = 6,
    //
    // Summary:
    //     The result value of the message box is No.
    No = 7,
    }

    // Summary:
    //     Specifies the icon that is displayed by a message box.
    public enum MessageBoxIcon
    {
    // Summary:
    //     No icon is displayed.
    None = 0,
    //
    // Summary:
    //     The message box contains a symbol consisting of white X in a circle with
    //     a red background.
    Error = 16,
    //
    // Summary:
    //     The message box contains a symbol consisting of a white X in a circle with
    //     a red background.
    Hand = 16,
    //
    // Summary:
    //     The message box contains a symbol consisting of white X in a circle with
    //     a red background.
    Stop = 16,
    //
    // Summary:
    //     The message box contains a symbol consisting of a question mark in a circle.
    Question = 32,
    //
    // Summary:
    //     The message box contains a symbol consisting of an exclamation point in a
    //     triangle with a yellow background.
    Exclamation = 48,
    //
    // Summary:
    //     The message box contains a symbol consisting of an exclamation point in a
    //     triangle with a yellow background.
    Warning = 48,
    //
    // Summary:
    //     The message box contains a symbol consisting of a lowercase letter i in a
    //     circle.
    Information = 64,
    //
    // Summary:
    //     The message box contains a symbol consisting of a lowercase letter i in a
    //     circle.
    Asterisk = 64,
    }

    public interface IDialogService
    {
        bool OpenFileDialog(bool checkFileExists,string Filter, out string FileName);
        void OpenGenericDialog(object Context,IRegionManager RegionManager);
    MessageBoxResult ShowMessageBox(string message, string caption, MessageBoxButton buttons, MessageBoxIcon icon);
    }

And the implementation :

public class DialogService : IDialogService
{
    public bool OpenFileDialog(bool checkFileExists, string Filter, out string FileName) 
    {
        FileName = ""; 
        OpenFileDialog openFileDialog = new OpenFileDialog();
        openFileDialog.Multiselect = false;
        //openFileDialog.Filter = "All Image Files | *.jpg;*.png | All files | *.*";
        openFileDialog.Filter = Filter;
        openFileDialog.CheckFileExists = checkFileExists;
        bool result = ((bool)openFileDialog.ShowDialog());
        if (result)
        {
            FileName = openFileDialog.FileName;
        }
        return result;
    }


    public void OpenGenericDialog(object Context,IRegionManager RegionManager)
    {
        GenericDialogWindow dlg = new GenericDialogWindow(Context,RegionManager);
        dlg.Owner = System.Windows.Application.Current.MainWindow;
        dlg.Show();
    }

    public MessageBoxResult ShowMessageBox(string message, string caption, MessageBoxButton buttons, MessageBoxIcon icon)
    {
        return (DialogServiceInterfaceLibrary.MessageBoxResult)System.Windows.MessageBox.Show(message, caption, 
            (System.Windows.MessageBoxButton)buttons,
            (System.Windows.MessageBoxImage)icon);
    }
}

Then inject the IDialogservice into the viewmodel. The Interface and concrete implementation are typically in a different assembly

MainWindowViewModel(IDialogService dialogservice){
    _dialogservice = dialogservice;
}

private void ShowUsercontrol()
{
   _dialogservice.ShowMessageBox(... //you get what i mean ;-)
}

The dialogservice is able to open standard windows dialogs like a file open dialog. The generic version can be used as well, but then you need to understand prism which is a bit more complex. Prism also allows using interaction requests to communicate from the viewmodel to the view. I prefer that way of working in prism, but if you don't know prism forget that remark. It is probably too complex for a simple confirmation window. As simple dialogservice like this is ideal for a simple confirmation window.

Having your dependencies injected via the constructor of the viewmodel, moves you already in the right direction of using a Inversion of control container. And allows for easier unit testing because you can mock the injected interfaces to check if they are called properly and so on.

Philip Stuyck
  • 7,344
  • 3
  • 28
  • 39
  • Thank you for your help , did i need to add any external DLL for this – Rahul Rathod Aug 06 '15 at 10:27
  • That depends on your requirements. If you want to be strict, there is a separate assembly for the interface and one for the implementation. You need to refer to the interface assembly. And if you don't use an IOC container you need a reference to the impl assembly as well. – Philip Stuyck Aug 06 '15 at 10:49
  • can you please explain this two point to me 1.IRegionManager RegionManager 2 . in which namesapace GenericDialogWindow is present – Rahul Rathod Aug 07 '15 at 11:00
  • that belongs to prism, you don't need the OpenGenericDialog just leave it out for your purposes. You only asked for a simple confirmation dialog that is the ShowMessageBox method. – Philip Stuyck Aug 07 '15 at 11:13
  • Thank you Now it works ...... first i got confused in IRegionManager ....Thanks again it helps lot bro.. – Rahul Rathod Aug 07 '15 at 11:44
  • I'm new to MVVM and I'm trying to understand how to inject this service into my viewmodel. I tried Philip's example and passed it as a parameter to my viewmodel's constructor but I get this error coming from its correlated view: ```The type "MainWindowViewModel" does not include any accessible constructors```. What am I doing wrong and what is the right way to inject the service? Many thanks. – user2430797 Jan 22 '20 at 23:06
  • Is your constructor public ? – Philip Stuyck Jan 30 '20 at 10:00
3
  • DialogService approach is more suitable in this scenario than messaging mechanism. It's more straightforward, easier to debug, easier to write, easier to understand, you don't need any 3rd party framework etc.

  • TIP: Dependency injection is nice, however you usually need quite a lot of services, like NavigationService, DialogService, NotificationService, etc, and if you need to inject them to a lot of viewmodels, the ctors becomes large, boring, repeating the same injections etc etc. Instead of dependency injection you can use any other "testable" approach.

Since DialogService will be the same in all viewmodels, you don't have to inject it necessarily to each viewmodel, but you can use some kind of AppContext class or service locator.

Example of ViewModelBase class:

public class ViewModelBase : BindableBase
{
      public virtual IDialogService DialogService 
      { 
         get { return AppContext.Current.DialogService; } 
      }
}

example of concrete viewmodel:

public class HomePageViewModel : ViewModelBase
{
      ...

      public void Cancel_CommandExecute()
      { 
         var dlgResult = DialogService.ShowMessageBox("Do you really want to discard unsaved changes?", "Confirm Exit", DialogButtons.YesNo);
         if (dlgResult != MessageBoxResult.Yes) return;
      }
}

Dialog Service:

public interface IDialogService
{
    MessageBoxResult ShowMessageBox(string messageBoxText, string caption = null, MessageBoxButton buttons = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.None);
}

public class DialogService : IDialogService
{
    public virtual MessageBoxResult ShowMessageBox(string messageBoxText, string caption = null, MessageBoxButton buttons = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.None)
    { 
       return MessageBox.Show(messageBoxText, caption, buttons, icon, defaultResult);
    }
}

In your tests you can mock either AppContext.Current, or you can override ViewModelBase.DialogService property.

Maybe it is not the cleanest way of mocking the DialogService, but it's a pragmatic approach. It makes your viewmodels code cleaner, more readable and maintainable since you don't have inject and store DialogService instance in each viewmodel. Your viewmodels are still decoupled from views, testable, blendable, etc.

Liero
  • 25,216
  • 29
  • 151
  • 297
  • Should the base class inherit from `BindableBase`? Let's say if someone wants a simple case without including the Prism? – mcy Jul 19 '18 at 13:44
1

You could define and fill the Binding in code behind. Since code behind is part of the View, calling a Messagebox in there does not break MVVM pattern. This way, you can show a confirm dialog before setting the value of a Binding.

The code you need in code behind is:

public partial class MainWindow : Window
{
    private DependencyProperty myDP;

    public MainWindow()
    {
        ...
        Binding myBinding = new Binding();
        myBinding.Path = new PropertyPath("myValue"); //myValue is a variable in ViewModel
        myBinding.Source = DataContext;
        myDP = DependencyProperty.Register("myValue", typeof(/*class or primitive type*/), typeof(MainWindow));
        BindingOperations.SetBinding(this, myDP, myBinding);
        ...
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        MessageBoxResult result = MessageBox.Show("Do you really want to do that?", "", MessageBoxButton.YesNo);
        if (result == MessageBoxResult.Yes
        {
            SetValue(myDP, /*value*/); //this sets the Binding value. myValue in ViewModel is set
        }
    }
}

To call the Button_Click-Method when the button is clicked, add

Click="Button_Click"

to the XAML-Definition of your button.

Breeze
  • 2,010
  • 2
  • 32
  • 43
  • 1. I cannot agree with statement "ViewModel and View should never communicate in any other way than Bindings". You should not create views or access in viewmodels, but you can access viewmodels from views. 2. Your apploach makes it impossible to test logic related to dialogs, e.g whether viewmodel shows the dialog or how it reacts to different answers – Liero Aug 06 '15 at 11:38
0

You should not (never) call UI methods inside your View Model if you want to stick to the MVVM Pattern.

The proper way to open/close a window from your ViewModel is to send a message to your View using the Messanger of MVVMLight or the EventAggregator of Prism.

EventAggregator allows your ViewModel to send a message to a list of subscribers. When you subscribe to a specific message you attach a function to execute.

I understand that you are learning the mechanism of this pattern so you could code your own EventAggregator and use it.

Paradise228
  • 717
  • 3
  • 16
  • You don't need an event mechanism just to show a confirmation dialog. Prism has different mechanism for that : InteractionRequests. – Philip Stuyck Aug 06 '15 at 08:38
0

I have used MVVM Light messaging for this. The PRISM library also provides a nice way of doing this.

To handle interactions triggered from the view model and interactions fired by controls located in the view the Prism library provides InteractionRequests and InteractionRequestTriggers, along with the custom InvokeCommandAction action. InvokeCommandAction is used to connect a trigger including events to a WPF command.

Create an InteractionRequest property in the ViewModel:

public InteractionRequest<IConfirmation> ConfirmationRequest { get; private set; }

Invoke the interaction like this:

private void RaiseConfirmation()
{
    this.ConfirmationRequest.Raise(
        new Confirmation { Content = "Confirmation Message", Title = "Confirmation" },
        c => { InteractionResultMessage = c.Confirmed ? "The user accepted." : "The user cancelled."; });
}

To use interaction requests you need to define the corresponding InteractionRequestTrigger in the view's XAML code:

<prism:InteractionRequestTrigger SourceObject="{Binding ConfirmationRequest, Mode=OneWay}">
    <prism:PopupWindowAction IsModal="True" CenterOverAssociatedObject="True"/>
</prism:InteractionRequestTrigger>

See Interactivity QuickStart Using the Prism Library 5.0 for WPF

Glen Thomas
  • 10,190
  • 5
  • 33
  • 65