Can I overlay a WPF window on top of another?
Asked Answered
H

5

34

I have a WPF window, which contains a WindowsFormsHost element. I need to draw things on top of this element, but the nature of WindowsFormsHost means that it's always on the top of the drawing pile. As I cannot draw in the same WPF window on top of the WindowsFormsHost component, can I overlay another window on top of it?

I've tried this in a rudimentary way, but I have a few problems:

1) I can't stop windows from other apps going in between the main window, and the overlay window.

2) When I Alt-Tab, the overlay window appears in the window list, which is pretty ugly.

Basically I need the concept of a "child window", and window which to all intents and purposes appears as part of another window. UserControls won't work for me, as the WindowsFormsHost will always draw on top of it.

Any Ideas?


Update [May 23 '11 at 10:13 ]

Thank you both for answers.

I've tried the ChildWindow approach, and the WindowsFormsHost element still draws on top. As I understand it, only a true window can draw on top of a WindowsFormsHost, anything in the same window will go under the WindowsFormsHost.

An element with the WindowsFormsHost will still draw under a WinForms component, they are always drawn on top, and that seems non-negotiable...

I guess what I'm looking for is a way to dock an external window to act as part of the main window. On the Mac, there is the concept of a true "child window", I'm looking for something like that.

Hardunn answered 22/5, 2011 at 12:12 Comment(1)
Without WinAPI and Third-party libraries https://mcmap.net/q/451748/-render-wpf-control-on-top-of-windowsformshostEucken
W
44

I've worked around this problem by using a Popup rather than a transparent Window

Update

I ended up with a subclassed Popup which I call AirspacePopup.

What AirspacePopup does

  • Follow its PlacementTarget.
  • Is not always-on-top, but placed relative to the Window in which it is being placed. This solution comes from Chris Cavanagh's Blog.
  • Is allowed to be moved "outside of the screen". This is achieved by clipping the Popup and setting negative Margin on its child once it moves off-screen. This solution comes from this StackOverflow post by Rick Sladkey

Here is an example where AirspacePopup is used to draw an Ellipse on top of a WebBrowser Control (which is in fact a WinForms Control) but it will work just as well with any WindowsFormsHost.

<Grid>
    <local:AirspacePopup PlacementTarget="{Binding ElementName=webBrowser}"
                         FollowPlacementTarget="True"
                         AllowOutsideScreenPlacement="True"
                         ParentWindow="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                         IsOpen="True"
                         AllowsTransparency="True"
                         Placement="Center"
                         Width="{Binding ElementName=googleBrowser, Path=ActualWidth}"
                         Height="{Binding ElementName=googleBrowser, Path=ActualHeight}">
        <Grid>
            <Ellipse Width="100" Height="100" Fill="Green" Margin="100"/>
        </Grid>
    </local:AirspacePopup>
    <WebBrowser Name="webBrowser" Loaded="WebBrowser_Loaded"/>
</Grid>

Simple Code behind to navigate..

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void WebBrowser_Loaded(object sender, RoutedEventArgs e)
    {
        WebBrowser webbrowser = sender as WebBrowser;
        webbrowser.Navigate("http://www.stackoverflow.com");
    }
}

AirspacePopup

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Interop;

public class AirspacePopup : Popup
{
    public static readonly DependencyProperty IsTopmostProperty =
        DependencyProperty.Register("IsTopmost",
                                    typeof(bool),
                                    typeof(AirspacePopup),
                                    new FrameworkPropertyMetadata(false, OnIsTopmostChanged));

    public static readonly DependencyProperty FollowPlacementTargetProperty =
        DependencyProperty.RegisterAttached("FollowPlacementTarget",
                                            typeof(bool),
                                            typeof(AirspacePopup),
                                            new UIPropertyMetadata(false));

    public static readonly DependencyProperty AllowOutsideScreenPlacementProperty =
        DependencyProperty.RegisterAttached("AllowOutsideScreenPlacement",
                                            typeof(bool),
                                            typeof(AirspacePopup),
                                            new UIPropertyMetadata(false));

    public static readonly DependencyProperty ParentWindowProperty =
        DependencyProperty.RegisterAttached("ParentWindow",
                                            typeof(Window),
                                            typeof(AirspacePopup),
                                            new UIPropertyMetadata(null, ParentWindowPropertyChanged));

    private static void OnIsTopmostChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
    {
        AirspacePopup airspacePopup = source as AirspacePopup;
        airspacePopup.SetTopmostState(airspacePopup.IsTopmost);
    }

    private static void ParentWindowPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
    {
        AirspacePopup airspacePopup = source as AirspacePopup;
        airspacePopup.ParentWindowChanged();
    }

    private bool? m_appliedTopMost;
    private bool m_alreadyLoaded;
    private Window m_parentWindow;

    public AirspacePopup()
    {
        Loaded += OnPopupLoaded;
        Unloaded += OnPopupUnloaded;

        DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor.FromProperty(PlacementTargetProperty, typeof(AirspacePopup));
        descriptor.AddValueChanged(this, PlacementTargetChanged);
    }

    public bool IsTopmost
    {
        get { return (bool)GetValue(IsTopmostProperty); }
        set { SetValue(IsTopmostProperty, value); }
    }
    public bool FollowPlacementTarget
    {
        get { return (bool)GetValue(FollowPlacementTargetProperty); }
        set { SetValue(FollowPlacementTargetProperty, value); }
    }
    public bool AllowOutsideScreenPlacement
    {
        get { return (bool)GetValue(AllowOutsideScreenPlacementProperty); }
        set { SetValue(AllowOutsideScreenPlacementProperty, value); }
    }
    public Window ParentWindow
    {
        get { return (Window)GetValue(ParentWindowProperty); }
        set { SetValue(ParentWindowProperty, value); }
    }

    private void ParentWindowChanged()
    {
        if (ParentWindow != null)
        {
            ParentWindow.LocationChanged += (sender, e2) =>
            {
                UpdatePopupPosition();
            };
            ParentWindow.SizeChanged += (sender, e2) =>
            {
                UpdatePopupPosition();
            };
        }
    }
    private void PlacementTargetChanged(object sender, EventArgs e)
    {
        FrameworkElement placementTarget = this.PlacementTarget as FrameworkElement;
        if (placementTarget != null)
        {
            placementTarget.SizeChanged += (sender2, e2) =>
            {
                UpdatePopupPosition();
            };
        }
    }

    private void UpdatePopupPosition()
    {
        FrameworkElement placementTarget = this.PlacementTarget as FrameworkElement;
        FrameworkElement child = this.Child as FrameworkElement;

        if (PresentationSource.FromVisual(placementTarget) != null &&
            AllowOutsideScreenPlacement == true)
        {
            double leftOffset = CutLeft(placementTarget);
            double topOffset = CutTop(placementTarget);
            double rightOffset = CutRight(placementTarget);
            double bottomOffset = CutBottom(placementTarget);
            Debug.WriteLine(bottomOffset);
            this.Width = Math.Max(0, Math.Min(leftOffset, rightOffset) + placementTarget.ActualWidth);
            this.Height = Math.Max(0, Math.Min(topOffset, bottomOffset) + placementTarget.ActualHeight);

            if (child != null)
            {
                child.Margin = new Thickness(leftOffset, topOffset, rightOffset, bottomOffset);
            }
        }
        if (FollowPlacementTarget == true)
        {
            this.HorizontalOffset += 0.01;
            this.HorizontalOffset -= 0.01;
        }
    }
    private double CutLeft(FrameworkElement placementTarget)
    {
        Point point = placementTarget.PointToScreen(new Point(0, placementTarget.ActualWidth));
        return Math.Min(0, point.X);
    }
    private double CutTop(FrameworkElement placementTarget)
    {
        Point point = placementTarget.PointToScreen(new Point(placementTarget.ActualHeight, 0));
        return Math.Min(0, point.Y);
    }
    private double CutRight(FrameworkElement placementTarget)
    {
        Point point = placementTarget.PointToScreen(new Point(0, placementTarget.ActualWidth));
        point.X += placementTarget.ActualWidth;
        return Math.Min(0, SystemParameters.VirtualScreenWidth - (Math.Max(SystemParameters.VirtualScreenWidth, point.X)));
    }
    private double CutBottom(FrameworkElement placementTarget)
    {
        Point point = placementTarget.PointToScreen(new Point(placementTarget.ActualHeight, 0));
        point.Y += placementTarget.ActualHeight;
        return Math.Min(0, SystemParameters.VirtualScreenHeight - (Math.Max(SystemParameters.VirtualScreenHeight, point.Y)));
    }

