RoutedUICommand PreviewExecuted漏洞? [英] RoutedUICommand PreviewExecuted Bug?

查看:194
本文介绍了RoutedUICommand PreviewExecuted漏洞?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我建设使用MVVM设计模式的应用程序,我想利用在ApplicationCommands类中定义的RoutedUICommands的。由于视图的CommandBindings属性(读取用户控件)不,我们不能绑定在一个视图模型直接查看定义CommandBindings一个的DependencyProperty。我解决了这个通过定义编程结合这一个抽象的视图类,基于一个ViewModel接口,保证了每一个视图模型具有CommandBindings的一个ObservableCollection上。这一切工作正常,但是,在某些情况下我要执行它在不同的类(视图和视图模型)相同的命令定义的逻辑。 ,例如,保存文档时



在视图模型的代码保存文档磁盘:



 私人无效InitializeCommands()
{
CommandBindings =新CommandBindingCollection();
ExecutedRoutedEventHandler executeSave =(发件人,E)=>
{
document.Save(路径);
将IsModified = FALSE;
};
CanExecuteRoutedEventHandler canSave =(发件人,E)=>
{
e.CanExecute =将IsModified;
};
保存的CommandBinding =新的CommandBinding(ApplicationCommands.Save,executeSave,canSave);
CommandBindings.Add(保存);
}



乍一看前面的代码是我想做的事,但在TextBox在该文件所绑定的视图,只有当它失去它的焦点更新其来源。不过,我可以将文档保存而不按Ctrl + S失去焦点。这意味着文档,其中源更新,有效地忽略的变化更改之前保存。但由于改变了UpdateSourceTrigger到的PropertyChanged是不是出于性能的考虑一种可行的选择,别的东西必须在保存之前强制更新。所以我想,让使用PreviewExecuted事件迫使更新的PreviewExecuted事件,像这样:

  //查找保存命令(如果存在)
的foreach(在CommandBindings CB的CommandBinding)
{
延长的行为,如果(cb.Command.Equals(ApplicationCommands.Save))
{
cb.PreviewExecuted + =(发件人,E)=>
{
如果(将IsModified)
{
BindingExpression是= rtb.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
}
e.Handled = FALSE;
};
}
}



然而,分配一个处理程序的PreviewExecuted事件似乎完全取消的情况下,甚至当我明确设置处理的属性设置为false。所以,我前面的代码示例中定义的executeSave事件处理程序不再被执行。需要注意的是,当我改变cb.PreviewExecuted到cb.Executed代码两片执行,但不正确的顺序。



我认为这是.NET中的一个Bug,因为你应该能够处理程序添加到PreviewExecuted和执行,并让他们为了来执行,只要是处理你不庆祝活动。



任何人都可以证实这种行为?还是我错了? ?是否有这个Bug一种解决方法


解决方案

编辑2:从查看源代码,它看来,在内部它的工作方式是:




  1. 的UIElement 通话 CommandManager.TranslateInput()反应用户输入(鼠标或键盘)。

  2. 命令管理然后通过 CommandBindings 不同层面寻找与输入相关的命令。

  3. 当命令找到了 CanExecute()方法被调用,如果返回真正()执行被称为

  4. 在的的RoutedCommand 每做essencially同样的事情的方法情况下 - 它提出了一个对附着事件 CommandManager.PreviewCanExecuteEvent CommandManager.CanExecuteEvent (或 PreviewExecutedEvent ExecutedEvent )的的UIElement 启动该进程。这结束了第一阶段。

  5. 现在,在的UIElement 有这四个事件的注册类处理程序和这些处理程序只需调用 CommandManager.OnCanExecute() CommandManager.CanExecute()(用于预览和实际事件)。

  6. 只有在这里 CommandManager.OnCanExecute() CommandManager.OnExecute()其中,处理程序登记方法与的CommandBinding 被调用。如果没有任何发现命令管理事件转移到的UIElement 的母公司,新的周期开始,直到该指令被处理或达到视觉树的根。




如果你看一下一流的CommandBinding源代码有OnExecuted()方法,它负责调用,通过您的CommandBinding注册PreviewExecuted的处理程序和执行的事件。有该位有:

  PreviewExecuted(发件人,E); 
e.Handled = TRUE;

这设置事件的处理您的PreviewExecuted处理程序返回后马上所以执行的不叫




编辑1:纵观CanExecute&安培; PreviewCanExecute事件有一个关键的区别:

  PreviewCanExecute(发件人,E); 
如果(e.CanExecute)
{
e.Handled = TRUE;
}



设置进行处理,以真正是有条件的在这里,所以它是谁决定是否程序员要不要继续进行CanExecute。根本就没有设置CanExecuteRoutedEventArgs的CanExecute为true,你PreviewCanExecute处理程序和CanExecute处理程序将被调用。预览事件



至于 ContinueRouting 属性 - 设置为false时它可以防止进一步的路由预览事件,但它不以任何方式影响主要​​有以下事件。




请注意,当处理程序通过的CommandBinding注册的,它只能这样。



如果你仍然想同时拥有PreviewExecuted和执行来运行,你有两种选择:




  1. 您可以可以拨打从PreviewExecuted处理程序中执行路由命令()方法。想到它 - 因为你的PreviewExecuted完成之前调用执行的处理程序,你可能会遇到同步问题。对我来说,这看起来并不像一个很好的路要走。

  2. 您可以通过分别注册PreviewExecuted处理CommandManager.AddPreviewExecutedHandler()静态方法。这将直接自UIElement类调用,并不会涉及的CommandBinding。 编辑2:看看在文章开头的4点 - 这是我们加入的处理程序事件



从它的外观 - 这是做故意这样。为什么?人们只能猜测...


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?

解决方案

EDIT 2: From looking at the source code it seems that internally it works like that:

  1. The UIElement calls CommandManager.TranslateInput() in reaction to user input (mouse or keyboard).
  2. The CommandManager then goes through CommandBindings on different levels looking for a command associated with the input.
  3. When the command is found its CanExecute() method is called and if it returns true the Executed() is called.
  4. In case of RoutedCommand each of the methods does essencially the same thing - it raises a pair of attached events CommandManager.PreviewCanExecuteEvent and CommandManager.CanExecuteEvent (or PreviewExecutedEvent and ExecutedEvent) on the UIElement that initiated the process. That concludes the first phase.
  5. Now the UIElement has class handlers registered for those four events and these handlers simply call CommandManager.OnCanExecute() and CommandManager.CanExecute() (for both preview and actual events).
  6. It is only here in CommandManager.OnCanExecute() and CommandManager.OnExecute() methods where the handlers registered with CommandBinding are invoked. If there are none found the CommandManager transfers the event up to the UIElement's parent, and the new cycle begins until the command is handled or the root of the visual tree is reached.

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:

PreviewExecuted(sender, e); 
e.Handled = true;

this sets the event as handled right after your PreviewExecuted handler returns and so the Executed is not called.

EDIT 1: Looking at CanExecute & PreviewCanExecute events there is a key difference:

  PreviewCanExecute(sender, e); 
  if (e.CanExecute)
  { 
    e.Handled = true; 
  }

setting Handled to true is conditional here and so it is the programmer who decides whether or not to proceed with CanExecute. Simply do not set the CanExecuteRoutedEventArgs's CanExecute to true in your PreviewCanExecute handler and the CanExecute handler will be called.

As to ContinueRouting property of Preview event - when set to false it prevents the Preview event from further routing, but it does not affect the following main event in any way.

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:

  1. You can can call 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.
  2. You can register PreviewExecuted handler separately through 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...

这篇关于RoutedUICommand PreviewExecuted漏洞?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