简体   繁体   中英

Child view focus in MVVM application to see Command

I am developing an MVVM application. I have a main Window, which looks more or less like this:

<Window>
   <ContentControl Content={Binding ContentViewModel} />
</Window>

Then I have this ViewModel, which exposes a certain number of Commands, and I want these commands to be available to the user both from the UI (with buttons, etc), AND from the keyboard, using KeyBindings.

The commands work properly from the UI buttons. But the Keybindings don't always work, it'd seem to me that the problem is that the loaded view is not always in focus. This is the code for the view.

<UserControl>
    <UserControl.InputBindings>
        <KeyBinding Key="Delete" Command="{Binding RemoveEntityCommand,  ElementName=Designer}" />
    </UserControl.InputBindings>
    <Grid>
        <namespace:Designer x:Name="Designer" />
    </Grid>
</UserControl>

How to solve this permanently for an MVVM application? I've encountered this problem multiple times.

Note: all namespace declarations removed for simplicity.

Thanks.

I would probably attach a Command to the KeyDown or KeyUp event of the Window instead of the UserControl , and route it from there.

It can either be routed to the ShellViewModel , which will in turn pass it to the current ContentViewModel if needed, or perhaps use some kind of Messaging system that broadcasts special key combinations, and ViewModel's can subscribe to them.

What I would do is implement a PreviewKeyUp event and use that to call a method on my view model, something like this:

protected void PreviewKeyUp(object sender, KeyEventArgs args) 
{
    args.Handled = myViewModel.HandleKeyUp(args.Key);
}

public bool HandleKeyUp(Key key) 
{
   // Determine if you should execute a command
   if(myCommands.ShouldExecuteOnKey(key))
   {
       // Execute the commad
       return true;
   }

   return false;
}

A lot of people seem to think that MVVM means no code-behind , but that's not always true, or even possible.

Yeah, key bindings are a pain. And I agree with Rachel that you likely want something available at the window level in this specific case.

You can avoid code behind and get the goodness of mvvm by doing the following:

  1. make your commands aware of key gestures
  2. give a KeyBinding an additional property that takes advantage of (1)

This is obviously a bit of infrastructure work tho it is testable and reusable once you have it in place. In this case, since you want the bindings available at the window level, your ShellVm holds the commands and delegates to child view models as needed. I've left just enough code below to give you a feel for the idea

HTH,
Berryl

sample bindings

<Window.InputBindings>
    <cmdRef:KeyBindingEx  CommandReference="{Binding AddCommand}"/>
</Window.InputBindings>

extended KeyBinding

public class KeyBindingEx : KeyBinding
{
    public static readonly DependencyProperty CommandReferenceProperty = DependencyProperty
        .Register("CommandReference", typeof(CommandReference), typeof(KeyBindingEx),          
                  new PropertyMetadata(OnCommandReferenceChanged));

    private static void OnCommandReferenceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
        var kb = (KeyBinding) d;
        var cmdRef = e.NewValue as VmCommand;
        if(cmdRef==null) return;

        kb.Key = cmdRef.GestureKey;
        kb.Modifiers = cmdRef.GestureModifier;
        kb.Command = cmdRef;
    }

    public CommandReference CommandReference
    {
        get { return (CommandReference)GetValue(CommandReferenceProperty); }
        set { SetValue(CommandReferenceProperty, value); }
    }
}

Command Reference base class extract

public class CommandReference : PropertyChangedBase { ...

    public Key GestureKey
    {
        get { return _gestureKey; }
        set
        {
            if (_gestureKey == value) return;

            _gestureKey = value;
            NotifyOfPropertyChange(() => GestureKey);
        }
    }
    private Key _gestureKey;        
}

/// <summary>A command whose primary purpose is to relay its functionality to other objects by invoking delegates.</summary>
public class VmCommand : CommandReference, ICommand
{
    ...

    /// <summary>Action to be called when the Execute method of the command gets called</summary>
    public Action ExecuteDelegate { get; protected set; }

    /// <summary>Predicate to execute when the CanExecute of the command gets called (default is <c>true</c>)</summary>
    public Func<bool> CanExecuteDelegate { get; protected set; }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM