当前位置: 首页 > 工具软件 > Popup > 使用案例 >

WPF解决Popup窗口随动及显隐

贾骏
2023-12-01

使用自定义Popup控件,解决WPF控件被Winform遮挡、初始化时Z顺序错误导致显隐异常、输入框无法输入的问题,还可以设置Popup以全屏方式展现。

public class CusPopup : 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(CusPopup), 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(CusPopup), 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(CusPopup), 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(CusPopup), new PropertyMetadata(false, OnFullScreenChanged));

    /// <summary>
    /// 全屏设置改变事件
    /// </summary>
    /// <param name="d"></param>
    /// <param name="e"></param>
    private static void OnFullScreenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        CusPopup pop = (CusPopup)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 CusPopup()
    {
        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;
    }

    private void SetPopupOwner()
    {
        if (this == null) return;
        var hwndSource = (PresentationSource.FromVisual(this)) as HwndSource;
        if (hwndSource == null) return;
        var hwnd = hwndSource.Handle;
        WindowInteropHelper helper = new WindowInteropHelper(_parentWindow);
        SetWindowLong(hwnd, -8, new IntPtr(helper.Handle.ToInt32()));
    }

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

        if (IsHasTextBox)
        {
            ActivatePopup();
        }
        else
        {
            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 = (CusPopup)obj;

        thisobj.SetTopmostState(thisobj.IsTopmost);
    }
    /// <summary>
    /// 重写open事件
    /// </summary>
    /// <param name="e"></param>
    protected override void OnOpened(EventArgs e)
    {
        //设置状态
        SetTopmostState(IsTopmost);
        base.OnOpened(e);
    }
    /// <summary>
    /// 设置置顶状态
    /// </summary>
    /// <param name="isTop"></param>
    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;

        Debug.WriteLine("setting z-order " + isTop);
        //设置所有者窗口,跟随其所有者窗口的Z序显示,避免失去焦点后被遮盖
        var windowHwnd = new WindowInteropHelper(_parentWindow).Handle;
        SetWindowLong(hwnd, -8, windowHwnd);

        if (isTop)
        {
            SetWindowPos(hwnd, HWND_TOPMOST, rect.Left, rect.Top, (int)Width, (int)Height, TOPMOST_FLAGS);
        }
        else
        {
            /*
             z顺序只会在点击时得到刷新/反射
             标题栏(与外部的其他部分相对比窗口)除非我先将弹出窗口设置为hwndbottom
             然后HWND_TOP HWND_NOTOPMOST之前
             */
            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;
    }


    [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")]
    [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 TOPMOST_FLAGS =
        SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSENDCHANGING;
    #endregion
}
 类似资料: