您当前的位置:滚动 >  >> 正文
【焦点热闻】WPF:自定义PopHost控件实现PopUp控件与父窗体关联,去除TopMost状态,激活别的进程时隐藏

时间:2023-06-09 17:04:46    来源:博客园


(资料图片)

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窗体,导致主窗体失活,再调用一圈失活事件处理,导致很卡。

关键词: