HWND 子级的 WPF 渲染问题

wpf*_*abe 5 wpf rendering hwnd webbrowser-control

我想可以肯定地说 WPF 将其内容呈现为窗口背景。不存在传统 HWND 意义上的子窗口。因此,当人们在 WPF 应用程序中引入基于 HWND 的东西(例如 Web 浏览器)时,视觉外观方面的情况就开始出错。

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

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

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

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


由 Melak 更新

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

Ray*_*rns 1

建议的解决方案

我建议使用以下签名的简单“AirRepair”类:

public class AirRepair : Decorator
{
  public HwndHost Win32Host ... // DP bound to HwndHost
  public Geometry RepairArea ... // Default is entire decorated control,
                                 // or use OpacityMask
}
Run Code Online (Sandbox Code Playgroud)

使用这种方式:

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

  <AirRepair Win32Host="{Binding ElementName=browser}"
             Margin="10" HorizontalAlignment="Left" ...>
    <TextBox ... />
  </AirRepair>
</Grid>
Run Code Online (Sandbox Code Playgroud)

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

怎么运行的

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

  1. HwndHost在给定的using下创建一个子 hWndHwndSource
  2. 将其 hRgn 设置为适当的区域
  3. 将其设置RootVisual为a Border,其是装饰控件的BackgroundaVisualBrush
  4. WM_MOUSEMOVE将子 hWnd 接收到的等转发到主 WPF 窗口

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

一些重要的实施细节

获取父级 hWnd

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

创建子 hWnd

可以使用该类在托管代码中创建子 hWnd、为其设置父级并挂钩HwndSource。当 Win32Host 的父 hWnd 不再可用时,请务必将其丢弃。

定位子 hWnd

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

 var compositionTarget = PresentationSource.FromVisual(this).CompositionTarget;
 var position = compositionTarget.TransformToDevice(
                  this.TransformToVisual(Win32Host));
Run Code Online (Sandbox Code Playgroud)

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

计算 hRgn 和不透明度

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

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

在子 hWnd 上绘制内容

使用AirRepair 装饰VisualBrushVisual,而不是装饰控件。这允许在不改变内容的情况下替换修饰的控件。

childHwndSource.RootVisual =
  new Border
  {
    Background = new VisualBrush
    {
      Visual = this,
      ViewBoxUnits = BrushMappingMode.Absolute,
      ViewPortUnits = BrushMappingMode.Absolute,
    }
  };
Run Code Online (Sandbox Code Playgroud)

转发鼠标消息

使用 添加钩子,然后在容器中HwndSource.AddHook使用 Win32 :PostMessage

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);
};
Run Code Online (Sandbox Code Playgroud)