Question

Comment puis-je gérer l'événement Keyboard.KeyDown sans utiliser code-behind? Nous essayons d’utiliser le modèle MVVM et d’éviter d’écrire un gestionnaire d’événements dans un fichier code-behind.

Était-ce utile?

La solution

Un peu tard, mais voilà.

L’équipe WPF de Microsoft a récemment publié une première version de son MVF WPF Boîte à outils . Vous y trouverez une classe appelée CommandReference pouvant gérer des opérations telles que les liaisons de clé. Regardez leur modèle MVF WPF pour voir comment cela fonctionne.

Autres conseils

Pour apporter une réponse mise à jour, le framework .net 4.0 vous permet de le faire facilement en vous permettant de lier une commande KeyBinding à une commande d'un modèle de vue.

Alors ... Si vous voulez écouter la touche Entrée, vous feriez quelque chose comme ça:

<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 - il y a comme mille réponses et je vais en ajouter une autre ..

Ce qui est vraiment évident dans un genre de "pourquoi-je-ne-suis-pas-je-réalise-ce-front-slap" est que le code derrière et le ViewModel sont assis dans la même pièce pour parler , il n'y a donc aucune raison pour qu'ils ne soient pas autorisés à avoir une conversation.

Si vous y réfléchissez, le XAML est déjà intimement couplé à l'API de ViewModel, vous pouvez donc aussi bien en faire une dépendance à partir du code précédent.

Les autres règles évidentes à obéir ou à ignorer s'appliquent toujours (interfaces, contrôles nuls < - surtout si vous utilisez Blend ...)

Je fais toujours une propriété dans le code-behind comme ceci:

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

Ceci est le code client. Le contrôle nul sert à contrôler l’hébergement, de la même manière que les autres.

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

Traitez votre événement dans le code derrière comme vous le souhaitez (les événements de l'interface utilisateur sont centrés sur l'interface utilisateur, donc c'est OK), puis créez une méthode sur ViewModelClass qui peut répondre à cet événement. Les préoccupations sont encore séparées.

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

Toutes ces autres propriétés attachées au vaudou sont très cool et les techniques sont vraiment utiles pour d’autres choses, mais ici vous pourriez vous en tirer avec quelque chose de plus simple ...

Je le fais en utilisant un comportement attaché avec 3 propriétés de dépendance; l'un est la commande à exécuter, l'un est le paramètre à transmettre à la commande et l'autre est la clé qui provoquera l'exécution de la commande. Voici le code:

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;
    }
}

Pour utiliser ceci depuis xaml, vous pouvez faire quelque chose comme ceci:

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

Modifier: CommandModelBase est une classe de base que j'utilise pour toutes les commandes. Il est basé sur la classe CommandModel de l'article de Dan Crevier sur MVVM ( ici ). Voici le code source de la version légèrement modifiée que j'utilise avec 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
    }

Vos commentaires et suggestions d'amélioration seraient les bienvenus.

J'ai examiné cette question il y a quelques mois et j'ai écrit une extension de balisage qui fait l'affaire. Il peut être utilisé comme une reliure normale:

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

Le code source complet de cette extension est disponible ici:

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

Sachez que cette solution de contournement n’est probablement pas très & "propre &"; car elle utilise des classes et des champs privés par réflexion ...

La réponse courte est que vous ne pouvez pas gérer les événements d'entrée au clavier droit sans code-behind, mais vous pouvez gérer InputBindings avec MVVM (je peux vous montrer un exemple pertinent si c'est ce dont vous avez besoin).

Pouvez-vous fournir plus d'informations sur ce que vous voulez faire dans le gestionnaire?

Code-behind ne doit pas être entièrement évité avec MVVM. Il doit simplement être utilisé pour des tâches strictement liées à l'interface utilisateur. Un exemple cardinal serait d'avoir un type de «formulaire de saisie de données» qui, une fois chargé, doit définir le focus sur le premier élément d'entrée (zone de texte, liste déroulante, etc.). Vous attribuez généralement à cet élément un attribut x: Name, puis connectez l'événement 'Loaded' de Window / Page / UserControl pour définir le focus sur cet élément. Cela convient parfaitement au modèle, car la tâche est centrée sur l'interface utilisateur et n'a rien à voir avec les données qu'elle représente.

Je sais que cette question est très ancienne, mais j’en suis venu à cela, car ce type de fonctionnalité a été simplement rendu plus facile à implémenter dans Silverlight (5). Alors peut-être que d’autres viendront ici aussi.

J'ai écrit cette solution simple après ne pas avoir trouvé ce que je cherchais. Il s'est avéré que c'était plutôt simple. Cela devrait fonctionner à la fois dans Silverlight 5 et dans 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");
    }

Utilisation:

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

Notez que Command est une chaîne et non un bindable ICommand. Je sais que ce n’est pas aussi flexible, mais c’est plus propre lorsqu’il est utilisé et ce dont vous avez besoin 99% du temps. Bien que cela ne devrait pas être un problème à changer.

Semblable à la réponse de karlipoppins, mais j'ai constaté que cela ne fonctionnait pas sans les ajouts / modifications suivants:

<TextBox Text="{Binding UploadNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
    <TextBox.InputBindings>
        <KeyBinding Key="Enter" Command="{Binding FindUploadCommand}" />
    </TextBox.InputBindings>
</TextBox>
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top