Looking for the most elegant solution to bind a button command to a ViewModel ICommand property, while allowing confirmation in the View.
What I would like to do:
- Only allow a user to click a button when he/she should
- When the button is clicked, ask a confirmation
- If comfirmed, do work in the ViewModel, otherwise cancel
- Do not break MVVM architecture
The confirmation requirement can be fulfilled by showing a messagebox from the ViewModel. However, I don't think this is the way to go. Doesn't it break MVVM? What if CanExecute depends on the state of both UI (code-behind) and ViewModel? Also, what about testability when having a messagebox pop-up from the ViewModel?
Another thing I tried is binding both OnClick (to View) and Command (to ViewModel). Although the Event is always executed before the Command, there seems to be no way to cancel the command from being executed. Also, the execution order seems to be an undocumented feature, so something that you should not rely on. Besides this, it still does not allow CanExecute to take into account View logic.
Next I came up with the following solution:
View (XAML)
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Button Content="Do Work" Command="{Binding Path=ViewModel.SaveCommand}"/>
</Grid>
<SelectTemplateUserControl Visibility="Collapsed" OnTemplateSelected="SelectTemplate_OnTemplateSelected"/>
</Window>
View (Code-Behind)
public partial class MainWindow : Window
{
private readonly MainViewModel _viewModel = new MainViewModel();
public MainWindow()
{
InitializeComponent();
}
public MainViewModel ViewModel { get { return this._viewModel; } }
public ICommand SaveCommand { get { return new RelayCommand<int>(this.Save, this.CanSave);} }
private bool CanSave(int templateId)
{
return this._viewModel.SaveCommand.CanExecute(null);
}
private void Save(int templateId)
{
var messageBoxResult = MessageBox.Show("Do you want to overwrite?", "Overwrite?", MessageBoxButton.OKCancel);
if (messageBoxResult == MessageBoxResult.Cancel)
return;
// Call method to hide main Grid and set SelectTemplateUserControl to visible..
}
private void SelectTemplate_OnTemplateSelected(object sender, int templateId)
{
this._viewModel.SaveCommand.Execute(templateId);
}
}
ViewModel
public class MainViewModel : ViewModelBase
{
public ICommand SaveCommand { get { return new RelayCommand<int>(this.Save, this.CanSave); } }
private bool CanSave(int templateId)
{
// Can Save Logic, returning a bool
}
private void Save(int templateId)
{
// Save Logic....
}
}
I think it follows the MVVM pattern nicely, it also achieves Single Responsiblity. But is this the best way of doing it? Are there other possibilities?