    private void OnPopupLoaded(object sender, RoutedEventArgs e)
    {
        if (m_alreadyLoaded) 
            return;

        m_alreadyLoaded = true;

        if (Child != null)
        {
            Child.AddHandler(PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(OnChildPreviewMouseLeftButtonDown), true);
        }

        m_parentWindow = Window.GetWindow(this);

        if (m_parentWindow == null) 
            return;

        m_parentWindow.Activated += OnParentWindowActivated;
        m_parentWindow.Deactivated += OnParentWindowDeactivated;
    }

    private void OnPopupUnloaded(object sender, RoutedEventArgs e)
    {
        if (m_parentWindow == null)
            return;
        m_parentWindow.Activated -= OnParentWindowActivated;
        m_parentWindow.Deactivated -= OnParentWindowDeactivated;
    }

    private void OnParentWindowActivated(object sender, EventArgs e)
    {
        SetTopmostState(true);
    }

    private void OnParentWindowDeactivated(object sender, EventArgs e)
    {
        if (IsTopmost == false)
        {
            SetTopmostState(IsTopmost);
        }
    }

    private void OnChildPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        SetTopmostState(true);
        if (!m_parentWindow.IsActive && IsTopmost == false)
        {
            m_parentWindow.Activate();
        }
    }

    protected override void OnOpened(EventArgs e)
    {
        SetTopmostState(IsTopmost);
        base.OnOpened(e);
    }

    private void SetTopmostState(bool isTop)
    {
        // Don’t apply state if it’s the same as incoming state
        if (m_appliedTopMost.HasValue && m_appliedTopMost == isTop)
        {
            return;
        }

        if (Child == null) 
            return;

        var hwndSource = (PresentationSource.FromVisual(Child)) as HwndSource;

        if (hwndSource == null) 
            return;
        var hwnd = hwndSource.Handle;

        RECT rect;

        if (!GetWindowRect(hwnd, out rect)) 
            return;

        Debug.WriteLine("setting z-order " + isTop);

        if (isTop)
        {
            SetWindowPos(hwnd, HWND_TOPMOST, rect.Left, rect.Top, (int)Width, (int)Height, TOPMOST_FLAGS);
        }
        else
        {
            // Z-Order would only get refreshed/reflected if clicking the
            // the titlebar (as opposed to other parts of the external
            // window) unless I first set the popup to HWND_BOTTOM
            // then HWND_TOP before HWND_NOTOPMOST
            SetWindowPos(hwnd, HWND_BOTTOM, rect.Left, rect.Top, (int)Width, (int)Height, TOPMOST_FLAGS);
            SetWindowPos(hwnd, HWND_TOP, rect.Left, rect.Top, (int)Width, (int)Height, TOPMOST_FLAGS);
            SetWindowPos(hwnd, HWND_NOTOPMOST, rect.Left, rect.Top, (int)Width, (int)Height, TOPMOST_FLAGS);
        }

        m_appliedTopMost = isTop;
    }

    #region P/Invoke imports & definitions
    #pragma warning disable 1591 //Xml-doc
    #pragma warning disable 169 //Never used-warning
    // ReSharper disable InconsistentNaming
    // Imports etc. with their naming rules

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT

    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
    }

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

    [DllImport("user32.dll")]
    private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X,
    int Y, int cx, int cy, uint uFlags);

    static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
    static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
    static readonly IntPtr HWND_TOP = new IntPtr(0);
    static readonly IntPtr HWND_BOTTOM = new IntPtr(1);

    private const UInt32 SWP_NOSIZE = 0x0001;
    const UInt32 SWP_NOMOVE = 0x0002;
    const UInt32 SWP_NOZORDER = 0x0004;
    const UInt32 SWP_NOREDRAW = 0x0008;
    const UInt32 SWP_NOACTIVATE = 0x0010;

    const UInt32 SWP_FRAMECHANGED = 0x0020; /* The frame changed: send WM_NCCALCSIZE */
    const UInt32 SWP_SHOWWINDOW = 0x0040;
    const UInt32 SWP_HIDEWINDOW = 0x0080;
    const UInt32 SWP_NOCOPYBITS = 0x0100;
    const UInt32 SWP_NOOWNERZORDER = 0x0200; /* Don’t do owner Z ordering */
    const UInt32 SWP_NOSENDCHANGING = 0x0400; /* Don’t send WM_WINDOWPOSCHANGING */

    const UInt32 TOPMOST_FLAGS = 
        SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSENDCHANGING;

    // ReSharper restore InconsistentNaming
    #pragma warning restore 1591
    #pragma warning restore 169
    #endregion
}
Wolfe answered 23/6, 2011 at 10:58 Comment(4)
How can I make this HitTestVisible? I tried to set the Grids background to "transparent", but it did not work. When I set background to "white" and set opacity to 0.01 it is HitTestVisible! Maybe someone has an idea?Garik
There is a small gap on the bottom when the window is maximized, where the popup window does not follow correctly. How do I go about fixing this?Garik
This doesn't work well with the dwm animation when restoring the window from a minimized state. The ellipse appears immediately while the main window is still animating. Is there a solution for this?Snappish
As of 2021, still seems to be the most reliable solutionRatan
C
4

After alot of testing the different solutions:

  1. Microsoft.DwayneNeed (https://microsoftdwayneneed.codeplex.com/)

    Pros:
    • Works well as far as i can tell (Have not tested it that much but it looked like it)
    • The performance-hit seems to be pritty low.

    Cons:
    • Relativly big library.

  2. AirspaceFixer (https://github.com/chris84948/AirspaceFixer)

    Pros:
    • Works well (Have not tested it that much but it looked like it)

    Cons:
    • There is a small performance hit. (Bigger than with Microsoft.DwayneNeed as far as i can tell)

  3. AirspacePopup from Fredrik Hedblad (https://mcmap.net/q/437487/-can-i-overlay-a-wpf-window-on-top-of-another)

    Pros:
    • Simple Code. Mostly :D

    Cons:
    • On fullscreen a part of the bottom is missing.
    • Multimonitor-problems: When moving from one Window to another the overlay isnt completly over the winformshostwindow.

For me the best solution was to use Microsoft.DwayneNeed. (I used it to get CefSharp-Winforms into a WPF-application)

Because geting it to work isnt straight forward her is a small tutorial:

  1. Go to https://microsoftdwayneneed.codeplex.com/SourceControl/latest
  2. Click download.
  3. Open Microsoft.DwayneNeed.sln
  4. Compile it.
  5. Reference Microsoft.DwayneNeed.dll in the created Debug or Releas folder.
  6. Add
    xmlns:interop="clr-namespace:Microsoft.DwayneNeed.Interop;assembly=Microsoft.DwayneNeed
    to your Window.
  7. Add
    <interop:AirspaceDecorator AirspaceMode="Redirect" Background="White" IsInputRedirectionEnabled="True" IsOutputRedirectionEnabled="True">
    <WindowsFormsHost x:Name="windowsFormsHost1" Visibility="Visible" />
    </interop:AirspaceDecorator>
    to your Grid.

Example:

<Window x:Class="Toll.MainWindow"
		xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
		xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
		xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
		xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
		xmlns:interop="clr-namespace:Microsoft.DwayneNeed.Interop;assembly=Microsoft.DwayneNeed"
		xmlns:local="clr-namespace:Toll"
		mc:Ignorable="d"
		Title="MainWindow" Width="1500" Height="800" Closing="Window_Closing">
	<Grid Name="root">
		<interop:AirspaceDecorator AirspaceMode="Redirect"
						   Background="White"
						   IsInputRedirectionEnabled="True"
						   IsOutputRedirectionEnabled="True">
			<WindowsFormsHost x:Name="windowsFormsHost1" Visibility="Visible" />
		</interop:AirspaceDecorator>
		
		<Ellipse Width="100" Height="100" Fill="Green" Margin="100"/>
	</Grid>
</Window>
Carl answered 29/4, 2017 at 11:50 Comment(4)
Doesn't work for me: System.NullReferenceException Object reference not set to an instance of an object. at Microsoft.DwayneNeed.Interop.RedirectedHwndHost.UpdateOutputRedirection() at Microsoft.DwayneNeed.Interop.RedirectedHwndHost.<.ctor>b__31_2(Object e, EventArgs a) at System.Windows.Threading.DispatcherTimer.FireTick(Object unused) at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs) ....Briton
All works good and simple with Microsoft.DwayneNeed. BUT!!! in my case very important feature inside WinForm control is the ability to drag&drop content, and unfortunately it stops to work after decorating WindowsFormsHost with AirspaceDecorator. I wasn't able to track down and fix that problem:(Thetis
Using the Microsoft.DwayneNeed there is a huge problem .... when I have focus on an TextBox on a Winform and I press on lets say the chrome (or any other program) and back again on the TextBox on the Winform when I start typing it types on the other program that I clicked before, the focus is on the TextBox ... :/ any one did have this problem?!Plumlee
@Briton I got the same issue but I have to edit the library source code a bit. If you want to do this, navigate to Microsoft.DwayneNeed/Interop/RedirectedWindow.cs, then go to line 165 and edit _interopBitmap.Invalidate(); into _interopBitmap?.Invalidate();. This seems to be a mindless hack but this thing works and I still don't encounter any issue so far.Uninstructed
S
0

You could do the "overlaying" part in the WindowsFormsHost. So there you could have a child element which should be on top of other content in the hosted element.

Salty answered 22/5, 2011 at 12:26 Comment(1)
WPF doesn't appear to let you layer anything on top of the WindowsFormsHost - so I don't think this will work.Gregorio
P
0

In a typical MVP application using the CAL or Event aggregator, you might have to create another window and name it popup or child (ChildWindow.XAML) and have a get method in (ChildWindow.XAML.CS) like

public static ChildWindow Get()
        {
            ChildWindow dialogBox = new ChildWindow();
            return dialogBox;
        } 

Have a property in the maniwindow which can return Childwindow type when in need. like

public bool ShowChildWindow
{
    get
    {
        return PopUpDialog.Get().ShowDialog().GetValueOrDefault();
    }
}

Hope this helps,

Portray answered 22/5, 2011 at 12:29 Comment(0)
U
0

I'd suggest using the MahApps library.

I've been struggling with this issue for some time and found the MahApps library fixed the airspace problem without any configuration on my part. For more information see their FAQ on this:

https://github.com/MahApps/MahApps.Metro/wiki/FAQ#1-why-is-so-and-so-winforms-control-invisible-or-not-rendering-why-is-the-webbrowser-or-other-control-covering-my-flyout-or-another-control-airspace

Underhill answered 26/7, 2016 at 18:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.