简体   繁体   中英

Handling input gestures in a user control (MVVM)

Using the MVVM pattern, how do I handle key gestures?

UserControl.InputBindings won't work as it isn't focusable.

I have defined an ICommand that should be called when the proper key is typed, but am at a loss as how to connect the command with the View.

Thanks, Stefan

I solved this problem by creating a DelegateCommand class. It looks exactly like the RelayCommand (See Josh Smith), with the exception that it allows for updating the callbacks.

public class DelegateCommand : ICommand
{
    Action<object> _execute;
    Predicate<object> _canExecute;

    #region Constructors

    public DelegateCommand()
    {

    }

    public DelegateCommand(Action<object> execute)
        : this(execute, null)
    {
    }

    public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }
    #endregion // Constructors

    public void Delegate(Action<object> execute)
    {
        _execute = execute;
    }

    public void Delegate(Action<object> execute, Predicate<object> canExecute)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    #region ICommand Members

    [DebuggerStepThrough]
    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? _execute != null : _canExecute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        if (CanExecute(parameter))
            _execute(parameter);
    }

    #endregion // ICommand Members
}

Then I created a class to hold static application commands.

public class CustomCommands
{
    private readonly static DelegateCommand admin;

    static CustomCommands()
    {
        admin = new DelegateCommand();
    }

    public static DelegateCommand AdminCommand
    {
        get { return admin; }
    }
}

Then I added a key binding to the main window since user controls don't receive key gestures.

<Window.InputBindings>
    <KeyBinding Key="A" Modifiers="Control" Command="my:CustomCommands.AdminCommand"/>
</Window.InputBindings>

Then, in my ViewModel I can handle the event like this:

public class OfflineViewModel : ViewModelBase
{
    public OfflineViewModel()
    {
        CustomCommands.AdminCommand.Delegate(ShowAdmin);
    }

    public override void Removed()
    {
        base.Removed();

        ReleaseAdminCommand();
    }

    public override void Hidden()
    {
        base.Hidden();

        ReleaseAdminCommand();
    }

    void HookAdminCommand()
    {
        CustomCommands.AdminCommand.Delegate(ShowAdmin);
    }

    void ReleaseAdminCommand()
    {
        // Remove handling
        CustomCommands.AdminCommand.Delegate(null, null);
    }

    void ShowAdmin(object parameter)
    {
        Navigation.Push(new AdminViewModel());
    }
}

Optionally I could use events inside the DelegateCommand.

This works for me (.Net 4.0)

<UserControl>
    <UserControl.InputBindings>
        <KeyBinding Gesture="CTRL+C" Command="{Binding CancelCommand}" />
        <KeyBinding Gesture="F5" Command="{Binding StartCommand}" />
        <KeyBinding Gesture="CTRL+F5" Command="{Binding FreshStartCommand}" />
        <KeyBinding Gesture="F10" Command="{Binding ContinueCommand}" />
        <KeyBinding Gesture="F9" Command="{Binding RepeatCommand}" />
        <KeyBinding Gesture="ALT+F4" Command="{Binding CloseCommand}" />
        <KeyBinding Gesture="CTRL+N" Command="{Binding NewUUTCommand}" />
    </UserControl.InputBindings>

    ....

    <StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Center">
        <Button x:Name="BtnStart" Content="STARTEN" Command="{Binding StartCommand}"/>
        <Button Content="STOP" Command="{Binding StopCommand}"/>
    </StackPanel>

    ....

    <FocusManager.FocusedElement>
        <Binding ElementName="BtnStart"/>
    </FocusManager.FocusedElement>
</UserControl>

The trick is to ensure that the focus is set into the UserControl. For me, this wasn't happening automatically. Once the focus is set then the KeyBinding works. (Note the focus is set right at the end as the element has to be defined first)

For completeness, here is the ViewModel code.

public ICommand StartCommand
{
    get
    {
        if (this._startCommand == null)
        {
            this._startCommand = new Mvvm.RelayCommand(parm => DoStart(), parm => DoCanStart());
        } return this._startCommand;
    }
}

private bool DoCanStart()
{
    return !IsRunning && ReadyToRun;
}

private void DoStart()
{
    log.Debug("Start test");
    ...
}

Look at this example: http://tomlev2.wordpress.com/2009/03/17/wpf-using-inputbindings-with-the-mvvm-pattern/

It binds it to DataContext of the root element with XML markup. Hope, it helps you.

This solution has a limitation : it works only for the DataContext of the XAML root. So you can't use it, for instance, to define an InputBinding on a control whose DataContext is also redefined, because the markup extension will access the root DataContext.

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