Pergunta

Como posso manipular o evento Keyboard.KeyDown sem o uso de code-behind? Nós estamos tentando usar o padrão MVVM e evitar escrever um manipulador de eventos no arquivo code-behind.

Foi útil?

Solução

Um pouco tarde, mas aqui vai.

Equipe WPF da Microsoft lançou recentemente uma versão inicial de seu WPF MVVM Toolkit . Nele, você encontrará uma classe chamada CommandReference que podem lidar com coisas como atalhos de teclado. Olhada em seu modelo de WPF MVVM para ver como ele funciona.

Outras dicas

Para trazer uma resposta atualizado, o .NET Framework 4.0 permite que você faça isso muito bem, permitindo que você vincular um comando KeyBinding a um comando em uma viewmodel.

Então ... Se você queria ouvir a tecla Enter, você faria algo assim:

<TextBox AcceptsReturn="False">
    <TextBox.InputBindings>
        <KeyBinding 
            Key="Enter" 
            Command="{Binding SearchCommand}" 
            CommandParameter="{Binding Path=Text, RelativeSource={RelativeSource AncestorType={x:Type TextBox}}}" />
    </TextBox.InputBindings>
</TextBox>

WOW - não há como mil respostas e aqui eu estou indo para adicionar outro ..

A coisa realmente óbvio em um 'por que-não-I-realizar-este-testa-tapa' tipo de forma é que o código-behind eo sit ViewModel na mesma sala por assim dizer, de modo não há nenhuma razão para que eles não estão autorizados a ter uma conversa.

Se você pensar sobre isso, o XAML já está intimamente acoplado a API do ViewModel, assim que você poderia muito bem ir e fazer uma dependência sobre ele a partir de trás código.

As outras regras óbvias para obedecer ou ignorar ainda se aplica (interfaces, cheques nulos <- especialmente se você usar Mistura ...)

Eu sempre fazer uma propriedade no código-behind como este:

private ViewModelClass ViewModel { get { return DataContext as ViewModelClass; } }

Este é o código do cliente. A verificação nulo é para ajudar controle de hospedagem como como na mistura.

void someEventHandler(object sender, KeyDownEventArgs e)
{
    if (ViewModel == null) return;
    /* ... */
    ViewModel.HandleKeyDown(e);
}

Lidar com o seu evento no código por trás de como você quer (eventos de interface são UI-centric por isso é OK) e, em seguida, ter um método no ViewModelClass que possa responder a esse evento. As preocupações ainda são separados.

ViewModelClass
{
    public void HandleKeyDown(KeyEventArgs e) { /* ... */ }
}

Todas estas outras propriedades e voodoo anexados é muito legal e as técnicas são realmente útil para algumas outras coisas, mas aqui você pode ir longe com algo mais simples ...

eu fazer isso usando um comportamento anexado com 3 propriedades de dependência; um é o comando a ser executado, um é o parâmetro para passar para o comando ea outra é a chave que fará com que o comando a ser executado. Aqui está o código:

public static class CreateKeyDownCommandBinding
{
    /// <summary>
    /// Command to execute.
    /// </summary>
    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.RegisterAttached("Command",
        typeof(CommandModelBase),
        typeof(CreateKeyDownCommandBinding),
        new PropertyMetadata(new PropertyChangedCallback(OnCommandInvalidated)));

    /// <summary>
    /// Parameter to be passed to the command.
    /// </summary>
    public static readonly DependencyProperty ParameterProperty =
        DependencyProperty.RegisterAttached("Parameter",
        typeof(object),
        typeof(CreateKeyDownCommandBinding),
        new PropertyMetadata(new PropertyChangedCallback(OnParameterInvalidated)));

    /// <summary>
    /// The key to be used as a trigger to execute the command.
    /// </summary>
    public static readonly DependencyProperty KeyProperty =
        DependencyProperty.RegisterAttached("Key",
        typeof(Key),
        typeof(CreateKeyDownCommandBinding));

    /// <summary>
    /// Get the command to execute.
    /// </summary>
    /// <param name="sender"></param>
    /// <returns></returns>
    public static CommandModelBase GetCommand(DependencyObject sender)
    {
        return (CommandModelBase)sender.GetValue(CommandProperty);
    }

    /// <summary>
    /// Set the command to execute.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="command"></param>
    public static void SetCommand(DependencyObject sender, CommandModelBase command)
    {
        sender.SetValue(CommandProperty, command);
    }

    /// <summary>
    /// Get the parameter to pass to the command.
    /// </summary>
    /// <param name="sender"></param>
    /// <returns></returns>
    public static object GetParameter(DependencyObject sender)
    {
        return sender.GetValue(ParameterProperty);
    }

    /// <summary>
    /// Set the parameter to pass to the command.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="parameter"></param>
    public static void SetParameter(DependencyObject sender, object parameter)
    {
        sender.SetValue(ParameterProperty, parameter);
    }

    /// <summary>
    /// Get the key to trigger the command.
    /// </summary>
    /// <param name="sender"></param>
    /// <returns></returns>
    public static Key GetKey(DependencyObject sender)
    {
        return (Key)sender.GetValue(KeyProperty);
    }

    /// <summary>
    /// Set the key which triggers the command.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="key"></param>
    public static void SetKey(DependencyObject sender, Key key)
    {
        sender.SetValue(KeyProperty, key);
    }

    /// <summary>
    /// When the command property is being set attach a listener for the
    /// key down event.  When the command is being unset (when the
    /// UIElement is unloaded for instance) remove the listener.
    /// </summary>
    /// <param name="dependencyObject"></param>
    /// <param name="e"></param>
    static void OnCommandInvalidated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        UIElement element = (UIElement)dependencyObject;
        if (e.OldValue == null && e.NewValue != null)
        {
            element.AddHandler(UIElement.KeyDownEvent,
                new KeyEventHandler(OnKeyDown), true);
        }

        if (e.OldValue != null && e.NewValue == null)
        {
            element.RemoveHandler(UIElement.KeyDownEvent,
                new KeyEventHandler(OnKeyDown));
        }
    }

    /// <summary>
    /// When the parameter property is set update the command binding to
    /// include it.
    /// </summary>
    /// <param name="dependencyObject"></param>
    /// <param name="e"></param>
    static void OnParameterInvalidated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        UIElement element = (UIElement)dependencyObject;
        element.CommandBindings.Clear();

        // Setup the binding
        CommandModelBase commandModel = e.NewValue as CommandModelBase;
        if (commandModel != null)
        {
            element.CommandBindings.Add(new CommandBinding(commandModel.Command,
            commandModel.OnExecute, commandModel.OnQueryEnabled));
        }
    }

    /// <summary>
    /// When the trigger key is pressed on the element, check whether
    /// the command should execute and then execute it.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    static void OnKeyDown(object sender, KeyEventArgs e)
    {
        UIElement element = sender as UIElement;
        Key triggerKey = (Key)element.GetValue(KeyProperty);

        if (e.Key != triggerKey)
        {
            return;
        }

        CommandModelBase cmdModel = (CommandModelBase)element.GetValue(CommandProperty);
        object parameter = element.GetValue(ParameterProperty);
        if (cmdModel.CanExecute(parameter))
        {
            cmdModel.Execute(parameter);
        }
        e.Handled = true;
    }
}

Para usar este de xaml você pode fazer algo como isto:

<TextBox framework:CreateKeyDownCommandBinding.Command="{Binding MyCommand}">
    <framework:CreateKeyDownCommandBinding.Key>Enter</framework:CreateKeyDownCommandBinding.Key>
</TextBox>

Editar: CommandModelBase é uma classe base que eu uso para todos os comandos. É com base na classe CommandModel do artigo de Dan Crevier em MVVM ( aqui ). Aqui está a fonte para o uso I versão ligeiramente modificada com CreateKeyDownCommandBinding:

public abstract class CommandModelBase : ICommand
    {
        RoutedCommand routedCommand_;

        /// <summary>
        /// Expose a command that can be bound to from XAML.
        /// </summary>
        public RoutedCommand Command
        {
            get { return routedCommand_; }
        }

        /// <summary>
        /// Initialise the command.
        /// </summary>
        public CommandModelBase()
        {
            routedCommand_ = new RoutedCommand();
        }

        /// <summary>
        /// Default implementation always allows the command to execute.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void OnQueryEnabled(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = CanExecute(e.Parameter);
            e.Handled = true;
        }

        /// <summary>
        /// Subclasses must provide the execution logic.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void OnExecute(object sender, ExecutedRoutedEventArgs e)
        {
            Execute(e.Parameter);
        }

        #region ICommand Members

        public virtual bool CanExecute(object parameter)
        {
            return true;
        }

        public event EventHandler CanExecuteChanged;

        public abstract void Execute(object parameter);

        #endregion
    }

Comentários e sugestões de melhorias seria muito bem-vindos.

Eu olhei para essa questão há alguns meses, e eu escrevi uma extensão de marcação que faz o truque. Ele pode ser usado como um regulares de ligação:

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

O código fonte completo para esta extensão pode ser encontrada aqui:

http: // www.thomaslevesque.com/2009/03/17/wpf-using-inputbindings-with-the-mvvm-pattern/

Por favor, esteja ciente de que esta solução não é provavelmente muito "limpa", pois utiliza algumas aulas particulares e campos através da reflexão ...

A resposta curta é que você não pode lidar com eventos de entrada do teclado em linha reta, sem code-behind, mas você pode lidar com InputBindings com MVVM (eu posso mostrar-lhe um exemplo relevante se é isso que você precisa).

Você pode fornecer mais informações sobre o que você quer fazer no manipulador?

Código-behind não está a ser totalmente evitada com MVVM. É simplesmente a ser utilizada para tarefas estritamente relacionadas com a UI. Um exemplo cardeal seria ter algum tipo de 'forma de entrada de dados' que, quando carregado, precisa de se concentrar definido para o primeiro elemento de entrada (caixa de texto, caixa de combinação, qualquer que seja). Você teria normalmente atribuir esse elemento um atributo x: Nome, em seguida, ligar a janela / página / UserControl do 'Loaded' evento para o foco definido para esse elemento. Isto é perfeitamente ok pelo padrão porque a tarefa é UI-centric e não tem nada a ver com os dados que ela representa.

Eu sei que esta questão é muito antiga, mas eu vim com isso porque esse tipo de funcionalidade foi apenas mais fácil de implementar em Silverlight (5). Então, talvez outros vão passar por aqui também.

Eu escrevi esta solução simples depois que eu não poderia encontrar o que eu estava procurando. Transformaram out foi bastante simples. Ele deve funcionar tanto em Silverlight 5 e WPF.

public class KeyToCommandExtension : IMarkupExtension<Delegate>
{
    public string Command { get; set; }
    public Key Key { get; set; }

    private void KeyEvent(object sender, KeyEventArgs e)
    {
        if (Key != Key.None && e.Key != Key) return;

        var target = (FrameworkElement)sender;

        if (target.DataContext == null) return;

        var property = target.DataContext.GetType().GetProperty(Command, BindingFlags.Public | BindingFlags.Instance, null, typeof(ICommand), new Type[0], null);

        if (property == null) return;

        var command = (ICommand)property.GetValue(target.DataContext, null);

        if (command != null && command.CanExecute(Key))
            command.Execute(Key);
    }

    public Delegate ProvideValue(IServiceProvider serviceProvider)
    {
        if (string.IsNullOrEmpty(Command))
            throw new InvalidOperationException("Command not set");

        var targetProvider = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));

        if (!(targetProvider.TargetObject is FrameworkElement))
            throw new InvalidOperationException("Target object must be FrameworkElement");

        if (!(targetProvider.TargetProperty is EventInfo))
            throw new InvalidOperationException("Target property must be event");

        return Delegate.CreateDelegate(typeof(KeyEventHandler), this, "KeyEvent");
    }

Uso:

<TextBox KeyUp="{MarkupExtensions:KeyToCommand Command=LoginCommand, Key=Enter}"/>

Observe que Command é uma string e não um ICommand bindable. Eu sei que isso não é tão flexível, mas é mais limpo quando usado, eo que você precisa de 99% do tempo. Embora não deve ser um problema para a mudança.

Semelhante a resposta karlipoppins, mas eu achei que não funcionou sem as seguintes adições / alterações:

<TextBox Text="{Binding UploadNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
    <TextBox.InputBindings>
        <KeyBinding Key="Enter" Command="{Binding FindUploadCommand}" />
    </TextBox.InputBindings>
</TextBox>
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top