elm*_*mar 5 c# wpf routed-commands
我正在使用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);
}
Run Code Online (Sandbox Code Playgroud)
乍一看,前面的代码就是我想要做的,但是文档绑定到的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;
};
}
}
Run Code Online (Sandbox Code Playgroud)
但是,为PreviewExecuted事件分配处理程序似乎完全取消了该事件,即使我将Handled属性显式设置为false也是如此.因此,我在先前的代码示例中定义的executeSave事件处理程序不再执行.请注意,当我将cb.PreviewExecuted更改为cb.Executed时,两段代码都会执行,但执行顺序不正确.
我认为这是.Net中的一个Bug,因为你应该能够为PreviewExecuted和Executed添加一个处理程序并让它们按顺序执行,前提是你没有将事件标记为已处理.
谁能证实这种行为?还是我错了?这个Bug有解决方法吗?
编辑2:从源代码来看,它的内部工作原理似乎是这样的:
- 对用户输入(鼠标或键盘)做出反应的
UIElement调用。CommandManager.TranslateInput()- 然后在不同的级别上
CommandManager查找CommandBindings与输入关联的命令。- 当命令被发现时,它的
CanExecute()方法被调用,如果它返回,true则被Executed()调用。- 如果
RoutedCommand每个方法本质上执行相同的操作 - 它会在启动进程时引发一对附加事件CommandManager.PreviewCanExecuteEvent和CommandManager.CanExecuteEvent(或PreviewExecutedEvent和)。第一阶段就此结束。ExecutedEventUIElement- 现在,已经
UIElement为这四个事件注册了类处理程序,这些处理程序只需调用CommandManager.OnCanExecute()andCommandManager.CanExecute()(对于预览事件和实际事件)。- 仅在此处
CommandManager.OnCanExecute()和方法中调用CommandManager.OnExecute()注册的处理程序。CommandBinding如果没有找到,则将CommandManager事件传输到 的UIElement父级,并且新的循环开始,直到处理命令或到达可视化树的根。
如果您查看 CommandBinding 类源代码,就会发现 OnExecuted() 方法负责调用您通过 CommandBinding 为 PreviewExecuted 和 Executed 事件注册的处理程序。那里有一点:
PreviewExecuted(sender, e);
e.Handled = true;
Run Code Online (Sandbox Code Playgroud)
这会将事件设置为在 PreviewExecuted 处理程序返回后立即处理,因此不会调用 Executed。
编辑 1:查看 CanExecute 和 PreviewCanExecute 事件有一个关键区别:
Run Code Online (Sandbox Code Playgroud)PreviewCanExecute(sender, e); if (e.CanExecute) { e.Handled = true; }此处将 Handled 设置为 true 是有条件的,因此由程序员决定是否继续执行 CanExecute。只需不要在 PreviewCanExecute 处理程序中将 CanExecuteRoatedEventArgs 的 CanExecute 设置为 true,就会调用 CanExecute 处理程序。
至于
ContinueRouting预览事件的属性 - 当设置为 false 时,它会阻止预览事件进一步路由,但不会以任何方式影响后续的主要事件。
请注意,只有通过 CommandBinding 注册处理程序时,它才以这种方式工作。
如果您仍然希望同时运行 PreviewExecuted 和 Executed,您有两个选择:
Execute()从 PreviewExecuted 处理程序中调用路由命令的方法。只是想一想 - 当您在 PreviewExecuted 完成之前调用 Executed 处理程序时,您可能会遇到同步问题。对我来说,这看起来不是一个好方法。CommandManager.AddPreviewExecutedHandler()静态方法单独注册 PreviewExecuted 处理程序。这将直接从 UIElement 类调用,不会涉及 CommandBinding。EDIT 2: Look at the point 4 at the beginning of the post - these are the events we're adding the handlers for.从表面上看——这是故意的。为什么?人们只能猜测...
| 归档时间: |
|
| 查看次数: |
1315 次 |
| 最近记录: |