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.
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.
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?
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?
Update by Meleak
I'm offering this Bounty to anyone who can post an implementation of Ray Burns Answer AirRepair
. I tried myself but in vain
Suggested solution
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
}
Used this way:
<Grid>
<WebBrowser x:Name="browser" ... />
<AirRepair Win32Host="{Binding ElementName=browser}"
Margin="10" HorizontalAlignment="Left" ...>
<TextBox ... />
</AirRepair>
</Grid>
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.
How it works
AirRepair solves airspace issues by:
- Creating a child hWnd under the given
HwndHost
using HwndSource
- Setting its hRgn to the appropriate area
- Setting its
RootVisual
to a Border
whose Background
is a VisualBrush
of the decorated control
- Forwarding
WM_MOUSEMOVE
etc received by the child hWnd to the main WPF window
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.
Some important implementation details
Getting the parent hWnd
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.
Creating the child 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.
Positioning the child 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));
The UIELement.LayoutUpdated
event should be used to keep this up to date.
Computing the hRgn and opacity
Optional: Omit if only rectangular repair areas are supported
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.
Drawing the content on the child hWnd
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,
}
};
Forwarding mouse messages
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);
};
Take a read through WPF Interoperation: "Airspace" and Window Regions Overview.
if you're looking for a quick and easy solution, just use the popup control, here's an example
http://karlshifflett.wordpress.com/2009/06/13/wpf-float-buttons-over-web-browser-control/
If you are specifically targeting a web browser, there have been a couple of attempts at this. Chris Cavanagh has created an excellent solution based on Chrome.