public
Last active

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

  • Download Gist
NonTopmostPopup.cs
C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
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
}

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.

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.

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

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

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.