RoutedUICommand PreviewExecuted 错误?
我正在使用 MVVM 设计模式构建一个应用程序,并且我想使用 ApplicationCommands 类中定义的 RoutedUICommands。由于 View 的 CommandBindings 属性(读取 UserControl)不是 DependencyProperty,因此我们无法将 ViewModel 中定义的 CommandBindings 直接绑定到 View。我通过定义一个抽象 View 类来解决这个问题,该类基于 ViewModel 接口以编程方式绑定它,确保每个 ViewModel 都有一个 CommandBindings 的 ObservableCollection。这一切都工作正常,但是,在某些情况下,我想执行在不同类(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);
}
乍一看,前面的代码就是我想要做的,但是文档绑定到的视图中的 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 中的一个错误,因为您应该能够向 PreviewExecuted 和 Executed 添加一个处理程序,并让它们按顺序执行,前提是您不将事件标记为已处理。
任何人都可以证实这种行为吗?还是我错了?这个 Bug 有解决方法吗?
I'm building an application using the MVVM design pattern and I want to make use of the RoutedUICommands defined in the ApplicationCommands class. Since the CommandBindings property of a View (read UserControl) isn't a DependencyProperty we can't bind CommandBindings defined in a ViewModel to the View directly. I solved this by defining an abstract View class which binds this programmatically, based on a ViewModel interface which ensures every ViewModel has an ObservableCollection of CommandBindings. This all works fine, however, in some scenarios I want to execute logic which is defined in different classes (the View and ViewModel) same command. For instance, when saving a document.
In the ViewModel the code saves the document to disk:
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);
}
At first sight the previous code is all I wanted to do, but the TextBox in the View to which the document is bound, only updates its Source when it loses its focus. However, I can save a document without losing focus by pressing Ctrl+S. This means the document is saved before the changes where Updated in the source, effectively ignoring the changes. But since changing the UpdateSourceTrigger to PropertyChanged isn't a viable option for performance reasons, something else must force an update before saving. So I thought, lets use the PreviewExecuted event to force the update in the PreviewExecuted event, like so:
//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;
};
}
}
However, assigning an handler to the PreviewExecuted event seems to cancel the event altogether, even when I explicitly set the Handled property to false. So the executeSave eventhandler I defined in the previous code sample isn't executed anymore. Note that when I change the cb.PreviewExecuted to cb.Executed both pieces of code do execute, but not in the correct order.
I think this is a Bug in .Net, because you should be able to add a handler to PreviewExecuted and Executed and have them be executed in order, provided you don't mark the event as handled.
Can anyone confirm this behavior? Or am I wrong? Is there a workaround for this Bug?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
如果您查看 CommandBinding 类源代码,就会发现 OnExecuted() 方法负责调用您通过 CommandBinding 为 PreviewExecuted 和 Executed 事件注册的处理程序。那里有一点:
这将事件设置为在 PreviewExecuted 处理程序返回后立即处理,因此不会调用 Executed。
请注意,只有通过 CommandBinding 注册处理程序时,它才以这种方式工作。
如果您仍然希望同时运行 PreviewExecuted 和 Executed,您有两个选择:
CommandManager.AddPreviewExecutedHandler()
静态方法单独注册PreviewExecuted处理程序。这将直接从 UIElement 类调用,不会涉及 CommandBinding。编辑 2:查看帖子开头的第 4 点 - 这些是我们要为其添加处理程序的事件。
从表面上看 - 这是故意这样做的。为什么?人们只能猜测...
If you look at the CommandBinding class source code there is OnExecuted() method that is responsible for calling the handlers you register for PreviewExecuted and Executed events through CommandBinding. There is that bit there:
this sets the event as handled right after your PreviewExecuted handler returns and so the Executed is not called.
Note, that it only works this way when handlers are registered through CommandBinding.
If you still want to have both PreviewExecuted and Executed to run you have two options:
Execute()
method of the routed command from within PreviewExecuted handler. Just thinking about it - you might run into sync issues as you're calling Executed handler before the PreviewExecuted is finished. To me this doesn't look like a good way to go.CommandManager.AddPreviewExecutedHandler()
static method. This will be called directly from UIElement class and will not involve CommandBinding.EDIT 2: Look at the point 4 at the beginning of the post - these are the events we're adding the handlers for.
From the looks of it - it was done this way on purpose. Why? One can only guess...
我构建了以下解决方法,以获得缺少的ContinueRouting行为:
I build the following workaround, to obtain the missing ContinueRouting behavior: