-
-
Save jkhk/3053290 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. Specific for popup and Windows Forms hos
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Runtime.InteropServices; | |
using System.Windows; | |
using System.Windows.Controls.Primitives; | |
using System.Windows.Forms; | |
using System.Windows.Forms.Integration; | |
using System.Windows.Input; | |
using System.Windows.Interop; | |
namespace Honeywell.Desktop.TimeControl | |
{ | |
public class PopupEx : Popup | |
{ | |
public static readonly DependencyProperty IsTopmostProperty = | |
DependencyProperty.Register("IsTopmost", | |
typeof(bool), | |
typeof(PopupEx), | |
new FrameworkPropertyMetadata(false, OnIsTopmostChanged)); | |
public bool IsTopmost | |
{ | |
get { return (bool)GetValue(IsTopmostProperty); } | |
set { SetValue(IsTopmostProperty, value); } | |
} | |
private bool? _appliedTopMost; | |
private bool _alreadyLoaded; | |
private Form _parentForm; | |
public PopupEx() | |
{ | |
Loaded += PopupNonTopmostLoaded; | |
Unloaded += PopupNonTopmostUnloaded; | |
} | |
private void PopupNonTopmostUnloaded(object sender, RoutedEventArgs e) | |
{ | |
if (_parentForm == null) | |
{ | |
return; | |
} | |
_parentForm.Activated -= OnParentWindowActivated; | |
_parentForm.Deactivate -= OnParentWindowDeactivated; | |
} | |
void PopupNonTopmostLoaded(object sender, RoutedEventArgs e) | |
{ | |
if (_alreadyLoaded) | |
{ | |
return; | |
} | |
_alreadyLoaded = true; | |
if (Child != null) | |
{ | |
Child.AddHandler(PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(ChildPreviewMouseLeftButtonDown), true); | |
} | |
var wpfHandle = PresentationSource.FromVisual(this) as HwndSource; | |
if (wpfHandle == null) | |
{ | |
return; | |
} | |
var elementHost = Control.FromChildHandle(wpfHandle.Handle) as ElementHost; | |
if (elementHost == null) | |
{ | |
return; | |
} | |
_parentForm = elementHost.FindForm(); | |
if (_parentForm == null) | |
{ | |
return; | |
} | |
_parentForm.Activated += OnParentWindowActivated; | |
_parentForm.Deactivate += OnParentWindowDeactivated; | |
} | |
private void OnParentWindowDeactivated(object sender, EventArgs e) | |
{ | |
if (IsTopmost == false) | |
{ | |
SetTopmostState(IsTopmost); | |
} | |
} | |
private void OnParentWindowActivated(object sender, EventArgs e) | |
{ | |
SetTopmostState(true); | |
} | |
void ChildPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) | |
{ | |
SetTopmostState(true); | |
if (!_parentForm.Focused && IsTopmost == false) | |
{ | |
_parentForm.Activate(); | |
} | |
} | |
private static void OnIsTopmostChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) | |
{ | |
var popup = obj as PopupEx; | |
if (popup != null) | |
{ | |
popup.SetTopmostState(popup.IsTopmost); | |
} | |
} | |
protected override void OnOpened(EventArgs e) | |
{ | |
_appliedTopMost = null; | |
SetTopmostState(IsTopmost); | |
base.OnOpened(e); | |
} | |
private void SetTopmostState(bool isTop) | |
{ | |
if (_appliedTopMost.HasValue && _appliedTopMost == isTop) | |
{ | |
// Don’t apply state if it’s the same as incoming state | |
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; | |
} | |
if (isTop) | |
{ | |
SetWindowPos(hwnd, HwndTopmost, rect.Left, rect.Top, (int)Width, (int)Height, TopmostFlags); | |
} | |
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, HwndBottom, rect.Left, rect.Top, (int)Width, (int)Height, TopmostFlags); | |
SetWindowPos(hwnd, HwndTop, rect.Left, rect.Top, (int)Width, (int)Height, TopmostFlags); | |
SetWindowPos(hwnd, HwndNotopmost, rect.Left, rect.Top, (int)Width, (int)Height, TopmostFlags ); | |
} | |
_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 HwndTopmost = new IntPtr(-1); | |
static readonly IntPtr HwndNotopmost = new IntPtr(-2); | |
static readonly IntPtr HwndTop = new IntPtr(0); | |
static readonly IntPtr HwndBottom = new IntPtr(1); | |
const UInt32 SwpNosize = 0x0001; | |
const UInt32 SwpNomove = 0x0002; | |
const UInt32 SwpNozorder = 0x0004; | |
const UInt32 SwpNoredraw = 0x0008; | |
const UInt32 SwpNoactivate = 0x0010; | |
const UInt32 SwpFramechanged = 0x0020; /* The frame changed: send WM_NCCALCSIZE */ | |
const UInt32 SwpShowwindow = 0x0040; | |
const UInt32 SwpHidewindow = 0x0080; | |
const UInt32 SwpNocopybits = 0x0100; | |
const UInt32 SwpNoownerzorder = 0x0200; /* Don’t do owner Z ordering */ | |
const UInt32 SwpNosendchanging = 0x0400; /* Don’t send WM_WINDOWPOSCHANGING */ | |
const UInt32 TopmostFlags = SwpNoactivate | SwpNoownerzorder | SwpNosize | SwpNomove | SwpNoredraw | SwpNosendchanging; | |
#endregion | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment