[英]Keyboard events in a WPF MVVM application?
如何在不使用代碼隱藏的情況下處理 Keyboard.KeyDown 事件? 我們正在嘗試使用 MVVM 模式並避免在代碼隱藏文件中編寫事件處理程序。
為了提供更新的答案,.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>
哇 - 好像有一千個答案,在這里我要添加另一個..
以“為什么我沒有意識到這個額頭拍”的方式,真正明顯的事情是代碼隱藏和ViewModel
可以說是坐在同一個房間里,所以沒有不允許他們交談的原因。
如果您考慮一下,XAML 已經與 ViewModel 的 API 緊密耦合,因此您也可以使用 go 並從后面的代碼中依賴它。
其他要遵守或忽略的明顯規則仍然適用(接口,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 為中心的,所以沒關系),然后在 ViewModelClass 上有一個可以響應該事件的方法。 這些擔憂仍然是分開的。
ViewModelClass
{
public void HandleKeyDown(KeyEventArgs e) { /* ... */ }
}
所有這些其他附加屬性和巫毒都非常酷,這些技術對其他一些事情真的很有用,但在這里你可能會得到一些更簡單的東西......
我通過使用具有 3 個依賴屬性的附加行為來做到這一點; 一個是要執行的命令,一個是要傳遞給命令的參數,另一個是導致命令執行的鍵。 這是代碼:
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 是我用於所有命令的基礎 class。 它基於 Dan Crevier 關於 MVVM 的文章( 此處)中的 CommandModel class。 這是我與 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
}
非常歡迎提出改進意見和建議。
有點晚了,但就到這里。
微軟的 WPF 團隊最近發布了他們的WPF MVVM 工具包的早期版本。 在其中,您會發現一個名為 CommandReference 的 class 可以處理諸如鍵綁定之類的事情。 查看他們的 WPF MVVM 模板,了解它是如何工作的。
幾個月前我研究了這個問題,我寫了一個標記擴展來解決這個問題。 它可以像常規綁定一樣使用:
<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 相關的任務。 一個主要示例是具有某種類型的“數據輸入表單”,加載時需要將焦點設置到第一個輸入元素(文本框,combobox,等等)。 您通常會為該元素分配一個 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
是一個字符串,而不是可綁定的ICommand
。 我知道這不是那么靈活,但它在使用時更干凈,而且你 99% 的時間都需要它。 雖然改變應該不是問題。
類似於 karlipoppins 答案,但我發現如果沒有以下添加/更改,它就無法工作:
<TextBox Text="{Binding UploadNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding FindUploadCommand}" />
</TextBox.InputBindings>
</TextBox>
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.