Using Setwindowpos With Multiple Monitors

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 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);

SetWindowPos and multiple monitors with different resolutions

The issue isn't with coordinate transformation. It is that windows isn't allowing SetWindowPos to adjust the window so it is larger than the screen. Of course, it is basing this on the primary monitor size.

See: Can a window be resized past the screen size/offscreen?



Related Topics



Leave a reply



Submit