I have a WPF application and I need to know how to center the wain window programatically (not in XAML).
I need to be able to do this both at startup and in response to certain user events. It has to be dynamically calculated since the window size itself is dynamic.
What's the simplest way to do this? Under old Win32 code, I'd call the system metrics functions and work it all out. Is that still the way it's done or is there a simple CenterWindowOnScreen() function I can now call.
Well, for startup time, you can set the startup location:
window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
Later, you'll need to query it. The information (at least for the primary screen) is available via SystemParameters.PrimaryScreenWidth/Height.
private void CenterWindowOnScreen()
{
double screenWidth = System.Windows.SystemParameters.PrimaryScreenWidth;
double screenHeight = System.Windows.SystemParameters.PrimaryScreenHeight;
double windowWidth = this.Width;
double windowHeight = this.Height;
this.Left = (screenWidth / 2) - (windowWidth / 2);
this.Top = (screenHeight / 2) - (windowHeight / 2);
}
You can use this method to set the window position to the center of your screen.
Isn't it just as simple to set
WindowStartupLocation="CenterScreen"
In the XAML definition for the window.
I had to combine a few of these answers to cover all bases in my case:
Peter's method to find the current monitor - rather than the just the Primary monitor (seriously who has just 1 monitor at work anymore?)
#Wild_A's method to use the workarea rather than the screen bounds to take into account the space for the taskbar.
I had to add DPI scaling, specifically for a tablet displaying 1280x800 as 1024x640, but which is useful to cover edge cases, for which I found an answer for here. Note the dpiScaling variable is null if called on first load before the UI is displayed (explained here)
//get the current monitor
Screen currentMonitor = Screen.FromHandle(new System.Windows.Interop.WindowInteropHelper(Application.Current.MainWindow).Handle);
//find out if our app is being scaled by the monitor
PresentationSource source = PresentationSource.FromVisual(Application.Current.MainWindow);
double dpiScaling = (source != null && source.CompositionTarget != null ? source.CompositionTarget.TransformFromDevice.M11 : 1);
//get the available area of the monitor
Rectangle workArea = currentMonitor.WorkingArea;
var workAreaWidth = (int)Math.Floor(workArea.Width*dpiScaling);
var workAreaHeight = (int)Math.Floor(workArea.Height*dpiScaling);
//move to the centre
Application.Current.MainWindow.Left = (((workAreaWidth - (myWindowWidth * dpiScaling)) / 2) + (workArea.Left * dpiScaling));
Application.Current.MainWindow.Top = (((workAreaHeight - (myWindowHeight * dpiScaling)) / 2) + (workArea.Top * dpiScaling));
where myWindowWidth and myWindowHeight are variables I used to manually set the size of the window earlier.
Rect workArea = System.Windows.SystemParameters.WorkArea;
this.Left = (workArea.Width - this.Width) / 2 + workArea.Left;
this.Top = (workArea.Height - this.Height) / 2 + workArea.Top;
This takes into account the taskbar size (by using System.Windows.SystemParameters.WorkArea) and position (by adding workArea.Left and workArea.Top)
In case you need to draw a window in an multiple screen environment.
I've made a static class where the following method can be re-used:
public static void PostitionWindowOnScreen(Window window, double horizontalShift = 0, double verticalShift = 0)
{
Screen screen = Screen.FromHandle(new System.Windows.Interop.WindowInteropHelper(window).Handle);
window.Left = screen.Bounds.X + ((screen.Bounds.Width - window.ActualWidth) / 2) + horizontalShift;
window.Top = screen.Bounds.Y + ((screen.Bounds.Height - window.ActualHeight) / 2) + verticalShift;
}
In the constructor of the Window now just call the method:
this.Loaded += (s, a) => Globals.PostitionWindowOnScreen(this, 0, 0)
As a basic solution, you can use the window's StartupLocation property, set it to one of the enum values defined in System.Windows.WindowStartupLocation enumeration, there is one for center of screen:
_wpfWindow.StartupLocation = System.Windows.WindowStartupLocation.CenterScreen;
Unfortunately it's not always quite so simple; you need to account for multiple monitors, taskbars, etc. The "CenterScreen" option opens the window in the center of the screen that has the mouse cursor. See this SO question for a lot of information, or reference the api.
In the window element just add this attribute-value pair:
WindowStartupLocation="CenterScreen"
What I am using in my app, it is working for multiple displays and for different DPI setting
//center a window on chosen screen
public static void CenterWindow(Window w, System.Windows.Forms.Screen screen = null)
{
if(screen == null)
screen = System.Windows.Forms.Screen.PrimaryScreen;
int screenW = screen.Bounds.Width;
int screenH = screen.Bounds.Height;
int screenTop = screen.Bounds.Top;
int screenLeft = screen.Bounds.Left;
w.Left = PixelsToPoints((int)(screenLeft + (screenW - PointsToPixels(w.Width, "X")) / 2), "X");
w.Top = PixelsToPoints((int)(screenTop + (screenH - PointsToPixels(w.Height, "Y")) / 2), "Y");
}
public static double PixelsToPoints(int pixels, string direction)
{
if (direction == "X")
{
return pixels * SystemParameters.WorkArea.Width / System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Width;
}
else
{
return pixels * SystemParameters.WorkArea.Height / System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Height;
}
}
public static double PointsToPixels(double wpfPoints, string direction)
{
if (direction == "X")
{
return wpfPoints * System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Width / SystemParameters.WorkArea.Width;
}
else
{
return wpfPoints * System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Height / SystemParameters.WorkArea.Height;
}
}
Another short way to make mainwindow render at center of the computer screen using code behind.
this.Left = (System.Windows.SystemParameters.PrimaryScreenWidth - this.Width)/2;
this.Top = (System.Windows.SystemParameters.PrimaryScreenHeight - this.Height)/2;
Based on #Wild_A answer I just subscribed to the SizeChanged event, and added this event handler:
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
try
{
Rect workArea = SystemParameters.WorkArea;
this.Left = (workArea.Width - e.NewSize.Width) / 2 + workArea.Left;
this.Top = (workArea.Height - e.NewSize.Height) / 2 + workArea.Top;
}
catch (Exception ex) { ... Handel exception; }
}
Just use:
WindowStartupLocation="CenterScreen"
And in case you only want to center horizontally / vertically, You can override the OnActivated method and set left or top to zero like this:
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
// to center Vertically
Left = 0;
// or user top = 0 to center Horizontally
//top = 0;
}
Go to property window of MainWindow.xaml
find WindowStartupLocation property from Common category
select CenterScreen option from dropdown
Run the Application
For Full Screen
Go to property window of MainWindow.xaml
find WindowState property from Common category
select Maximized option from dropdown
Run the Application
Copy-paste good quality extension code.
Runtime:
using System;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Interop;
namespace Extensions
{
/// <summary>
/// <see cref="Window"/> extensions.
/// </summary>
public static class WindowExtensions
{
/// <summary>
/// Moves the window to the center of the current screen, also considering dpi.
/// </summary>
/// <param name="window"></param>
/// <exception cref="ArgumentNullException"></exception>
public static void MoveToCenter(this Window window)
{
window = window ?? throw new ArgumentNullException(nameof(window));
var helper = new WindowInteropHelper(window);
var screen = Screen.FromHandle(helper.Handle);
var area = screen.WorkingArea;
var source = PresentationSource.FromVisual(window);
var dpi = source?.CompositionTarget?.TransformFromDevice.M11 ?? 1.0;
window.Left = dpi * area.Left + (dpi * area.Width - window.Width) / 2;
window.Top = dpi * area.Top + (dpi * area.Height - window.Height) / 2;
}
}
}
Initial position:
<Window WindowStartupLocation="CenterScreen">
</Window>
You will have to find this line :
Title="MainWindow" Height="450" Width="800"
And you add this line to it :
WindowStartupLocation="CenterScreen"
To become this :
Title="MainWindow" Height="450" Width="800" WindowStartupLocation="CenterScreen">
Thank me Later ♥
If you it to be maximized at once
this.WindowState = System.Windows.WindowState.Maximized;
Related
I have been chasing a memory leak and have gotten down to a pretty simple case. Whenever the canvas size changes, the window clears the canvas and adds 1,000 random points to the canvas. It also reports the memory usage in the titlebar of the window.
Memory starts out below 2MB.
If you grab the corner of the window and move it around a bit, the memory usage skyrockets to over 100MB in 10 sec. and the UI gets sluggish. Stop moving for 10 sec and the memory creeps back to 11MB (Not shown in the titlebar because there isn't an update but never back to the original <2MB).
Is it possible that simple wpf graphics are this broken? Am I doing something wrong? I call GC.Collect() on every repaint, so why does any memory recovery take so long?
Release/debug mode doesn't make any difference. Making the lines invisible doesn't make any difference. Changing line characteristics doesn't matter Using different color brushes on each line doesn't matter. The leak is proportional to the number of lines on the canvas.
How can it be that simple lines on a canvas aren't collected when the canvas is cleared?
Any ideas greatly appreciated!
Here's the code:
<Window x:Class="SillyLeakTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" SizeChanged="Window_SizeChanged">
<Canvas x:Name="theCanvas"/>
</Window>
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
namespace SillyLeakTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
Random rand = new Random();
public MainWindow()
{
InitializeComponent();
}
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
GC.Collect();
Title = "Memory Used: " + GC.GetTotalMemory(true).ToString("##,#");
Point windowSize = new Point(theCanvas.ActualWidth, theCanvas.ActualHeight);
Point center = new Point(windowSize.X / 2, windowSize.Y / 2);
theCanvas.Children.Clear();
SolidColorBrush color = new SolidColorBrush(Color.FromArgb(255, (byte)rand.Next(1, 255),
(byte)rand.Next(1, 255), (byte)rand.Next(1, 255)));
double scale = 1;
for (int i = 0; i < 1000; i++)
{
Point p = new Point(rand.Next(-(int)center.X, (int)center.X), rand.Next(-(int)center.Y, (int)center.Y));
theCanvas.Children.Add(new Line
{
X1 = center.X + p.Y * scale,
Y1 = center.Y - p.X * scale,
X2 = center.X + p.Y + 2 * scale,
Y2 = center.Y - p.X + 2 * scale,
StrokeThickness = 1,
//StrokeEndLineCap = PenLineCap.Round,
//StrokeStartLineCap = PenLineCap.Round,
// Stroke = new SolidColorBrush(P1.TheColor)
Stroke = color,
});
}
}
}
}
OK I can confirm. WPF does some weird sht when debugger attached. Start without debugger via CTRL-F5
There is no screen decoration and the app runs smooth and fast , tops out at ~2.8mb.
Attach debugger from VS and suddenly we have a colored border and that strange debug panel at the top of the screen. Now the memory grows exponentially.
see Different behavior of WPF Application: IDE debugging vs directly running the executable
So I have this custom panel that I am making act like a form (I'm experimenting) and whenever I move it around the screen with the mouse it results in 20%+ CPU usage. The snippet below is the cause because if I comment it out it works fine, but I obviously can't move the panel with the mouse then.
private void MoveWithEdgeLock()
{
targetLocation = new Point(Cursor.Position.X - downLocation.X, Cursor.Position.Y - downLocation.Y);
Point placement = targetLocation;
if (targetLocation.X <= EDGELOCK && targetLocation.X >= -EDGELOCK)
{
placement = new Point(0, placement.Y);
}
else if (targetLocation.X + Width >= Screen.PrimaryScreen.Bounds.Right - EDGELOCK && targetLocation.X + Width <= Screen.PrimaryScreen.Bounds.Right + EDGELOCK)
{
placement = new Point(Screen.PrimaryScreen.Bounds.Right - Width, placement.Y);
}
if (targetLocation.Y <= EDGELOCK && targetLocation.Y >= -EDGELOCK)
{
placement = new Point(placement.X, 0);
}
else if (targetLocation.Y + Height >= Screen.PrimaryScreen.Bounds.Bottom - TASKBAR_HEIGHT - EDGELOCK && targetLocation.Y + Height <= (Screen.PrimaryScreen.Bounds.Bottom - TASKBAR_HEIGHT) + EDGELOCK)
{
placement = new Point(placement.X, Screen.PrimaryScreen.Bounds.Bottom - TASKBAR_HEIGHT - Height);
}
Location = placement;
}
Even if I simply make it this;
Location = new Point(Cursor.Position.X - downLocation.X, Cursor.Position.Y - downLocation.Y);
It still results in high CPU usage.
Oh and I call either of these like so:
protected override void OnMouseMove(MouseEventArgs e)
{
if (canMove)
MoveWithEdgeLock();
base.OnMouseMove(e);
}
CanMove simply gets set via OnMouseDown/OnMouseUp.
Sorry for long lines, I'm used to writing on one line.
EDIT
As I have said in a comment, I have a borderless form taking up my entire screen - 1920x1080. That does have a BackgroundImage and I convert/force it to use Format32bppPArgb and resize it to Clientsize.
I have tried removing the BackgroundImage (so it was just a black back color) and that made no difference.
I have also tried remove all controls on the panel and that also made no difference.
I have even tried using the P/Invoke method as noted HERE, and it still results in high CPU Usage
As the application starts, I'd like my WPF window to automatically snap to the right edge of the screen. Is there a way to do that? I also want to be able to retain its dimensions. So, unlike the snapping behavior that happens when you drag a window to the edge of the screen, causing the window to resize to either a portion of the screen or full screen, I want my window to simply snap to the edge at a certain location by default or if dragged by the user to a specific location afterwards, without resizing. I still want to retain the ability of the user to drag the window away from the edge.
Is there anything like that already implemented or would I have to create my own behavior schema? I tried numerous search keyword combinations, but couldn't find anything similar to what I'm doing. Some of the searches included disabling snapping behavior or providing snapping behavior, but nothing in the way I described above.
EDIT:
I haven't been able to find a ready solution, so I wrote my own. This solution is based on BenVlodgi's suggestions, so I thank him for helping me out. This is a very rough implementation and still requires a lot of polishing and better code techniques, but it works and it's a good base for anyone wanting to try this. It's incredibly simple and works very well with WPF. The only limitation of this implementation is that I haven't tried getting it to work with two screens yet, but it's incredibly simple (I'm just not going to have time for it and I don't need that functionality at this point). So, here's the code and I hope that it helps someone out there:
public partial class MainWindow : Window
{
// Get the working area of the screen. It excludes any dockings or toolbars, which
// is exactly what we want.
private System.Drawing.Rectangle screen =
System.Windows.Forms.Screen.PrimaryScreen.WorkingArea;
// This will be the flag for the automatic positioning.
private bool dragging = false;
// The usual initialization routine
public MainWindow()
{
InitializeComponent();
}
// Wait until window is lodaded, but prior to being rendered to set position. This
// is done because prior to being loaded you'll get NaN for this.Height and 0 for
// this.ActualHeight.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Sets the initial position.
SetInitialWindowPosition();
// Sets the monitoring timer loop.
InitializeWindowPositionMonitoring();
}
// Allows the window to be dragged where the are no other controls present.
// PreviewMouseButton could be used, but then you have to do more work to ensure that if
// you're pressing down on a button, it executes its routine if dragging was not performed.
private void Window_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
// Set the dragging flag to true, so that position would not be reset automatically.
if (e.ChangedButton == System.Windows.Input.MouseButton.Left)
{
dragging = true;
this.DragMove();
}
}
// Similar to MouseDown. We're setting dragging flag to false to allow automatic
// positioning.
private void Window_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (e.ChangedButton == System.Windows.Input.MouseButton.Left)
{
dragging = false;
}
}
// Sets the initial position of the window. I made mine static for now, but later it can
// be modified to be whatever the user chooses in the settings.
private void SetInitialWindowPosition()
{
this.Left = screen.Width - this.Width;
this.Top = screen.Height / 2 - this.Height / 2;
}
// Setup the monitoring routine that automatically positions the window based on its location
// relative to the working area.
private void InitializeWindowPositionMonitoring()
{
var timer = new System.Windows.Threading.DispatcherTimer();
timer.Tick += delegate
{
// Check if window is being dragged (assuming that mouse down on any portion of the
// window is connected to dragging). This is a fairly safe assumption and held
// true thus far. Even if you're not performing any dragging, then the position
// doesn't change and nothing gets reset. You can add an extra check to see if
// position has changed, but there is no significant performance gain.
// Correct me if I'm wrong, but this is just O(n) execution, where n is the number of
// ticks the mouse has been held down on that window.
if (!dragging)
{
// Checking the left side of the window.
if (this.Left > screen.Width - this.Width)
{
this.Left = screen.Width - this.Width;
}
else if (this.Left < 0)
{
this.Left = 0;
}
// Checking the top of the window.
if (this.Top > screen.Height - this.Height)
{
this.Top = screen.Height - this.Height;
}
else if (this.Top < 0)
{
this.Top = 0;
}
}
};
// Adjust this based on performance and preference. I set mine to 10 milliseconds.
timer.Interval = new TimeSpan(0, 0, 0, 0, 10);
timer.Start();
}
}
Make sure that your window has the following:
MouseDown="Window_MouseDown"
MouseUp="Window_MouseUp"
WindowStartupLocation="Manual"
Loaded="Window_Loaded"
Also, this doesn't work well with Windows native components of the window, such as the top bar, so I disable the style and create my own (which is actually good for me, since I don't want the windows style for this):
WindowStyle="None"
I don't like the polling approach, but it's been excessively difficult to find a better solution that is still simple in WPF, so I'm gonna post my own.
The solution that I found is actually quite simple, in that it reimplements the behaviour of the DragMove() method of the window, which gives you the option to change the window position while it's being dragged. The following code reimplements DragMove() by storing the distance between the top left corner of the window and the mouse cursor.
public partial class MainWindow : Window
{
// this is the offset of the mouse cursor from the top left corner of the window
private Point offset = new Point();
public MainWindow()
{
InitializeComponent();
}
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Point cursorPos = PointToScreen(Mouse.GetPosition(this));
Point windowPos = new Point(this.Left, this.Top);
offset = (Point)(cursorPos - windowPos);
// capturing the mouse here will redirect all events to this window, even if
// the mouse cursor should leave the window area
Mouse.Capture(this, CaptureMode.Element);
}
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Mouse.Capture(null);
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
if (Mouse.Captured == this && Mouse.LeftButton == MouseButtonState.Pressed)
{
Point cursorPos = PointToScreen(Mouse.GetPosition(this));
double newLeft = cursorPos.X - offset.X;
double newTop = cursorPos.Y - offset.Y;
// here you can change the window position and implement
// the snapping behaviour that you need
this.Left = newLeft;
this.Top = newTop;
}
}
}
Now you could implement the snapping / sticky window behaviour like this: The window will stick to the edge of the screen if it's within a range of 25 pixels (plus or minus).
int snappingMargin = 25;
if (Math.Abs(SystemParameters.WorkArea.Left - newLeft) < snappingMargin)
newLeft = SystemParameters.WorkArea.Left;
else if (Math.Abs(newLeft + this.ActualWidth - SystemParameters.WorkArea.Left - SystemParameters.WorkArea.Width) < snappingMargin)
newLeft = SystemParameters.WorkArea.Left + SystemParameters.WorkArea.Width - this.ActualWidth;
if (Math.Abs(SystemParameters.WorkArea.Top - newTop) < snappingMargin)
newTop = SystemParameters.WorkArea.Top;
else if (Math.Abs(newTop + this.ActualHeight - SystemParameters.WorkArea.Top - SystemParameters.WorkArea.Height) < snappingMargin)
newTop = SystemParameters.WorkArea.Top + SystemParameters.WorkArea.Height - this.ActualHeight;
The downside of this approach is that the snapping will not work, if the window is being dragged on the title bar, because that doesn't fire the OnMouseLeftButtonDown event (which I don't need, because my window is borderless). Maybe it will still help someone.
There is no API calls you can make (as far as I've seen) to use the Windows snapping features, however you could just get the System.Windows.Forms.Screen.PrimaryScreen.WorkingArea of the screen and set your Top, Left, Height and Width Properties of your Window accordingly.
Edit: The above suggestion does require Forms, which you probably don't want. I believe the WPF equivalent is System.Windows.SystemParameters.WorkArea
I have a problem with multimonitor system and maximizing my borderless WPF C# .NET 4.0 window. One monitor is 1680x1050 (primary) and the second is 1920x1080 (secondary). When I'm maximizing my window on the primary screen - no problems, even when I switch the order of those two. But every time I'm trying to maximize it on the secondary screen it's being cut off to the size of primary monitor. I see that the window size is given appropriate but this doesn't work.
Getting the size of monitor:
private System.Windows.Forms.Screen GetCurrentScreen()
{
System.Drawing.Point centerPoint = new System.Drawing.Point((int)(Left + Width / 2), (int)(Top + Height / 2));
foreach (System.Windows.Forms.Screen s in System.Windows.Forms.Screen.AllScreens)
{
if (s.Bounds.Contains(centerPoint))
{
return s;
}
}
return null;
}
Maximizing:
private void Maximize
{
if (this.WindowState == WindowState.Normal)
{
var scr = GetCurrentScreen();
//this.MaxHeight = scr.WorkingArea.Height;
//this.MaxWidth = scr.WorkingArea.Width;
if (scr != null)
{
if (scr.Primary)
{
this.MaxHeight = SystemParameters.MaximizedPrimaryScreenHeight;
this.MaxWidth = SystemParameters.MaximizedPrimaryScreenWidth;
}
else
{
this.MaxHeight = double.PositiveInfinity; //even ridiculous values don't work
this.MaxWidth = double.PositiveInfinity;
this.Height = scr.WorkingArea.Height; // correct values of 2nd screen
this.Width = scr.WorkingArea.Width;
}
}
else
{
this.MaxHeight = SystemParameters.MaximizedPrimaryScreenHeight;
this.MaxWidth = SystemParameters.MaximizedPrimaryScreenWidth;
}
this.WindowState = WindowState.Maximized;
}
What I get is:
http://imgur.com/ZYzVV9Q,yf7lSfY#1
What I want:
http://imgur.com/ZYzVV9Q,yf7lSfY#0
Thanks
This worked for me last time I had to maximize a window on my secondary screen:
Screen sTwo = Screen.AllScreens[1];
this.Top = sTwo.Bounds.Top;
this.Left = sTwo.Bounds.Left;
this.Width = sTwo.Bounds.Width;
this.Height = sTwo.Bounds.Height;
Make sure you add the reference
using System.Windows.Forms;
And set
AllowsTransparency="True"
in your *.xaml
There is a possibility that some of your code is causing this issue as I cannot replicate it on my two screens... they are both the same size, but if I change the resolution on one, the application still maximises correctly. If you start a new WPF Application and just add
WindowState = "Maximized"
to the Window declaration, do you still have the same problem?
Try to override or correct ArrangeOverride method:
http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.arrangeoverride.aspx
When overridden in a derived class, positions child elements and
determines a size for a FrameworkElement derived class
Symptoms are similar.
Is there a way to check that a WinForm is fully visible on the screen (eg is not out of bounds of the screen?)
I've tried using SystemInformation.VirtualScreen for this, which works great as long as the virtual screen is a rectangle, but as soon as it's not (eg 3 screens in a L shape), SystemInformation.VirtualScreen returns the smallest rectangle containing all the visible pixels (so a window on the upper right corner of the L won't be visible although it's in the virtual screen)
The reason I'm trying to achieve this is that I'd like my program to open its child windows in the last location they were on, but I don't want those window to be out of view if the user changes is setup (eg unplugs the extra screen from his laptop)
Here's how I eventually did it :
bool isPointVisibleOnAScreen(Point p)
{
foreach (Screen s in Screen.AllScreens)
{
if (p.X < s.Bounds.Right && p.X > s.Bounds.Left && p.Y > s.Bounds.Top && p.Y < s.Bounds.Bottom)
return true;
}
return false;
}
bool isFormFullyVisible(Form f)
{
return isPointVisibleOnAScreen(new Point(f.Left, f.Top)) && isPointVisibleOnAScreen(new Point(f.Right, f.Top)) && isPointVisibleOnAScreen(new Point(f.Left, f.Bottom)) && isPointVisibleOnAScreen(new Point(f.Right, f.Bottom));
}
There might be some false positive if the user has a "hole" in his display setup (see example below) but I don't think any of my users will ever be in such a situation :)
[1]
[2][X][3]
Here's how I would do it:
This will move the control (form) inside the Display bounds as close as it can to the original location.
private void EnsureVisible(Control ctrl)
{
Rectangle ctrlRect = ctrl.DisplayRectangle; //The dimensions of the ctrl
ctrlRect.Y = ctrl.Top; //Add in the real Top and Left Vals
ctrlRect.X = ctrl.Left;
Rectangle screenRect = Screen.GetWorkingArea(ctrl); //The Working Area fo the screen showing most of the Ctrl
//Now tweak the ctrl's Top and Left until it's fully visible.
ctrl.Left += Math.Min(0, screenRect.Left + screenRect.Width - ctrl.Left - ctrl.Width);
ctrl.Left -= Math.Min(0, ctrl.Left - screenRect.Left);
ctrl.Top += Math.Min(0, screenRect.Top + screenRect.Height - ctrl.Top - ctrl.Height);
ctrl.Top -= Math.Min(0, ctrl.Top - screenRect.Top);
}
Of course to answer your original question instead of moving the control you could just check if any of the 4 Math.Min's returned something other than 0.
This is my solution. It solves the "hole" problem.
/// <summary>
/// True if a window is completely visible
/// </summary>
static bool WindowAllVisible(Rectangle windowRectangle)
{
int areaOfWindow = windowRectangle.Width * windowRectangle.Height;
int areaVisible = 0;
foreach (Screen screen in Screen.AllScreens)
{
Rectangle windowPortionOnScreen = screen.WorkingArea;
windowPortionOnScreen.Intersect(windowRectangle);
areaVisible += windowPortionOnScreen.Width * windowPortionOnScreen.Height;
if (areaVisible >= areaOfWindow)
{
return true;
}
}
return false;
}
I was trying to do this exact same thing detect if the window opened off screen then accordingly reposition it to the nearest location where it was previously found at. I look all over the internet and nothing worked from all the solutions people offered.
So i took it upon myself to make my own class that does just exactly this and it works 100%.
Here is my code
public static class ScreenOperations
{
public static bool IsWindowOnAnyScreen(Window Window, short WindowSizeX, short WindowSizeY, bool AutoAdjustWindow)
{
var Screen = System.Windows.Forms.Screen.FromHandle(new WindowInteropHelper(Window).Handle);
bool LeftSideTest = false, TopSideTest = false, BottomSideTest = false, RightSideTest = false;
if (Window.Left >= Screen.WorkingArea.Left)
LeftSideTest = true;
if (Window.Top >= Screen.WorkingArea.Top)
TopSideTest = true;
if ((Window.Top + WindowSizeY) <= Screen.WorkingArea.Bottom)
BottomSideTest = true;
if ((Window.Left + WindowSizeX) <= Screen.WorkingArea.Right)
RightSideTest = true;
if (LeftSideTest && TopSideTest && BottomSideTest && RightSideTest)
return true;
else
{
if (AutoAdjustWindow)
{
if (!LeftSideTest)
Window.Left = Window.Left - (Window.Left - Screen.WorkingArea.Left);
if (!TopSideTest)
Window.Top = Window.Top - (Window.Top - Screen.WorkingArea.Top);
if (!BottomSideTest)
Window.Top = Window.Top - ((Window.Top + WindowSizeY) - Screen.WorkingArea.Bottom);
if (!RightSideTest)
Window.Left = Window.Left - ((Window.Left + WindowSizeX) - Screen.WorkingArea.Right);
}
}
return false;
}
}
Usage: ScreenOperations.IsWindowOnAnyScreen(WPFWindow, WPFWindowSizeX, WPFWindowSizeY, true);
this will check if the window is offscreen at all, that being 1 pixel under the taskbar or 1 pixel off the users current monitor.
It detects which monitor the window if on first so it should work with multi-monitors.
this method returns true if the window is on the screen and false if its not.
The last parameter is for auto adjusting the window to the nearest part on the screen for you. if you put false for that parameter it will not adjust the window for you.
So this is a complete WPF solution to this issue, and WinForm converting should be easy if you know how to do it, Change Window to Form and FromHandle(Form.Handle) should work.
Check whether Screen.AllScreens.Any(s => s.WorkingArea.Contains(rect))