按键在文本框MVVM中

时间:2009-11-10 20:40:45

标签: wpf data-binding mvvm

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

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

4 个答案:

答案 0 :(得分:9)

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

<TextBox ...>
  <TextBox.InputBindings>
    <KeyBinding
      Key="Q"
      Modifiers="Control" 
      Command="my:ModelAirplaneViewModel.AddGlueCommand" />

当您尝试设置Command =“{Binding AddGlueCommand}”以从ViewModel获取ICommand时,您的问题就开始了。由于Command不是DependencyProperty,因此无法在其上设置绑定。

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

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

<TextBox my:BindingHelper.SetDataContextOnInputBindings="true">
  <TextBox.InputBindings>
    <KeyBinding
      Key="Q"
      Modifiers="Control" 
      my:BindingHelper.BindableCommand="{Binding ModelGlueCommand}" />

这个附加属性很容易实现:在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);
}

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

<Window.CommandBindings>
  <my:CommandMapper Command="my:RoutedCommands.AddGlue" MapToCommand="{Binding AddGlue}" />
  ...

在这种情况下,每个对象中的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);
  }
}

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

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

答案 1 :(得分:6)

出色的WPF框架Caliburn可以很好地解决这个问题。

        <TextBox cm:Message.Attach="[Gesture Key: Enter] = [Action Search]" />

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

答案 2 :(得分:4)

从代码隐藏事件处理到MVVM命令的最简单转换可能是来自Expression Blend Samples的触发器和操作。

以下是一段代码,演示了如何使用以下命令处理文本框内的按键事件:

    <TextBox>
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="KeyDown">
                <si:InvokeDataCommand Command="{Binding MyCommand}"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </TextBox>

答案 3 :(得分:1)

最好的选择可能是使用Attached Property来执行此操作。如果您有Blend SDK,Behavior<T>类可以使这更加简单。

例如,可以很容易地修改此TextBox Behavior以在每次按键时触发ICommand,而不是单击Enter上的按钮。