WPF MVVM应用程序中的键盘事件?
如何处理Keyboard.KeyDown事件而不使用代码隐藏? 我们正在尝试使用MVVM模式,并避免在代码隐藏文件中编写事件处理程序。
有点晚了,但是这里。
微软WPF团队最近发布了WPF MVVM Toolkit的早期版本。 在这里面,你会发现一个名为CommandReference的类,它可以处理像键绑定这样的事情。 看看他们的WPF 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密切相关,所以你可以从后面的代码中去依赖它。
其他明显的规则服从或忽略仍然适用(接口,空检查< – 特别是如果您使用混合…)
我总是在这样的代码背后做一个属性:
private ViewModelClass ViewModel { get { return DataContext as ViewModelClass; } }
这是客户端代码。 空检查用于帮助控制托pipe,就像在混合中一样。
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是我用于所有命令的基类。 它基于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 }
有关改进的意见和build议将非常受欢迎。
简单的答案是你不能处理没有代码隐藏的直接键盘input事件,但你可以用MVVM处理InputBindings (我可以给你一个相关的例子,如果这是你所需要的)。
你能提供更多关于你想在处理程序中做什么的信息吗?
代码隐藏是不能完全避免与MVVM。 它只是用于严格的UI相关的任务。 一个基本的例子是有一些types的“数据input表单”,加载时需要将焦点设置为第一个input元素(文本框,combobox,无论什么)。 您通常会将该元素分配给x:Name属性,然后挂接Window / Page / UserControl的“Loaded”事件以将焦点设置为该元素。 这种模式是完全可以的,因为任务是以UI为中心的,与它所代表的数据无关。
几个月前,我研究了这个问题,并且写了一个标记扩展来实现这个function。 它可以像常规绑定一样使用:
<Window.InputBindings> <KeyBinding Key="E" Modifiers="Control" Command="{input:CommandBinding EditCommand}"/> </Window.InputBindings>
这个扩展的完整源代码可以在这里find:
http://www.thomaslevesque.com/2009/03/17/wpf-using-inputbindings-with-the-mvvm-pattern/
请注意,这个解决方法可能不是很“干净”,因为它通过reflection使用了一些私有的类和字段。
我知道这个问题是非常古老的,但是我之所以这样做,是因为这种function在Silverlight(5)中更容易实现。 所以也许别人也会来这里
我找不到我要找的东西之后,我写了这个简单的解决scheme。 原来这很简单。 它应该可以在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
是一个string,而不是一个可绑定的ICommand
。 我知道这不是很灵活,但使用时更清洁,99%的时间需要。 虽然改变不应该是一个问题。
与karlipoppins的答案类似,但我发现没有以下添加/更改不起作用:
<TextBox Text="{Binding UploadNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> <TextBox.InputBindings> <KeyBinding Key="Enter" Command="{Binding FindUploadCommand}" /> </TextBox.InputBindings> </TextBox>