Chris Cavanagh’s Blog

Non-Topmost WPF Popup

August 13, 2008 · 9 Comments

The WPF Popup control is always “topmost” over other application windows.  If you’re happy with a dirty workaround to remove the Topmost state, you can derive your own Popup control similar to this:

public class PopupNonTopmost : Popup
{
    protected override void OnOpened( EventArgs e )
    {
        var hwnd = ( (HwndSource)PresentationSource.FromVisual( this.Child ) ).Handle;
        RECT rect;

        if ( GetWindowRect( hwnd, out rect ) )
        {
            SetWindowPos( hwnd, -2, rect.Left, rect.Top, (int)this.Width, (int)this.Height, 0 );
        }
    }

    #region P/Invoke imports & definitions

    [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", EntryPoint = "SetWindowPos" )]
    private static extern int SetWindowPos( IntPtr hWnd, int hwndInsertAfter, int x, int y, int cx, int cy, int wFlags );

    #endregion
}

This is based on code by digitalnetbizz, with the addition of calling GetWindowRect for the current window position.  Obviously it’d be nicer if Popup had a Topmost DependencyProperty you could use.  Here’s my attempt which borrows the Window element’s TopmostProperty:

public class PopupNonTopmost : Popup
{
    public static DependencyProperty TopmostProperty = Window.TopmostProperty.AddOwner(
        typeof( PopupNonTopmost ),
        new FrameworkPropertyMetadata( false, OnTopmostChanged ) );

    public bool Topmost
    {
        get { return (bool)GetValue( TopmostProperty ); }
        set { SetValue( TopmostProperty, value ); }
    }

    private static void OnTopmostChanged( DependencyObject obj, DependencyPropertyChangedEventArgs e )
    {
        ( obj as PopupNonTopmost ).UpdateWindow();
    }

    protected override void OnOpened( EventArgs e )
    {
        UpdateWindow();
    }

    private void UpdateWindow()
    {
        var hwnd = ( (HwndSource)PresentationSource.FromVisual( this.Child ) ).Handle;
        RECT rect;

        if ( GetWindowRect( hwnd, out rect ) )
        {
            SetWindowPos( hwnd, Topmost ? -1 : -2, rect.Left, rect.Top, (int)this.Width, (int)this.Height, 0 );
        }
    }

    #region P/Invoke imports & definitions

    [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", EntryPoint = "SetWindowPos" )]
    private static extern int SetWindowPos( IntPtr hWnd, int hwndInsertAfter, int x, int y, int cx, int cy, int wFlags );

    #endregion
}

I’ve not tested this extensively (it compiled :) ), so just let me know if it’s not happy!  Likewise if I’ve missed a glaringly obvious shiny new Topmost property in WPF, let me know :)

Categories: .NET

