[ACCEPTED]-Restoring Window Size/Position With Multiple Monitors-appsettings
Try this code. Points of interest:
- Checks if the window is (partially) visible on any screen's working area. E.g. dragging it behind the task bar or moving it completely offscreen resets the position to windows default.
- Saves the correct bounds even if the Form is minimized or maximized (common error)
- Saves the WindowState correctly. Saving FormWindowState.Minimized is disabled by design.
The bounds 5 and state are stored in the appsettings 4 with their corresponding type so there's 3 no need to do any string parsing. Let the 2 framework do its serialization magic.
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
// this is the default
this.WindowState = FormWindowState.Normal;
this.StartPosition = FormStartPosition.WindowsDefaultBounds;
// check if the saved bounds are nonzero and visible on any screen
if (Settings.Default.WindowPosition != Rectangle.Empty &&
IsVisibleOnAnyScreen(Settings.Default.WindowPosition))
{
// first set the bounds
this.StartPosition = FormStartPosition.Manual;
this.DesktopBounds = Settings.Default.WindowPosition;
// afterwards set the window state to the saved value (which could be Maximized)
this.WindowState = Settings.Default.WindowState;
}
else
{
// this resets the upper left corner of the window to windows standards
this.StartPosition = FormStartPosition.WindowsDefaultLocation;
// we can still apply the saved size
this.Size = Settings.Default.WindowPosition.Size;
}
}
private bool IsVisibleOnAnyScreen(Rectangle rect)
{
foreach (Screen screen in Screen.AllScreens)
{
if (screen.WorkingArea.IntersectsWith(rect))
{
return true;
}
}
return false;
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
// only save the WindowState if Normal or Maximized
switch (this.WindowState)
{
case FormWindowState.Normal:
case FormWindowState.Maximized:
Settings.Default.WindowState = this.WindowState;
break;
default:
Settings.Default.WindowState = FormWindowState.Normal;
break;
}
// reset window state to normal to get the correct bounds
// also make the form invisible to prevent distracting the user
this.Visible = false;
this.WindowState = FormWindowState.Normal;
Settings.Default.WindowPosition = this.DesktopBounds;
Settings.Default.Save();
}
}
The 1 settings file for reference:
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="ScreenTest" GeneratedClassName="Settings">
<Profiles />
<Settings>
<Setting Name="WindowPosition" Type="System.Drawing.Rectangle" Scope="User">
<Value Profile="(Default)">0, 0, 0, 0</Value>
</Setting>
<Setting Name="WindowState" Type="System.Windows.Forms.FormWindowState" Scope="User">
<Value Profile="(Default)">Normal</Value>
</Setting>
</Settings>
</SettingsFile>
The answer provided by VVS was a great help! I 35 found two minor issues with it though, so 34 I am reposting the bulk of his code with 33 these revisions:
(1) The very first time 32 the application runs, the form is opened 31 in a Normal state but is sized such that 30 it appears as just a title bar. I added 29 a conditional in the constructor to fix 28 this.
(2) If the application is closed while 27 minimized or maximized the code in OnClosing 26 fails to remember the dimensions of the 25 window in its Normal state. (The 3 lines 24 of code--which I have now commented out--seems 23 reasonable but for some reason just does 22 not work.) Fortunately I had previously 21 solved this problem and have included that 20 code in a new region at the end of the code 19 to track window state as it happens rather 18 than wait for closing.
With these two fixes 17 in place, I have tested:
A. closing in normal 16 state--restores to same size/position and 15 state
B. closing in minimized state--restores 14 to normal state with last normal size/position
C. closing 13 in maximized state--restores to maximized 12 state and remembers its last size/position 11 when one later adjusts to normal state.
D. closing 10 on monitor 2--restores to monitor 2.
E. closing 9 on monitor 2 then disconnecting monitor 8 2--restores to same position on monitor 7 1
David: your code allowed me to achieve 6 points D and E almost effortlessly--not 5 only did you provide a solution for my question, you 4 provided it in a complete program so I had 3 it up and running almost within seconds 2 of pasting it into Visual Studio. So a big 1 thank you for that!
public partial class MainForm : Form
{
bool windowInitialized;
public MainForm()
{
InitializeComponent();
// this is the default
this.WindowState = FormWindowState.Normal;
this.StartPosition = FormStartPosition.WindowsDefaultBounds;
// check if the saved bounds are nonzero and visible on any screen
if (Settings.Default.WindowPosition != Rectangle.Empty &&
IsVisibleOnAnyScreen(Settings.Default.WindowPosition))
{
// first set the bounds
this.StartPosition = FormStartPosition.Manual;
this.DesktopBounds = Settings.Default.WindowPosition;
// afterwards set the window state to the saved value (which could be Maximized)
this.WindowState = Settings.Default.WindowState;
}
else
{
// this resets the upper left corner of the window to windows standards
this.StartPosition = FormStartPosition.WindowsDefaultLocation;
// we can still apply the saved size
// msorens: added gatekeeper, otherwise first time appears as just a title bar!
if (Settings.Default.WindowPosition != Rectangle.Empty)
{
this.Size = Settings.Default.WindowPosition.Size;
}
}
windowInitialized = true;
}
private bool IsVisibleOnAnyScreen(Rectangle rect)
{
foreach (Screen screen in Screen.AllScreens)
{
if (screen.WorkingArea.IntersectsWith(rect))
{
return true;
}
}
return false;
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
// only save the WindowState if Normal or Maximized
switch (this.WindowState)
{
case FormWindowState.Normal:
case FormWindowState.Maximized:
Settings.Default.WindowState = this.WindowState;
break;
default:
Settings.Default.WindowState = FormWindowState.Normal;
break;
}
# region msorens: this code does *not* handle minimized/maximized window.
// reset window state to normal to get the correct bounds
// also make the form invisible to prevent distracting the user
//this.Visible = false;
//this.WindowState = FormWindowState.Normal;
//Settings.Default.WindowPosition = this.DesktopBounds;
# endregion
Settings.Default.Save();
}
# region window size/position
// msorens: Added region to handle closing when window is minimized or maximized.
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
TrackWindowState();
}
protected override void OnMove(EventArgs e)
{
base.OnMove(e);
TrackWindowState();
}
// On a move or resize in Normal state, record the new values as they occur.
// This solves the problem of closing the app when minimized or maximized.
private void TrackWindowState()
{
// Don't record the window setup, otherwise we lose the persistent values!
if (!windowInitialized) { return; }
if (WindowState == FormWindowState.Normal)
{
Settings.Default.WindowPosition = this.DesktopBounds;
}
}
# endregion window size/position
}
Most of the other solutions here rely on 11 manually figuring out the current positioning 10 of each monitor. The edge cases are extremely 9 difficult to figure out, and very few apps 8 can get it right rolling their own.
The SetWindowPlacement 7 function within Windows itself correctly 6 handles all of the edge cases - if the window 5 would be positioned off of a visible screen, it 4 adjusts it accordingly.
The best example 3 I've seen in C# is on David Rickard's blog. Not 2 only does it show how to use SetWindowPlacement, it 1 also shows how to serialize the entire result. http://blogs.msdn.com/b/davidrickard/archive/2010/03/09/saving-window-size-and-location-in-wpf-and-winforms.aspx
This is the perfect one I think based on your answers and comments.
This solution is to save/restore form size and position with multi monitors + multi document, multi form or multi main form support. It 21 is not MDI form but Microsoft Word like multi document 20 with different main form instance.
Thanks 19 to VVS, msorens and Ian Goldby. I merge 18 the solution from VVS, msorens and MSDN 17 Application.Run Method (ApplicationContext) example to make the multi MainForm but 16 not MDI.
This fix of includes the comment 15 from Ian Goldby which uses Form.RestoreBounds
to eliminate 14 OnResize()
, OnMove()
and TrackWindowState()
.
I also fix to remember the Monitor 13 when the Form move to the other Monitor 12 and getting maximized before exit because 11 I not tracking the OnResize, OnMove. By 10 this fix, this solution support Windows 7 Snap feature which you 9 can drag the titlebar or Win+Arrow key to 8 snap Form window into any monitor edge or 7 make it maximized/normal as well as minimized.
This 6 solution implemented in Program but not 5 in the main Form to support multi main Form. However 4 you can use for single main Form also.
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using SimpleTestForm.Properties;
using System.Drawing;
namespace SimpleTestForm
{
static class Program
{
static MultiMainFormAppContext appContext;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
appContext = new MultiMainFormAppContext();
Application.Run(appContext);
}
/// <summary>
/// Create a new MainForm and restore the form size and position if necessary. This method can be called like from Menu File > New click event.
/// </summary>
/// <returns></returns>
public static MainForm createNewMainForm()
{
return appContext.createNewMainForm();
}
/// <summary>
/// Get the current active MainForm event if a dialog is opened. Useful to create Dictionary (MainForm, T) to store Form/document dependent field. Please set the Owner of child form to prevent null reference exception.
/// </summary>
/// <returns></returns>
public static MainForm GetCurrentMainFormInstance()
{
Form mainForm = Form.ActiveForm;
while (!(mainForm is MainForm) && mainForm.Owner != null)
mainForm = mainForm.Owner;
return mainForm as MainForm;
}
}
class MultiMainFormAppContext : ApplicationContext
{
List<MainForm> mainForms = new List<MainForm>();
Point newRestoredLocation = Point.Empty;
internal MultiMainFormAppContext()
{
createNewMainForm();
}
internal MainForm createNewMainForm()
{
MainForm mainForm = new MainForm();
mainForm.FormClosed += new FormClosedEventHandler(mainForm_FormClosed);
mainForm.LocationChanged += new EventHandler(mainForm_LocationChanged);
RestoreFormSizeNPosition(mainForm);
PreventSameLocation(mainForm);
mainForms.Add(mainForm);
mainForm.Show();
return mainForm;
}
private void PreventSameLocation(MainForm mainForm)
{
const int distance = 20;
foreach (MainForm otherMainForm in mainForms)
{
if (Math.Abs(otherMainForm.Location.X - mainForm.Location.X) < distance &&
Math.Abs(otherMainForm.Location.Y - mainForm.Location.Y) < distance)
mainForm.Location = new Point(mainForm.Location.X + distance, mainForm.Location.Y + distance);
}
}
/// <summary>
/// Restore the form size and position with multi monitor support.
/// </summary>
private void RestoreFormSizeNPosition(MainForm mainForm)
{
// this is the default
mainForm.WindowState = FormWindowState.Normal;
mainForm.StartPosition = FormStartPosition.WindowsDefaultBounds;
// check if the saved bounds are nonzero and visible on any screen
if (Settings.Default.WindowPosition != Rectangle.Empty &&
IsVisibleOnAnyScreen(Settings.Default.WindowPosition))
{
// first set the bounds
mainForm.StartPosition = FormStartPosition.Manual;
mainForm.DesktopBounds = Settings.Default.WindowPosition;
// afterwards set the window state to the saved value (which could be Maximized)
mainForm.WindowState = Settings.Default.WindowState;
}
else
{
// this resets the upper left corner of the window to windows standards
mainForm.StartPosition = FormStartPosition.WindowsDefaultLocation;
// we can still apply the saved size if not empty
if (Settings.Default.WindowPosition != Rectangle.Empty)
{
mainForm.Size = Settings.Default.WindowPosition.Size;
}
}
}
private void SaveFormSizeNPosition(MainForm mainForm)
{
// only save the WindowState as Normal or Maximized
Settings.Default.WindowState = FormWindowState.Normal;
if (mainForm.WindowState == FormWindowState.Normal || mainForm.WindowState == FormWindowState.Maximized)
Settings.Default.WindowState = mainForm.WindowState;
if (mainForm.WindowState == FormWindowState.Normal)
{
Settings.Default.WindowPosition = mainForm.DesktopBounds;
}
else
{
if (newRestoredLocation == Point.Empty)
Settings.Default.WindowPosition = mainForm.RestoreBounds;
else
Settings.Default.WindowPosition = new Rectangle(newRestoredLocation, mainForm.RestoreBounds.Size);
}
Settings.Default.Save();
}
private bool IsVisibleOnAnyScreen(Rectangle rect)
{
foreach (Screen screen in Screen.AllScreens)
{
if (screen.WorkingArea.IntersectsWith(rect))
return true;
}
return false;
}
void mainForm_LocationChanged(object sender, EventArgs e)
{
MainForm mainForm = sender as MainForm;
if (mainForm.WindowState == FormWindowState.Maximized)
{
// get the center location of the form incase like RibbonForm will be bigger and maximized Location wll be negative value that Screen.FromPoint(mainForm.Location) will going to the other monitor resides on the left or top of primary monitor.
// Another thing, you might consider the form is in the monitor even if the location (top left corner) is on another monitor because majority area is on the monitor, so center point is the best way.
Point centerFormMaximized = new Point (mainForm.DesktopBounds.Left + mainForm.DesktopBounds.Width/2, mainForm.DesktopBounds.Top + mainForm.DesktopBounds.Height/2);
Point centerFormRestored = new Point(mainForm.RestoreBounds.Left + mainForm.RestoreBounds.Width / 2, mainForm.RestoreBounds.Top + mainForm.RestoreBounds.Height / 2);
Screen screenMaximized = Screen.FromPoint(centerFormMaximized);
Screen screenRestored = Screen.FromPoint(centerFormRestored);
// we need to change the Location of mainForm.RestoreBounds to the new screen where the form currently maximized.
// RestoreBounds does not update the Location if you change the screen but never restore to FormWindowState.Normal
if (screenMaximized.DeviceName != screenRestored.DeviceName)
{
newRestoredLocation = mainForm.RestoreBounds.Location;
int screenOffsetX = screenMaximized.Bounds.Location.X - screenRestored.Bounds.Location.X;
int screenOffsetY = screenMaximized.Bounds.Location.Y - screenRestored.Bounds.Location.Y;
newRestoredLocation.Offset(screenOffsetX, screenOffsetY);
return;
}
}
newRestoredLocation = Point.Empty;
}
void mainForm_FormClosed(object sender, FormClosedEventArgs e)
{
MainForm mainForm = sender as MainForm;
SaveFormSizeNPosition(mainForm);
mainForm.FormClosed -= new FormClosedEventHandler(mainForm_FormClosed);
mainForm.LocationChanged -= new EventHandler(mainForm_LocationChanged);
mainForm.Dispose();
mainForms.Remove(mainForm);
if (mainForms.Count == 0) ExitThread();
}
}
}
Edit: PreventSameLocation 3 method added to make sure the 2nd form opened 2 not exactly on top of the 1st form and user 1 will notice the newly opened form.
If you have multiple monitors, I believe 18 the screen UI dimensions are simply larger. So 17 the normal "1 monitor" approach of storing 16 and restoring the location will just work. I 15 haven't tried this because I am away from 14 my second monitor but it shouldn't be hard 13 to test. The way you asked the Question 12 is seems like you haven't tested it.
Your 11 second requirement will mean you will have 10 to check the max sceen dimensions when restoring 9 the app, and then reposition as necessary. To 8 do this latter bit, I use this code:
private System.Drawing.Rectangle ConstrainToScreen(System.Drawing.Rectangle bounds)
{
Screen screen = Screen.FromRectangle(bounds);
System.Drawing.Rectangle workingArea = screen.WorkingArea;
int width = Math.Min(bounds.Width, workingArea.Width);
int height = Math.Min(bounds.Height, workingArea.Height);
// mmm....minimax
int left = Math.Min(workingArea.Right - width, Math.Max(bounds.Left, workingArea.Left));
int top = Math.Min(workingArea.Bottom - height, Math.Max(bounds.Top, workingArea.Top));
return new System.Drawing.Rectangle(left, top, width, height);
}
I call 7 this method when restoring the form. I 6 store the screen geometry in the registry 5 on form close, and then read the geometry 4 on form open. I get the bounds, but then 3 constrain the restored bounds to the actual 2 current screen, using the method above.
Save 1 on close:
// store the size of the form
int w = 0, h = 0, left = 0, top = 0;
if (this.Bounds.Width < this.MinimumSize.Width || this.Bounds.Height < this.MinimumSize.Height)
{
// The form is currently minimized.
// RestoreBounds is the size of the window prior to last minimize action.
w = this.RestoreBounds.Width;
h = this.RestoreBounds.Height;
left = this.RestoreBounds.Location.X;
top = this.RestoreBounds.Location.Y;
}
else
{
w = this.Bounds.Width;
h = this.Bounds.Height;
left = this.Location.X;
top = this.Location.Y;
}
AppCuKey.SetValue(_rvn_Geometry,
String.Format("{0},{1},{2},{3},{4}",
left, top, w, h, (int)this.WindowState));
Restore on form open:
// restore the geometry of the form
string s = (string)AppCuKey.GetValue(_rvn_Geometry);
if (!String.IsNullOrEmpty(s))
{
int[] p = Array.ConvertAll<string, int>(s.Split(','),
new Converter<string, int>((t) => { return Int32.Parse(t); }));
if (p != null && p.Length == 5)
this.Bounds = ConstrainToScreen(new System.Drawing.Rectangle(p[0], p[1], p[2], p[3]));
}
This is an old question, but here is a VB 7 version based on the previous answers.
One 6 problem with the answers suggested by VVS 5 and Michael Sorens is that a saved position 4 that only shows a couple of pixels on a 3 screen counts as visible. This solution 2 requires at least 50x50 pixels in intersection 1 before restoring the previous location.
Settings:
<Settings>
<Setting Name="WindowState" Type="System.Windows.Forms.FormWindowState" Scope="User">
<Value Profile="(Default)">Normal</Value>
</Setting>
<Setting Name="WindowBounds" Type="System.Drawing.Rectangle" Scope="User">
<Value Profile="(Default)">10, 10, 800, 600</Value>
</Setting>
</Settings>
Form:
Partial Public Class MainForm
Private loadingComplete As Boolean = False
Public Sub New()
InitializeComponent()
RestoreWindowLocation()
End Sub
Private Sub MainForm_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
loadingComplete = True
End Sub
Private Sub MainForm_Resize(sender As System.Object, e As System.EventArgs) Handles MyBase.Resize
TrackWindowLocation()
End Sub
Private Sub MainForm_Move(sender As System.Object, e As System.EventArgs) Handles MyBase.Move
TrackWindowLocation()
End Sub
Private Sub MainForm_FormClosing(sender As System.Object, e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing
SaveWindowLocation()
End Sub
Private Sub RestoreWindowLocation()
If IsRectangleVisible(My.Settings.WindowBounds) Then
Me.StartPosition = FormStartPosition.Manual
Me.DesktopBounds = My.Settings.WindowBounds
End If
If Not My.Settings.WindowState = FormWindowState.Minimized Then
Me.WindowState = My.Settings.WindowState
End If
End Sub
Private Sub TrackWindowLocation()
If loadingComplete Then
If Me.WindowState = FormWindowState.Normal Then
My.Settings.WindowBounds = Me.DesktopBounds
My.Settings.WindowState = Me.WindowState
End If
End If
End Sub
Private Sub SaveWindowLocation()
If Not Me.WindowState = FormWindowState.Minimized Then
My.Settings.WindowState = Me.WindowState
End If
If Me.WindowState = FormWindowState.Normal Then
My.Settings.WindowBounds = Me.DesktopBounds
End If
My.Settings.Save()
End Sub
Private Function IsRectangleVisible(Rectangle As Rectangle) As Boolean
For Each screen As Screen In screen.AllScreens
Dim r As Rectangle = Rectangle.Intersect(Rectangle, screen.WorkingArea)
If Not r.IsEmpty Then
If r.Width > 50 And r.Height > 50 Then Return True
End If
Next
Return False
End Function
End Class
More Related questions
We use cookies to improve the performance of the site. By staying on our site, you agree to the terms of use of cookies.