Windows 窗体和 WPF 互操作性输入体系结构
WPF 与 Windows 窗体之间的互操作要求这两种技术都具有适当的键盘输入处理。 本主题介绍这些技术如何实现键盘和消息处理,以便在混合应用程序中实现平滑互操作。
本主题包含以下小节:
无模式窗体和对话框
WindowsFormsHost 键盘和消息处理
ElementHost 键盘和消息处理
无模式窗体和对话框
在 WindowsFormsHost 元素上调用 EnableWindowsFormsInterop 方法以从基于 WPF 的应用程序打开无模式窗体或对话框。
在 ElementHost 控件上调用 EnableModelessKeyboardInterop 方法,以在基于Windows 窗体的应用程序中打开无模式 WPF 页面。
WindowsFormsHost 键盘和消息处理
当由基于 WPF 的应用程序承载时,Windows 窗体键盘和消息处理包括以下内容:
WindowsFormsHost 类从 WPF 消息循环中获取消息,该循环由 ComponentDispatcher 类实现。
WindowsFormsHost 类创建一个代理 Windows 窗体消息循环,以确保进行普通的 Windows 窗体键盘处理。
WindowsFormsHost 类实现 IKeyboardInputSink 接口以协调焦点管理与 WPF。
WindowsFormsHost 控件自行注册并启动其消息循环。
以下部分更详细地描述了该过程的这些部分。
从 WPF 消息循环中获取消息
ComponentDispatcher 类实现 WPF 的消息循环管理器。 ComponentDispatcher 类提供挂钩,使外部客户端能够在 WPF 处理消息之前对其进行筛选。
互操作实现处理 ComponentDispatcher.ThreadFilterMessage 事件,使 Windows 窗体控件能够在 WPF 控件之前处理消息。
代理 Windows 窗体消息循环
默认情况下,System.Windows.Forms.Application 类包含 Windows 窗体应用程序的主要消息循环。 在互操作期间,Windows 窗体消息循环不处理消息。 因此,必须重现此逻辑。 ComponentDispatcher.ThreadFilterMessage 事件的处理程序执行以下步骤:
使用 IMessageFilter 接口筛选消息。
调用 Control.PreProcessMessage 方法。
如果需要,转换并调度消息。
如果没有其他控件处理消息,则将消息传递给承载控件。
IKeyboardInputSink 实现
代理消息循环处理键盘管理。 因此,IKeyboardInputSink.TabInto 方法是唯一需要在 WindowsFormsHost 类中实现的 IKeyboardInputSink 成员。
默认情况下,HwndHost 类为其 IKeyboardInputSink.TabInto 实现返回 false
。 这可以防止从 WPF 控件到 Windows 窗体控件的 Tab 键输入。
IKeyboardInputSink.TabInto 方法的 WindowsFormsHost 实现执行以下步骤:
查找 WindowsFormsHost 控件包含并且可以接收焦点的第一个或最后一个 Windows 窗体控件。 控件选项取决于遍历信息。
将焦点设置为控件并返回
true
。如果没有控件可以接收焦点,则返回
false
。
WindowsFormsHost 注册
创建 WindowsFormsHost 控件的窗口句柄时,WindowsFormsHost 控件调用一个内部静态方法,该方法为消息循环注册其存在。
在注册期间,WindowsFormsHost 控件检查消息循环。 如果尚未启动消息循环,则创建 ComponentDispatcher.ThreadFilterMessage 事件处理程序。 当附加 ComponentDispatcher.ThreadFilterMessage 事件处理程序时,消息循环被认为正在运行。
当窗口句柄被销毁时,WindowsFormsHost 控件将自己从注册中移除。
ElementHost 键盘和消息处理
当由 Windows 窗体应用程序承载时,WPF 键盘和消息处理包括以下内容:
Tab 键和箭头键。
命令键和对话框键。
Windows 窗体快捷键处理。
以下部分更详细地描述了这些部分。
接口实现
在 Windows 窗体中,键盘消息被路由到具有焦点的控件的窗口句柄。 在 ElementHost 控件中,这些消息被路由到托管元素。 为此,ElementHost 控件提供了一个 HwndSource 实例。 如果 ElementHost 控件具有焦点,则 HwndSource 实例会路由大多数键盘输入,以便 WPF InputManager 类可以处理它。
HwndSource 类实现 IKeyboardInputSink 和 IKeyboardInputSite 接口。
键盘互操作依赖于实现 OnNoMoreTabStops 方法来处理将焦点移出托管元素的 Tab 键和箭头键输入。
Tab 键和箭头键
Windows 窗体选择逻辑映射到 IKeyboardInputSink.TabInto 和 OnNoMoreTabStops 方法以实现 Tab 键和箭头键导航。 重写 Select 方法可完成此映射。
命令键和对话框键
为了让 WPF 第一次有机会处理命令键和对话框键,Windows 窗体命令预处理连接到 TranslateAccelerator 方法。 重写 Control.ProcessCmdKey 方法可连接这两种技术。
使用 TranslateAccelerator 方法,托管元素可以处理任何键消息,例如 WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN 或 WM_SYSKEYUP,包括命令键,例如 Tab、Enter、Esc 和箭头键。 如果未处理键消息,则将其向上发送到 Windows 窗体上次层次结构以进行处理。
快捷键处理
若要正确处理快捷键,Windows 窗体快捷键处理必须连接到 WPF AccessKeyManager 类。 此外,所有 WM_CHAR 消息必须正确路由到托管元素。
由于 TranslateChar 方法的默认 HwndSource 实现返回 false
,因此使用以下逻辑处理 WM_CHAR 消息:
重写 Control.IsInputChar 方法以确保将所有 WM_CHAR 消息转发到托管元素。
如果按下 Alt 键,则消息为 WM_SYSCHAR。 Windows 窗体不通过 IsInputChar 方法预处理此消息。 因此,将重写 ProcessMnemonic 方法以查询 WPF AccessKeyManager,获取已注册的快捷键。 如果找到已注册的快捷键,AccessKeyManager 会对其进行处理。
如果未按下 Alt 键,WPF InputManager 类将处理未处理的输入。 如果输入是快捷键,AccessKeyManager 会对其进行处理。 为未处理的 WM_CHAR 消息处理 PostProcessInput 事件。
当用户按下 Alt 键时,快捷键视觉提示会显示在整个窗体上。 为了支持这种行为,活动窗体上的所有 ElementHost 控件都会接收 WM_SYSKEYDOWN 消息,而不管哪个控件具有焦点。
消息仅发送到活动窗体中的 ElementHost 控件。