【问题标题】:Key press inside of textbox MVVM文本框MVVM内的按键
【发布时间】:2010-12-15 05:25:02
【问题描述】:

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

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

【问题讨论】:

  • 只是出于好奇,您为什么要避免使用代码隐藏?
  • 因为我想要未绑定到特定视图模型的可重用组件。

标签: wpf data-binding mvvm


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

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

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

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

    【讨论】:

    • 如果我只想让命令在特定键上触发呢?
    • 如果您的触发器变得更加具体,您将不得不实现自己的触发器。例如。带有属性 Key 的 KeyPressTrigger 仅在按下指定的 Key 时才会触发(执行指定的操作 - 在其 InvokeDataCommand 上方的 sn-p 中)。
    【解决方案3】:

    首先,如果你想绑定一个 RoutedUICommand 很简单——只需添加到 UIElement.InputBindings 集合中:

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

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

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

    显然,您可以创建一个附加属性,您可以将其应用于 TextBox,该属性将贯穿所有 InputBindings,在每个 InputBindings 上调用 BindingOperations.GetBinding 以查找命令绑定并使用显式源更新这些绑定,从而允许您执行以下操作:

    <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 也可能是矫枉过正。正如您所提到的,可以简单地手动处理事件。

    【讨论】:

    • 似乎在.net 4.0中,KeyBinding的命令可以绑定到viewmodel中的命令。
    • CommandBinding 不是 DependencyObject,不能在其上声明 DependencyProperty。该解决方案不正确吗?
    【解决方案4】:

    优秀的WPF框架Caliburn很好的解决了这个问题。

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

    语法 [Action Search] 绑定到视图模型中的方法。完全不需要 ICommand。

    【讨论】:

    • 很有趣,但是我真的不喜欢他们没有为此使用标记扩展的事实。相反,他们解析字符串。
    • @Kugel:+1。 AP:我不知道“漂亮”。我不是魔术弦的粉丝。
    • 有趣的是,搜索不需要在视图模型中声明。它也可以在视图模型的祖先视图模型之一中声明。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多