WPF渲染问题:HWND子窗口阻挡视图

5
我认为可以肯定地说,WPF将其内容呈现为窗口背景。在传统的HWND意义下,没有子窗口。因此,在WPF应用程序中引入基于HWND的内容(如WebBrowser)时,视觉外观方面的事情开始朝着错误的方向发展。

考虑一个带有两个子元素的网格的窗口,WebBrowser和其他东西,例如TextBox。如果WebBrowser代表了一个红色圆圈,那么TextBox将在其上方呈现。在WebBrowser的情况下,任何TextBox都找不到。这是因为TextBox被呈现为主窗口的背景,而WebBrowser实际上是主窗口的HWND子窗口,遮盖了背景。

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

我考虑使用第二个透明的无边框WPF窗口,将其重新父级化,使其成为主窗口的所有权,并进行一些其他技巧来实现它。

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


Meleak的更新

我向任何能够发表Ray Burns Answer的实现的人提供此悬赏。我自己试过了但失败了


你最终实现了它吗?如果是这样,它起作用了吗? - Fredrik Hedblad
不,我从未继续实现它,因为尽管它可能满足最初的问题,但我的要求发生了变化,标准的WebBrowser不再适合我。我最终使用了Awesomium和Chris Cavanagh的WPF包装器。它运行得非常好。对我来说,没有HWND问题了。自那时以来,Awesomium已经发展壮大,现在具有自己的WPF包装器。如果WebBrowser是您关注的主要问题,我建议您查看Awesomium。 - wpfwannabe
感谢您的建议,不幸的是,“WebBrowser”并不是我的主要问题,而是一堆“WindowsFormsHost”。我现在用自定义的“Popup”控件解决了这个问题,但我对这种解决方法并不满意。 - Fredrik Hedblad
4个回答

2

好的,谢谢。这篇文章除了更详细地解释我已经假定的内容之外,并没有帮助到我。我想我将不得不尝试两个 WPF 窗口,看看是否能够得到我想要的行为。 - wpfwannabe

1
建议的解决方案

我建议使用一个简单的“AirRepair”类,其签名如下:

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 通过以下方式解决空间问题:

  1. 在给定的 HwndHost 下创建一个子 hWnd,使用 HwndSource
  2. 将其 hRgn 设置为适当的区域
  3. 将其 RootVisual 设置为一个 Border,其 Background 是装饰控件的 VisualBrush
  4. 将子 hWnd 接收到的 WM_MOUSEMOVE 等转发到主 WPF 窗口
这样做的结果是,WPF继续在Win32内容后面绘制内容,但AirRepair的子窗口在单独的Win32控件中前面重新绘制相同的内容。 一些重要的实现细节 获取父hWnd 当设置Win32Host时,它可能有也可能没有hWnd。 PropertyChangedCallback应使用PresentationSource.AddSourceChangedHandler / PresentationSource.RemoveSourceChangedHandler检测可能的hWnd更改,然后在Dispatcher.BeginInvoke回调中更新自己的hWnd指针,以便HwndHost有机会完成处理SourceChanged事件。 创建子hWnd 可以使用HwndSource类在托管代码中创建、父级和挂钩子。确保在Win32Host的父hWnd不再可用时将其处理掉。 定位子hWnd 子hWnd的窗口位置(相对于其父级)可以计算为:
 var compositionTarget = PresentationSource.FromVisual(this).CompositionTarget;
 var position = compositionTarget.TransformToDevice(
                  this.TransformToVisual(Win32Host));

应使用UIELement.LayoutUpdated事件来保持此内容的最新状态。

计算hRgn和不透明度

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

当设置了RepairAreaOpacityMask,并且子窗口句柄hWnd存在时,使用RenderTargetBitmap使用OpacityMask绘制RepairArea,然后从中创建。如果RepairArea为空,则使用矩形。如果OpacityMask为空,则使用黑色。 RenderTargetBitmap大小通过将AirRepair修饰符的坐标转换为设备坐标来设置。请注意,这不能正确处理变量OpacityMask,例如动画刷或VisualBrushVisual正在更改。

在子窗口句柄上绘制内容

使用一个VisualBrush,其Visual是AirRepair修饰符,而不是已装饰控件。这允许替换已装饰控件而不更改内容。

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

转发鼠标消息

使用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);
};

好的,最初我以为这是一个完整的解决方案,但在重新阅读后,我意识到这是一个散乱的解决方案 ;-) 让我来拼凑这个谜题(如果我的当前解决方案不太可行)。虽然让我感到困惑的是,所有的代码片段都是从你的头脑中搬出来的,还是你真的从一个可行的解决方案中粘贴过来的?如果是前者,那很棒,但你如何确信它能正常工作呢?如果是后者,为什么没有链接到代码呢?;-)无论如何,非常感谢。这看起来非常有前途。 - wpfwannabe
这些代码片段都是我凭记忆写出来的,但每一个都是我之前编写过的代码。我以前从未以这种方式解决过领空问题,但我所描述的每一部分都是我实际完成过的事情,我相当有信心它能够正常工作。我已经考虑这个设计很久了,但尚未有时间以这种方式将其组合起来。当我看到你的问题时,我决定分享这个设计,因为它似乎比你正在考虑的更简单和更灵活。 - Ray Burns

1

我已经在使用一个自定义的非最顶层的“Popup”,它可以跟随屏幕上的主机,但是使用“Popup”非常不稳定,而且很难调试,因为即使使用了非最顶层的修复程序,它仍然可能出现在调试器之上。此外,还有其他一些小问题。 - Fredrik Hedblad

0
如果你的目标是特定的网页浏览器,已经有一些尝试。Chris Cavanagh 基于 Chrome 创建了一个非常好的解决方案。

是的,实际上我正在针对一个网络浏览器,但是克里斯的解决方案似乎对我所尝试实现的目标来说过于复杂了。我肯定会将其作为一个选项考虑,因为它看起来真的很棒。谢谢! - wpfwannabe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接