我正在使用MVVM设计模式构建应用程序,我想使用ApplicationCommands类中定义的RoutedUICommands。由于View的CommandBindings属性(读取UserControl)不是DependencyProperty,因此我们无法将ViewModel中定义的CommandBindings直接绑定到View。我通过定义一个抽象的View类来解决这个问题,该类基于ViewModel接口以编程方式绑定它,该接口确保每个ViewModel都有一个ObBableCollection的CommandBindings。这一切都运行正常,但是,在某些情况下我想执行在不同类(View和ViewModel)相同命令中定义的逻辑。例如,保存文档时。
在ViewModel中,代码将文档保存到磁盘:
private void InitializeCommands()
{
CommandBindings = new CommandBindingCollection();
ExecutedRoutedEventHandler executeSave = (sender, e) =>
{
document.Save(path);
IsModified = false;
};
CanExecuteRoutedEventHandler canSave = (sender, e) =>
{
e.CanExecute = IsModified;
};
CommandBinding save = new CommandBinding(ApplicationCommands.Save, executeSave, canSave);
CommandBindings.Add(save);
}
乍一看,前面的代码就是我想要做的,但是文档绑定到的View中的TextBox只会在失去焦点时更新它的Source。但是,我可以通过按Ctrl + S保存文档而不会失去焦点。这意味着文档在源中更新的更改之前保存,实际上忽略了更改。但是,由于出于性能原因将UpdateSourceTrigger更改为PropertyChanged不是一个可行的选项,因此在保存之前必须强制更新其他内容。所以我想,让我们使用PreviewExecuted事件强制更新PreviewExecuted事件,如下所示:
//Find the Save command and extend behavior if it is present
foreach (CommandBinding cb in CommandBindings)
{
if (cb.Command.Equals(ApplicationCommands.Save))
{
cb.PreviewExecuted += (sender, e) =>
{
if (IsModified)
{
BindingExpression be = rtb.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
}
e.Handled = false;
};
}
}
但是,为PreviewExecuted事件分配处理程序似乎完全取消了该事件,即使我将Handled属性显式设置为false也是如此。因此,我在先前的代码示例中定义的executeSave事件处理程序不再执行。请注意,当我将cb.PreviewExecuted更改为cb.Executed两段代码执行执行时,但执行顺序不正确。
我认为这是.Net中的一个Bug,因为你应该能够为PreviewExecuted和Executed添加一个处理程序并让它们按顺序执行,前提是你没有将事件标记为已处理。
任何人都可以确认这种行为吗?还是我错了?这个Bug有解决方法吗?
答案 0 :(得分:3)
编辑2:从查看源代码看,内部似乎就是这样:
UIElement
调用CommandManager.TranslateInput()
以响应用户输入(鼠标或键盘)。CommandManager
然后在不同级别上查看CommandBindings
,查找与输入相关联的命令。- 当找到命令时,会调用其
CanExecute()
方法,如果它返回true
,则会调用Executed()
。- 如果
RoutedCommand
每个方法基本上都是相同的 - 它会引发一对附加事件CommandManager.PreviewCanExecuteEvent
和CommandManager.CanExecuteEvent
(或PreviewExecutedEvent
和{{1启动该过程的ExecutedEvent
。第一阶段到此结束。- 现在
UIElement
已为这四个事件注册了类处理程序,这些处理程序只需调用UIElement
和CommandManager.OnCanExecute()
(对于预览和实际事件)。- 仅在
醇>CommandManager.CanExecute()
和CommandManager.OnCanExecute()
方法中调用向CommandManager.OnExecute()
注册的处理程序。如果没有找到,则CommandBinding
将事件传递给CommandManager
的父级,并且新的循环开始,直到处理命令或到达可视树的根。
如果查看CommandBinding类源代码,则有OnExecuted()方法负责调用您通过CommandBinding注册PreviewExecuted和Executed事件的处理程序。那里有一点:
UIElement
这会将事件设置为在PreviewExecuted处理程序返回后立即处理,因此不会调用Executed。
编辑1:看CanExecute& PreviewCanExecute事件有一个关键区别:
PreviewExecuted(sender, e); e.Handled = true;
设置Handled为true在这里是有条件的,因此程序员决定是否继续使用CanExecute。只是不要在PreviewCanExecute处理程序中将CanExecuteRoutedEventArgs的CanExecute设置为true,并且将调用CanExecute处理程序。
对于Preview事件的
PreviewCanExecute(sender, e); if (e.CanExecute) { e.Handled = true; }
属性 - 当设置为false时,它会阻止Preview事件进一步路由,但它不会以任何方式影响以下主事件。
请注意,只有通过CommandBinding注册处理程序时,它才会以这种方式工作。
如果您仍希望同时运行PreviewExecuted和Executed,则有两种选择:
ContinueRouting
方法。只是考虑一下 - 在PreviewExecuted完成之前,您可能会遇到同步问题,因为您正在调用Executed处理程序。对我而言,这看起来不是一个好方法。Execute()
静态方法单独注册PreviewExecuted处理程序。这将直接从UIElement类调用,不涉及CommandBinding。 CommandManager.AddPreviewExecutedHandler()
从它的外观来看 - 它是故意这样做的。为什么?人们只能猜测......
答案 1 :(得分:1)
我构建了以下解决方法,以获取缺少的ContinueRouting行为:
foreach (CommandBinding cb in CommandBindings)
{
if (cb.Command.Equals(ApplicationCommands.Save))
{
ExecutedRoutedEventHandler f = null;
f = (sender, e) =>
{
if (IsModified)
{
BindingExpression be = rtb.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
// There is a "Feature/Bug" in .Net which cancels the route when adding PreviewExecuted
// So we remove the handler and call execute again
cb.PreviewExecuted -= f;
cb.Command.Execute(null);
}
};
cb.PreviewExecuted += f;
}
}