在学习WPF时,我一直在阅读大量的书籍和网站。似乎不断回避我的一件事是我们应该如何正确连接RoutedCommands。在一篇文章中,作者指出,XAML文件的代码隐藏只能包含对InitializeComponent的调用。我可以落后于此。它使XAML文件只不过是一个演示文档,并满足了我对分离关注点的邪恶渴望。
另一方面,我看到的每个可以解决双击事件的示例似乎都希望您编写代码。我的理解是,我们希望摆脱代码隐藏文件中的重复代码(而且,我只是为了这一点),所以在我看来,这不是正确的方法。菜单命令,工具栏按钮点击等也是如此。
想象一下,例如,我有一个打开文档的命令。该命令必须显示“打开”对话框,然后打开文档并将其缓存在应用程序状态中。 (此应用程序仅允许您一次处理一个文档。)用户可以通过以下任一方式调用此命令:
如果我信任Web上的大多数源代码,我必须编写至少两个Click事件处理程序,然后调用该命令,污染代码隐藏文件。对我而言,这似乎打败了拥有命令的目的。我想到我在某处读到有一种方法可以在XAML中以声明方式将命令绑定到这些内容,并且它会为您执行此操作,甚至在无法执行时禁用该命令。但现在我似乎无法找到它,也不是一个如何做到的好例子。
有人可以向我解释一下吗?在这一点上,它们开始看起来像伏都教和弹片。
答案 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;
}