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