Popup 解决置顶显示问题

原文: Popup 解决置顶显示问题

前言

Popup显示时会置顶显示。尤其是 Popup设置了StayOpen=true时,会一直置顶显示,问题更明显。

置顶显示问题现象:

 

解决方案

怎么解决问题?

获取绑定UserControl所在的窗口,窗口层级变化时,通知更新当前Popup的Tostmost属性。

1. 添加附加属性

在属性变更中,监听Loaded/UnLoaded事件,在加载后处理相应的逻辑。

 1         private static readonly DependencyProperty TopmostInCurrentWindowProperty = DependencyProperty.RegisterAttached("TopmostInCurrentWindow",
 2             typeof(bool), typeof(Popup), new FrameworkPropertyMetadata(false, OnTopmostInCurrentWindowChanged));
 3 
 4         public static bool GetTopmostInCurrentWindow(DependencyObject obj)
 5         {
 6             return (bool)obj.GetValue(TopmostInCurrentWindowProperty);
 7         }
 8 
 9         public static void SetTopmostInCurrentWindow(DependencyObject obj, bool value)
10         {
11             obj.SetValue(TopmostInCurrentWindowProperty, value);
12         }
13 
14         private static void OnTopmostInCurrentWindowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
15         {
16             if (d is Popup popup)
17             {
18                 _popup = popup;
19                 popup.Loaded -= OnPopupLoaded;
20                 popup.Unloaded -= OnPopupUnloaded;
21                 if ((bool)e.NewValue)
22                 {
23                     popup.Loaded += OnPopupLoaded;
24                     popup.Unloaded += OnPopupUnloaded;
25                 }
26             }
27         }

2. 添加事件监听

  • 在Popup.Loaded事件中,监听Popup所在窗口的唤醒事件。同时,Unloaded事件中注销所在窗口的事件监听。
  • 在窗口唤醒事件监听逻辑中,设置当前popup选择性的置顶显示
  • 添加Popup的MouseDown事件监听,点击Popup的内容后,Popup置顶显示、窗口层级也发相应的变化。
 1        static void OnPopupLoaded(object sender, RoutedEventArgs e)
 2         {
 3             if (!(sender is Popup popup))
 4                 return;
 5 
 6             popup.Child?.AddHandler(UIElement.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(OnChildPreviewMouseLeftButtonDown), true);
 7 
 8             _parentWindow = Window.GetWindow(popup);
 9             if (_parentWindow == null)
10                 return;
11 
12             _parentWindow.Activated -= OnParentWindowActivated;
13             _parentWindow.Deactivated -= OnParentWindowDeactivated;
14             _parentWindow.Activated += OnParentWindowActivated;
15             _parentWindow.Deactivated += OnParentWindowDeactivated;
16         }
17 
18         static void OnPopupUnloaded(object sender, RoutedEventArgs e)
19         {
20             if (_parentWindow == null)
21                 return;
22             _parentWindow.Activated -= OnParentWindowActivated;
23             _parentWindow.Deactivated -= OnParentWindowDeactivated;
24         }
25 
26         private static void OnParentWindowActivated(object sender, EventArgs e)
27         {
28             SetTopmostState(true);
29         }
30 
31         private static void OnParentWindowDeactivated(object sender, EventArgs e)
32         {
33             //Parent Window Deactivated
34             if (IsTopmost == false)
35             {
36                 SetTopmostState(IsTopmost);
37             }
38         }
39 
40         static void OnChildPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
41         {
42             SetTopmostState(true);
43 
44             if (!_parentWindow.IsActive && IsTopmost == false)
45             {
46                 _parentWindow.Activate();
47             }
48         }

3. 选择性置顶显示

  • 记录/设置当前Popup的置顶显示状态
  • 选择性置顶显示-可以显示在最顶层,也可以只显示在当前窗口的上层。
 1         private static bool IsTopmost
 2         {
 3             get => _isTopmost;
 4             set
 5             {
 6                 _isTopmost = value;
 7                 SetTopmostState(value);
 8             }
 9         }
10 
11         private static void SetTopmostState(bool isTop)
12         {
13             if (_appliedTopMost.HasValue && _appliedTopMost == isTop)
14             {
15                 return;
16             }
17 
18             if (_popup?.Child == null)
19                 return;
20 
21             var hwndSource = (PresentationSource.FromVisual(_popup.Child)) as HwndSource;
22 
23             if (hwndSource == null)
24                 return;
25             var hwnd = hwndSource.Handle;
26 
27             RECT rect;
28 
29             if (!GetWindowRect(hwnd, out rect))
30                 return;
31 
32             if (isTop)
33             {
34                 SetWindowPos(hwnd, HWND_TOPMOST, rect.Left, rect.Top, (int)_popup.Width, (int)_popup.Height, TOPMOST_FLAGS);
35             }
36             else
37             {
38                 // 重新激活Topmost,需要bottom->top->notop
39                 SetWindowPos(hwnd, HWND_BOTTOM, rect.Left, rect.Top, (int)_popup.Width, (int)_popup.Height, TOPMOST_FLAGS);
40                 SetWindowPos(hwnd, HWND_TOP, rect.Left, rect.Top, (int)_popup.Width, (int)_popup.Height, TOPMOST_FLAGS);
41                 SetWindowPos(hwnd, HWND_NOTOPMOST, rect.Left, rect.Top, (int)_popup.Width, (int)_popup.Height, TOPMOST_FLAGS);
42             }
43 
44             _appliedTopMost = isTop;
45         }

以下是窗口消息处理、私有字段:

通过user32.dll的GetWindowRect和SetWindowPos函数,处理Popup层级问题

 1         #region 窗口消息
 2 
 3         [StructLayout(LayoutKind.Sequential)]
 4         public struct RECT
 5 
 6         {
 7             public int Left;
 8             public int Top;
 9             public int Right;
10             public int Bottom;
11         }
12 
13         [DllImport("user32.dll")]
14         [return: MarshalAs(UnmanagedType.Bool)]
15         private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
16 
17         [DllImport("user32.dll")]
18         private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
19 
20         static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
21         static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
22         static readonly IntPtr HWND_TOP = new IntPtr(0);
23         static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
24 
25         private const UInt32 SWP_NOSIZE = 0x0001;
26         const UInt32 SWP_NOMOVE = 0x0002;
27         const UInt32 SWP_NOREDRAW = 0x0008;
28         const UInt32 SWP_NOACTIVATE = 0x0010;
29 
30         const UInt32 SWP_NOOWNERZORDER = 0x0200;
31         const UInt32 SWP_NOSENDCHANGING = 0x0400;
32 
33         //很重要,窗口切换等需要将popup显示层级重新刷新
34         const UInt32 TOPMOST_FLAGS =
35             SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSENDCHANGING;
36 
37 
38         #endregion
39 
40         #region private fileds
41 
42         private static bool? _appliedTopMost;
43         private static bool _alreadyLoaded;
44         private static Window _parentWindow;
45         private static Popup _popup;
46         private static bool _isTopmost;
47 
48         #endregion
View Code

下载 此Demo 

 

