解决Popup StayOpen=true时,永远置顶的问题
前言
Popup显示时会置顶显示。尤其是 Popup设置了StayOpen=true时,会一直置顶显示,问题更明显。
置顶显示问题现象:

解决方案
怎么解决问题?
获取绑定UserControl所在的窗口,窗口层级变化时,通知更新当前Popup的Tostmost属性。
1. 添加附加属性
在属性变更中,监听Loaded/UnLoaded事件,在加载后处理相应的逻辑。
private static readonly DependencyProperty TopmostInCurrentWindowProperty = DependencyProperty.RegisterAttached("TopmostInCurrentWindow",
typeof(bool), typeof(Popup), new FrameworkPropertyMetadata(false, OnTopmostInCurrentWindowChanged));
public static bool GetTopmostInCurrentWindow(DependencyObject obj)
{
return (bool)obj.GetValue(TopmostInCurrentWindowProperty);
}
public static void SetTopmostInCurrentWindow(DependencyObject obj, bool value)
{
obj.SetValue(TopmostInCurrentWindowProperty, value);
}
private static void OnTopmostInCurrentWindowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Popup popup)
{
_popup = popup;
popup.Loaded -= OnPopupLoaded;
popup.Unloaded -= OnPopupUnloaded;
if ((bool)e.NewValue)
{
popup.Loaded += OnPopupLoaded;
popup.Unloaded += OnPopupUnloaded;
}
}
}
2. 添加事件监听
- 在Popup.Loaded事件中,监听Popup所在窗口的唤醒事件。同时,Unloaded事件中注销所在窗口的事件监听。
- 在窗口唤醒事件监听逻辑中,设置当前popup选择性的置顶显示
- 添加Popup的MouseDown事件监听,点击Popup的内容后,Popup置顶显示、窗口层级也发相应的变化。
static void OnPopupLoaded(object sender, RoutedEventArgs e)
{
if (!(sender is Popup popup))
return; popup.Child?.AddHandler(UIElement.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(OnChildPreviewMouseLeftButtonDown), true); _parentWindow = Window.GetWindow(popup);
if (_parentWindow == null)
return; _parentWindow.Activated -= OnParentWindowActivated;
_parentWindow.Deactivated -= OnParentWindowDeactivated;
_parentWindow.Activated += OnParentWindowActivated;
_parentWindow.Deactivated += OnParentWindowDeactivated;
} static void OnPopupUnloaded(object sender, RoutedEventArgs e)
{
if (_parentWindow == null)
return;
_parentWindow.Activated -= OnParentWindowActivated;
_parentWindow.Deactivated -= OnParentWindowDeactivated;
} private static void OnParentWindowActivated(object sender, EventArgs e)
{
SetTopmostState(true);
} private static void OnParentWindowDeactivated(object sender, EventArgs e)
{
//Parent Window Deactivated
if (IsTopmost == false)
{
SetTopmostState(IsTopmost);
}
} static void OnChildPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
SetTopmostState(true); if (!_parentWindow.IsActive && IsTopmost == false)
{
_parentWindow.Activate();
}
}
3. 选择性置顶显示
- 记录/设置当前Popup的置顶显示状态
- 选择性置顶显示-可以显示在最顶层,也可以只显示在当前窗口的上层。
private static bool IsTopmost
{
get => _isTopmost;
set
{
_isTopmost = value;
SetTopmostState(value);
}
} private static void SetTopmostState(bool isTop)
{
if (_appliedTopMost.HasValue && _appliedTopMost == isTop)
{
return;
} if (_popup?.Child == null)
return; var hwndSource = (PresentationSource.FromVisual(_popup.Child)) as HwndSource; if (hwndSource == null)
return;
var hwnd = hwndSource.Handle; RECT rect; if (!GetWindowRect(hwnd, out rect))
return; if (isTop)
{
SetWindowPos(hwnd, HWND_TOPMOST, rect.Left, rect.Top, (int)_popup.Width, (int)_popup.Height, TOPMOST_FLAGS);
}
else
{
// 重新激活Topmost,需要bottom->top->notop
SetWindowPos(hwnd, HWND_BOTTOM, rect.Left, rect.Top, (int)_popup.Width, (int)_popup.Height, TOPMOST_FLAGS);
SetWindowPos(hwnd, HWND_TOP, rect.Left, rect.Top, (int)_popup.Width, (int)_popup.Height, TOPMOST_FLAGS);
SetWindowPos(hwnd, HWND_NOTOPMOST, rect.Left, rect.Top, (int)_popup.Width, (int)_popup.Height, TOPMOST_FLAGS);
} _appliedTopMost = isTop;
}
以下是窗口消息处理、私有字段:
通过user32.dll的GetWindowRect和SetWindowPos函数,处理Popup层级问题
#region 窗口消息
[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(-);
static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-);
static readonly IntPtr HWND_TOP = new IntPtr();
static readonly IntPtr HWND_BOTTOM = new IntPtr();
private const UInt32 SWP_NOSIZE = 0x0001;
const UInt32 SWP_NOMOVE = 0x0002;
const UInt32 SWP_NOREDRAW = 0x0008;
const UInt32 SWP_NOACTIVATE = 0x0010;
const UInt32 SWP_NOOWNERZORDER = 0x0200;
const UInt32 SWP_NOSENDCHANGING = 0x0400;
//很重要,窗口切换等需要将popup显示层级重新刷新
const UInt32 TOPMOST_FLAGS =
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSENDCHANGING;
#endregion
#region private fileds
private static bool? _appliedTopMost;
private static bool _alreadyLoaded;
private static Window _parentWindow;
private static Popup _popup;
private static bool _isTopmost;
#endregion
下载 此Demo
注:也可以通过自定义用户控件Popup实现,逻辑一样:
/// <summary>
/// 解决StayOpen=true时,永远置顶的问题
/// </summary>
public class MyPopup : Popup
{
public static readonly DependencyProperty IsTopmostProperty = DependencyProperty.Register("IsTopmost", typeof(bool), typeof(MyPopup), new FrameworkPropertyMetadata(false, OnIsTopmostChanged)); private bool? _appliedTopMost;
private bool _alreadyLoaded;
private Window _parentWindow; public bool IsTopmost
{
get { return (bool)GetValue(IsTopmostProperty); }
set { SetValue(IsTopmostProperty, value); }
} /// <summary>
/// ctor
/// </summary>
public MyPopup()
{
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)
{
SetTopmostState(true);
} void OnParentWindowDeactivated(object sender, EventArgs e)
{
Debug.WriteLine("Parent Window Deactivated"); if (IsTopmost == false)
{
SetTopmostState(IsTopmost);
}
} void OnChildPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{ SetTopmostState(true); if (!_parentWindow.IsActive && IsTopmost == false)
{
_parentWindow.Activate();
}
} private static void OnIsTopmostChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var thisobj = (MyPopup)obj; thisobj.SetTopmostState(thisobj.IsTopmost);
} protected override void OnOpened(EventArgs e)
{
SetTopmostState(IsTopmost);
base.OnOpened(e);
} private void SetTopmostState(bool isTop)
{
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; if (isTop)
{
SetWindowPos(hwnd, HWND_TOPMOST, rect.Left, rect.Top, (int)Width, (int)Height, TOPMOST_FLAGS);
}
else
{
// 重新激活Topmost,需要bottom->top->notop
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;
} [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(-);
static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-);
static readonly IntPtr HWND_TOP = new IntPtr();
static readonly IntPtr HWND_BOTTOM = new IntPtr(); 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 */ //很重要,窗口切换等需要将popup显示层级重新刷新
const UInt32 TOPMOST_FLAGS =
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSENDCHANGING;
}
解决方案总结
添加如上附加属性或者用户控件Popup后,能解决置顶问题,Popup只会出现在所在窗口上层。
截图如下:

解决Popup StayOpen=true时,永远置顶的问题的更多相关文章
- css解决无论页面长短footer永远置底
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- Popup 解决置顶显示问题
原文:Popup 解决置顶显示问题 前言 Popup显示时会置顶显示.尤其是 Popup设置了StayOpen=true时,会一直置顶显示,问题更明显. 置顶显示问题现象: 解决方案 怎么解决问题? ...
- WPF popup置顶
在程序写一个popup发现他会在置顶,在网上找了两大神代码http://www.cnblogs.com/Leaco/p/3164394.html http://blog.csdn.net/baijin ...
- 解决popup不随着window一起移动的问题
原文:解决popup不随着window一起移动的问题 当我们设置Popup的StayOpen="True"时,会发现移动窗体或者改变窗体的Size的时候,Popup并不会跟随着一起 ...
- 窗口置顶 - 仿TopWind
前置学习:低级鼠标hook,获得鼠标状态. 这个在原来获得鼠标状态的基础上,加上一个事件处理即可. TopWind就是一个可以置顶窗口的文件,避免复制粘贴的时候的来回切换(大窗口与小窗口),算是一个实 ...
- vc 使窗口置顶 在最前面
bool SetWindowTop(CWnd* pWnd){ if(!pWnd) { return false; } if(pWnd->GetExStyle()&WS_EX_TOPM ...
- 2018-5-28-WPF-popup置顶
title author date CreateTime categories WPF popup置顶 lindexi 2018-05-28 09:58:53 +0800 2018-2-13 17:2 ...
- 让WPF的Popup不总置顶的解决方案
使用WPF的Popup的时候会发现有一个问题,它总是会置顶,只要Popup的StayOpen不设置为False,它就一直呆在最顶端,挡住其他的窗口. 解决方案是继承Popup重新定义控件PopupEx ...
- WPF Popup 置顶问题
原文 WPF Popup 置顶问题 问题: 使用wpf的popup,当在popup中弹出MessageBox或者打开对话框的时候,popup总是置顶,并遮住MessageBox或对话框. 解决: 写如 ...
随机推荐
- 命名参数名(含*args , * *kw的区别)
要限制关键字参数的名字,就可以用命名关键字参数 # coding=utf-8 # 命名关键字参数需要一个特殊分隔符*,*后面的参数被视为命名关键字参数.调用方式如下 def person(name, ...
- 在Linux的Terminal中显示文本文件特定行的内容
假设要操纵的文本文件的文件名是 textFile现在想做的事情是在不以编辑模式打开文件的情况下在终端直接提取并输出指定文本文件的指定行的内容 在终端提取指定文本文件的指定行的内容 Tool Comma ...
- 查看centos版本及32还是64位
1.[root@mini1 ~]# cat /etc/issue 2.[root@mini1 ~]# cat /etc/redhat-release 查看位数: [root@mini1 ~]# g ...
- [BZOJ4011][HNOI2015] 落忆枫音(学习笔记) - 拓扑+DP
其实就是贴一下防止自己忘了,毕竟看了题解才做出来 Orz PoPoQQQ 原文链接 Description 背景太长了 给定一个DAG,和一对点(x, y), 在DAG中由x到y连一条有向边,求生成树 ...
- python基础——内置函数
python基础--内置函数 一.内置函数(python3.x) 内置参数详解官方文档: https://docs.python.org/3/library/functions.html?highl ...
- Linux命令基础
开启Linux操作系统,root用户登录GNOME图形界面,如下图: 切换到虚拟终端2,使用普通用户身份登录,查看系统提示符,如下图: 使用命令退出虚拟终端2上的登录用户,如下图: 切换到虚拟终端5, ...
- find()用法
>>> str = '编程改变世界'>>> str.find('编')0>>> str.find('程')1>>> str.fi ...
- 用委托(Delegate)的BeginInvoke和EndInvoke方法操作线程
让我们首先了解下什么时候用到C#异步调用: .NET Framework 允许您C#异步调用任何方法.定义与您需要调用的方法具有相同签名的委托:公共语言运行库将自动为该委托定义具有适当签名的Begin ...
- Java基础学习(五)-- Java中常用的工具类、枚举、Java中的单例模式之详解
Java中的常用类 1.Math : 位于java.lang包中 (1)Math.PI:返回一个最接近圆周率的 (2)Math.abs(-10):返回一个数的绝对值 (3)Math.cbrt(27): ...
- 前端之旅HTML与CSS篇之a便签中放入其他块元素会撑大高度的原因
原因:a元素下有一个匿名文本,这个文本外有一个匿名行级盒子,它有的默认vertical-align是baseline的,而且往往因为上文line-height的影响,使它有个line-height,从 ...