Restoring Window Size/Position with Multiple Monitors

Restoring Window Size/Position With Multiple Monitors

The answer provided by VVS was a great help! I found two minor issues with it though, so I am reposting the bulk of his code with these revisions:

(1) The very first time the application runs, the form is opened in a Normal state but is sized such that it appears as just a title bar. I added a conditional in the constructor to fix this.

(2) If the application is closed while minimized or maximized the code in OnClosing fails to remember the dimensions of the window in its Normal state. (The 3 lines of code--which I have now commented out--seems reasonable but for some reason just does not work.) Fortunately I had previously solved this problem and have included that code in a new region at the end of the code to track window state as it happens rather than wait for closing.


With these two fixes in place, I have tested:

A. closing in normal state--restores to same size/position and state

B. closing in minimized state--restores to normal state with last normal size/position

C. closing in maximized state--restores to maximized state and remembers its last size/position when one later adjusts to normal state.

D. closing on monitor 2--restores to monitor 2.

E. closing on monitor 2 then disconnecting monitor 2--restores to same position on monitor 1

David: your code allowed me to achieve points D and E almost effortlessly--not only did you provide a solution for my question, you provided it in a complete program so I had it up and running almost within seconds of pasting it into Visual Studio. So a big 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
}

Remembering window settings (size, position, location) and restoring, but to screen 1

I presume by "screen 1" you mean "primary display". All other displays are secondary, which may or may not exist when you run your app, and, if exist, may not be the same displays when you saved settings, nor may be in the same arrangement at the time of app start.

Look at this API: https://learn.microsoft.com/dotnet/api/system.windows.forms.screen.allscreens

You will have to do several things.

  1. When saving settings you have to translate the current window coordinates to desired coordinates on your primary display. This includes account for resolutions and scale factors of the current display and the primary monitor (if they are different).
    E.g. your primary monitor could be at 1920x1200 100% SF, and your secondary monitor when a form is at 2560x1440 125%.
    When calculating new coordinates you need to ensure the new coordinates don't end up outside visible area on the primary monitor.

  2. When persist information persist at 100% SF.

  3. When restore recalibrate the position if the primary monitor isn't at 100% SF, as well as ensure that the coordinates are within the visible area of the monitor (the primary monitor may have changed between the app runs).

Persisting form location and size for dual monitors

Take a look at C#: How to make a form remember its Bounds and WindowState (Taking dual monitor setups into account)

Using SetWindowPos with multiple monitors

System Displays disposition and VirtualScreen

In a Windows System, the Primary Screen (programming perspective) is the Display device which has its upper left corner position set at Point(0,0).

This implies that the Displays positioned on the left of the Primary Screen, will have negative X coordinates (the Y coordinate could be negative if the Display is in Portrait layout).

The Displays on the right will have positive X coordinates (the Y coordinate could be negative if the Display is in Portrait layout).

Displays on the Left of the Primary Screen:

In other words, Displays that have a negative Point.X origin.

The Point.X origin is the sum of all of the preceding Screens[].Width, subtracted from the Point.X origin coordinate of the Primary Screen.

Displays on the Right of the Primary Screen:

In other words, Displays that have a positive Point.X origin.

The Point.X origin is the sum of all of the preceding Screens[].Width, Primary included, added to the origin Point.X coordinate of the Primary Screen.


Important note about Dpi Awareness:

If the application is not DPI Aware, all these measures can be compromised by the virtualization and automatic DPI Scaling performed by the System. All measures will be uniformed to a default 96 Dpi: the application will receive scaled values. This also includes the values retrieved from non-Dpi ware Win32 API functions. See:

High DPI Desktop Application Development on Windows

Enable support for all targeted Systems in the app.manifest file, uncommenting the required sections.

Add/Uncomment the DpiAware and DpiAwareness sections in the app.manifest file.

The PerMonitorV2 Dpi Awareness mode can be set in the app.config file (available from Windows 10 Creators Edition).

See also:

DPI and Device-Independent Pixels

Mixed-Mode DPI Scaling and DPI-aware APIs


Example:

Consider a System with 3 Monitors:

PrimaryScreen             (\\.\DISPLAY1):  Width: (1920 x 1080)
Secondary Display (Right) (\\.\DISPLAY2): Width: (1360 x 768)
Secondary Display (Left) (\\.\DISPLAY3): Width: (1680 x 1050)

PrimaryScreen:
Bounds: (0, 0, 1920, 1080) Left: 0 Right: 1920 Top: 0 Bottom: 1080
Secondary Display (Right):
Bounds: (1360, 0, 1360, 768) Left: 1360 Right: 2720 Top: 0 Bottom: 768
Secondary Display (Left):
Bounds: (-1680, 0, 1680, 1050) Left: -1680 Right: 0 Top: 0 Bottom: 1050

Multi Display Disposition 1

If we change, using the System applet, the Primary Screen reference, setting it to \\.\DISPLAY3, the coordinates will be modified accordingly:

Multi Display Disposition 1

Virtual Screen

The Virtual Screen is a virtual display, which dimensions are represented by:

Origin: the origin coordinate of the left-most Screen

Width: the sum of all the Screens Widths.

Height: the Height of the highest Screen.

These measure are reported by SystemInformation.VirtualScreen

The Primary Screen Size is reported by SystemInformation.PrimaryMonitorSize

All the Screens current measures and position can also be retrieved using Screen.AllScreens and inspecting each \\.\DISPLAY[N] properties.

Using the preceding example as reference, in the first disposition, the VirtualScreen bounds are:

