Comment gérer l'événement Keyboard.KeyDown sans utiliser le code-behind ? Nous essayons d'utiliser le modèle MVVM et d'éviter d'écrire un gestionnaire d'événement dans le fichier code-behind.
Réponses
Trop de publicités?Pour apporter une réponse actualisée, le cadre .net 4.0 vous permet de le faire joliment en vous permettant de lier un KeyBinding Command à une commande dans un viewmodel.
Donc... Si vous vouliez é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 un millier de réponses et je vais en ajouter une autre
La chose la plus évidente, dans le genre "pourquoi n'ai-je pas réalisé cela ?", c'est que le code-behind et le ViewModel
sont assis dans la même pièce pour ainsi dire, 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 lié à l'API du ViewModel, alors vous pouvez tout aussi bien créer une dépendance à partir du code.
Les autres règles évidentes à respecter ou à ignorer s'appliquent toujours (interfaces, contrôles de nullité <-- surtout si vous utilisez Blend...).
Je crée toujours une propriété dans le code-behind comme ceci :
private ViewModelClass ViewModel { get { return DataContext as ViewModelClass; } }
C'est le code client. Le contrôle de nullité est pour aider le contrôle de l'hébergement comme dans blend.
void someEventHandler(object sender, KeyDownEventArgs e)
{
if (ViewModel == null) return;
/* ... */
ViewModel.HandleKeyDown(e);
}
Gérez votre événement dans le code derrière comme vous le voulez (les événements de l'interface utilisateur sont centrés sur l'interface utilisateur donc c'est OK) et ensuite ayez une méthode sur la ViewModelClass qui peut répondre à cet événement. Les préoccupations sont toujours séparées.
ViewModelClass
{
public void HandleKeyDown(KeyEventArgs e) { /* ... */ }
}
Toutes ces autres propriétés attachées et le vaudou sont très cool et les techniques sont vraiment utiles pour d'autres choses, mais ici vous pourriez vous en sortir avec quelque chose de plus simple...
Un peu tard, mais c'est parti.
L'équipe WPF de Microsoft a récemment publié une version préliminaire de son Boîte à outils MVVM WPF . Vous y trouverez une classe appelée CommandReference qui permet de gérer des éléments tels que les combinaisons de touches. Regardez leur modèle WPF MVVM pour voir comment cela fonctionne.
Je le fais en utilisant un comportement joint avec 3 propriétés de dépendance ; l'une est la commande à exécuter, l'autre est le paramètre à passer à la commande et la dernière 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 l'utiliser à partir de xaml, vous pouvez faire quelque chose comme ceci :
<TextBox framework:CreateKeyDownCommandBinding.Command="{Binding MyCommand}">
<framework:CreateKeyDownCommandBinding.Key>Enter</framework:CreateKeyDownCommandBinding.Key>
</TextBox>
Edit : CommandModelBase est une classe de base que j'utilise pour toutes les commandes. Elle est basée sur la classe CommandModel de l'article de Dan Crevier sur MVVM ( aquí ). Voici la 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
}
Les commentaires et suggestions d'amélioration sont les bienvenus.
La réponse courte est que vous ne pouvez pas gérer les événements d'entrée de clavier direct 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 ?
Le code-behind n'est pas à éviter complètement avec MVVM. Il doit simplement être utilisé pour des tâches strictement liées à l'interface utilisateur. Un exemple typique serait d'avoir un type de "formulaire de saisie de données" qui, lorsqu'il est chargé, doit mettre le focus sur le premier élément de saisie (zone de texte, combobox, etc.). Vous attribuez généralement à cet élément un attribut x:Name, puis vous utilisez l'événement "Loaded" de Window/Page/UserControl pour mettre l'accent sur cet élément. Cette méthode est parfaitement compatible avec le 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.