Skip to content

Instantly share code, notes, and snippets.

@flq
Created April 5, 2011 08:04
Show Gist options
  • Save flq/903202 to your computer and use it in GitHub Desktop.
Save flq/903202 to your computer and use it in GitHub Desktop.
WPF: Popup that is only topmost with respect to parent window. Taken from the comments at http://chriscavanagh.wordpress.com/2008/08/13/non-topmost-wpf-popup/ (Joe Gershgorin) which was in not easy to digest state
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Interop;
/// <summary>
/// Popup with code to not be the topmost control
/// </summary>
public class NonTopmostPopup : Popup
{
/// <summary>
/// Is Topmost dependency property
/// </summary>
public static readonly DependencyProperty IsTopmostProperty = DependencyProperty.Register("IsTopmost", typeof(bool), typeof(NonTopmostPopup), new FrameworkPropertyMetadata(false, OnIsTopmostChanged));
private bool? _appliedTopMost;
private bool _alreadyLoaded;
private Window _parentWindow;
/// <summary>
/// Get/Set IsTopmost
/// </summary>
public bool IsTopmost
{
get { return (bool)GetValue(IsTopmostProperty); }
set { SetValue(IsTopmostProperty, value); }
}
/// <summary>
/// ctor
/// </summary>
public NonTopmostPopup()
{
Loaded += OnPopupLoaded;
Unloaded += OnPopupUnloaded;
}
void OnPopupLoaded(object sender, RoutedEventArgs e)
{
if (_alreadyLoaded)
return;
_alreadyLoaded = true;
if (Child != null)
{
Child.AddHandler(PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(OnChildPreviewMouseLeftButtonDown), true);
}
_parentWindow = Window.GetWindow(this);
if (_parentWindow == null)
return;
_parentWindow.Activated += OnParentWindowActivated;
_parentWindow.Deactivated += OnParentWindowDeactivated;
}
private void OnPopupUnloaded(object sender, RoutedEventArgs e)
{
if (_parentWindow == null)
return;
_parentWindow.Activated -= OnParentWindowActivated;
_parentWindow.Deactivated -= OnParentWindowDeactivated;
}
void OnParentWindowActivated(object sender, EventArgs e)
{
Debug.WriteLine("Parent Window Activated");
SetTopmostState(true);
}
void OnParentWindowDeactivated(object sender, EventArgs e)
{
Debug.WriteLine("Parent Window Deactivated");
if (IsTopmost == false)
{
SetTopmostState(IsTopmost);
}
}
void OnChildPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("Child Mouse Left Button Down");
SetTopmostState(true);
if (!_parentWindow.IsActive && IsTopmost == false)
{
_parentWindow.Activate();
Debug.WriteLine("Activating Parent from child Left Button Down");
}
}
private static void OnIsTopmostChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var thisobj = (NonTopmostPopup)obj;
thisobj.SetTopmostState(thisobj.IsTopmost);
}
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 (_appliedTopMost.HasValue && _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);
}
_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
}
@gunters63
Copy link

Works nicely, thanks!

I had only one small problem with it. I hide all popups through an attached behavior when the main window gets minimized. After restoring the main window (and the popups with it through an event handled in this behavior) the topmost state of the popups was wrong when i activated another window on top of my app, the popup ended on top of the other window. Adding _appliedTopMost = null as first line in the override of OnOpened fixed the problem.

Converting this NonTopmostPopup to a behaviour would be nice (like originally in Chris blog) though it will probably be a lot more complicated.

@flq
Copy link
Author

flq commented Jul 6, 2011

There aren't any overrides of popup's methods/properties. There are two instance variables belonging to popup but I suppose they may just as well be vars of the behavior.
If a behavior gets attached before the "Loaded" event it should work quite nicely.

@ucci
Copy link

ucci commented Dec 2, 2013

Thanks. It's works for me. your my hero!

@jitendrapathariya
Copy link

superb..thanks a ton..it's working for me

@Ahmedbenss
Copy link

awsome ! thanks for you.

@AlexanderPoschenrieder
Copy link

Excelent, this works.
Thanks.

@ConnectNitish
Copy link

Can any one please share a sample project with WebBrowserControl of WPF if possible ( or any ) for reference of use of class.

@amitiscalling
Copy link

please share the sample project!

@amittkSharma
Copy link

This works awesome, but I am facing a small problem when I drag the parent window the pop up does not move with the parent window. It looks like pop up is not the part of application. Can you please let me know how I can move the popup along with the window?

@VishalDPrajapati
Copy link

But this popup control doesn't set initial focus to itself or any of its child control.how to do it??

@3yuan
Copy link

3yuan commented Mar 14, 2019

That works great, thanks! But would you mind to add a license for this?

@zd0214
Copy link

zd0214 commented Jun 16, 2022

it works!

This works awesome, but I am facing a small problem when I drag the parent window the pop up does not move with the parent window. It looks like pop up is not the part of application. Can you please let me know how I can move the popup along with the window?

_parentWindow.LocationChanged += _parentWindow_LocationChanged; private void _parentWindow_LocationChanged(object sender, System.EventArgs e) { try { var method = typeof(Popup).GetMethod("UpdatePosition", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); if (this.IsOpen) { method.Invoke(this, null); } } catch { return; } }

@flq
Copy link
Author

flq commented Jun 16, 2022

That works great, thanks! But would you mind to add a license for this?

Apache 2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment