解决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或对话框. 解决: 写如 ...
随机推荐
- SpringBoot单元测试中的事务和Session
1.Springboot中使用junit编写单元测试,并且测试结果不影响数据库. 2.
- 新概念英语(1-61)A bad cold
新概念英语(1-61)A bad cold What is good news for Jimmy? A:Where's Jimmy? B:He's in bed. A:What's the matt ...
- linux centos6.8 下安装mysql 步骤
安装环境:vmware12.centos6.8.centos中配置阿里云数据元 1.下载mysql 运行: sudo yum -y install mysql-server 如果下载失败,可以卸载重新 ...
- bad interpreter:No such file or directory 解决方法
今天在执行一个从网上考下来的脚本的时候,出现了下面的错误: Linux下面一个脚本死活也运行不了, 我检查了数遍,不可能有错. 提示:bad interpreter:No such file or d ...
- leetcode算法:Trim a Binar Search Tree
Given a binary search tree and the lowest and highest boundaries as L and R, trim the tree so that a ...
- WPF利用附加属性修改ShowGridLines效果
1.思路主要代码 wpf的gridline原本效果是虚线类型的.有时候需要设计成表格形式的,因此有了用附加属性来自动绘制边框线的想法. 思路:绘制Line并添加到grid的children里,但效果并 ...
- linux远程传输
scp scp 命令是 SSH中最方便有用的命令了,scp就是secure copy,是用来进行远程文件拷贝的.数据传输使用 ssh,并且和ssh 使用相同的认证方式,提供相同的安全保证. 与rcp ...
- view-xpath
https://addons.mozilla.org/en-US/firefox/ WebDriver Element Locator
- 1025InnoDB log file 设置多大合适
转自 http://blog.csdn.net/langkeziju/article/details/51094289 数据库的东西,往往一个参数就牵涉N多知识点.所以简单的说一下.大家都知道inno ...
- JS实现数组去重方法总结(六种方法)
方法一: 双层循环,外层循环元素,内层循环时比较值 如果有相同的值则跳过,不相同则push进数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Array. ...