How can I bind key gestures in Caliburn.Micro?
Asked Answered
M

4

10

How can I get Caliburn.Micro to map a key gesture to an action method on my ViewModel?

For example, I want to implement a tabbed interface, and I want my ShellViewModel to have a NewTab method, which the user should to be able to invoke by pressing Ctrl+T on the keyboard.

I know that the full Caliburn framework has support for gestures, but how can I do this using Caliburn.Micro? Is there perhaps some way to bind an action to a RoutedCommand (since RoutedCommands already support input gestures)? Or some other way to get gesture support?

Mcclary answered 15/11, 2010 at 4:7 Comment(0)
M
12

I modified example to enable support for global key-bindings. You just need to add the folowing code to your view:

<i:Interaction.Triggers>
        <common:InputBindingTrigger>
            <common:InputBindingTrigger.InputBinding>
                <KeyBinding Modifiers="Control" Key="D"/>
            </common:InputBindingTrigger.InputBinding>
            <cl:ActionMessage MethodName="DoTheMagic"/>
        </common:InputBindingTrigger>
    </i:Interaction.Triggers>

And whenever Ctr+D is pressed the method DoTheMagic will be exexuted. Here is the modified InputBindingTrigger code:

public class InputBindingTrigger : TriggerBase<FrameworkElement>, ICommand
  {
    public static readonly DependencyProperty InputBindingProperty =
      DependencyProperty.Register("InputBinding", typeof (InputBinding)
        , typeof (InputBindingTrigger)
        , new UIPropertyMetadata(null));

    public InputBinding InputBinding
    {
      get { return (InputBinding) GetValue(InputBindingProperty); }
      set { SetValue(InputBindingProperty, value); }
    }

    public event EventHandler CanExecuteChanged = delegate { };

    public bool CanExecute(object parameter)
    {
      // action is anyway blocked by Caliburn at the invoke level
      return true;
    }

    public void Execute(object parameter)
    {
      InvokeActions(parameter);
    }

    protected override void OnAttached()
    {
      if (InputBinding != null)
      {
        InputBinding.Command = this;        
        AssociatedObject.Loaded += delegate {
          var window = GetWindow(AssociatedObject);
          window.InputBindings.Add(InputBinding);
        };
      }
      base.OnAttached();
    }

    private Window GetWindow(FrameworkElement frameworkElement)
    {
      if (frameworkElement is Window)
        return frameworkElement as Window;

      var parent = frameworkElement.Parent as FrameworkElement;      
      Debug.Assert(parent != null);

      return GetWindow(parent);
    }
  }
Monteria answered 23/11, 2011 at 16:58 Comment(4)
// action is anyway blocked by Caliburn at the invoke level This is not true, it still firesPlenish
yep, It fires, do you have any idea how you could check the appropriate Can method if the action is allowed to fire.Monteria
No, i tried to debug your code to find a reference to the Can propery, but no success :/Plenish
same as the accepted answer: this approach would quickly render the UserControl disabled.Lovage
M
6

Caliburn.Micro's Actions mechanism is built on top of System.Windows.Interactivity. So, you can create a custom trigger based on TriggerBase to do whatever you want, including global keyboard gestures. Then, just plug the ActionMessage into your trigger and viola!

Mizzle answered 15/11, 2010 at 12:52 Comment(5)
I know nothing about TriggerBase or how it might tie in with InputGestures... where would I even start?Mcclary
System.Windows.Interactivity is part of the Blend SDK. If you do some searching on Blend Behaviors you should find a lot of blogs with samples.Mizzle
Hey EisenbergEffect, is there a way to scope this? I noticed when I do this within a user control, this also triggers things in the parent window if I have the same Key binding.Vie
@Vie - I put the triggers on a custom control and it scoped the ActionMessage to the correct VM in the control DataContext.Silva
@Mizzle Hi, do your team have any future plan to include the basic support for keyboard actions?Lovage
L
0

Inherit from Caliburn's ActionMessage (which is a TriggerAction) and attach the derived trigger to the KeyDown event in XAML and set the ActionMessage.MethodName property. Add a property to the derived trigger of what key combination you are looking for and override the Invoke method to filter by that key combination, calling base.Invoke(...) if the key matches.

Leptorrhine answered 8/7, 2011 at 16:59 Comment(0)
A
-1

If you marshal a command through the View to the View Model you can control the CanExecute from the View Model. I've been using this method in multiple Caliburn projects. Might not be as "slick" as using Interactivity, but CanExecute works.

<UserControl x:Class="MyView"
      ...
      Name="View"
>

  <UserControl.InputBindings>
    <KeyBinding Key="F5" 
                Command="{Binding RefreshCommand, ElementName=View, Mode=OneWay}" />
  </UserControl.InputBindings>

  <Button Command="{Binding Path=RefreshCommand, ElementName=View, Mode=OneWay}"/>

In your View class, you wire the command to the View Model which is referenced in the MyView.DataContext property.

Class MyView

    Public Property RefreshCommand As _
    New RelayCommand(AddressOf Refresh,
                     Function()
                         If ViewModel Is Nothing Then
                             Return False
                         Else
                             Return ViewModel.CanRefresh
                         End If
                     End Function)

    Private Sub Refresh()
        ViewModel.Refresh()
    End Sub

    Private ReadOnly Property ViewModel As MyViewModel
        Get
            Return DirectCast(DataContext, MyViewModel)
        End Get
    End Property

End Class
Alternative answered 21/4, 2016 at 14:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.