Friday, 16 July 2010

Using Binding.FallbackValue to Disable a UI Element When its Command can’t be Found

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"
       Title="MainWindow" Height="350" Width="525">
       <Menu DockPanel.Dock="Top">
           <MenuItem Header="_File">
               <MenuItem Header="New" Command="{Binding NewDocumentCommand}"/>
       <ContentPresenter Content="{Binding CurrentDocument}"/>

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()

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:

    <local:NullCommand x:Key="NullCommand"/>

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