I've been trying to learn proper MVVM usage with WPF.
In practice, MVVM can be summed up in one sentence: The view model is responsible for providing all of the data and the functionality that is required by its view.
How do you use commands when the scope of action goes further than the view model?
In MVVM, each view has a view model, so there is no further than the view model. A great way to handle the use of ICommand
s in WPF is to utilise one of the delegate
-based Command
s available, such as the popular RelayCommand
. You can find details for that in the WPF Apps With The Model-View-ViewModel Design Pattern page on MSDN.
In the UI, they're used just like any other ICommand
, but in the view model, you can just use methods or even inline delegate
s to handle the ICommand.Execute
and ICommand.CanExecute
methods. I use my own version of the RelayCommand
and for your scenario, I'd do something like this:
public ICommand CloseCommand
{
get
{
return new ActionCommand(action => Close(null), canExecute => CanClose(null));
}
}
private void Close(object commandParameter)
{
if (SomeDataItem.HasChanges)
{
if (WindowManager.AskUserIfTheyWantToSave(SomeDataItem))
DataProvider.Save(SomeDataItem);
}
HardDriveManager.SaveSettings();
WindowManager.CloseMainWindow();
}
Now obviously, you don't have my ...Manager
classes, but it doesn't matter how you save this and that... this just shows you what is possible.
what if closing the application also requires other actions, such as closing a database connection?
Modern database technologies don't really require you to manually close a database connection at any point.
At this point, I'd like to make it clear that when using MVVM, it is perfectly acceptable to handle events in the code behind of UserControl
s, or even the MainWindow
. So there is absolutely no problem with saving settings in MainWindow.xaml.cs
like this:
Loaded += MainWindow_Loaded;
Closing += MainWindow_Closing;
...
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Settings.Default.Reload();
WindowStartupLocation = WindowStartupLocation.Manual;
Left = Settings.Default.ApplicationLocation.X;
Top = Settings.Default.ApplicationLocation.Y;
Width = Settings.Default.ApplicationSize.Width;
Height = Settings.Default.ApplicationSize.Height;
WindowState = Settings.Default.IsApplicationMaximised ? WindowState.Maximized : WindowState.Normal;
}
private void MainWindow_Closing(object sender, EventArgs e)
{
Settings.Default.ApplicationLocation = new Point(Left, Top);
Settings.Default.ApplicationSize = new Size(Width, Height);
Settings.Default.IsApplicationMaximised = WindowState == WindowState.Maximized;
Settings.Default.Save();
}
Do I declare an event in the viewmodel and have the command trigger the event?
Events are UI objects and should never be seen in a view model... we don't want any UI related dlls in the view model project references. When you really need to handle an event, you can just handle it in the code behind and remember that you can access the data bound view model from the view code behind like this:
SomeViewModel viewModel = (SomeViewModel)DataContext;
Or perhaps better, is to handle the event in an Attached Property that you can simply attach in XAML. Rather than extending this rather long answer explaining how to do that, I advise you to search online, as there are plenty of examples. Oh actually, I just remembered that I explained how to do this to someone in the What's the best way to pass event to ViewModel? post here on Stack Overflow.
So this went on longer than I had intended, but hopefully it answered (some of) your questions.