HWND 子进程的 WPF 渲染问题 [英] WPF rendering problem with HWND children in the way

查看:87
本文介绍了HWND 子进程的 WPF 渲染问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想可以肯定地说 WPF 将其内容呈现为窗口背景.没有传统 HWND 意义上的子窗口.因此,当人们在 WPF 应用程序(如 WebBrowser)中引入基于 HWND 的东西时,视觉外观方面就会出现问题.

I suppose it is safe to say that WPF renders its contents as a window background. There are no child windows in a traditional HWND sense. So, when one introduces something HWND based in a WPF app, like a WebBrowser, things start to go wrong way it terms of visual appearance.

考虑一个 Window 有一个 Grid 和两个孩子,WebBrowser 和其他东西,例如文本框.如果 WebBrowser 是一个红色圆圈,则 TextBox 将呈现在它之上.对于 WebBrowser,在任何地方都找不到 TextBox.这是因为 TextBox 被渲染为主窗口的背景,而 WebBrowser 实际上是主窗口的 HWND 子窗口,遮挡了背景.

Consider a Window having a Grid with two children, WebBrowser and something else, e.g. TextBox. If WebBrowser were a red circle instead, the TextBox would render on top of it. In case of WebBrowser, no TextBox is to be found anywhere. This is because TextBox is rendered as main window's background and WebBrowser is actually a HWND child of the main window obscuring the background.

所以一切(不好).如何实现所需的行为?我想让 TextBox 呈现在 WebBrowser 之上.有人遇到过这个问题吗?

So all is (not) well. How does one achieve the desired behavior? I want to have TextBox rendered on top of WebBrowser. Has anyone encountered this problem?

我正在考虑拥有第二个透明的顶级无边界 WPF 窗口,重新设置它的父级,以便主窗口拥有它并执行一些其他技巧来实现它.

I am thinking along the lines of having a second transparent top-level borderless WPF window, re-parent it so that the main window owns it and do some other tricks to make it happen.

在我深入研究之前,我想知道是否有人有明显或更简单的解决方案?

Before I dig in, I was wondering if anybody had an obvious or a simpler solution?

我向任何可以发布 Ray Burns Answer AirRepair 实施的人提供此赏金.我自己尝试过,但没有成功

I'm offering this Bounty to anyone who can post an implementation of Ray Burns Answer AirRepair. I tried myself but in vain

推荐答案

建议的解决方案

我建议使用带有此签名的简单AirRepair"类:

I suggest a simple "AirRepair" class with this signature:

public class AirRepair : Decorator
{
  public HwndHost Win32Host ... // DP bound to HwndHost
  public Geometry RepairArea ... // Default is entire decorated control,
                                 // or use OpacityMask
}

这样使用:

<Grid>
  <WebBrowser x:Name="browser" ... />

  <AirRepair Win32Host="{Binding ElementName=browser}"
             Margin="10" HorizontalAlignment="Left" ...>
    <TextBox ... />
  </AirRepair>
</Grid>

AirRepair 可与 WebBrowserWindowsFormsHost 或任何其他 HwndHost 一起使用.装饰控件覆盖的区域显示在 Win32 内容内,它接受焦点和鼠标事件.对于非矩形装饰控件,可以通过 RepairArea 和/或 OpacityMask 属性指定要显示的区域.

AirRepair can be used with WebBrowser, WindowsFormsHost, or any other HwndHost. The area covered by the decorated control is displayed inside the Win32 content and it accepts focus and mouse events. For non-rectangular decorated controls, the area to display can be specified by the RepairArea and/or OpacityMask properties.

工作原理

AirRepair 通过以下方式解决空域问题:

AirRepair solves airspace issues by:

  1. 使用 HwndSource
  2. 在给定的 HwndHost 下创建子 hWnd
  3. 将其 hRgn 设置为适当的区域
  4. 将其 RootVisual 设置为 Border,其 Background 是装饰控件的 VisualBrush
  5. 将子hWnd收到的WM_MOUSEMOVE等转发到WPF主窗口
  1. Creating a child hWnd under the given HwndHost using HwndSource
  2. Setting its hRgn to the appropriate area
  3. Setting its RootVisual to a Border whose Background is a VisualBrush of the decorated control
  4. Forwarding WM_MOUSEMOVE etc received by the child hWnd to the main WPF window

