Render WPF control on top of WindowsFormsHost

23,373

Solution 1

This "airspace" issue is suppose to be fixed in WPF vNext. There are a couple solutions out there, such as here, here, and here.

One way to do this is to host the WPF content in a transparent Popup or Window, which overlays the Interop content.

Solution 2

Late to the party, I know, but I recently came across this issue using a WebBrowser control.

The final fix was to create a screenshot of the web browser whenever I hosted a modal dialog over the top. Since this was a little fiddly, I turned it into a Github project, hopefully this helps a little -

https://github.com/chris84948/AirspaceFixer

(It's on Nuget too, under AirspaceFixer)

Once you have the project all you need to do is this

xmlns:asf="clr-namespace:AirspaceFixer;assembly=AirspaceFixer"

<asf:AirspacePanel FixAirspace="{Binding FixAirspace}">
    <WebBrowser x:Name="Browser" />
</asf:AirspacePanel>

Where FixAirspace is the dependency property that switches from the "real" view of the content, to the screenshot or "fake" view.

Solution 3

Try this on for size:

<hacks:AirspaceOverlay>
    <hacks:AirspaceOverlay.OverlayChild>
        <Canvas ToolTip = "A tooltip over a DirectX surface" Background="#01000000" Name="Overlay" />
    </hacks:AirspaceOverlay.OverlayChild>
    <controls:OpenGLControlWrappingWindowsFormsHost />
</hacks:AirspaceOverlay>


// Adapted from http://blogs.msdn.com/b/pantal/archive/2007/07/31/managed-directx-interop-with-wpf-part-2.aspx & http://www.4mghc.com/topics/69774/1/in-wpf-how-can-you-draw-a-line-over-a-windowsformshost
public class AirspaceOverlay : Decorator
{
    private readonly Window _transparentInputWindow;
    private Window _parentWindow;

    public AirspaceOverlay()
    {
        _transparentInputWindow = CreateTransparentWindow();
        _transparentInputWindow.PreviewMouseDown += TransparentInputWindow_PreviewMouseDown;
    }

    public object OverlayChild
    {
        get { return _transparentInputWindow.Content; }
        set { _transparentInputWindow.Content = value; }
    }

    private static Window CreateTransparentWindow()
    {
        var transparentInputWindow = new Window();

        //Make the window itself transparent, with no style.
        transparentInputWindow.Background = Brushes.Transparent;
        transparentInputWindow.AllowsTransparency = true;
        transparentInputWindow.WindowStyle = WindowStyle.None;

        //Hide from taskbar until it becomes a child
        transparentInputWindow.ShowInTaskbar = false;

        //HACK: This window and it's child controls should never have focus, as window styling of an invisible window 
        //will confuse user.
        transparentInputWindow.Focusable = false;

        return transparentInputWindow;
    }

    void TransparentInputWindow_PreviewMouseDown(object sender, MouseButtonEventArgs e)
    {
        _parentWindow.Focus();
    }

    protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
    {
        base.OnRenderSizeChanged(sizeInfo);
        UpdateOverlaySize();
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        base.OnRender(drawingContext);
        if (_transparentInputWindow.Visibility != Visibility.Visible)
        {
            UpdateOverlaySize();
            _transparentInputWindow.Show();
            _parentWindow = GetParentWindow(this);
            _transparentInputWindow.Owner = _parentWindow;
            _parentWindow.LocationChanged += ParentWindow_LocationChanged;
            _parentWindow.SizeChanged += ParentWindow_SizeChanged;
        }
    }

    private static Window GetParentWindow(DependencyObject o)
    {
        var parent = VisualTreeHelper.GetParent(o);
        if (parent != null)
            return GetParentWindow(parent);
        var fe = o as FrameworkElement;
        if (fe is Window)
            return fe as Window;
        if (fe != null && fe.Parent != null)
            return GetParentWindow(fe.Parent);  
        throw new ApplicationException("A window parent could not be found for " + o); 
    }

    private void ParentWindow_LocationChanged(object sender, EventArgs e)
    {
        UpdateOverlaySize();
    }

    private void ParentWindow_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        UpdateOverlaySize();
    }

    private void UpdateOverlaySize()
    {
        var hostTopLeft = PointToScreen(new Point(0, 0));
        _transparentInputWindow.Left = hostTopLeft.X;
        _transparentInputWindow.Top = hostTopLeft.Y;
        _transparentInputWindow.Width = ActualWidth;
        _transparentInputWindow.Height = ActualHeight;
    }
}

Solution 4

Here's a link to the best answer I've seen on this subject so far: Can I overlay a WPF window on top of another?

Solution 5

If anyone finds themselves unsatisfied with the hacks, setting the Visibility of the WindowsFormsHost to Collapsed or Hidden is always an option.

Share:
23,373
Vitalij
Author by

Vitalij

A full time software engineer working for M&amp;S. I am also an entrepreneur.

Updated on May 05, 2020

Comments

  • Vitalij
    Vitalij about 4 years

    I know that default WPF behavior is to render WPF controls and then on top render WinForms, but are there any way to render WPF on top of WindowsFormsHost?

    Edit: I have found a temp hack as well. When wpf control overlaps WindowsFormsHost, I change the size of the WindowsFormsHost (This only works when you have rectangular object which overlaps, doesn't work for other shapes.)

  • johnildergleidisson
    johnildergleidisson about 10 years
    NOTE: I comment this as an attempt to not get the hopes up for whoever reads this, the "airspace" problem in @CodeNaked's posted article had a fix (hwndhost.IsRedirected) implemented in .NET 4.5 beta, but then dropped in the final release for whatever reason. Read this, and this
  • Juho
    Juho about 6 years
    Transparent Window is actually a good solution (though still a hack). It requires few lines of code behind to keep the position and size, etc, correct but it seems to work very reliably.