2

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

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

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

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

如果我信任 Web 上的大多数资源,我必须编写至少两个 Click 事件处理程序,然后调用该命令,从而污染代码隐藏文件。在我看来,这似乎违背了拥有命令的目的。我以为我在某处读到有一种方法可以在 XAML 中以声明方式将命令绑定到这些东西,它会为您完成,甚至在命令无法执行时禁用该命令。但现在我似乎找不到它,也找不到一个很好的例子来说明如何做到这一点。

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

4

2 回答 2

4

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

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

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

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

在这种情况下,通常的方法是硬着头皮将代码放在代码隐藏中,但您尝试将其保留为每个事件处理程序一行。基本上,您的代码隐藏只是调用视图模型上的相关方法,这才是真正处理事件的方法。

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

于 2010-11-05T00:45:51.897 回答
2

WPF 中的命令比较麻烦,但它确实为您解决了更新 IsEnabled 的问题。这是典型的例子。第 1 步是可选的,因为有很多内置的常用命令可以减少样板的数量。

第 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;
  }
于 2010-11-04T22:26:38.133 回答