WPF架构混淆RE:路由命令,事件和手势

时间:2010-11-04 21:47:59

标签: wpf xaml routed-commands

在学习WPF时,我一直在阅读大量的书籍和网站。似乎不断回避我的一件事是我们应该如何正确连接RoutedCommands。在一篇文章中,作者指出,XAML文件的代码隐藏只能包含对InitializeComponent的调用。我可以落后于此。它使XAML文件只不过是一个演示文档,并满足了我对分离关注点的邪恶渴望。

另一方面,我看到的每个可以解决双击事件的示例似乎都希望您编写代码。我的理解是,我们希望摆脱代码隐藏文件中的重复代码(而且,我只是为了这一点),所以在我看来,这不是正确的方法。菜单命令,工具栏按钮点击等也是如此。

想象一下,例如,我有一个打开文档的命令。该命令必须显示“打开”对话框,然后打开文档并将其缓存在应用程序状态中。 (此应用程序仅允许您一次处理一个文档。)用户可以通过以下任一方式调用此命令:

  • 选择文件 - >从菜单中打开。
  • 键入Ctrl + O。
  • 单击工具栏上的“打开”按钮。

如果我信任Web上的大多数源代码,我必须编写至少两个Click事件处理程序,然后调用该命令,污染代码隐藏文件。对我而言,这似乎打败了拥有命令的目的。我想到我在某处读到有一种方法可以在XAML中以声明方式将命令绑定到这些内容,并且它会为您执行此操作,甚至在无法执行时禁用该命令。但现在我似乎无法找到它,也不是一个如何做到的好例子。

有人可以向我解释一下吗?在这一点上,它们开始看起来像伏都教和弹片。

2 个答案:

答案 0 :(得分:4)

避免使用命令进行代码隐藏的常用方法是避免使用RoutedCommands。在MVVM(Model-View-ViewModel)主题的各种变体中,人们倾向于使用ICommand的自定义实现。他们编写了一个放置在UI的DataContext中的ViewModel类。此ViewModel公开类型为ICommand的属性,这些命令属性通过数据绑定连接到菜单项,按钮等。 (而且它通常只是ICommand的一个实现一次又一次地使用 - 在网上搜索RelayCommand或DelegateCommand或DelegatingCommand,你会看到模式 - 它基本上是ICommand作为委托的包装器,可选支持启用/禁用。)

在这个习语中,你几乎从不使用像ApplicationCommands.Open这样的内置命令。对于那些事情唯一真正的用途是,如果您希望通过控件本质上处理焦点敏感命令。例如,TextBox内置了编辑,复制,粘贴等命令处理。这避免了代码隐藏问题,因为它是一个完整的自定义控件,而自定义控件实际上没有代码隐藏 - 它们都是代码。 (Xaml实际上是一个完全独立的对象,模板,并不是控件的一部分。)无论如何,它不是你的代码 - 你有一个已经知道如何支持命令的控件,所以你可以完全保留在Xaml中。

命令路由在该特定场景中很有意思,因为它允许您放置一组与各种编辑控件关联的菜单项,并且路由系统根据焦点位置确定哪个文本框(或其他)将处理命令。如果那不是您想要的,命令路由可能对您没有多大用处。

然而,当你发现你真的必须在代码隐藏中放置代码时,这里有一个更大的问题。如果您使用自定义ICommand实现(尽管存在奇怪的异常),命令通常不是该场景的示例,但是稍微更有趣的用户输入事件是。你提到双击,但是,如果你正在进行任何类型的异常交互,你往往会想要鼠标上/下等等。

在这种情况下,通常的方法是咬住子弹并将代码放在代码隐藏中,但是你试图将它保持在每个事件处理程序一行。基本上,你的代码隐藏只是调用viewmodel上的相关方法,这就是真正处理事件的方法。

可爱的是,它使编写自动化测试变得非常容易。想要模拟鼠标进入UI的特定部分吗?无需乱搞单元测试UI自动化框架 - 只需直接调用相关方法即可!

答案 1 :(得分:2)

WPF中的命令非常繁琐,但它确实解决了为您更新IsEnabled的问题。这是典型的例子。第1步是可选的,因为有很多built-in common commands来减少锅炉板的数量。

步骤1.(可选)在静态类中创建命令

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;

namespace WpfApplication1
{
   public static class Commands
   {
      public static RoutedCommand WibbleCommand = new RoutedUICommand
      (
         "Wibble",
         "Wibble",
         typeof(Commands),
         new InputGestureCollection()
            {
               new KeyGesture(Key.O, ModifierKeys.Control)
            }
      );
   }
}

步骤2:在xaml中声明命令绑定

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">
   <Window.CommandBindings>
      <CommandBinding
         Command="{x:Static local:Commands.WibbleCommand}"
         Executed="WibbleCommandExecuted"
         CanExecute="WibbleCommandCanExecute"
      />
   </Window.CommandBindings>

第3步:连接控件(菜单项,按钮等)

这里的长绑定是为了纠正Button默认不使用命令文本的事实。

  <Button Command="{x:Static local:Commands.WibbleCommand}" Width="200" Height="80">
     <TextBlock Text="{Binding Path=Command.Text, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}">
     </TextBlock>
  </Button>

步骤4:在代码隐藏中实现Execute和CanExecute的处理程序

小心使用CanExecute!这将经常被调用,所以尽量不要做任何昂贵的事情。

  private void WibbleCommandExecuted(object sender, ExecutedRoutedEventArgs e)
  {
     MessageBox.Show("Wibbled!");
  }

  private void WibbleCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
  {
     e.CanExecute = DateTime.Now.Minute % 2 == 0;
  }