(资料图片)
WPF中的popup控件默认TopMost模式,因此会在切换别的进程窗体时,仍然显示不隐藏。网上找了一圈,有国内帖子实现的代码(已无法找到网址),但在英文网站上找到这篇应该才是原出处-Non-Topmost WPF Popup | Chris Cavanagh"s Blog (wordpress.com)
里面原文包括评论,可以大致实现我的需求,但是问题在于它的代码在左键点击PupUp控件区域时并不是每次都能使得该PopUp控件置于当前所有窗体最前。这与我们使用子窗体show模式的表现不同,因此有了以下改造后的代码。
using System;using System.Collections.Generic;using System.Diagnostics;using System.Linq;using System.Runtime.InteropServices;using System.Text;using System.Threading.Tasks;using System.Windows;using System.Windows.Controls.Primitives;using System.Windows.Input;using System.Windows.Interop;using System.Windows.Media;namespace MyPopupHost{ public class PopupHost : Popup { /// /// 应用状态 /// private bool? _appliedTopMost; /// /// 是否已经加载 /// private bool _alreadyLoaded; /// /// popup所在的窗体 /// private Window _parentWindow; /// /// 是否顶置 /// public bool IsTopmost { get { return (bool)GetValue(IsTopmostProperty); } set { SetValue(IsTopmostProperty, value); } } /// /// 是否顶置依赖属性(默认不顶置) /// public static readonly DependencyProperty IsTopmostProperty = DependencyProperty.Register("IsTopmost", typeof(bool), typeof(PopupHost), new FrameworkPropertyMetadata(false, OnIsTopmostChanged)); /// /// 是否跟随父窗体移动(默认为True) /// public bool IsMove { get { return (bool)GetValue(IsMoveProperty); } set { SetValue(IsMoveProperty, value); } } public static readonly DependencyProperty IsMoveProperty = DependencyProperty.Register("IsMove", typeof(bool), typeof(PopupHost), new PropertyMetadata(true)); /// /// 是否包含TextBox控件 /// public bool IsHasTextBox { get { return (bool)GetValue(IsHasTextBoxProperty); } set { SetValue(IsHasTextBoxProperty, value); } } public static readonly DependencyProperty IsHasTextBoxProperty = DependencyProperty.Register("IsHasTextBox", typeof(bool), typeof(PopupHost), new PropertyMetadata(false)); /// /// 是否全屏 /// public bool IsFullScreen { get { return (bool)GetValue(IsFullScreenProperty); } set { SetValue(IsFullScreenProperty, value); } } public static readonly DependencyProperty IsFullScreenProperty = DependencyProperty.Register("IsFullScreen", typeof(bool), typeof(PopupHost), new PropertyMetadata(false, OnFullScreenChanged)); /// /// 全屏设置改变事件 /// /// /// private static void OnFullScreenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { PopupHost pop = (PopupHost)d; if ((bool)e.NewValue == true) { pop.Opened += Pop_Opened; } } /// /// 全屏时打开pop事件 /// /// /// private static void Pop_Opened(object sender, EventArgs e) { Popup pop = sender as Popup; DependencyObject parent = pop.Child; do { parent = System.Windows.Media.VisualTreeHelper.GetParent(parent); if (parent != null && parent.ToString() == "System.Windows.Controls.Primitives.PopupRoot") { var element = parent as FrameworkElement; var mainWin = Application.Current.MainWindow; element.Height = mainWin.ActualHeight; element.Width = mainWin.ActualWidth; break; } } while (parent != null); } /// /// 构造函数 /// public PopupHost() { Loaded += OnPopupLoaded; Unloaded += OnPopupUnloaded; } /// /// popup加载事件 /// /// /// 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 (IsMove) _parentWindow.LocationChanged += delegate { var offset = this.HorizontalOffset; this.HorizontalOffset = offset + 1; this.HorizontalOffset = offset; }; if (_parentWindow == null) return; _parentWindow.Activated += OnParentWindowActivated; _parentWindow.Deactivated += OnParentWindowDeactivated; //SetPopupOwner(); } private void OnPopupUnloaded(object sender, RoutedEventArgs e) { if (_parentWindow == null) return; _parentWindow.Activated -= OnParentWindowActivated; _parentWindow.Deactivated -= OnParentWindowDeactivated; } /// /// 主窗体激活事件 /// /// /// void OnParentWindowActivated(object sender, EventArgs e) { Debug.WriteLine($"{this.GetHashCode()}:Parent Window Activated"); ShowPop(); } /// /// 主窗体不在激活状态事件 /// /// /// void OnParentWindowDeactivated(object sender, EventArgs e) { Debug.WriteLine($"{this.GetHashCode()}:Parent Window Deactivated"); if (IsTopmost == false) { HidePop(); } } /// /// 子元素的鼠标左键按下事件 /// /// /// void OnChildPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { //SetTopmostState(true); if (IsHasTextBox) { ActivatePopup(); } else { ShowPop(); //if (!_parentWindow.IsActive && IsTopmost == false) //{ // _parentWindow.Activate(); //} } } /// /// IsTopmost属性改变事件 /// /// /// private static void OnIsTopmostChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { var thisobj = (PopupHost)obj; thisobj.ShowPop(); } /// /// 重写open事件 /// /// protected override void OnOpened(EventArgs e) { base.OnOpened(e); ShowPop(); } private void ShowPop() { //设置状态 if (IsTopmost) SetPopPos(PopupHostZState.TopMost); else SetPopPos(PopupHostZState.Top); } private void HidePop()=>SetPopPos(PopupHostZState.Bottom); private void SetPopPos(PopupHostZState popupHostZState) { if (Child == null) return; var hwndSource = (PresentationSource.FromVisual(Child)) as HwndSource; if (hwndSource == null) return; var hwnd = hwndSource.Handle; if (!GetWindowRect(hwnd, out RECT rect)) return; Debug.WriteLine($"{this.GetHashCode()}:Setting z-order: " + Enum.GetName(typeof(PopupHostZState), popupHostZState)); //设置所有者窗口,跟随其所有者窗口的Z序显示,避免失去焦点后被遮盖 var windowHwnd = new WindowInteropHelper(_parentWindow).Handle; SetWindowLong(hwnd, -8, windowHwnd); //SetParent(hwnd, windowHwnd); //设置窗口 IntPtr hWndInsertAfter = IntPtr.Zero; switch(popupHostZState) { case PopupHostZState.TopMost: hWndInsertAfter = HWND_TOPMOST; break; case PopupHostZState.NoTopMost: hWndInsertAfter = HWND_NOTOPMOST; break; case PopupHostZState.Top: hWndInsertAfter = HWND_TOP; break; case PopupHostZState.Bottom: hWndInsertAfter = HWND_BOTTOM; break; } if(popupHostZState == PopupHostZState.Bottom) SetWindowPos(hwnd, hWndInsertAfter, rect.Left, rect.Top, (int)Width, (int)Height, NOACTIVATE_FLAGS); else SetWindowPos(hwnd, hWndInsertAfter, rect.Left, rect.Top, (int)Width, (int)Height, NOACTIVATE_FLAGS); } [DllImport("USER32.DLL")] public static extern IntPtr SetFocus(IntPtr hWnd); /// /// 激活Pop(解决无法删除TextBox文字) /// /// public void ActivatePopup() { if (Child == null) return; //try to get a handle on the popup itself (via its child) HwndSource source = (HwndSource)PresentationSource.FromVisual(Child); IntPtr handle = source.Handle; //activate the popup SetFocus(handle); } #region P / Invoke 入口和定义 [StructLayout(LayoutKind.Sequential)] public struct RECT { public int Left; public int Top; public int Right; public int Bottom; } [DllImport("user32.dll", SetLastError = true)] static extern int SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong); [DllImport("user32.dll", SetLastError = true)] static extern IntPtr GetParent(IntPtr hWndChild); [DllImport("user32.dll", SetLastError = true)] static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent); [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(-1); static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2); static readonly IntPtr HWND_TOP = new IntPtr(0); static readonly IntPtr HWND_BOTTOM = new IntPtr(1); 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 */ const UInt32 NOACTIVATE_FLAGS = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSENDCHANGING; const UInt32 ACTIVE_FLAGS = SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSENDCHANGING; #endregion } internal enum PopupHostZState { TopMost, NoTopMost, Top, Bottom }}
关键代码就是SetPopPos方法,其中根据传入的PopupHostZState枚举对应设置PopHost控件的Z-Order相关参数。必须先关联父窗体,然后在父窗体激活时设置PopHost控件为Top,即非TopMost窗体里最前,在父窗体失活(切换到别的进程窗体,本进程里由父窗体弹出的窗体是子窗体,不会导致父窗体失活)时,设置PopHost控件为Bottom。
private void SetPopPos(PopupHostZState popupHostZState){ if (Child == null) return; var hwndSource = (PresentationSource.FromVisual(Child)) as HwndSource; if (hwndSource == null) return; var hwnd = hwndSource.Handle; if (!GetWindowRect(hwnd, out RECT rect)) return; Debug.WriteLine($"{this.GetHashCode()}:Setting z-order: " + Enum.GetName(typeof(PopupHostZState), popupHostZState)); //设置所有者窗口,跟随其所有者窗口的Z序显示,避免失去焦点后被遮盖 var windowHwnd = new WindowInteropHelper(_parentWindow).Handle; SetWindowLong(hwnd, -8, windowHwnd); //SetParent(hwnd, windowHwnd); //设置窗口 IntPtr hWndInsertAfter = IntPtr.Zero; switch(popupHostZState) { case PopupHostZState.TopMost: hWndInsertAfter = HWND_TOPMOST; break; case PopupHostZState.NoTopMost: hWndInsertAfter = HWND_NOTOPMOST; break; case PopupHostZState.Top: hWndInsertAfter = HWND_TOP; break; case PopupHostZState.Bottom: hWndInsertAfter = HWND_BOTTOM; break; } if(popupHostZState == PopupHostZState.Bottom) SetWindowPos(hwnd, hWndInsertAfter, rect.Left, rect.Top, (int)Width, (int)Height, NOACTIVATE_FLAGS); else SetWindowPos(hwnd, hWndInsertAfter, rect.Left, rect.Top, (int)Width, (int)Height, NOACTIVATE_FLAGS);}
注意:PopUp本身也是一个窗体,如果在Win32的SetWindowPos方法中,没有设置NOACTIVATE_FLAGS,包含SWP_NOACTIVATE(此参数意思是不激活窗体),那么每次都会激活PopUp窗体,导致主窗体失活,再调用一圈失活事件处理,导致很卡。
关键词: