Rahul Rathod
Rahul Rathod

Reputation: 115

Confirmation Window In MVVM design pattern

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();
   }
}

Upvotes: 3

Views: 6840

Answers (5)

Liero
Liero

Reputation: 27348

  • 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.

Upvotes: 3

Breeze
Breeze

Reputation: 2058

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.

Upvotes: 1

Glen Thomas
Glen Thomas

Reputation: 10744

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

Upvotes: 0

Philip Stuyck
Philip Stuyck

Reputation: 7467

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.

Upvotes: 11

Paradise228
Paradise228

Reputation: 717

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.

Upvotes: 0

Related Questions