Reputation: 193472
I've got a WPF application which calls MessageBox.Show() way back in the ViewModel (to check if the user really wants to delete). This actually works, but goes against the grain of MVVM since the ViewModel should not explicitly determine what happens on the View.
So now I am thinking how can I best implement the MessageBox.Show() functionality in my MVVM application, options:
I could have a message with the text "Are you sure...?" along with two buttons Yes and No all in a Border in my XAML, and create a trigger on the template so that it is collapsed/visible based on a ViewModelProperty called AreYourSureDialogueBoxIsVisible, and then when I need this dialogue box, assign AreYourSureDialogueBoxIsVisible to "true", and also handle the two buttons via DelegateCommand back in my ViewModel.
I could also somehow try to handle this with triggers in XAML so that the Delete button actually just makes some Border element appear with the message and buttons in it, and the Yes button did the actually deleting.
Both solutions seem to be too complex for what used to be a couple lines of code with MessageBox.Show().
In what ways have you successfully implemented Dialogue Boxes in your MVVM applications?
Upvotes: 49
Views: 26277
Reputation: 31
I've made a simple MessageBox wrapper control for us to use in pure MVVM solution and still allowing unit testing capability. The details are in my blog Link
mukapu
Upvotes: 1
Reputation: 13
The idea is not by putting a service or the like that handles the MessageBox on the view model. It still violates MVVM, as the view model still handles the displaying of MessageBox indirectly. The view model should know nothing about the view and focus on managing data. And it's the responsibility of the View to manage the displaying of a view component.
So you have to make the view observe the view model, and then do the UI logic on the view based on the observation. One way to achieve that is by listening to the ViewModel's event and show the MessageBox based on the argument of the event.
This is just one example on showing how you can achieve this, and you can customize and leverage it for your own need and purpose:
MainWindow.xaml
<Window x:Class="MessageBoxMVVMExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MessageBoxMVVMExample"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<TextBox Text="{Binding Input}"/>
<Button Content="Submit" Command="{Binding SubmitCommand}"/>
</StackPanel>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private BaseViewModel _viewModel = new MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = _viewModel;
_viewModel.NewMessageDialog += OnNewMessageDialog;
}
private void OnNewMessageDialog(MessageDialogEventArgs args)
{
// you can also put it on a service to achieve decoupling
MessageBox.Show(args.Message, args.MessageType.ToString());
}
}
MainWindowViewModel.cs
public class MainWindowViewModel : BaseViewModel
{
private string _input = String.Empty;
public string Input
{
get => _input;
set
{
_input = value;
OnPropertyChanged(new(nameof(Input)));
}
}
public RelayCommand SubmitCommand => new(o => ValidateNumeric());
public void ValidateNumeric()
{
decimal dec;
if (decimal.TryParse(Input, out dec))
{
OnNewMessageDialog(new(MessageType.SUCCESS, "Yes, it's a valid number!"));
}
else
{
OnNewMessageDialog(new(MessageType.ERROR, "No, it's an invalid number!"));
}
}
}
BaseViewModel.cs
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public event MessageDialogEventHandler? NewMessageDialog;
protected void OnPropertyChanged(PropertyChangedEventArgs args)
{
PropertyChanged?.Invoke(this, args);
}
protected void OnNewMessageDialog(MessageDialogEventArgs args)
{
NewMessageDialog?.Invoke(args);
}
}
public enum MessageType {
INFO,
ERROR,
WARNING,
SUCCESS
}
public class MessageDialogEventArgs
{
public MessageType MessageType { get; set; } = MessageType.INFO;
public string Message { get; set; } = string.Empty;
public MessageDialogEventArgs(MessageType messageType, string message)
{
MessageType = messageType;
Message = message;
}
}
public delegate void MessageDialogEventHandler(MessageDialogEventArgs messageDialogEventArgs);
RelayCommand.cs
public class RelayCommand : ICommand
{
private Action<object> _callback;
private Func<object, bool>? _canExecute;
public event EventHandler? CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> callback, Func<object, bool>? canExecute)
{
this._callback = callback;
this._canExecute = canExecute;
}
public RelayCommand(Action<object> callback) : this(callback, null) { }
public bool CanExecute(object? parameter)
{
return this._canExecute == null || this._canExecute(parameter);
}
public void Execute(object? parameter)
{
this._callback(parameter);
}
}
Upvotes: 0
Reputation: 121
Working fine [MVVM]
if (System.Windows.MessageBox.Show("Are you need to delete?", "",
System.Windows.MessageBoxButton.YesNo) == System.Windows.MessageBoxResult.Yes)
{
// Do your action
}
Upvotes: -2
Reputation: 9109
I just create an interface (IMessageDisplay or similar) which gets injected into the VM, and it has methods like a MessageBox (ShowMessage() etc). You can implement that using a standard messagebox, or something more WPF specific (I use this one on CodePlex by Prajeesh).
That way everything's separated and testable.
Upvotes: 5
Reputation: 1360
There is so many answers on this topic that vary from creating a custom class to using third party libraries. I would say use a third party library if you want cool pop ups with nice visuals.
But if you just want to use the regular message box from microsoft for your WPF app here is an MVVM/unit test friendly implementation:
Initially I thought I would just inherit from message box and wrap it with an interface but I couldn't due to Message box not having a public constructor, so here is the "easy" solution:
Decompiling Message box in visual studio you can see all the method overloads, I checked which ones I wanted then created a new class and added the methods, wrapped it with an interface and ta-da! Now you can use ninject to bind the interface and class, inject it and use Moq to unit test e.t.c.
Create an interface (only added a few of the overloads as I don't need them all):
public interface IMessageBox
{
/// <summary>Displays a message box that has a message, title bar caption, and button; and that returns a result.</summary>
MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button);
/// <summary>Displays a message box that has a message, title bar caption, button, and icon; and that returns a result.</summary>
MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon);
/// <summary>Displays a message box that has a message and title bar caption; and that returns a result.</summary>
MessageBoxResult Show(string messageBoxText, string caption);
}
Then we have the class that will inherit from it:
public class MessageBoxHelper : IMessageBox
{
/// <summary>Displays a message box that has a message, title bar caption, button, and icon; and that returns a result.</summary>
public MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button,
MessageBoxImage icon)
{
return MessageBox.Show(messageBoxText, caption, button, icon, MessageBoxResult.None,
MessageBoxOptions.None);
}
/// <summary>Displays a message box that has a message, title bar caption, and button; and that returns a result.</summary>
public MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button)
{
return MessageBox.Show(messageBoxText, caption, button, MessageBoxImage.None, MessageBoxResult.None,
MessageBoxOptions.None);
}
/// <summary>Displays a message box that has a message and title bar caption; and that returns a result.</summary>
public MessageBoxResult Show(string messageBoxText, string caption)
{
return MessageBox.Show(messageBoxText, caption, MessageBoxButton.OK, MessageBoxImage.None,
MessageBoxResult.None, MessageBoxOptions.None);
}
/// <summary>Displays a message box that has a message and that returns a result.</summary>
public MessageBoxResult Show(string messageBoxText)
{
return MessageBox.Show(messageBoxText, string.Empty, MessageBoxButton.OK, MessageBoxImage.None,
MessageBoxResult.None, MessageBoxOptions.None);
}
}
Now just use this when injecting e.t.c and boom u have a flimsy abstraction that will do the trick... which is fine depending on where you will use it. My case is a simple app only meant to do a few things, so no point over engineering a solution. Hope this helps someone.
Upvotes: 2
Reputation: 6962
Of the two you mention, I prefer option #2. The Delete button on the page just makes the "Confirm Delete Dialog" appear. The "Confirm Delete Dialog" actually kicks off the Delete.
Have you checked out Karl Shifflett's WPF Line Of Business Slides and Demos? I know he does something like this. I'll try to remember where.
EDIT: Check out Demo #11 "Data Validation in MVVM" (EditContactItemsControlSelectionViewModel.DeleteCommand). Karl calls a popup from the ViewModal (What!? :-). I actually like your idea better. Seems easier to Unit Test.
Upvotes: 5
Reputation: 5675
To expand on Dean Chalk's answer now that his link is kaput:
In the App.xaml.cs file we hook up the confirm dialog to the viewmodel.
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var confirm = (Func<string, string, bool>)((msg, capt) => MessageBox.Show(msg, capt, MessageBoxButton.YesNo) == MessageBoxResult.Yes);
var window = new MainWindowView();
var viewModel = new MainWindowViewModel(confirm);
window.DataContext = viewModel;
...
}
In the view (MainWindowView.xaml) we have a button that calls a command in the ViewModel
<Button Command="{Binding Path=DeleteCommand}" />
The viewmodel (MainWindowViewModel.cs) uses a delegate command to show the "Are you sure?" dialog and perform the action. In this example it is a SimpleCommand
similar to this, but any implementation of ICommand should do.
private readonly Func<string, string, bool> _confirm;
//constructor
public MainWindowViewModel(Func<string, string, bool> confirm)
{
_confirm = confirm;
...
}
#region Delete Command
private SimpleCommand _deleteCommand;
public ICommand DeleteCommand
{
get { return _deleteCommand ?? (_deleteCommand = new SimpleCommand(ExecuteDeleteCommand, CanExecuteDeleteCommand)); }
}
public bool CanExecuteDeleteCommand()
{
//put your logic here whether to allow deletes
return true;
}
public void ExecuteDeleteCommand()
{
bool doDelete =_confirm("Are you sure?", "Confirm Delete");
if (doDelete)
{
//delete from database
...
}
}
#endregion
Upvotes: 5
Reputation: 2992
I recently came across this problem where I had to replace the MessageBox.Show in the ViewModels with some fully MVVM complaint message box mechanism.
To achieve this I used InteractionRequest<Notification>
and InteractionRequest<Confirmation>
along with interaction triggers and wrote my own Views for the message box.
What I've implemented is published here
Upvotes: 0
Reputation: 66637
Just in case anyone else is still reading and unsatisfied:
I just wanted to handle 'notification' type MessageBoxes (i.e. I don't care about the DialogResult
), but the problem that I have with most solutions I've read about is that they seem to indirectly force you to choose your View implementation (that is, currently I have a MessageBox.Show
, but if I later decide to just fiddle with the visibility of a hidden panel directly in my View, that won't mesh very nicely with an INotification
interface passed in to the ViewModel).
So I went for quick and dirty:
The ViewModel has a string NotificationMessage
property, with changes notified to PropertyChanged
.
The View subscribes to PropertyChanged
, and if it sees the NotificationMessage
property come through, does whatever it wants.
OK, so this means the View has code-behind, and the name of PropertyChanged
is hard-coded, but it would be hard-coded in the XAML anyway. And it means I avoid all the stuff like converters for Visibility, and properties to say whether the notification is still visible or not.
(Admittedly this is just for a limited use-case (fire and forget), I haven't given much thought to how I might want to extend it.)
Upvotes: 3
Reputation: 21855
What about raising an Event like "MessageBoxRequested"
handled in the codebehind of the View (anyway it's View only code so I don't see any problem with having this code on the codebehind).
Upvotes: 3
Reputation: 2114
WPF & Silverlight MessageBoxes
MVVM supported
http://slwpfmessagebox.codeplex.com/
Upvotes: 1
Reputation:
I would just throw it from the VM. I dont want to have to use someone else's service or write my own just to throw a messagebox.
Upvotes: 0
Reputation: 2788
Services to the rescue. Using Onyx (disclaimer, I'm the author) this is as easy as:
public void Foo()
{
IDisplayMessage dm = this.View.GetService<IDisplayMessage>();
dm.Show("Hello, world!");
}
In a running application, this will indirectly call MessageBox.Show("Hello, world!"). When testing, the IDisplayMessage service can be mocked and provided to the ViewModel to do what ever you want to accomplish during the test.
Upvotes: 13