我已经将 Josh Smith 的Using RoutedCommands 与 WPF 中的 ViewModel、vbfox 的Using CommandBindings in MVVM以及我自己在 XAML 中声明 CommandBinding 时指定 RoutedUICommand 文本和 KeyGesture 结合起来。
优点:
- 无需像 Smith 那样显式引用 ViewModel。他是这样的:
xmlns:vm="clr-namespace:VMCommanding.ViewModel"
...
<UserControl.CommandBindings>
<jas:CommandSinkBinding
Command="vm:CommunityViewModel.KillAllMembersCommand" />
</UserControl.CommandBindings>
我正在使用MVVM Light Toolkit 的Locator 类来间接获取 ViewModel,所以我不想要这个。此外,由于这是在后台创建普通的 CommandBinding,因此您不能使用 Bindings 来指定命令(CommandBinding 不继承自 DependencyObject)。vbfox 通过创建自己的 CommandBindings 类来解决这个问题。
- 无需像 vbfox 那样为单独的 ICommand 创建。他是这样的:
<f:Mvvm.CommandBindings>
<f:MvvmCommandBindingCollection>
<f:MvvmCommandBinding
Command="f:MyRoutedCommands.SomeRoutedCommand"
Target="{Binding MyCommandInViewModel}"
CanExecuteChangedSuggestRequery="True" />
</f:MvvmCommandBindingCollection>
</f:Mvvm.CommandBindings>
因此,您必须在某处(在您的 View 目录中?)创建一个 RoutedUICommand,并在您的 ViewModel 中创建一个单独的 ICommand。
相反,我“只”必须在我的 ViewModel 中实现 ToggleReadComand:
_commandSink.RegisterCommand(_toggleReadCommand,
p => CanToggleRead,
p => ToggleRead());
...
public static readonly RoutedUICommand _toggleReadCommand =
new RoutedUICommand();
public static RoutedUICommand ToggleReadCommand
{
get { return _toggleReadCommand; }
}
private bool CanToggleRead
{
get { return Files.CurrentItem != null; }
}
private void ToggleRead()
{
ToggleFileInfoFields(f => f.Read = !f.Read);
}
我像这样使用它:
<cmd:Mvvm.CommandBindings>
<cmd:MvvmCommandBindingCollection>
<cmd:MvvmCommandBinding Command="{Binding ToggleReadCommand}"
Gesture="Toggle _Read|Ctrl+R" />
</cmd:MvvmCommandBindingCollection>
</cmd:Mvvm.CommandBindings>
...
<MenuItem Header="_View">
<MenuItem Command="{Binding ToggleReadCommand}" />
</MenuItem>
我获得了使用 RoutedUICommands 的所有优点:命令文本与快捷键(包括禁用支持)一起自动显示在我的 MenuItem 中,我的菜单项 UI(或任何其他绑定到 ToggleReadCommand 的控件)通过 XAML 在一个地方指定在我看来(不再有单独的 CommandBindings 和 InputBindings),但我仍然可以将我的命令代码放在它所属的 ViewModel 中。
现在这是我的问题。首先,我希望能够做到这一点(模拟普通 Window.CommandBindings 的声明方式):
<cmd:Mvvm.CommandBindings>
<cmd:MvvmCommandBinding Command="{Binding ToggleReadCommand}"
Gesture="Toggle _Read|Ctrl+R" />
</cmd:Mvvm.CommandBindings>
如何避免必须显式指定 MvvmCommandBindingCollection?
Gesture="Toggle _Read|Ctrl+R"
我的手势支持有效,但我在 Visual Studio 2012 XAML 编辑器下方收到“对象引用未设置为对象的实例”错误指示?我在 vbfox 的 MvvmCodeBinding.cs 中添加了以下内容:
public static readonly DependencyProperty GestureProperty =
DependencyProperty.Register(
"Gesture",
typeof(String),
typeof(MvvmCommandBinding),
new PropertyMetadata(null, OnGestureChanged));
static void OnGestureChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MvvmCommandBinding) d).OnGestureChanged((String) e.NewValue);
}
private string _gesture;
void OnGestureChanged(String newValue)
{
_gesture = newValue;
var command = _commandSinkBinding.Command as RoutedUICommand;
if (command == null)
return;
CreateGesture(command, newValue);
}
private static void CreateGesture(RoutedUICommand command, string gestureStr)
{
if (string.IsNullOrEmpty(gestureStr) || command == null)
return;
var fields = gestureStr.Split(new char[] {'|'}, 2);
if (fields.Length == 1)
command.Text = gestureStr;
else {
command.Text = fields[0];
var gesture = fields[1];
var kgc = new KeyGestureConverter();
var kg = kgc.ConvertFromString(gesture) as System.Windows.Input.KeyGesture;
command.InputGestures.Add(kg);
}
}
[Bindable(true)]
public String Gesture
{
get { return (String) GetValue(GestureProperty); }
set { SetValue(GestureProperty, value); }
}
(我还将 vbfox's 更改CommandBinding _commandBinding
为 Smith's CommandSinkBinding _commandSinkBinding
,并从 OnCommandChanged() 调用 CreateGesture() 以防手势在命令之前创建)。如何摆脱 XAML 编辑器错误指示?
最后,这有点像弗兰肯斯坦怪物。我敢肯定有各种重复的功能。vbfox 明确表示他提出了自己的代码以避免使用 Smith 的 VMCommanding。另外,我开始使用 MVVM Light Toolkit。我目前的东西只是一个“概念证明”,以表明我的方法会奏效。
有人知道是否有更好的方法来完成所有这些已经在某处实施的方法吗?我希望能够在 XAML 中声明我的 RoutedUICommand(通过绑定)在一行中,该行还设置文本和手势(不需要单独的 CommandBinding 和 InputBinding)。我希望在我的 ViewModel 中实现实际的 RoutedUICommands。
如果不是这样,我可能会通过仔细查看这三个包并进行一些重构来消除很多 kruft。