none
Binding a command in Inputbinding RRS feed

  • Question

  • Hi everyone,
    I want to bind the Command property of KeyBinding like this in XAML but get only exception since Command seems not to be a dependency property.

    <MenuItem.InputBindings>

        <KeyBinding Key="Delete" Command="{Binding RemoveNodeCommand}"/>

    </MenuItem.InputBindings>

    Do you know a workaround to get this work?

    Thanks.

    Saturday, June 14, 2008 4:00 PM

Answers

  • In MVVM pattern, you use set the ViewModel instance to the DataContext property of the root Element such as Window and Page, the problem is that when you specify something like the following in XAML:

    <TreeView.InputBindings>
    <KeyBinding Key="Delete" local:ExtendedKeyBinding.BindableCommand="Delete" />
    </TreeView.InputBindings>

    KeyBinding is not part of the element tree (either visual tree or logical tree) which TreeView resides, so that the data binding engine cannot find the proper DataContext from the contextual location on KeyBinding, which means that the binding source is not available, so the Binding on KeyBinding will simply fails.

    I think there is one possible solution to this problem something like the virtual branch hackery Josh demonstrated in this codeproject article:
    http://www.codeproject.com/KB/WPF/AttachingVirtualBranches.aspx

    Hope this clears things up a little bit.
    Wednesday, June 18, 2008 9:51 AM

