WPF:自定义PopHost控件实现PopUp控件与父窗体关联,去除TopMost状态,激活别的进程时隐藏

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
    {
        /// <summary>
        /// 应用状态
        /// </summary>
        private bool? _appliedTopMost;
        /// <summary>
        /// 是否已经加载
        /// </summary>
        private bool _alreadyLoaded;
        /// <summary>
        /// popup所在的窗体
        /// </summary>
        private Window _parentWindow;

        /// <summary>
        /// 是否顶置
        /// </summary>
        public bool IsTopmost
        {
            get { return (bool)GetValue(IsTopmostProperty); }
            set { SetValue(IsTopmostProperty, value); }
        }
        /// <summary>
        /// 是否顶置依赖属性(默认不顶置)
        /// </summary>
        public static readonly DependencyProperty IsTopmostProperty = DependencyProperty.Register("IsTopmost", typeof(bool), typeof(PopupHost), new FrameworkPropertyMetadata(false, OnIsTopmostChanged));

        /// <summary>
        /// 是否跟随父窗体移动(默认为True)
        /// </summary>
        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));

        /// <summary>
        /// 是否包含TextBox控件
        /// </summary>
        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));

        /// <summary>
        /// 是否全屏
        /// </summary>
        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));

        /// <summary>
        /// 全屏设置改变事件
        /// </summary>
        /// <param name="d"></param>
        /// <param name="e"></param>
        private static void OnFullScreenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            PopupHost pop = (PopupHost)d;
            if ((bool)e.NewValue == true)
            {
                pop.Opened += Pop_Opened;
            }
        }
        /// <summary>
        /// 全屏时打开pop事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        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);
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        public PopupHost()
        {
            Loaded += OnPopupLoaded;
            Unloaded += OnPopupUnloaded;
        }

        /// <summary>
        /// popup加载事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        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;
        }

        /// <summary>
        /// 主窗体激活事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void OnParentWindowActivated(object sender, EventArgs e)
        {
            Debug.WriteLine($"{this.GetHashCode()}:Parent Window Activated");
            ShowPop();
        }
        /// <summary>
        /// 主窗体不在激活状态事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void OnParentWindowDeactivated(object sender, EventArgs e)
        {
            Debug.WriteLine($"{this.GetHashCode()}:Parent Window Deactivated");
            if (IsTopmost == false)
            {
                HidePop();
            }
        }
        /// <summary>
        /// 子元素的鼠标左键按下事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void OnChildPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            //SetTopmostState(true);

            if (IsHasTextBox)
            {
                ActivatePopup();
            }
            else
            {
                ShowPop();
                //if (!_parentWindow.IsActive && IsTopmost == false)
                //{
                //    _parentWindow.Activate();
                //}
            }
        }

        /// <summary>
        /// IsTopmost属性改变事件
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="e"></param>
        private static void OnIsTopmostChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var thisobj = (PopupHost)obj;
            thisobj.ShowPop();
        }

        /// <summary>
        /// 重写open事件
        /// </summary>
        /// <param name="e"></param>
        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);
        /// <summary>
        /// 激活Pop(解决无法删除TextBox文字)
        /// </summary>
        /// <param name="popup"></param>
        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窗体,导致主窗体失活,再调用一圈失活事件处理,导致很卡。

posted on   鹿果一夏  阅读(867)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· 2 本地部署DeepSeek模型构建本地知识库+联网搜索详细步骤
< 2025年2月 >
26 27 28 29 30 31 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 1
2 3 4 5 6 7 8

导航

统计

点击右上角即可分享
微信分享提示