Saturday, March 5, 2011

WPF - Binding InputBindings to View Model Commands using MVVM

In this post we will be discussing about how we can bind mouse and key gestures directly with Commands in the view model. Generally when we talk about this type of binding we end up using RoutedCommand directly in the view having Execute and CanExecute methods in the view itself. This is a new feature of WPF 4.

In order to understand this, let's consider a sample application with some input bindings. When the particular key combination are received by WPF runtime, it should update properties in view model. These properties supports change notifications by implementing INotifyPropertyChanged interface.
<Window x:Class="WpfApplication_InputBindings_MVVM.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication_InputBindings_MVVM"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <Window.InputBindings>
        <KeyBinding Key="F2" Modifiers="Control" Command="{Binding OperationCommand}" />
    </Window.InputBindings>    
    <Grid>
        <Label Content="Message" Height="26" HorizontalAlignment="Left" Margin="12,47,0,0" 
               Name="label1" VerticalAlignment="Top" Width="67" FontWeight="Bold" />
        <TextBlock Height="27" HorizontalAlignment="Left" Margin="86,53,0,0" 
                   Name="textBlockMessage" Text="{Binding Message}" VerticalAlignment="Top" Width="405" />
    </Grid>
</Window>
In the above view we have added KeyBinding for "Ctrl + F2" key combination. The view model should have a command OperationCommand. When user presses the specified key combination, it should execute the command methods on the view model. The view is using a new instance of MainWindowViewModel as its DataContext. It is also binding Text property of the only TextBlock to a property Message from the DataContext.

Let's look at the definition of MainWindowViewModel. Since it needs to support Property change notification to the view, it is using one of the available change notification system. It is using this mechanism for its Message property. As part of its contract for INotifyPropertyChanged, it provides an event PropertyChanged. We have used RelayCommand for supporting command execution from view model [http://msdn.microsoft.com/en-us/magazine/dd419663.aspx].
class MainWindowViewModel : INotifyPropertyChanged
{
    #region Operation Command
    
    RelayCommand _operationCommand;
    public ICommand OperationCommand
    {
        get
        {
            if (_operationCommand == null)
            {
                _operationCommand = new RelayCommand(param => this.ExecuteCommand(),
                    param => this.CanExecuteCommand);
            }
            return _operationCommand;
        }
    }

    public void ExecuteCommand()
    {
        Message = "Command Executed";
    }

    bool CanExecuteCommand
    {
        get { return true; }
    }
    
    #endregion Operation Command

    #region Properties

    private string _message;
    public string Message
    {
        get { return _message; }
        set
        {
            _message = value;
            OnPropertyChanged("Message");
        }
    }

    #endregion Properties

    #region INotifyPropertyChanged implementation

    public event PropertyChangedEventHandler PropertyChanged = delegate { };
    private void OnPropertyChanged(string propertyName)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion INotifyPropertyChanged implementation
}
When we run the application and hit the specified key combination, it first checks if the command is eligible for execution from CanExecuteCommand property. If it returns true then it executes ExecuteCommand method. In this method we are setting Message property to "Command Executed". Since this property supports change notification so the same changes are updated on the view.

Let us run this now. The application is displayed as follows:


As expected, When we press Ctrl + F2 key combination the view is updated as follows:


Stealing Key Gestures from Application Commands:
Still we have simple command bound to the Window. What if we have a command with key gestures from default Commands available by the framework including ApplicationCommands? Now let's progress this example into a little complex one. Let's change the input bindings to a key gestures Ctrl + V.
<Window x:Class="WpfApplication_InputBindings_MVVM.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication_InputBindings_MVVM"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <Window.InputBindings>
        <KeyBinding Key="V" Modifiers="Control" Command="{Binding OperationCommand}" />
    </Window.InputBindings>    
    <Grid>
        <Label Content="Message" Height="26" HorizontalAlignment="Left" Margin="12,47,0,0" 
               Name="label1" VerticalAlignment="Top" Width="67" FontWeight="Bold" />
        <TextBlock Height="27" HorizontalAlignment="Left" Margin="86,53,0,0" 
                   Name="textBlockMessage" Text="{Binding Message}" VerticalAlignment="Top" Width="405" />
        <TextBox Height="29" HorizontalAlignment="Left" Margin="82,152,0,0" Name="textBox1" 
                 VerticalAlignment="Top" Width="409" />
    </Grid>