All replies

  • Only dependency property can be data bound, KeyBinding.Command property is not a dependency property, probably you need to set value of Command property at codebehind instead.

    Hope this helps
    Tuesday, June 17, 2008 7:01 AM
  • Hi Marco,
    thanks for your reply. I am trying to use attached properties to change the Command like this way:



    public class ExtendedKeyBinding

    {

    public static ICommand GetBindableCommand(DependencyObject obj)

    {

    return (ICommand)obj.GetValue(ExtendedKeyBinding.BindableCommandProperty);

    }

    public static void SetBindableCommand(DependencyObject obj, ICommand value)

    {

    obj.SetValue(ExtendedKeyBinding.BindableCommandProperty, value);

    }

    public static DependencyProperty BindableCommandProperty = DependencyProperty.RegisterAttached("BindableCommand",

    typeof(ICommand),

    typeof(ExtendedKeyBinding),

    new PropertyMetadata(null, new PropertyChangedCallback(OnBindableCommandChanged)));

    private static void OnBindableCommandChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)

    {

    InputBinding ib = depObj as InputBinding; // BREAK POINT

    ib.Command = (ICommand) e.NewValue;

    }


    XAML:

    <TreeView.InputBindings>
    <KeyBinding Key="Delete" local:ExtendedKeyBinding.BindableCommand="{Binding RemoveNodeCommand}" />
    </TreeView.InputBindings>

    It is somehow very strange. I made a break point in the row as commented above but the program seems never to get there. But when I change the XAML code to:

    <TreeView.InputBindings>
    <KeyBinding Key="Delete" local:ExtendedKeyBinding.BindableCommand="Delete" />
    </TreeView.InputBindings>

    It gets the break point. So Binding seems to be the problem here.

    Where did it go wrong?

    Tuesday, June 17, 2008 12:01 PM
  • Binding requires either an explicit Source you specified, or implicit Source from the DataContext property of the target elements, note that this works:

    public class MyCommand : ICommand
    {
        public bool CanExecute(object parameter)
        {
            throw new NotImplementedException();
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            throw new NotImplementedException();
        }
    }

    <Window.Resources>
      <local:MyCommand x:Key="cmd"/>
    </Window.Resources>
    <Window.InputBindings>
      <KeyBinding Key="Delete" local:ExtendedKeyBinding.BindableCommand="{Binding Source={StaticResource cmd}}" />
    </Window.InputBindings>

    Although attached property is a clear trick here to enable data binding for KeyBinding.Command property, But in my personal opinion, it's not worth trying indeed, and I personally prefer to setting the InputBinding or CommandBinding at codebehind instead.

    Hope this helps
    Wednesday, June 18, 2008 4:59 AM
  • Hi Marco,

    thanks again for you reply! I am using MVVM pattern so I have no chance to set the InputBinding.Command in CodeBehind :(

    About Binding:

    As I said, it is somehow very strange to understand.  I've tried this and it doesn't work either (it never gets the break point). The SearchProjectsCommand should be correctly exposed in the ProjectSelectorViewModel because when I click on Button the action is proceed. But when Ctrl + M is pressed, nothing happens...

    It seems to me that in the <InputBindings> block the DataContext of the data templated control is NOT available.


    <DataTemplate DataType="{x:Type local:ProjectSelectorViewModel}">

    <Grid Grid.Column="1" Width="Auto" Height="Auto" Background="#00000000">

    .............

    <Button Command="{Binding SearchProjectsCommand}"

    Margin="0,175,38,0"

    Name="searchButton"

    HorizontalAlignment="Right"

    Width="72" Height="24"

    VerticalAlignment="Top"

    d:LayoutOverrides="VerticalAlignment"

    Content="Search"

    TabIndex="5">

     

    <Button.InputBindings>

     

    <KeyBinding Key="M" Modifiers="Control" local:ExtendedKeyBinding.BindableCommand="{Binding SearchProjectsCommand}" />

     

    </Button.InputBindings>

     

    </Button>

    ....

    </DataTemplate>


    Wednesday, June 18, 2008 8:14 AM
  • In MVVM pattern, you use set the ViewModel instance to the DataContext property of the root Element such as Window and Page, the problem is that when you specify something like the following in XAML:

    <TreeView.InputBindings>
    <KeyBinding Key="Delete" local:ExtendedKeyBinding.BindableCommand="Delete" />
    </TreeView.InputBindings>

    KeyBinding is not part of the element tree (either visual tree or logical tree) which TreeView resides, so that the data binding engine cannot find the proper DataContext from the contextual location on KeyBinding, which means that the binding source is not available, so the Binding on KeyBinding will simply fails.

    I think there is one possible solution to this problem something like the virtual branch hackery Josh demonstrated in this codeproject article:
    http://www.codeproject.com/KB/WPF/AttachingVirtualBranches.aspx

    Hope this clears things up a little bit.
    Wednesday, June 18, 2008 9:51 AM
  • Hi Marco,
    Great explanation and hint, thanks you! It clarified me much.
    I am reading the article right now, it looks very promising.

    Wednesday, June 18, 2008 12:24 PM
  •  Finally I've solved this problem thanks to the Virtual Branch in the link hinted by Marco....The solution is based on the VirtualBranch class posted by boardenin in the CodeProject forum in the discussion thread about Josh's tutorial about Virtual Branch.

    However, I modified that class a little bit as the original version didn't work for me.



    public class VirtualBranch : DependencyObject

    {

    private const string _dataContextPropertyName = "DataContext";

    private const string _sourcePropertyName = "Source";

    private const string _destinationPropertyName = "Destination";

    public static readonly DependencyProperty DataContextProperty =

    DependencyProperty.Register(_dataContextPropertyName, typeof(object), typeof(VirtualBranch));

    public static readonly DependencyProperty SourceProperty =

    DependencyProperty.RegisterAttached(_sourcePropertyName, typeof(object), typeof(VirtualBranch), new PropertyMetadata(OnSourceChanged));

    public static readonly DependencyProperty DestinationProperty =

    DependencyProperty.RegisterAttached(_destinationPropertyName, typeof(object), typeof(VirtualBranch));

    public object DataContext

    {

    get { return GetValue(DataContextProperty); }

    set { SetValue(DataContextProperty, value); }

    }

    public static object GetSource(DependencyObject sender)

    {

    return sender.GetValue(SourceProperty);

    }

    public static void SetSource(DependencyObject sender, object value)

    {

    sender.SetValue(SourceProperty, value);

    }

    public static object GetDestination(DependencyObject sender)

    {

    return sender.GetValue(DestinationProperty);

    }

    public static void SetDestination(DependencyObject sender, object value)

    {

    sender.SetValue(DestinationProperty, value);

    }

    /// <summary>

    /// This is called when the source attached property is changed

    /// </summary>

    /// <param name="sender"></param>

    /// <param name="e"></param>

    public static void OnSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)

    {

    WaitUntilInitializeAndSetDestination(sender, sender.GetValue(SourceProperty));

    }

    #region helpers

    /// <summary>

    /// Wait until the control is completely initialized, then set the property name as command parameter

    /// </summary>

    /// <param name="element"></param>

    private static void WaitUntilInitializeAndSetDestination(DependencyObject sender, object value)

    {

    Control element = sender as Control;

    // Wait until the element is initialised

    if (!element.IsInitialized)

    {

    EventHandler callback = null;

    callback = delegate

    {

    element.Initialized -= callback;

    WaitUntilInitializeAndSetDestination(element, value);

    };

    element.Initialized += callback;

    return;

    }

    sender.SetValue(DestinationProperty, value);

    }

    #endregion

    }


    Derive the classes KeyBinding, MouseBinding and add two bindable dependency properties (DPs) BindableCommand, BindableCommandParameter so that Data Binding works. Here is the ExtendedKeyBinding class (ExtendedMouseBinding is almost identical):

    public class ExtendedKeyBinding: KeyBinding

    {

    #region Dependency Properties

    public static readonly DependencyProperty BindableCommandProperty =

    DependencyProperty.Register("BindableCommand", typeof(ICommand), typeof(ExtendedKeyBinding), new PropertyMetadata(null, BindableCommandChanged));

    /// </summary>

    public static readonly DependencyProperty BindableCommandParameterProperty =

    DependencyProperty.Register("BindableCommandParameter", typeof(object), typeof(ExtendedKeyBinding), new PropertyMetadata(null, BindableCommandParameterChanged));

    public ICommand BindableCommand

    {

    get { return (ICommand)GetValue(BindableCommandProperty); }

    set { SetValue(BindableCommandProperty, value); }

    }

    public object BindableCommandParameter

    {

    get { return (object)GetValue(BindableCommandParameterProperty); }

    set { SetValue(BindableCommandParameterProperty, value); }

    }

    private static void BindableCommandChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)

    {

    if (e.NewValue != null)

    (sender as InputBinding).Command = (ICommand)e.NewValue;

    }

    private static void BindableCommandParameterChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)

    {

    if (e.NewValue != null)

    (sender as InputBinding).CommandParameter = (object)e.NewValue;

    }

    #endregion

    }

    In the property change callback of these properties, set the not bindable Command and CommandParameter of InputBinding to the new value.

    It is also possible to use attached property instead of DPs.

    The XAML code in my case. <local:VirtualBranch x:Key="ProjectDataContextBridge"/> is the point where the DataContext of the data templated ViewModel is push into and other elements that don't belong to the Visual Tree can bind to this DataContext.

    <!--Data template to display a project view model-->

    <DataTemplate x:Key="CompleteProjectTempl">
    <Grid>
    <Grid.Resources>

    <local:VirtualBranch x:Key="ProjectDataContextBridge"/>

    </Grid.Resources>

    <Grid.ColumnDefinitions>

    <ColumnDefinition Width="400" MinWidth="200"/>

    <ColumnDefinition Width="*"/>

    </Grid.ColumnDefinitions>

     

    <Grid Grid.Row="0" Grid.Column="0">

    <local:ProjectStructureTree

    Items="{Binding Path=Root}"

    SelectedItem="{Binding Path=ActiveElement, Mode=TwoWay}"

    x:Name="ProjectTree"

    Width="Auto" FontSize="14"

    SelectNodesOnRightClick="True"

    NodeContextMenu="{StaticResource MyContextMenu}"

    TreeNodeStyle="{StaticResource MyTreeNodeStyle}"

    IsLazyLoading="False"

    ObserveChildItems="True"

    local:VirtualBranch.Source="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext}"

    local:VirtualBranch.Destination="{Binding Source={StaticResource ProjectDataContextBridge}, Path=DataContext, Mode=OneWayToSource}">

    <local:ProjectStructureTree.InputBindings>

    <local:ExtendedKeyBinding Key="Delete" BindableCommand="{Binding Source={StaticResource ProjectDataContextBridge}, Path=DataContext.RemoveNodeCommand}" />

    <local:ExtendedKeyBinding Key="Insert" BindableCommand="{Binding Source={StaticResource ProjectDataContextBridge}, Path=DataContext.AddNodeCommand}" />

    </local:ProjectStructureTree.InputBindings>

    </local:ProjectStructureTree>

    </Grid>

    <Grid Grid.Column="1">

    <ContentControl Width="Auto" Content="{Binding Path=ActiveElement}"/>

    </Grid>

    <GridSplitter Width="4" HorizontalAlignment="Left" Grid.Column="1" Margin="-2,0,0,0" Background="#FF6E6E6E" ResizeDirection="Columns" />

    </Grid>

    </DataTemplate>




    Is this "workaround" acceptable? What do you think?


    Thursday, June 19, 2008 5:02 PM
  • A much easier/better solution is outlined here:

    http://wpfmentor.blogspot.com/2008/11/adding-bindings-to-clr-properties-in.html

    fg
    Wednesday, November 26, 2008 2:03 PM
  • Have you done any evolution of that pattern we are facing the same issue. I fell that your pattern make the thing a bit more complex for the blend designer

    Cheers.

    Friday, January 23, 2009 6:55 PM
  • I wrote a markup extension to do exactly that, it's very easy to use. I had to cheat a little by accessing some private fields through reflection, but it works pretty well...

    It can be used like that :

        < Window.InputBindings >

            < KeyBinding Key ="E" Modifiers ="Control" Command ="{ input : CommandBinding EditCommand }"/>

        </ Window.InputBindings >

    The code is available here : http://tomlev.wordpress.com/2009/03/17/wpf-utiliser-les-inputbindings-avec-le-pattern-mvvm/
    (The post is in French, but in the code itself all identifiers and comments are in English, so it should be easy to understand)

    EDIT: I now have an English version of my blog, you can find the article about this markup extension here :
    http://tomlev2.wordpress.com/2009/03/17/wpf-using-inputbindings-with-the-mvvm-pattern/

    Wednesday, March 18, 2009 11:11 AM
  • Super,

    Je vais lire attentive ce poste.

    Eric.
    Sunday, March 29, 2009 3:23 AM
  • The Command property of the KeyBinding class doesn't support data binding. This issue is going to be solved in .NET 4.0 and you should be able to see it in the coming .NET 4.0 Beta 2 version.

    Until then you find varios workarounds for this issue. One is shown in the ShortCutKey sample of the WPF Application Framework (WAF).

    Monday, October 5, 2009 11:54 AM
  • Could anyone from MS would like to confirm that point? I heard rumour from someone on the .Net/WPF team a few month ago but the scope for the final version of the .Net 4.0 was not freeze yet at this point so there where having some incertitude.

    Monday, October 5, 2009 12:14 PM