按键在文本框MVVM中

Lee*_*eil 13 data-binding wpf mvvm

我刚刚开始使用MVVM并且我在查明如何将文本框中的按键绑定到视图模型中的ICommand时遇到问题.我知道我可以在代码隐藏中做到这一点,但我试图尽可能地避免这种情况.

更新:到目前为止,如果你有混合sdk或你没有遇到与我正在进行的交互dll的问题,那么这些解决方案都很好.除了必须使用混合sdk之外,还有其他更通用的解决方案吗?

Ray*_*rns 9

首先,如果要绑定RoutedUICommand,则很容易 - 只需添加到UIElement.InputBindings集合:

<TextBox ...>
  <TextBox.InputBindings>
    <KeyBinding
      Key="Q"
      Modifiers="Control" 
      Command="my:ModelAirplaneViewModel.AddGlueCommand" />
Run Code Online (Sandbox Code Playgroud)

当您尝试设置Command ="{Binding AddGlueCommand}"以从ViewModel获取ICommand时,您的麻烦就开始了.由于Command不是DependencyProperty,因此无法在其上设置Binding.

您的下一次尝试可能是创建一个附加属性BindableCommand,它具有更新Command的PropertyChangedCallback.这允许您访问绑定,但是由于InputBindings集合未设置InheritanceContext,因此无法使用FindAncestor查找ViewModel.

显然,您可以创建一个可以应用于TextBox的附加属性,该属性将遍历调用BindingOperations.GetBinding的所有InputBindings,以查找Command绑定并使用显式源更新这些Bindings,从而允许您执行以下操作:

<TextBox my:BindingHelper.SetDataContextOnInputBindings="true">
  <TextBox.InputBindings>
    <KeyBinding
      Key="Q"
      Modifiers="Control" 
      my:BindingHelper.BindableCommand="{Binding ModelGlueCommand}" />
Run Code Online (Sandbox Code Playgroud)

这个附加属性很容易实现:在PropertyChangedCallback上,它将在DispatcherPriority.Input上安排"刷新"并设置一个事件,以便在每次DataContext更改时重新安排"刷新".然后在"刷新"代码中,只需在每个InputBinding上设置DataContext:

...
public static readonly SetDataContextOnInputBindingsProperty = DependencyProperty.Register(... , new UIPropetyMetadata
{
   PropertyChangedCallback = (obj, e) =>
   {
     var element = obj as FrameworkElement;
     ScheduleUpdate(element);
     element.DataContextChanged += (obj2, e2) =>
     {
       ScheduleUpdate(element);
     };
   }
});
private void ScheduleUpdate(FrameworkElement element)
{
  Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(() =>
  {
    UpdateDataContexts(element);
  })
}

private void UpdateDataContexts(FrameworkElement target)
{
  var context = target.DataContext;
  foreach(var inputBinding in target.InputBindings)
    inputBinding.SetValue(FrameworkElement.DataContextProperty, context);
}
Run Code Online (Sandbox Code Playgroud)

两个附加属性的替代方法是创建一个CommandBinding子类,该子类接收路由命令并激活绑定命令:

<Window.CommandBindings>
  <my:CommandMapper Command="my:RoutedCommands.AddGlue" MapToCommand="{Binding AddGlue}" />
  ...
Run Code Online (Sandbox Code Playgroud)

在这种情况下,每个对象中的InputBindings将引用路由命令,而不是绑定.然后,此命令将路由到视图并映射.

CommandMapper的代码相对简单:

public class CommandMapper : CommandBinding
{
  ... // declaration of DependencyProperty 'MapToCommand'

  public CommandMapper() : base(Executed, CanExecute)
  {
  }
  private void Executed(object sender, ExecutedRoutedEventArgs e)
  {
    if(MapToCommand!=null)
      MapToCommand.Execute(e.Parameter);
  }
  private void CanExecute(object sender, CanExecuteRoutedEventArgs e)
  {
    e.CanExecute =
      MapToCommand==null ? null :
      MapToCommand.CanExecute(e.Parameter);
  }
}
Run Code Online (Sandbox Code Playgroud)

根据我的喜好,我更愿意使用附加的属性解决方案,因为它不是很多代码并且使我不必两次声明每个命令(作为RoutedCommand和我的ViewModel的属性).支持代码只出现一次,可以在所有项目中使用.

另一方面,如果你只做一次性项目并且不希望重复使用任何东西,甚至CommandMapper也可能过度.如您所述,可以手动处理事件.

  • 看来,使用.net 4.0,KeyBinding的命令可以绑定到viewmodel中的命令. (2认同)

Lee*_*eil 6

优秀的WPF框架Caliburn精美地解决了这个问题.

        <TextBox cm:Message.Attach="[Gesture Key: Enter] = [Action Search]" />
Run Code Online (Sandbox Code Playgroud)

语法[Action Search]绑定到视图模型中的方法.根本不需要ICommands.

  • 有趣的是,我真的不喜欢他们没有使用标记扩展的事实.相反,他们解析字符串. (6认同)
  • @Kugel:+1.AP:我不知道"漂亮".我不是魔术棒的粉丝. (2认同)