</Window>
In the above XAML, we have updated the key gestures to Ctrl + V. We move focus to the new TextBox and hit Ctrl + V [I have text "Muhammad Siddiqi" copied to clip board].


You can see that TextBox has stolen the key gestures. This has resulted in the text on the clip board being pasted to the Text Box. This is because of default paste command using this key gesture for the text box.

Binding Key Gesture of un-wanted command to ApplicationCommands.NotACommand:
The first solution is very simple. We can just assign those key gestures to ApplicationCommands.NotACommand. We can achieve the same using CommandManager available in System.Windows.Input namespace. Let's update the constructor of the view as follows:
public MainWindow()
{
    InitializeComponent();

    CommandManager.RegisterClassInputBinding(typeof(UIElement),
        new InputBinding(ApplicationCommands.NotACommand, 
            new KeyGesture(Key.V, ModifierKeys.Control)));
}
Now let's run the application again. When we hit Ctrl + V the command actually executes and key gestures are not stolen by Paste Command for the text box.


This approach seems to be working but it has a few limitations. First this is application wide i.e. this gesture is stolen for the whole application. The other thing is that we can not un-register this.

Vote: http://connect.microsoft.com/VisualStudio/feedback/details/481627/no-way-to-unregister-a-commandmanager-registerclasscommandbinding

Handling CanExecute Tunneling Event:
The other solution is to handle PreviewCanExecute event of the command causing the key gesture conflict. In the following example we are using CommandBinding to provide tunneling event handler for CanExecute for Paste Command. Since CommandBindings, unlike InputBindings, is a DependencyProperty if this command is handled above in the logical hierarchy this wouldn't be propagating down. Let's update XAML as follows:
<Window x:Class="WpfApplication_InputBindings_MVVM.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication_InputBindings_MVVM"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <Window.InputBindings>
        <KeyBinding Key="V" Modifiers="Control" Command="{Binding OperationCommand}" />
    </Window.InputBindings>    
    <Window.CommandBindings>
        <CommandBinding Command="Paste" PreviewCanExecute="CommandBinding_PreviewCanExecute" />
    </Window.CommandBindings>
    <Grid>
        <Label Content="Message" Height="26" HorizontalAlignment="Left" Margin="12,47,0,0" 
               Name="label1" VerticalAlignment="Top" Width="67" FontWeight="Bold" />
        <TextBlock Height="27" HorizontalAlignment="Left" Margin="86,53,0,0" 
                   Name="textBlockMessage" Text="{Binding Message}" VerticalAlignment="Top" Width="405" />
        <TextBox Height="29" HorizontalAlignment="Left" Margin="82,152,0,0" Name="textBox1" 
                 VerticalAlignment="Top" Width="409" />
    </Grid>
</Window>
Let's see the definition of CommandBinding_PreviewCanExecute:
private void CommandBinding_PreviewCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    e.Handled = true;
}
If we run this now and hit Ctrl + V by setting focus on the text box, this event handler is executed. Since this is handled here this would not be propagated to the text box. The view is updated as follows:


Download Code:

2 comments:

Unknown said...

Found best multi event command binder in codeplex

Multi Event Command Binder

Benefits:

1:Ability to bind multiple event to multiple commands.
2:In CommandArgs in command handler you can even get original event args this was not possible with existing command binders.
3:In CommandArgs in command handler you can even get original source of the event this was not possible with existing command binders.
4:In this you can control individual CanExecute methods.

Blogs
Multi Event Command BlogSpot
Multi Event Command WordPress

Aashayein said...

Thanks a Lot.. Brilliant piece of code i found here, made my task quite comfortable..