Popup 解决置顶显示问题
前言
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 解决置顶显示问题的更多相关文章
- mfc实现对话框全屏置顶显示
一.MFC让对话框窗口始终在最前 方法一:在对话框的属性中,把SystemModal设置为True. 二.全屏显示 在CDialog7::OnInitDialog()中加入: 先取得分辨率, int ...
- jquery动态创建元素 div元素随垂直滚动条位置变化置顶显示
刚打开页面效果 拖动滑动条之后效果 页面代码 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" & ...
- 解决Popup StayOpen=true时,永远置顶的问题
Popup设置了StayOpen=true时,会置顶显示. 如弹出了Popup后,打开QQ窗口,Popup显示在QQ聊天界面之上. 怎么解决问题? 获取绑定UserControl所在的窗口,窗口层级变 ...
- WPF Popup 置顶问题
原文 WPF Popup 置顶问题 问题: 使用wpf的popup,当在popup中弹出MessageBox或者打开对话框的时候,popup总是置顶,并遮住MessageBox或对话框. 解决: 写如 ...
- win8.1系统下,点击一个窗口,【当前活动窗口】该窗口无法置顶
两个或多个窗口同时显示在桌面的时候,点击下一层的窗口,无法置顶显示,无论怎么点击,还是隐藏在原置顶窗口的后面,只能手动把原置顶窗口最小化后,才能看到.例如,A窗口现在置顶,B窗口在A的后面,露出来一部 ...
- C# WPF 一直保持多个Topmost窗体的置顶顺序
原文:C# WPF 一直保持多个Topmost窗体的置顶顺序 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/m0_37862405/article/ ...
- [Winform]检测exe是否已经运行,并将其置顶
摘要 在很多pc应用中,基本上都需要有这样的判断,保证在一个终端只运行一个winform的client.并且如果最小化了,用户再次双击桌面图标的时候,将client置顶显示. 解决方案 需要使用win ...
- python tkinter窗口弹出置顶的方法
加上下面两句即可实现root窗口的置顶显示,可以用于某些程序的消息提示,能够弹出到桌面显示 root = Tk() root.wm_attributes('-topmost',1)
- python tkinter窗口置顶
下面两句即可实现root窗口的置顶显示,可以用于某些程序的消息提示,能够弹出到桌面显示 root = Tk()root.wm_attributes('-topmost',1)
随机推荐
- MVC中使用过滤器做权限认证需要注意的地方
最近一项目接近尾声,正准备调试的时候发现一条原本不应该执行的SQL语句执行报错了,本就不应该执行,所以当然也就出错,找了一下,问题如下. 我这里是直接重写OnActionExecuting方法来达到目 ...
- tcp 面向连接
TCP通信时通过三次握手建立连接,这个连接不是虚拟链路,每个IP报文是要寻址,通过路由转发的 那建立的这个连接能够起什么作用啊,感觉建立这个连接和不建立这个连接的效果是一样的啊!因为除去可靠性等机制, ...
- python沙箱逃逸的几道题
第一道 from __future__ import print_function print("Welcome to my Python sandbox! Enter commands b ...
- [19/04/24-星期三] GOF23_创建型模式(建造者模式、原型模式)
一.建造者模式 本质:分离了对象子组件的单独构造(由Builder负责)和装配的分离(由Director负责),从而可以构建出复杂的对象,这个模式适用于:某个对象的构建过程十分复杂 好处:由于构建和装 ...
- I、Q信号是如何产生的,I、Q信号复用的作用
接收机在中频部分实现模数变换和采样,采样后的信号和数字域的同频相乘,就可以得到基带的I.Q分量.在无线接口传输时,每一种使用特定的载波频率.码(扩频码和扰码)以及载波相对相位(I或Q)的信道都可以理解 ...
- PAT——1050. 螺旋矩阵
本题要求将给定的N个正整数按非递增的顺序,填入“螺旋矩阵”.所谓“螺旋矩阵”,是指从左上角第1个格子开始,按顺时针螺旋方向填充.要求矩阵的规模为m行n列,满足条件:m*n等于N:m>=n:且m- ...
- Angular动态表单生成(三)
ng-dynamic-forms实践篇(上) 定个小目标 先来定个小目标吧,我们要实现的效果: 动态生成一个表单,里面的字段如下: 字段名称 字段类型 验证 备注 姓名 text 必填,长度小于15 ...
- ASP.NET Core MVC中Controller的Action如何直接使用Response.Body的Stream流输出数据
在ASP.NET Core MVC中,我们有时候需要在Controller的Action中直接输出数据到Response.Body这个Stream流中,例如如果我们要输出一个很大的文件到客户端浏览器让 ...
- JavaScript编写简单的增加与减少元素
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- git提交代码到码云
日常代码一般提交到github比较多,但我还是钟爱马爸爸,没错就是码云. 码云是中文版的代码托管的网站,不存在打开网速问题,使用也蛮方便的,日常自己保存托管代码已经足够,平时使用git提交代码到码云是 ...