如果我有一个窗口,我怎样才能确保窗口永远不会隐藏在屏幕外?
这很重要,因为有时候如果用户添加或删除了一个监视器,如果我们记住以前的位置,窗口可能会永久隐藏在屏幕外。
我正在使用WPF
+ MVVM
。
答案 0 :(得分:12)
这个答案已在大型现实世界的应用程序中进行了测试。
从任何附加属性调用此方法将窗口移回可见屏幕:
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。
答案 1 :(得分:0)
主题很旧,但是如果有人在寻找其他解决方案,我这样做是为了在单击光标位置时显示一个窗口,并确保根据屏幕(工作区域)边界始终可见该窗口:
classFunctionOne