9 responses so far ↓

  • Dave Capps // October 17, 2008 at 5:18 am | Reply

    The code works well … except … when you set the popup to non-topmost it resets all the child elements to non-topmost as well. Which wouldn’t seem like a problem, but Combobox controls rely on having a topmost popup for when you drop them down, so any comboboxs inside your popup break.

    I’ve tried iterating through all visual children of the popup and setting combobox popups to be topmost again, but when I do it seems to set the owner popup back to be topmost. Which is really bizarre, I can’t see why that would happen.

    Any ideas…?

  • Chris Cavanagh // October 17, 2008 at 6:36 am | Reply

    Dave – That’s definitely not what I’d expect to happen :) I’ll take a closer look and update here if I find anything…

  • brian // December 22, 2008 at 2:24 pm | Reply

    In your call to SetWindowPos, I think the -1 and -2 should be reversed.

    SetWindowPos( hwnd, Topmost ? -1 : -2,

    should be

    SetWindowPos( hwnd, Topmost ? -2 : -1,

  • Joe Gershgorin // June 14, 2009 at 4:54 pm | Reply

    This solution almost got me there. It traded one problem for another. I have a draggable Popup, and with the above code it’s always under other windows, even if the popup has focus.

    I came up with solution that I’m mostly happy with. To make it ideal I really wish the GotFocus/LostFocus events of the popup control behaved/fired as described.

    My solution, sets the PopUp OnTopMost on leftbuttondown, and sets it back to the istopmost property when another window gets focus. I find out if a another window gets focus by hooking into the PopUp’s parent window deactivated event. To make sure the deactivated event gets fired I first have to activate it on mouse leftbuttondown, which means on the parent window comes to front also (the only possible downside, but no biggie for me).

    The side effect of this solution is that it should also solve Dave’s Combobox issue.

    Here’s my solution:

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

    private bool? _appliedTopMost;

    public bool IsTopmost
    {
    get { return (bool)GetValue(IsTopmostProperty); }
    set { SetValue(IsTopmostProperty, value); }
    }

    private bool _alreadyLoaded;
    public BetterPopup()
    {
    this.Loaded += new RoutedEventHandler(PopupNonTopmost_Loaded);

    }

    private Window _parentWindow;
    void PopupNonTopmost_Loaded(object sender, RoutedEventArgs e)
    {
    if (!_alreadyLoaded)
    {
    _alreadyLoaded = true;

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

    _parentWindow = Window.GetWindow(this);

    if (_parentWindow != null)
    {
    _parentWindow.Activated += new EventHandler(_parentWindow_Activated);
    _parentWindow.Deactivated += new EventHandler(ParentWindow_Deactivated);
    }
    }

    }

    void _parentWindow_Activated(object sender, EventArgs e)
    {
    Console.WriteLine(“Parent Window Activated”);
    SetTopmostState(true);
    }

    void ParentWindow_Deactivated(object sender, EventArgs e)
    {
    Console.WriteLine(“Parent Window Deactivated”);

    if (IsTopmost == false)
    {
    SetTopmostState(IsTopmost);
    }
    }

    void Child_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
    Console.WriteLine(“Child Mouse Left Button Down”);

    SetTopmostState(true);

    if (!_parentWindow.IsActive && IsTopmost == false)
    {
    _parentWindow.Activate();
    Console.WriteLine(“Activating Parent from child Left Button Down”);
    }
    }

    private static void OnIsTopmostChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
    var thisobj = obj as BetterPopup;

    thisobj.SetTopmostState(thisobj.IsTopmost);
    }

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

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

    if (this.Child != null)
    {
    var hwndSource = (PresentationSource.FromVisual(this.Child)) as HwndSource;

    if (hwndSource != null)
    {
    var hwnd = hwndSource.Handle;

    RECT rect;

    if (GetWindowRect(hwnd, out rect))
    {
    Console.WriteLine(“setting z-order ” + isTop);
    if (isTop)
    {
    SetWindowPos(hwnd, HWND_TOPMOST, rect.Left, rect.Top, (int)this.Width, (int)this.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)this.Width, (int)this.Height, TOPMOST_FLAGS);
    SetWindowPos(hwnd, HWND_TOP, rect.Left, rect.Top, (int)this.Width, (int)this.Height, TOPMOST_FLAGS);
    SetWindowPos(hwnd, HWND_NOTOPMOST, rect.Left, rect.Top, (int)this.Width, (int)this.Height, TOPMOST_FLAGS );
    }

    _appliedTopMost = isTop;
    }
    }
    }
    }

    #region P/Invoke imports & definitions

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

    const UInt32 SWP_NOSIZE = 0×0001;
    const UInt32 SWP_NOMOVE = 0×0002;
    const UInt32 SWP_NOZORDER = 0×0004;
    const UInt32 SWP_NOREDRAW = 0×0008;
    const UInt32 SWP_NOACTIVATE = 0×0010;
    const UInt32 SWP_FRAMECHANGED = 0×0020; /* The frame changed: send WM_NCCALCSIZE */
    const UInt32 SWP_SHOWWINDOW = 0×0040;
    const UInt32 SWP_HIDEWINDOW = 0×0080;
    const UInt32 SWP_NOCOPYBITS = 0×0100;
    const UInt32 SWP_NOOWNERZORDER = 0×0200; /* Don’t do owner Z ordering */
    const UInt32 SWP_NOSENDCHANGING = 0×0400; /* Don’t send WM_WINDOWPOSCHANGING */

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

    #endregion
    }

  • Chris Cavanagh // June 14, 2009 at 10:57 pm | Reply

    Joe – Thanks for figuring this out and taking the time to comment! It’s much appreciated :)

  • Varun // June 29, 2009 at 5:17 am | Reply

    This approach works fine however when I set some margin or Corner Radius to my popup window then it shows ugly black border around the popup window.

  • Joe Gershgorin // June 30, 2009 at 3:32 pm | Reply

    Thanks for the original posting Chris.

    Varun, do you have the AllowsTransparency property set to true?:

    http://msdn.microsoft.com/en-us/library/system.windows.controls.primitives.popup.allowstransparency.aspx

  • wpfbabu // September 13, 2009 at 9:48 pm | Reply

    hi Chris,
    i very happy that the above code helps me a lot to make my PopUp non-topmost ,if u don’t mind could you also make a code for the popup minimization in wpf
    where there is no proper answer in any of the websites so far..

  • Chris Cavanagh // September 13, 2009 at 11:23 pm | Reply

    wpfbabu – This page on pinvoke.net might help: http://www.pinvoke.net/default.aspx/user32/ShowWindow.html

    Once you get the window handle…:

    var hwnd = ( (HwndSource)PresentationSource.FromVisual( this.Child ) ).Handle;

    You should be able to call ShowWindow with WindowShowStyle.ShowMinimized:

    [DllImport("user32.dll")]
    static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

    ShowWindow( hwnd, WindowShowStyle.ShowMinimized );

    Hope this helps!

Leave a Comment