【问题标题】:WPF, VB , and the Application objectWPF、VB 和 Application 对象
【发布时间】:2010-07-11 14:16:36
【问题描述】:

场景:

  • VB 6 表单有一个 InteropControl (WinForms)。
  • InteropControl 有一个 ElementHost
  • ElementHost 拥有我的 WPF 控件

一切似乎都在工作,除了 Application.Current 在我需要时似乎为空。我真正想做的就是在第一个表单完全显示之前挂钩未处理的异常事件。

  1. 在这种情况下,是否曾经创建过 WPF 应用程序对象?
  2. 如果有,是什么时候创建的?
  3. 如果不是,是什么导致消息被抽取?
  4. 如果我在后台线程上启动 Application 对象会发生什么?

【问题讨论】:

  • 表单完全显示时WPFApplication对象不是null吗?
  • 我还没有机会检查。但由于我试图在表单的 Load 事件期间发现错误,我真的不能等那么久。

标签: wpf winforms vb6


【解决方案1】:

首先我将解释消息循环在互操作场景中的工作原理,然后我将回答您的问题并给出一些建议。

场景中的消息循环实现

您的方案涉及三种不同的技术:VB 6、WinForms 和 WPF。这些技术中的每一项都是在 Win32 之上实现的。每个都有自己的GetMessage()/DispatchMessage() 循环来泵送 Win32 窗口消息。

这里是每个GetMessage()/DispatchMessage() 循环的实现位置:

  • VB 6 在内部实现它
  • WinForms 在System.Windows.Forms.Application 中实现它
  • WPF 在System.Windows.Threading.Dispatcher 中实现它

WPF 应用程序对象是可选的

您的问题假设 WPF 在 Application 对象中实现消息循环。不是这种情况。在 WPF 中,WinForms 在 Application 对象中处理的所有基本功能都已移至其他对象,例如 Dispatcher、HwndSource、InputManager、KeyboardDevice、MouseDevice 等。

在 WPF 中,Application 对象是完全可选的。您可以构建具有复杂 UI 的完整 WPF 应用程序,而无需创建 Application 对象。应用程序对象仅在您需要它提供的服务之一时才有用,例如:

  • 一个普通的ResourceDictionary
  • WM_APPACTIVATE 消息映射到ActivatedDeactivated 事件中
  • WM_QUERYENDSESSION 消息映射到OnSessionEnding 事件中
  • 生命周期管理(启动/运行/关闭/退出)
  • 当最后一个窗口或主窗口关闭时自动关闭
  • WPF 窗口的默认图标
  • 记住打开的第一个窗口 (MainWindow)
  • NavigationService 事件的通用注册(Navigated 等)
  • StartupUri

Application 类还提供了几个有用的静态成员,例如 FindResourceGetResourceStreamLoadComponent,它们不需要 Application 对象存在。

当您拨打Application.Run() 时,它所做的只是:

  1. 安装处理WM_APPACTIVATEWM_QUERYENDSESSION的机制,并且
  2. 执行Dispatcher.Run()

所有实际的消息循环功能都在Dispatcher.Run() 中。

在 WPF 消息循环中注册未处理的异常

您尝试使用的Application.DispatcherUnhandledException 事件是Dispatcher.UnhandledException 事件的简单包装器。我认为他们将它包含在 Application 对象中是因为 WinForms 程序员希望它存在,但您的问题表明这可能适得其反。

要从 WPF 的 Dispatcher 注册未处理的异常,您所要做的就是:

Dispatcher.Current.UnhandledException += ...;

Application.Current 不同,Dispatcher.Current 不能为空:如果您从一个还没有 Dispatcher 的线程访问 Dispatcher.Current,则会自动创建一个。

一旦您订阅了Dispatcher.UnhandledException,当前线程上的Dispatcher 消息循环 中的任何未处理的异常都会导致您的事件处理程序被调用。请注意,这仅适用于Dispatcher.Run() 正在泵送消息时的未处理异常:当 VB 6 或 WinForms 等其他技术正在泵送消息时,将使用该技术的异常处理机制。

WPF 消息循环也是可选的

WPF 不仅可以在不创建应用程序对象的情况下运行,它还可以在不使用Dispatcher.Run() 的情况下运行,只要另一种技术正在泵送 Win32 窗口消息。这是通过创建一个虚拟窗口和/或子类化 WPF 窗口来安装消息挂钩来完成的。因此,无论哪个消息循环在泵送消息,WPF 都会按预期工作。

实际上,当您使用ElementHost 时,WPF Dispatcher 不会用于消息泵送,除非您使用以下方法之一:

  • Window.ShowDialog
  • Dispatcher.Invoke
  • Dispatcher.Run(或等效的 Application.Run
  • DispatcherOperation.Wait

因此,您的 WPF 异常处理程序可能不会被调用。相反,您需要在 VB 6 或 WinForms 级别安装异常处理程序。

回答您的问题

在这种情况下,是否曾经创建过 WPF 应用程序对象?

没有。

如果不是,是什么导致消息被抽出?

VB 6 正在发送消息。

如果我在后台线程上启动 Application 对象会发生什么?

很少:

  • 如果您有应用程序资源,这些资源将在后台线程上创建,当它们在主线程上使用时可能会导致异常。

  • 如果将处理程序添加到 Application.Current.DispatcherUnhandledException,它将仅适用于后台线程。换句话说,除非您在后台线程上创建窗口,否则永远不会调用处理程序。

  • 将从后台线程调用您的 Application.Startup,这可能是一件坏事。 StartupUri 也是如此。

推荐

从您的要求看来,您在加载 WPF 控件期间遇到了未处理的异常,并且您希望捕获该异常。在这种情况下,最好的计划可能是将您的 WPF 控件包装在一个简单的 ContentControl 中,其构造函数使用这样的代码来构造子对象:

Dispatcher.Current.UnhandledException += handler;
Disptacher.Current.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() =>
{
  Content = CreateChildControl();
  Dispatcher.Current.Invoke(DispatcherPriority.ApplicationIdle, new Action(() => {});
});

它是如何工作的:BeginInvoke 会延迟子项的构建,直到 VB 6 和/或 InteropControl 完成所有处理。创建子控件后的 Invoke 调用以低优先级调用空操作,导致所有待处理的 DispatcherOperations 完成。

最终结果是,在构造函数内或之后引发的任何异常现在都会传递给您的异常处理程序。

【讨论】:

  • 哇,你真的很了解你的东西。作为 InfoQ 的编辑,我可以为您提供至少 500 美元的价格来购买有关该主题的文章。如果您有兴趣,请通过 jonathan@infoq.com 与我联系。
【解决方案2】:

在 WPF 中,Application 对象不直接负责消息泵,它是Dispatcher。当你启动一个 WPF 应用程序时,Application.Run() 在启动时被调用,它调用Dispatcher.Run()

在您的互操作方案中,Application.Current 将返回 null,因为它从未创建。消息泵由 VB 处理,因为它创建了主窗口。如果您在代码中依赖它,您可以:

  • 创建一个新的Application 对象:

    if (Application.Current != null)
    {
        new Application();
    }
    

    应用程序是单例的,所以它会自动存储在Application.Current中。

  • 尽可能避免依赖它(我认为这是推荐的方式)。您应该注意,该类提供的许多服务(例如 Exit 事件)无论如何都不会在您的场景中可用。如果您只需要未处理的异常事件,您可以使用Dispatcher.CurrentDispatcher.UnhandledException

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多