注:也可以通过自定义用户控件Popup实现,逻辑一样:

  1     /// <summary>
  2     /// 解决StayOpen=true时,永远置顶的问题
  3     /// </summary>
  4     public class MyPopup : Popup
  5     {
  6         public static readonly DependencyProperty IsTopmostProperty = DependencyProperty.Register("IsTopmost", typeof(bool), typeof(MyPopup), new FrameworkPropertyMetadata(false, OnIsTopmostChanged));
  7 
  8         private bool? _appliedTopMost;
  9         private bool _alreadyLoaded;
 10         private Window _parentWindow;
 11 
 12         public bool IsTopmost
 13         {
 14             get { return (bool)GetValue(IsTopmostProperty); }
 15             set { SetValue(IsTopmostProperty, value); }
 16         }
 17 
 18         /// <summary>
 19         /// ctor
 20         /// </summary>
 21         public MyPopup()
 22         {
 23             Loaded += OnPopupLoaded;
 24             Unloaded += OnPopupUnloaded;
 25         }
 26 
 27 
 28         void OnPopupLoaded(object sender, RoutedEventArgs e)
 29         {
 30             if (_alreadyLoaded)
 31                 return;
 32 
 33             _alreadyLoaded = true;
 34 
 35             if (Child != null)
 36             {
 37                 Child.AddHandler(PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(OnChildPreviewMouseLeftButtonDown), true);
 38             }
 39 
 40             _parentWindow = Window.GetWindow(this);
 41 
 42             if (_parentWindow == null)
 43                 return;
 44 
 45             _parentWindow.Activated += OnParentWindowActivated;
 46             _parentWindow.Deactivated += OnParentWindowDeactivated;
 47         }
 48 
 49         private void OnPopupUnloaded(object sender, RoutedEventArgs e)
 50         {
 51             if (_parentWindow == null)
 52                 return;
 53             _parentWindow.Activated -= OnParentWindowActivated;
 54             _parentWindow.Deactivated -= OnParentWindowDeactivated;
 55         }
 56 
 57         void OnParentWindowActivated(object sender, EventArgs e)
 58         {
 59             SetTopmostState(true);
 60         }
 61 
 62         void OnParentWindowDeactivated(object sender, EventArgs e)
 63         {
 64             Debug.WriteLine("Parent Window Deactivated");
 65 
 66             if (IsTopmost == false)
 67             {
 68                 SetTopmostState(IsTopmost);
 69             }
 70         }
 71 
 72         void OnChildPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
 73         {
 74 
 75             SetTopmostState(true);
 76 
 77             if (!_parentWindow.IsActive && IsTopmost == false)
 78             {
 79                 _parentWindow.Activate();
 80             }
 81         }
 82 
 83         private static void OnIsTopmostChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
 84         {
 85             var thisobj = (MyPopup)obj;
 86 
 87             thisobj.SetTopmostState(thisobj.IsTopmost);
 88         }
 89 
 90         protected override void OnOpened(EventArgs e)
 91         {
 92             SetTopmostState(IsTopmost);
 93             base.OnOpened(e);
 94         }
 95 
 96         private void SetTopmostState(bool isTop)
 97         {
 98             if (_appliedTopMost.HasValue && _appliedTopMost == isTop)
 99             {
100                 return;
101             }
102 
103             if (Child == null)
104                 return;
105 
106             var hwndSource = (PresentationSource.FromVisual(Child)) as HwndSource;
107 
108             if (hwndSource == null)
109                 return;
110             var hwnd = hwndSource.Handle;
111 
112             RECT rect;
113 
114             if (!GetWindowRect(hwnd, out rect))
115                 return;
116 
117             if (isTop)
118             {
119                 SetWindowPos(hwnd, HWND_TOPMOST, rect.Left, rect.Top, (int)Width, (int)Height, TOPMOST_FLAGS);
120             }
121             else
122             {
123                 // 重新激活Topmost,需要bottom->top->notop
124                 SetWindowPos(hwnd, HWND_BOTTOM, rect.Left, rect.Top, (int)Width, (int)Height, TOPMOST_FLAGS);
125                 SetWindowPos(hwnd, HWND_TOP, rect.Left, rect.Top, (int)Width, (int)Height, TOPMOST_FLAGS);
126                 SetWindowPos(hwnd, HWND_NOTOPMOST, rect.Left, rect.Top, (int)Width, (int)Height, TOPMOST_FLAGS);
127             }
128 
129             _appliedTopMost = isTop;
130         }
131 
132         [StructLayout(LayoutKind.Sequential)]
133         public struct RECT
134 
135         {
136             public int Left;
137             public int Top;
138             public int Right;
139             public int Bottom;
140         }
141 
142         [DllImport("user32.dll")]
143         [return: MarshalAs(UnmanagedType.Bool)]
144         private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
145 
146         [DllImport("user32.dll")]
147         private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X,
148         int Y, int cx, int cy, uint uFlags);
149 
150         static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
151         static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
152         static readonly IntPtr HWND_TOP = new IntPtr(0);
153         static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
154 
155         private const UInt32 SWP_NOSIZE = 0x0001;
156         const UInt32 SWP_NOMOVE = 0x0002;
157         const UInt32 SWP_NOZORDER = 0x0004;
158         const UInt32 SWP_NOREDRAW = 0x0008;
159         const UInt32 SWP_NOACTIVATE = 0x0010;
160 
161         const UInt32 SWP_FRAMECHANGED = 0x0020; /* The frame changed: send WM_NCCALCSIZE */
162         const UInt32 SWP_SHOWWINDOW = 0x0040;
163         const UInt32 SWP_HIDEWINDOW = 0x0080;
164         const UInt32 SWP_NOCOPYBITS = 0x0100;
165         const UInt32 SWP_NOOWNERZORDER = 0x0200; /* Don’t do owner Z ordering */
166         const UInt32 SWP_NOSENDCHANGING = 0x0400; /* Don’t send WM_WINDOWPOSCHANGING */
167 
168         //很重要,窗口切换等需要将popup显示层级重新刷新
169         const UInt32 TOPMOST_FLAGS =
170         SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSENDCHANGING;
171     }
View Code

 

解决方案总结

添加如上附加属性或者用户控件Popup后,能解决置顶问题,Popup只会出现在所在窗口上层。

截图如下:

 

posted on 2019-04-10 01:26 NET未来之路 阅读( ...) 评论( ...) 编辑 收藏

转载于:https://www.cnblogs.com/lonelyxmas/p/10680883.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值