前言

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时,永远置顶的问题的更多相关文章

  1. css解决无论页面长短footer永远置底

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  2. Popup 解决置顶显示问题

    原文:Popup 解决置顶显示问题 前言 Popup显示时会置顶显示.尤其是 Popup设置了StayOpen=true时,会一直置顶显示,问题更明显. 置顶显示问题现象: 解决方案 怎么解决问题? ...

  3. WPF popup置顶

    在程序写一个popup发现他会在置顶,在网上找了两大神代码http://www.cnblogs.com/Leaco/p/3164394.html http://blog.csdn.net/baijin ...

  4. 解决popup不随着window一起移动的问题

    原文:解决popup不随着window一起移动的问题 当我们设置Popup的StayOpen="True"时,会发现移动窗体或者改变窗体的Size的时候,Popup并不会跟随着一起 ...

  5. 窗口置顶 - 仿TopWind

    前置学习:低级鼠标hook,获得鼠标状态. 这个在原来获得鼠标状态的基础上,加上一个事件处理即可. TopWind就是一个可以置顶窗口的文件,避免复制粘贴的时候的来回切换(大窗口与小窗口),算是一个实 ...

  6. vc 使窗口置顶 在最前面

    bool SetWindowTop(CWnd* pWnd){ if(!pWnd) {   return false; } if(pWnd->GetExStyle()&WS_EX_TOPM ...

  7. 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 ...

  8. 让WPF的Popup不总置顶的解决方案

    使用WPF的Popup的时候会发现有一个问题,它总是会置顶,只要Popup的StayOpen不设置为False,它就一直呆在最顶端,挡住其他的窗口. 解决方案是继承Popup重新定义控件PopupEx ...

  9. WPF Popup 置顶问题

    原文 WPF Popup 置顶问题 问题: 使用wpf的popup,当在popup中弹出MessageBox或者打开对话框的时候,popup总是置顶,并遮住MessageBox或对话框. 解决: 写如 ...

随机推荐

  1. SpringBoot应用的集成测试

    一.概念和定义 进行软件开发的时候,我们会写很多代码,不过,再过六个月(甚至一年以上)你知道自己的代码怎么运作么?通过测试(单元测试.集成测试.接口测试)可以保证系统的可维护性,当我们修改了某些代码时 ...

  2. 新概念英语(1-51)A pleasant climate

    新概念英语(1-51)A pleasant climate Does it ever snow in Greece? A:Where do you come from? B:I come from G ...

  3. 解决Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/Student_recruit]]

    查看web.xml文件的书写,特别注意路径与命名一致

  4. Python scrapy框架

    Scrapy Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架. 其可以应用在数据挖掘,信息处理或存储历史数据等一系列的程序中.其最初是为了页面抓取 (更确切来说, 网络抓取 )所设 ...

  5. 浅析开源数据库MySQL架构

    数据库是所有应用系统的核心,故保证数据库稳定.高效.安全地运行是所有企业日常工作的重中之重.数据库系统一旦出现问题无法提供服务,有可能导致整个系统都无法继续工作.所以,一个成功的数据库架构在高可用设计 ...

  6. 泡菜的使用pickle

    如何实现对列表和字典的写入? 需要将对象流式化,实现对象持久存储,这里用到的事pickle 一.制作泡菜 >>> list1 = ['我',123,3.14,['aaa',1]] # ...

  7. python的切片操作

    切片操作符是序列名后跟一个方括号,方括号中有一对可选的数字,并用冒号分割.注意这与你使用的索引操作符十分相似.记住数是可选的,而冒号是必须的. 切片操作符中的第一个数(冒号之前)表示切片开始的位置,第 ...

  8. C#之FTP上传下载(二)

    这个类几乎包含了对FTP常用的方法,有不对的地方,欢迎批评指正 public class FtpClient { #region 构造函数 /// <summary> /// 创建FTP工 ...

  9. ORACLE 行转列 案例解析

    -- 创建 国家城市信息 临时表                  WITH T_NATION_CITY_INFO AS(        SELECT '北京'   AS CITY,'中国' AS N ...

  10. java中的内部类概念

    内部类和外部类可以互相访问私有属性 1.普通内部类实例化方式 外部类.内部类 对象 = new 外部类().new 内部类(); class Outer{//外部类 private String ms ...