这样做的结果是,WPF 继续绘制 Win32 内容后面的内容,但 AirRepair 的子窗口将 Win32 内容前面的相同内容重新绘制在单独的Win32 控件.

The result of this is that WPF continues to draw the content behind the Win32 content but AirRepair's child window redraws the same content in front of the Win32 content in a separate Win32 control.

一些重要的实现细节

获取父 hWnd

Win32Host 最初设置时,它可能有也可能没有 hWnd.PropertyChangedCallback 应该使用 PresentationSource.AddSourceChangedHandler/PresentationSource.RemoveSourceChangedHandler 来检测可能的 hWnd 更改,然后在 Dispatcher 中更新自己的 hWnd 指针.BeginInvoke 回调以便 HwndHost 有机会完成对 SourceChanged 事件的处理.

When Win32Host is originally set, it may or may not have a hWnd. The PropertyChangedCallback should use PresentationSource.AddSourceChangedHandler / PresentationSource.RemoveSourceChangedHandler to detect possible hWnd changes, then update its own hWnd pointer in a Dispatcher.BeginInvoke callback so the HwndHost has a chance to finish handling the SourceChanged event.

创建子 hWnd

子 hWnd 可以使用 HwndSource 类在托管代码中创建、作为父级和挂钩.当 Win32Host 的父 hWnd 不再可用时,请务必对其进行处置.

The child hWnd can be created, parented and hooked in managed code using the HwndSource class. Be sure to dispose it when the Win32Host's parent hWnd is no longer available.

定位孩子 hWnd

子 hWnd 的窗口位置(相对于其父窗口)可以计算为:

The child hWnd's window position (relative to its parent) can be computed as:

 var compositionTarget = PresentationSource.FromVisual(this).CompositionTarget;
 var position = compositionTarget.TransformToDevice(
                  this.TransformToVisual(Win32Host));

应该使用 UIELement.LayoutUpdated 事件来保持最新状态.

The UIELement.LayoutUpdated event should be used to keep this up to date.

计算 hRgn 和不透明度

可选:如果仅支持矩形修复区域,则省略

Optional: Omit if only rectangular repair areas are supported

当设置了 RepairAreaOpacityMask 并且子 hWnd 存在时,使用 RenderTargetBitmap 绘制 RepairArea> 使用 OpacityMask 然后从中创建 hRgn.如果 RepairArea 为空,则使用矩形.如果 OpacityMask 为空,则使用黑色.RenderTargetBitmap 大小是通过将 AirRepair 装饰器的坐标转换为设备坐标来设置的.请注意,这不能正确处理变量 OpacityMask,例如动画画笔或 Visual 正在更改的 VisualBrush.

When the RepairArea or OpacityMask is set and the child hWnd exists, use a RenderTargetBitmap to paint the RepairArea using the OpacityMask then create the hRgn from it. If RepairArea is null, use a rectangle. If OpacityMask is null, use black. The RenderTargetBitmap size is set by transforming the AirRepair decorator's coordinates to device coordinates. Note that this does not properly handle a variable OpacityMask such as an animated brush or a VisualBrush whose Visual is changing.

在孩子 hWnd 上绘制内容

使用 VisualBrush,其 Visual 是 AirRepair 装饰器,而不是装饰控件.这允许在不更改内容的情况下替换装饰控件.

Use a VisualBrush whose Visual is the AirRepair decorator, not the decorated control. This allows the decorated control to be replaced without changing the content.

childHwndSource.RootVisual =
  new Border
  {
    Background = new VisualBrush
    {
      Visual = this,
      ViewBoxUnits = BrushMappingMode.Absolute,
      ViewPortUnits = BrushMappingMode.Absolute,
    }
  };

转发鼠标消息

使用HwndSource.AddHook添加一个钩子,然后使用Win32PostMessage到容器:

Add a hook using HwndSource.AddHook, then use Win32 PostMessage to the container:

childHwndSource.AddHook((hwnd, msg, wParam, lParam, handled) =>
{
  // Check if message needs forwarding and update parameters if necessary
  switch(msg)
  {
    default:
      return;  // Not recognized - do not forward

    case WM_MOUSEMOVE:
    ...
  }
  var target = PresentationSource.FromVisual(this).CompositionTarget as HwndTarget;
  if(target!=null)
    Win32Methods.PostMessage(target.Handle, msg, wParam, lParam);
};

这篇关于HWND 子进程的 WPF 渲染问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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