views:

488

answers:

4

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?

+1  A: 

Take a read through WPF Interoperation: "Airspace" and Window Regions Overview.

rh
Ok, thanks. This article does not help other than explain in more detail what I already assumed. I guess I will have to experiment with two WPF windows and see if I get the behavior I want.
wpfwannabe
A: 

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.

Abe Heidebrecht
Yes, I am actually targeting a web browser but Chris' solutions seems like an overkill for what I am trying to achieve. I will surely consider this as an option as it looks really awesome. Thanks!
wpfwannabe
+1  A: 

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:

  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

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);
};
Ray Burns
Ok, at first I thought this was a complete solution but after re-reading it I realized it was a solution scattered all over the place ;-) leaving me to piece the puzzle together (which I might do if my current solution turns not to hold water too good).It puzzles me though if all the code snippets came from the top off your head or did you actually paste them from a working solution? If former, great stuff but how can you tell it will work? If latter, why is there simply no link to code? ;-)Anyway, thanks a bunch. This looks very promising.
wpfwannabe
The code snippets are all off the top of my head but every one is taken from code I've written before. I've never solved the airspace problem in exactly this way before, but every one of the pieces I describe is something I have actually done, and I am quite confident it can be made to work. I've had this design in mind for a long long time but have not yet had time to put it together this way. When I saw your question I decided to share the design because it seemed simpler and more flexible than what you are considering.
Ray Burns
+1  A: 

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/

KyleGobel