In the MVVM world, things like message boxes (MessageBox.Show) and Dialogs (open file, save file etc), don’t naturally fit.
These popups are closely tied to the ‘View’ part of MVVM, but they can only really be invoked from the ‘ViewModel’ which will break the clean separation in MVVM.
If you google this issue, you will find a wide range of elaborate solutions, many of which are significant engineering projects in their own right.
I am a huge fan of implementing simple solutions wherever possible, as verbose code is the number one culprit in un-maintainable projects, so I was keen to find a solution that is simple, robust, elegant and doesnt break the MVVM pattern
The solution I came up with, is to use generic Action and Func Delegates.
OK, to illustrate my solution, I have created a new project using the ‘WPF Model-View-ViewModel Toolkit’,
(http://wpf.codeplex.com/wikipage?title=WPF%20Model-View-ViewModel%20Toolkit), which installs a project template in VS2008
Here is my altered ‘MainViewModel’.cs’ class
public class MainViewModel : ViewModelBase { private DelegateCommand exitCommand; private Action<string> popup; private Func<string, string, bool> confirm; public MainViewModel(Action<string> popup, Func<string, string, bool> confirm) { this.popup = popup; this.confirm = confirm; } public ICommand ExitCommand { get { if (exitCommand == null) exitCommand = new DelegateCommand(Exit); return exitCommand; } } private void Exit() { if (confirm("Are you sure you want to exit", "confirm exit")) Application.Current.Shutdown(); } }
As you can see, the MainViewModel’s constructor takes 2 delegates, 1 for popup and 1 for confirm
Now take a look at App.xaml.cs, where the View and the ViewModel get instantiated
private void OnStartup(object sender, StartupEventArgs args) { // messagebox var popup = (Action<string>)(msg => MessageBox.Show(msg)); // confirm box var confirm = (Func<string, string, bool>)((msg, capt) => MessageBox.Show(msg, capt, MessageBoxButton.YesNo) == MessageBoxResult.Yes); Views.MainView view = new Views.MainView(); view.DataContext = new ViewModels.MainViewModel(popup,confirm); view.Show(); }
If you look closely, you’ll see that my delegates actually map to methods in the static class ‘MessageBox’, which will give us the popups we need. The popup delegate will instantiate a simple message popup, and the confirm delegate will instantiate a message popup with confirm buttons.
And this is what happens when we click on the Exit menu item (note: this menu is created by default when you create a new project using the toolkit)
Now, when we want to run unit tests on our ViewModel, we can just pass in dummy delegates
[TestMethod()] public void MainViewModelConstructorTest() { var dummyPopup = (Action<string>)((a) => {return;}); var dummyConfirm = (Func<string,string,bool>)((a,b) => {return true;}); ViewModels.MainViewModel target = new ViewModels.MainViewModel( dummyPopup, dummyConfirm); Assert.Inconclusive("TODO: Implement code to verify target"); }