【问题标题】:In WPF, how to shift a window onto the screen if it is off the screen?在 WPF 中,如果窗口不在屏幕上,如何将窗口移到屏幕上?
【发布时间】:2016-10-21 23:22:44
【问题描述】:

如果我有一个窗口,如何确保窗口永远不会隐藏在屏幕之外?

这很重要,因为有时如果用户添加或删除监视器,如果我们记住了之前的位置,窗口可能会永久隐藏在屏幕之外。

我正在使用WPF + MVVM

【问题讨论】:

    标签: c# .net wpf


    【解决方案1】:

    这个答案已经在一个大规模的现实世界应用程序中进行了测试。

    从任何附加的属性中调用它以将窗口移回可见屏幕:

    public static class ShiftWindowOntoScreenHelper
    {
        /// <summary>
        ///     Intent:  
        ///     - Shift the window onto the visible screen.
        ///     - Shift the window away from overlapping the task bar.
        /// </summary>
        public static void ShiftWindowOntoScreen(Window window)
        {
            // Note that "window.BringIntoView()" does not work.                            
            if (window.Top < SystemParameters.VirtualScreenTop)
            {
                window.Top = SystemParameters.VirtualScreenTop;
            }
    
            if (window.Left < SystemParameters.VirtualScreenLeft)
            {
                window.Left = SystemParameters.VirtualScreenLeft;
            }
    
            if (window.Left + window.Width > SystemParameters.VirtualScreenLeft + SystemParameters.VirtualScreenWidth)
            {
                window.Left = SystemParameters.VirtualScreenWidth + SystemParameters.VirtualScreenLeft - window.Width;
            }
    
            if (window.Top + window.Height > SystemParameters.VirtualScreenTop + SystemParameters.VirtualScreenHeight)
            {
                window.Top = SystemParameters.VirtualScreenHeight + SystemParameters.VirtualScreenTop - window.Height;
            }
    
            // Shift window away from taskbar.
            {
                var taskBarLocation = GetTaskBarLocationPerScreen();
    
                // If taskbar is set to "auto-hide", then this list will be empty, and we will do nothing.
                foreach (var taskBar in taskBarLocation)
                {
                    Rectangle windowRect = new Rectangle((int)window.Left, (int)window.Top, (int)window.Width, (int)window.Height);
    
                    // Keep on shifting the window out of the way.
                    int avoidInfiniteLoopCounter = 25;
                    while (windowRect.IntersectsWith(taskBar))
                    {
                        avoidInfiniteLoopCounter--;
                        if (avoidInfiniteLoopCounter == 0)
                        {
                            break;
                        }
    
                        // Our window is covering the task bar. Shift it away.
                        var intersection = Rectangle.Intersect(taskBar, windowRect);
    
                        if (intersection.Width < window.Width
                            // This next one is a rare corner case. Handles situation where taskbar is big enough to
                            // completely contain the status window.
                            || taskBar.Contains(windowRect))
                        {
                            if (taskBar.Left == 0)
                            {
                                // Task bar is on the left. Push away to the right.
                                window.Left = window.Left + intersection.Width;
                            }
                            else
                            {
                                // Task bar is on the right. Push away to the left.
                                window.Left = window.Left - intersection.Width;
                            }
                        }
    
                        if (intersection.Height < window.Height
                            // This next one is a rare corner case. Handles situation where taskbar is big enough to
                            // completely contain the status window.
                            || taskBar.Contains(windowRect))
                        {
                            if (taskBar.Top == 0)
                            {
                                // Task bar is on the top. Push down.
                                window.Top = window.Top + intersection.Height;
                            }
                            else
                            {
                                // Task bar is on the bottom. Push up.
                                window.Top = window.Top - intersection.Height;
                            }
                        }
    
                        windowRect = new Rectangle((int)window.Left, (int)window.Top, (int)window.Width, (int)window.Height);
                    }
                }
            }
        }
    
        /// <summary>
        /// Returned location of taskbar on a per-screen basis, as a rectangle. See:
        /// https://stackoverflow.com/questions/1264406/how-do-i-get-the-taskbars-position-and-size/36285367#36285367.
        /// </summary>
        /// <returns>A list of taskbar locations. If this list is empty, then the taskbar is set to "Auto Hide".</returns>
        private static List<Rectangle> GetTaskBarLocationPerScreen()
        {
            List<Rectangle> dockedRects = new List<Rectangle>();
            foreach (var screen in Screen.AllScreens)
            {
                if (screen.Bounds.Equals(screen.WorkingArea) == true)
                {
                    // No taskbar on this screen.
                    continue;
                }
    
                Rectangle rect = new Rectangle();
    
                var leftDockedWidth = Math.Abs((Math.Abs(screen.Bounds.Left) - Math.Abs(screen.WorkingArea.Left)));
                var topDockedHeight = Math.Abs((Math.Abs(screen.Bounds.Top) - Math.Abs(screen.WorkingArea.Top)));
                var rightDockedWidth = ((screen.Bounds.Width - leftDockedWidth) - screen.WorkingArea.Width);
                var bottomDockedHeight = ((screen.Bounds.Height - topDockedHeight) - screen.WorkingArea.Height);
                if ((leftDockedWidth > 0))
                {
                    rect.X = screen.Bounds.Left;
                    rect.Y = screen.Bounds.Top;
                    rect.Width = leftDockedWidth;
                    rect.Height = screen.Bounds.Height;
                }
                else if ((rightDockedWidth > 0))
                {
                    rect.X = screen.WorkingArea.Right;
                    rect.Y = screen.Bounds.Top;
                    rect.Width = rightDockedWidth;
                    rect.Height = screen.Bounds.Height;
                }
                else if ((topDockedHeight > 0))
                {
                    rect.X = screen.WorkingArea.Left;
                    rect.Y = screen.Bounds.Top;
                    rect.Width = screen.WorkingArea.Width;
                    rect.Height = topDockedHeight;
                }
                else if ((bottomDockedHeight > 0))
                {
                    rect.X = screen.WorkingArea.Left;
                    rect.Y = screen.WorkingArea.Bottom;
                    rect.Width = screen.WorkingArea.Width;
                    rect.Height = bottomDockedHeight;
                }
                else
                {
                    // Nothing found!
                }
    
                dockedRects.Add(rect);
            }
    
            if (dockedRects.Count == 0)
            {
                // Taskbar is set to "Auto-Hide".
            }
    
            return dockedRects;
        }
    }
    

    作为奖励,您可以实现自己的拖放操作,当拖动完成时,窗口将移回屏幕上。

    如果窗口快速滑回可见区域而不是直接跳回可见区域,从用户的角度来看会更直观,但至少这种方法得到了正确的结果。

    /// <summary>
    ///     Intent: Add this Attached Property to any XAML element, to allow you to click and drag the entire window.
    ///     Essentially, it searches up the visual tree to find the first parent window, then calls ".DragMove()" on it. Once the drag finishes, it pushes
    ///     the window back onto the screen if part or all of it wasn't visible.
    /// </summary>
    public class EnableDragAttachedProperty
    {
        public static readonly DependencyProperty EnableDragProperty = DependencyProperty.RegisterAttached(
            "EnableDrag",
            typeof(bool),
            typeof(EnableDragAttachedProperty),
            new PropertyMetadata(default(bool), OnLoaded));
    
        private static void OnLoaded(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            try
            {
                var uiElement = dependencyObject as UIElement;
                if (uiElement == null || (dependencyPropertyChangedEventArgs.NewValue is bool) == false)
                {
                    return;
                }
                if ((bool)dependencyPropertyChangedEventArgs.NewValue == true)
                {
                    uiElement.MouseMove += UIElement_OnMouseMove;
                }
                else
                {
                    uiElement.MouseMove -= UIElement_OnMouseMove;
                }
            }
            catch (Exception ex)
            {
                 // Log exception here.
            }
        }
    
        /// <summary>
        ///     Intent: Fetches the parent window, so we can call "DragMove()"on it. Caches the results in a dictionary,
        ///     so we can apply this same property to multiple XAML elements.
        /// </summary>
        private static void UIElement_OnMouseMove(object sender, MouseEventArgs mouseEventArgs)
        {
            try
            {
                var uiElement = sender as UIElement;
                if (uiElement != null)
                {
                    Window window = GetParentWindow(uiElement);
    
                    if (mouseEventArgs.LeftButton == MouseButtonState.Pressed)
                    {
                        // DragMove is a synchronous call: once it completes, the drag is finished and the left mouse
                        // button has been released.
                        window?.DragMove();
    
                        // See answer in section 'Additional Links' below in the SO answer.
                    //HideAndShowWindowHelper.ShiftWindowIntoForeground(window);
    
                        // When the use has finished the drag and released the mouse button, we shift the window back
                        // onto the screen, it it ended up partially off the screen.
                        ShiftWindowOntoScreenHelper.ShiftWindowOntoScreen(window);
                    }
                }
            }
            catch (Exception ex)
            {
                _log.Warn($"Exception in {nameof(UIElement_OnMouseMove)}. " +
                          $"This means that we cannot shift and drag the Toast Notification window. " +
                          $"To fix, correct C# code.", ex);
            }
        }
    
        public static void SetEnableDrag(DependencyObject element, bool value)
        {
            element.SetValue(EnableDragProperty, value);
        }
    
        public static bool GetEnableDrag(DependencyObject element)
        {
            return (bool)element.GetValue(EnableDragProperty);
        }
    
        #region GetParentWindow
        private static readonly Dictionary<UIElement, Window> _parentWindow = new Dictionary<UIElement, Window>();
        private static readonly object _parentWindowLock = new object();
    
        /// <summary>
        ///     Intent: Given any UIElement, searches up the visual tree to find the parent Window.
        /// </summary>
        private static Window GetParentWindow(UIElement uiElement)
        {
            bool ifAlreadyFound;
            lock (_parentWindowLock)
            {
                ifAlreadyFound = _parentWindow.ContainsKey(uiElement) == true;
            }
    
            if (ifAlreadyFound == false)
            {
                DependencyObject parent = uiElement;
                int avoidInfiniteLoop = 0;
                // Search up the visual tree to find the first parent window.
                while ((parent is Window) == false)
                {
                    parent = VisualTreeHelper.GetParent(parent);
                    avoidInfiniteLoop++;
                    if (avoidInfiniteLoop == 1000)
                    {
                        // Something is wrong - we could not find the parent window.
                        return null;
                    }
                }
                lock (_parentWindowLock)
                {
                    _parentWindow[uiElement] = parent as Window;
                }
            }
            lock(_parentWindowLock)
            {
                return _parentWindow[uiElement];
            }
        }
        #endregion
    }
    

    其他链接

    有关如何避免通知窗口被其他窗口隐藏的提示,请参阅我的回答:Bring a window to the front in WPF

    【讨论】:

    • 在将窗口移动到现在已断开连接的当时连接的监视器时效果很好。但由于此代码仅与所有监视器的周围矩形进行比较,因此可能存在仍然不可见的间隙。当两台显示器的尺寸不同时,总是会发生这种情况。至少这段代码可以很好地与高 DPI 和多 DPI 设置配合使用。
    • 顺便说一句,多 DPI 设置(也可能是高 DPI)的任务栏检测被破坏。我已将此报告给引用的答案。
    • 当我有 3 个监视器但后来删除了一个时,我也遭受了运行 wpf 应用程序的痛苦,当然最后一次在现在删除的监视器上使用该表单。我复制/粘贴了您的第一个代码块,但需要将您的所有 Rectangle 引用更改为 System.Drawing.Rectangle,并像这样调用您的函数:ShiftWindowOntoScreenHelper.ShiftWindowOntoScreen((MainWindow)System.Windows.Application.Current.MainWindow);。我运行了我的 wpf 应用程序并 bam ......它出现了!衷心感谢您提供此解决方案!
    • @Zyre 不客气。代码并不完美,请参阅 ygoe 的 cmets。如果您发现任何改进,请发布到此线程。
    【解决方案2】:

    主题是旧的,但如果有人正在寻找另一种解决方案,我这样做是为了在光标位置单击时显示一个窗口,并确保根据屏幕(工作区域)边界始终可见该窗口:

    public static void PositionWindowOnScreen(Window window)
    {
        Screen activeScreen = Screen.FromPoint(Control.MousePosition);
    
        double xPositionToSet = Control.MousePosition.X;
        double yPositionToSet = Control.MousePosition.Y;
    
        if (xPositionToSet + window.Width > activeScreen.WorkingArea.Width + activeScreen.WorkingArea.X && 
            xPositionToSet - activeScreen.WorkingArea.X - window.Width > 0)
        {
            xPositionToSet = xPositionToSet - window.Width;
        }
    
        if (yPositionToSet + window.Height > activeScreen.WorkingArea.Height + activeScreen.WorkingArea.Y && 
            yPositionToSet - activeScreen.WorkingArea.Y - window.Height > 0)
        {
            yPositionToSet = yPositionToSet - window.Height;
        }
    
        window.WindowStartupLocation = WindowStartupLocation.Manual;
        window.Left = xPositionToSet;
        window.Top = yPositionToSet;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-12-23
      • 1970-01-01
      • 2011-03-22
      • 2023-03-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多