Domanda

Come posso gestire la Tastiera.Evento KeyDown senza l'utilizzo di codice-dietro?Stiamo cercando di utilizzare il modello MVVM ed evitare di scrivere un gestore di evento nel file code-behind.

È stato utile?

Soluzione

Un po 'in ritardo, ma qui va.

Il team WPF di Microsoft ha recentemente rilasciato una versione precedente del WPF MVVM Toolkit . In esso troverai una classe chiamata CommandReference che può gestire cose come i tasti. Guarda il loro modello MVPM WPF per vedere come funziona.

Altri suggerimenti

Per fornire una risposta aggiornata, il framework .net 4.0 ti consente di farlo bene lasciandoti associare un comando KeyBinding a un comando in un modello di visualizzazione.

Quindi ... Se vuoi ascoltare il tasto Invio, dovresti fare qualcosa del genere:

<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 - ci sono circa mille risposte e qui ne aggiungerò un'altra ..

La cosa veramente ovvia in un modo "why-not-real-this-front-slap" è che il code-behind e il ViewModel siedono nella stessa stanza per così dire , quindi non vi è alcun motivo per cui non sia loro permesso di conversare.

Se ci pensi, XAML è già intimamente accoppiato all'API di ViewModel, quindi potresti anche fare una dipendenza da esso dal codice dietro.

Le altre regole ovvie da obbedire o ignorare si applicano ancora (interfacce, controlli null < - specialmente se usi Blend ...)

Faccio sempre una proprietà nel code-behind in questo modo:

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

Questo è il codice client. Il controllo null serve per aiutare a controllare l'hosting come in blend.

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

Gestisci il tuo evento nel codice dietro come desideri (gli eventi dell'interfaccia utente sono incentrati sull'interfaccia utente quindi è OK) e quindi hanno un metodo su ViewModelClass in grado di rispondere a quell'evento. Le preoccupazioni sono ancora separate.

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

Tutte queste altre proprietà e voodoo collegati sono molto interessanti e le tecniche sono davvero utili per alcune altre cose, ma qui potresti cavartela con qualcosa di più semplice ...

Faccio questo usando un comportamento associato con 3 proprietà di dipendenza; uno è il comando da eseguire, uno è il parametro da passare al comando e l'altro è la chiave che determinerà l'esecuzione del comando. Ecco il codice:

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

Per usare questo da xaml puoi fare qualcosa del genere:

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

Modifica: CommandModelBase è una classe base che utilizzo per tutti i comandi. È basato sulla classe CommandModel dall'articolo di Dan Crevier su MVVM ( qui ). Ecco la fonte della versione leggermente modificata che uso con 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
    }

Commenti e suggerimenti per miglioramenti sarebbero i benvenuti.

Ho guardato in che problema un paio di mesi fa, e ho scritto un'estensione di markup che fa il trucco.Può essere utilizzato come un normale associazione :

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

Il codice sorgente completo per questa estensione può essere trovato qui :

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

Si prega di essere consapevoli del fatto che questa soluzione non è probabilmente molto "pulito", perché utilizza alcune lezioni private e campi, attraverso la riflessione...

La risposta breve è che non puoi gestire eventi di input da tastiera diretta senza code-behind, ma puoi gestire InputBindings con MVVM (posso mostrarti un esempio pertinente se questo è quello che ti serve).

Puoi fornire ulteriori informazioni su cosa vuoi fare nel gestore?

Code-behind non deve essere evitato del tutto con MVVM. Deve semplicemente essere utilizzato per attività strettamente correlate all'interfaccia utente. Un esempio cardinale sarebbe avere un qualche tipo di "modulo di inserimento dati" che, una volta caricato, deve focalizzare il primo elemento di input (casella di testo, casella combinata, qualunque cosa). Normalmente assegnereste a quell'elemento un attributo x: Name, quindi agganciate l'evento 'Loaded' di Window / Page / UserControl per impostare lo stato attivo su quell'elemento. Questo è perfettamente accettabile dal modello perché l'attività è incentrata sull'interfaccia utente e non ha nulla a che fare con i dati che rappresenta.

So che questa domanda è molto vecchia, ma mi è venuta questa domanda perché questo tipo di funzionalità è stata appena semplificata da implementare in Silverlight (5). Quindi forse anche altri verranno qui.

Ho scritto questa semplice soluzione dopo che non sono riuscito a trovare quello che stavo cercando. Si è scoperto che era piuttosto semplice. Dovrebbe funzionare sia in Silverlight 5 che in 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");
    }

Utilizzo:

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

Si noti che Command è una stringa e non è associabile ICommand. So che non è così flessibile, ma è più pulito quando utilizzato e ciò di cui hai bisogno il 99% delle volte. Anche se non dovrebbe essere un problema cambiare.

Simile alla risposta karlipoppins, ma ho scoperto che non funzionava senza le seguenti aggiunte / modifiche:

<TextBox Text="{Binding UploadNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
    <TextBox.InputBindings>
        <KeyBinding Key="Enter" Command="{Binding FindUploadCommand}" />
    </TextBox.InputBindings>
</TextBox>
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top