コード ビハインドを使用せずに Keyboard.KeyDown イベントを処理するにはどうすればよいですか? MVVM パターンを使用して、コード ビハインド ファイルにイベント ハンドラーを記述しないようにしています。
8 に答える
更新された答えをもたらすために、.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>
WOW - 1,000 通りの回答があります。ここに別の回答を追加します。
「なぜ私はこの額に気付かなかったのか」というような方法で本当に明白なことは、コードビハインドとViewModel
いわば同じ部屋に座っているということです。したがって、理由はありません。会話が許されない理由。
考えてみれば、XAML はすでに ViewModel の API と密接に結合されているため、コード ビハインドから XAML に依存するように設定することもできます。
従うか無視する他の明白なルールは引き続き適用されます (インターフェイス、null チェック <-- 特に Blend を使用する場合...)
私は常に次のようにコード ビハインドでプロパティを作成します。
private ViewModelClass ViewModel { get { return DataContext as ViewModelClass; } }
これはクライアントコードです。null チェックは、blend のようにホスティングを制御するためのものです。
void someEventHandler(object sender, KeyDownEventArgs e)
{
if (ViewModel == null) return;
/* ... */
ViewModel.HandleKeyDown(e);
}
必要に応じてコード ビハインドでイベントを処理し (UI イベントは UI 中心なので問題ありません)、そのイベントに応答できる ViewModelClass のメソッドを用意します。懸念事項はまだ分離されています。
ViewModelClass
{
public void HandleKeyDown(KeyEventArgs e) { /* ... */ }
}
これらの他のすべての添付プロパティとブードゥー教は非常にクールであり、テクニックは他のいくつかのことに本当に役立ちますが、ここではもっと単純なものでうまくいくかもしれません...
少し遅くなりましたが、ここまでです。
Microsoft の WPF チームは最近、 WPF MVVM Toolkitの初期バージョンをリリースしました 。その中に、キーバインドなどを処理できる CommandReference というクラスがあります。WPF MVVM テンプレートを見て、それがどのように機能するかを確認してください。
これを行うには、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>
編集: CommandModelBase は、すべてのコマンドに使用する基本クラスです。これは、Dan Crevier の MVVM に関する記事 (こちら)の CommandModel クラスに基づいています。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
}
改善のためのコメントや提案は大歓迎です。
私は数か月前にその問題を調査し、そのトリックを実行するマークアップ拡張機能を作成しました。通常のバインディングのように使用できます:
<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/
この回避策は、リフレクションを通じていくつかのプライベート クラスとフィールドを使用するため、おそらく「クリーン」ではないことに注意してください...
簡単に言えば、コードビハインドなしではストレートキーボード入力イベントを処理することはできませんが、MVVMを使用してInputBindingsを処理することはできます(これが必要な場合は、関連する例を示すことができます)。
ハンドラーで何をしたいのかについて、より多くの情報を提供できますか?
コードビハインドは、MVVMで完全に回避する必要はありません。厳密にUI関連のタスクに使用するだけです。基本的な例は、ロード時に最初の入力要素(テキストボックス、コンボボックスなど)にフォーカスを設定する必要があるある種の「データ入力フォーム」を持つことです。通常、その要素にx:Name属性を割り当ててから、Window / Page/UserControlの「Loaded」イベントをフックしてその要素にフォーカスを設定します。タスクはUI中心であり、それが表すデータとは何の関係もないため、これはパターンによって完全に問題ありません。
この質問が非常に古いことは知っていますが、このタイプの機能が 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
は文字列であり、 bindable ではないことに注意してくださいICommand
。これは柔軟ではないことはわかっていますが、使用するときれいになり、99% の確率で必要なものが得られます。変更しても問題はないはずですが。