web-dev-qa-db-ja.com

WPF MVVMアプリケーションのキーボードイベント?

コードビハインドを使用せずにKeyboard.KeyDownイベントを処理するにはどうすればよいですか? MVVMパターンを使用し、コードビハインドファイルにイベントハンドラーを記述しないようにしています。

47
Carlos

少し遅れましたが、ここに行きます。

マイクロソフトのWPFチームは、最近 WPF MVVM Toolkit の初期バージョンをリリースしました。その中には、キーバインドなどを処理できるCommandReferenceというクラスがあります。 WPF MVVMテンプレートを見て、その仕組みを確認してください。

8
djcouchycouch

最新の回答を得るために、.net 4.0フレームワークでは、KeyBindingコマンドをビューモデル内のコマンドにバインドできるようにすることで、これをうまく行うことができます。

したがって... Enterキーをリッスンする場合は、次のようにします。

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

すごい-1000の答えがありますが、ここで別の答えを追加します。

「なぜ私はこの額を平手打ちしなかったのか」という方法で本当に明らかなことは、コードビハインドとViewModelがいわば同じ部屋に座っていることです。 、したがって、彼らが会話をすることを許可されない理由はありません。

考えてみると、XAMLはすでにViewModelのAPIと密接に結合しているため、コードビハインドからXAMLに依存することもできます。

従うか無視する他の明白なルールはまだ適用されます(インターフェイス、nullチェック<-特にBlendを使用する場合...)

私は常にこのようなコードビハインドでプロパティを作成します。

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

これはクライアントコードです。 nullチェックは、ブレンドのようにホスティングを制御するのに役立ちます。

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

必要に応じてコードビハインドでイベントを処理し(UIイベントはUI中心なので、OKです)、そのイベントに応答できるViewModelClassのメソッドを持ちます。懸念はまだ分かれています。

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

これらの他の添付プロパティとブードゥーはすべて非常にクールであり、テクニックは他のいくつかのことに本当に役立ちますが、ここではもっと簡単なもので逃げることができます...

30
Pieter Breed

これを行うには、3つの依存関係プロパティを持つ添付の動作を使用します。 1つは実行するコマンド、1つはコマンドに渡すパラメーター、もう1つはコマンドを実行させるキーです。コードは次のとおりです。

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

Xamlからこれを使用するには、次のようなことができます。

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

Edit:CommandModelBaseは、すべてのコマンドに使用する基本クラスです。 MVVMに関するDan Crevierの記事のCommandModelクラスに基づいています( here )。 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
    }

改善のためのコメントや提案は大歓迎です。

8
Paul

数ヶ月前にその問題を調査し、トリックを行うマークアップ拡張機能を作成しました。通常のバインディングのように使用できます。

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

この拡張機能の完全なソースコードは、次の場所にあります。

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

この回避策は、リフレクションを通じていくつかのプライベートクラスとフィールドを使用するため、おそらくあまり「クリーン」ではないことに注意してください...

3
Thomas Levesque

簡単な答えは、コードビハインドなしでストレートキーボード入力イベントを処理することはできませんが、MVVMでInputBindingsを処理することです(これが必要な場合は関連する例を示します)。

ハンドラーで何をしたいのかについての詳細情報を提供できますか?

コードビハインドは、MVVMで完全に回避されるべきではありません。これは、厳密にUI関連のタスクに使用するだけです。基本的な例としては、ロード時に最初の入力要素(テキストボックス、コンボボックスなど)にフォーカスを設定する必要がある「データエントリフォーム」があります。通常、その要素にx:Name属性を割り当ててから、Window/Page/UserControlの「Loaded」イベントをフックして、その要素にフォーカスを設定します。タスクはUI中心であり、それが表すデータとは何の関係もないため、これはパターンによって完全に問題ありません。

2
Adrian

私はこの質問が非常に古いことを知っていますが、Silverlight(5)でこのタイプの機能を簡単に実装できるようになったので、私はこれに来ました。他の人もここに来るでしょう。

探しているものが見つからなかった後、この簡単なソリューションを書きました。かなり簡単だったことが判明しました。 Silverlight 5と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");
    }

使用法:

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

Commandは文字列であり、バインド可能なICommandではないことに注意してください。私はこれがそれほど柔軟ではないことを知っていますが、使用するとよりクリーンで、99%の時間を必要とします。変更することは問題ではないはずですが。

1

Karlipoppinsの回答に似ていますが、次の追加/変更がないと機能しないことがわかりました。

<TextBox Text="{Binding UploadNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
    <TextBox.InputBindings>
        <KeyBinding Key="Enter" Command="{Binding FindUploadCommand}" />
    </TextBox.InputBindings>
</TextBox>
0
SurfingSanta