I don’t know about you, but I often find myself using this pattern in my MVVM applications:
- An ApplicationViewModel which represents the application’s top level state. This will usually contain a property called something like CurrentDocument which will hold…
- A DocumentViewModel containing information about a single document.
This might look something like the following:
class ApplicationViewModel : ObservableObject { public ApplicationViewModel() { NewDocumentCommand = new DelegateCommand(NewDocument); } public DocumentViewModel CurrentDocument { get; private set; } public DelegateCommand NewDocumentCommand { get; private set; } void NewDocument() { CurrentDocument = new DocumentViewModel(); RaisePropertyChanged(() => this.CurrentDocument); } }
Here we have the application’s current document exposed as a property in the application view model, and an ICommand that creates a new document. Here’s how the UI’s xaml might look:
<Window x:Class="NullCommand.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:NullCommand" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <local:ApplicationViewModel/> </Window.DataContext> <DockPanel> <Menu DockPanel.Dock="Top"> <MenuItem Header="_File"> <MenuItem Header="New" Command="{Binding NewDocumentCommand}"/> </MenuItem> </Menu> <ContentPresenter Content="{Binding CurrentDocument}"/> </DockPanel> </Window>
Now, in Document we might want to provide a command to say, print the document:
class DocumentViewModel { public DocumentViewModel() { PrintCommand = new DelegateCommand(Print); } public DelegateCommand PrintCommand { get; private set; } void Print() { System.Windows.MessageBox.Show("Print!"); } }
This logically belongs in DocumentViewModel, as it’s an operation that is carried out on the document. However, we want to invoke this command from the main window’s menu:
<MenuItem Header="Print" Command="{Binding CurrentDocument.PrintCommand}"/>
Because the PrintCommand is a member of DocumentViewModel, we use the ApplicationViewModel.CurrentDocument property to access it. But there is one problem here – if we haven’t already created a new document, ApplicationViewModel.CurrentDocument will be null, and so WPF won’t be able to bind to PrintCommand. This is the point where we discover:
If WPF can’t find the bound command, it doesn’t disable the control.
Now, this is pretty stupid behaviour if you ask me. It would make more sense to disable a control when a bound command cannot be located (at least with an option). So what do we do here? The answer lies with the BindingBase.FallbackValue property. FallbackValue allows us to specify a value to use when a binding can’t be found. So all we need now is a NullCommand which we can fall back to, to disable the Print menu:
public class NullCommand : ICommand { public bool CanExecute(object parameter) { return false; } public event EventHandler CanExecuteChanged; public void Execute(object parameter) { throw new NotImplementedException("NullCommand cannot be executed."); } }
The way I usually use NullCommand is to add it as a resource to my App.xaml:
<Application.Resources> <local:NullCommand x:Key="NullCommand"/> </Application.Resources>
And our Print menu item now looks like this:
<MenuItem Header="Print" Command="{Binding Path=CurrentDocument.PrintCommand, FallbackValue={StaticResource NullCommand}}"/>
And hey presto! If the PrintCommand can’t be found, the MenuItem will now be disabled.
No comments:
Post a Comment