在WPF中,如果窗口离开屏幕,如何将窗口移动到屏幕上?

如果我有一个窗口,我怎样才能确保窗口永远不会隐藏在屏幕外?

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

我正在使用WPF + MVVM

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

从任何附加属性调用此方法将窗口移回可见屏幕:

 public static class ShiftWindowOntoScreenHelper { ///  /// Intent: /// - Shift the window onto the visible screen. /// - Shift the window away from overlapping the task bar. ///  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); } } } } ///  /// 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. ///  /// A list of taskbar locations. If this list is empty, then the taskbar is set to "Auto Hide". private static List GetTaskBarLocationPerScreen() { List dockedRects = new List(); 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; } } 

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

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

 ///  /// 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. ///  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. } } ///  /// 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. ///  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 _parentWindow = new Dictionary(); private static readonly object _parentWindowLock = new object(); ///  /// Intent: Given any UIElement, searches up the visual tree to find the parent Window. ///  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 } 

其他链接

有关如何避免其他窗口隐藏通知窗口的提示,请参阅我的答案: 在WPF中将窗口置于前面 。