Bounds: (-1680, 0, 3280, 1080)  Left: -1680  Right: 3280   Top: 0  Bottom: 1080

In the second disposition, the VirtualScreen bounds are:

Bounds: (0, 0, 4960, 1080)  Left: 0  Right: 4960   Top: 0  Bottom: 1080

Window Position inside a Display area:

The Screen class offers multiple methods that can be used to determine in which screen a specific window is currently displayed:

Screen.FromControl([Control reference])

Returns the Screen object that contains the largest section of the specified Control reference.

Screen.FromHandle([Window Handle])

Returns the Screen object that contains the largest section of the Window\Control referenced by an Handle

Screen.FromPoint([Point])

Returns the Screen object that contains a specific Point

Screen.FromRectangle([Rectangle])

Returns the Screen object that contains the largest section of the specified Rectangle

Screen.GetBounds() (overloaded)

Returns a Rectangle structure that references the Screen Bounds that contain:

  • a specific Point
  • largest section of the specified Rectangle
  • A Control reference

To determine the \\.\DISPLAY[N] in which the current Form is shown, call (for example):

Screen.FromHandle(this);

To determine in which Screen a secondary Form is shown:

(Using the Displays layout shown in the sample images)

var f2 = new Form2();
f2.Location = new Point(-1400, 100);
f2.Show();
Rectangle screenSize = Screen.GetBounds(f2);
Screen screen = Screen.FromHandle(f2.Handle);

screenSize will be equal to the \\.\DISPLAY3 Bounds.

screen will be the Screen object representing the \\.\DISPLAY3 properties.

screen object will also report the \\.\DISPLAY[N] name of the Screen in which form2 is shown.


Obtain the hMonitor Handle of a Screen object:

The .NET Reference Source shows that the hMonitor is returned calling [Screen].GetHashCode();

IntPtr monitorHwnd = new IntPtr([Screen].GetHashCode());

Or using the same native Win32 functions:

MonitorFromWindow, MonitorFromPoint and MonitorFromRect

[Flags]
internal enum MONITOR_DEFAULTTO
{
NULL = 0x00000000,
PRIMARY = 0x00000001,
NEAREST = 0x00000002,
}

[DllImport("User32.dll", SetLastError = true)]
internal static extern IntPtr MonitorFromWindow(IntPtr hwnd, MONITOR_DEFAULTTO dwFlags);

[DllImport("User32.dll", SetLastError = true)]
internal static extern IntPtr MonitorFromPoint([In] POINT pt, MONITOR_DEFAULTTO dwFlags);

[DllImport("User32.dll", SetLastError = true)]
internal static extern IntPtr MonitorFromRect([In] ref RECT lprc, MONITOR_DEFAULTTO dwFlags);
  • To detect Window movements between Monitors, you can handle WM_WINDOWPOSCHANGED messages, call MonitoFromWindow, then GetScaleFactorForMonitor to determine whether there's a DPI change and react to a new setting, eventually.

Obtain a Handle of the device context of a Screen:

A generic method to retrieve the hDC of any Display available.

The Screen coordinates or Screen Device can determined using one of the methods previously described when only a specific Screen reference is required.

The Screen.DeviceName property, which is retrieved calling GetMonitorInfo(), passing a MONITORINFOEX struct (see the declaration at the bottom) can be used as the lpszDriver parameter of GDI's CreateDC function. It will return the hDC of the display that Graphics.FromHdc can use to create a valid Graphics object, which will allow to paint on a specific screen.

Here, assuming at least two Displays are available:

[DllImport("gdi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern IntPtr CreateDC(string lpszDriver, string lpszDevice, string lpszOutput, IntPtr lpInitData);

[DllImport("gdi32.dll", SetLastError = true, EntryPoint = "DeleteDC")]
internal static extern bool DeleteDC([In] IntPtr hdc);

public static IntPtr CreateDCFromDeviceName(string deviceName)
{
return CreateDC(deviceName, null, null, IntPtr.Zero);
}

Screen[] screens = Screen.AllScreens;
IntPtr screenDC1 = CreateDCFromDeviceName(screens[0].DeviceName);
IntPtr screenDC2 = CreateDCFromDeviceName(screens[1].DeviceName);
using (Graphics g1 = Graphics.FromHdc(screenDC1))
using (Graphics g2 = Graphics.FromHdc(screenDC2))
using (Pen pen = new Pen(Color.Red, 10))
{
g1.DrawRectangle(pen, new Rectangle(new Point(100, 100), new Size(200, 200)));
g2.DrawRectangle(pen, new Rectangle(new Point(100, 100), new Size(200, 200)));
}

DeleteDC(screenDC1);
DeleteDC(screenDC2);

Declare MONITORINFOREX like this:

(dwFlags is set to 0x00000001 if it's the primary Monitor)

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public unsafe struct MONITORINFOEXW
{
public uint cbSize;
public RECT rcMonitor;
public RECT rcWork;
public uint dwFlags;
public fixed char szDevice[32];
}

Winforms: Restoring form position from a computer with two/more screens to a computer with one screen

Check the number of screen by using the Screen class in System.Windows.Forms. AllScreens contains the list of all screens currently on the system. If your saved position is no longer valid for this layout you should probably reset the position to a default one.

Is there a way to control browser size and position across multiple displays / monitors?

Go with a chrome/firefox extension that has access to window/tabs specific APIs.
Either embed your whole application in the extension or communicate with the extension through messages (chrome, there's an equivalent on firefox).

Support in Chrome is experimental.



Related Topics



Leave a reply



Submit