Frage

Wie kann ich das Keyboard.KeyDown Ereignis zu behandeln, ohne Code-behind? Wir versuchen, das MVVM Muster zu verwenden und vermeiden Sie einen Event-Handler in Code-Behind-Datei geschrieben werden.

War es hilfreich?

Lösung

Ein wenig spät, aber hier geht.

Microsofts WPF-Team vor kurzem veröffentlicht eine frühe Version ihrer WPF MVVM Toolkit . Darin finden Sie eine Klasse namens CommandReference finden, dass Dinge wie keybindings umgehen kann. Schauen Sie sich ihre WPF MVVM-Vorlage, um zu sehen, wie es funktioniert.

Andere Tipps

eine aktualisierte Antwort zu bringen, das .net Framework 4.0 ermöglicht es Ihnen, diese gut zu tun, indem wir dir einen KeyBinding Befehl auf einen Befehl in einem Ansichtsmodell binden.

So ... Wenn Sie die Enter-Taste hören wollen, Sie so etwas tun würde:

<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 - es ist wie tausend Antworten und hier werde ich ein weiteres hinzuzufügen ..

Die wirklich offensichtlich, was in einer ‚warum-Artikel nicht-I-realize-this-Stirn-Schlag‘ Art und Weise ist, dass die Code-behind und der ViewModel im selben Raum sitzen, so zu sprechen, so es gibt keinen Grund, warum sie erlaubt sind nicht ein Gespräch zu führen.

Wenn man darüber nachdenkt, ist das XAML bereits eng mit dem API Ansichtsmodell gekoppelt ist, so könnte man genauso gut gehen und eine Abhängigkeit von aus dem Code hinter machen.

Die anderen offensichtlichen Regeln zu befolgen oder zu ignorieren gilt nach wie vor (Schnittstellen, null Kontrollen <- vor allem, wenn Sie verwenden Mischung ...)

Ich mache immer eine Eigenschaft in der Code-Behind wie folgt aus:

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

Dies ist der Client-Code. Die Null-Prüfung ist für die Unterstützung Kontrolle Hosting als wie in Mischung.

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

Behandeln Sie Ihre Veranstaltung in der Code-behind wie Sie wollen (UI Ereignisse sind UI-centric so OK, es ist) und dann eine Methode auf dem ViewModelClass haben, die auf dieses Ereignis reagieren kann. Die Bedenken sind immer noch getrennt.

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

Alle diese anderen angefügten Eigenschaften und Voodoo ist sehr cool und die Techniken sind für einige andere Dinge wirklich nützlich, aber hier könnte man mit etwas einfacher ...

wegkommen

Ich tue dies durch ein angeschlossenes Verhalten mit 3 Abhängigkeitseigenschaften verwendet wird; eine ist der Befehl auszuführen, ist einer der Parameter auf den Befehl zu übergeben, und der andere ist der Schlüssel, der der Befehl ausgeführt werden verursacht. Hier ist der 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;
    }
}

Um dies zu verwenden, um von XAML Sie etwas tun können:

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

Edit: CommandModelBase ist eine Basisklasse I für alle Befehle verwenden. Es basiert auf der CommandModel Klasse von Dan Crevier Artikel über MVVM ( hier ). Hier ist die Quelle für die leicht modifizierte Version I mit CreateKeyDownCommandBinding verwenden:

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
    }

Kommentare und Verbesserungsvorschläge wären sehr willkommen.

sah ich in dieser Frage vor ein paar Monaten, und ich schrieb Erweiterung ein Markup, das der Trick funktioniert. Es kann wie eine reguläre Bindung verwendet werden:

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

Der vollständige Quellcode für diese Erweiterung kann hier gefunden werden:

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

Bitte beachten Sie, dass diese Problemumgehung ist wahrscheinlich nicht sehr „sauber“, weil es einige privaten Klassen und Felder durch die Reflexion verwendet ...

Kurze Antwort ist, dass Sie nicht gerade Tastatureingabeereignisse ohne Code-behind umgehen kann, aber Sie können behandeln Inputbindings mit MVVM (Ich kann Ihnen ein relevantes Beispiel zeigen, ob dies ist, was Sie brauchen).

Können Sie mehr Informationen darüber, was Sie in dem Handler zu tun?

-Code-behind nicht vollständig mit MVVM vermieden werden. Es ist einfach für streng UI-bezogenen Aufgaben verwendet werden. Eine Kardinal Beispiel wäre eine Art von ‚Dateneingabeformular‘ werden, das, wenn geladen wird, den Fokus auf das erste Eingabeelement (Textfeld, Combobox, was auch immer) setzen muss. Sie würden zuweisen häufig, dass das Element ein x: Name-Attribut, dann das Fenster / Seite / Usercontrol anschließen ‚Loaded‘ Ereignis Fokus auf dieses Element zu setzen. Das ist vollkommen in Ordnung, die durch das Muster, weil die Aufgabe UI-centric ist und hat nichts mit den Daten zu tun, die sie darstellt.

Ich weiß, diese Frage ist sehr alt, aber ich kam durch, weil diese Art von Funktionalität nur einfacher gemacht wurde in Silverlight (5) zu implementieren. Also vielleicht andere werden von hier kommen.

Ich schrieb diese einfache Lösung, nachdem ich nicht, was finden konnte, die ich suchte. Es stellte sich heraus, es war ziemlich einfach. Es sollte in beiden Silverlight 5 und WPF arbeiten.

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

Verbrauch:

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

Beachten Sie, dass Command ein String ist und kein bindable ICommand. Ich weiß, dass dies nicht so flexibel ist, aber es ist sauberer, wenn verwendet, und was Sie 99% der Zeit benötigen. Obwohl es kein Problem sein sollte geändert werden.

ähnlich wie karlipoppins Antwort, aber ich fand es nicht ohne die folgenden Ergänzungen / Änderungen funktionierte:

<TextBox Text="{Binding UploadNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
    <TextBox.InputBindings>
        <KeyBinding Key="Enter" Command="{Binding FindUploadCommand}" />
    </TextBox.InputBindings>
</TextBox>
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top