Mike Walton
Mike Walton

Reputation: 4386

WPF Command usage beyond MVVM

I've been trying to learn proper MVVM usage with WPF. I mostly get it, but I'm a little stuck on a specific issue with using commands. I get that a command allows me to perform the same action that can be triggered by multiple UI elements, but all examples I've seen of this have the command defined in the view model or the view.

How do you use commands when the scope of action goes further than the view model? For example, maybe I want to define a command for closing the application. This will allow a button and a menu item to both trigger closing, but what if closing the application also requires other actions, such as closing a database connection and writing a file? These types of tasks usually happen somewhere besides in the model or view.

In this scenario, Do I just wire the UI elements to an event instead? Do I declare an event in the viewmodel and have the command trigger the event?

Upvotes: 0

Views: 102

Answers (2)

Sheridan
Sheridan

Reputation: 69985

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 ICommands in WPF is to utilise one of the delegate-based Commands 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 delegates 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 UserControls, 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.

Upvotes: 3

Fede
Fede

Reputation: 44068

Do I just wire the UI elements to an event instead?

NO.

If your commands need to perform whatever actions, just call these actions in the Execute() method.

How you perform the interaction between the ViewModel and the rest of your application (Data Access layer, business logic, services) is completely up to you.

Upvotes: 1

Related Questions