Using SetWindowPos with multiple monitors
Using user32.dll
and C# I wrote the method that you see below. Using a process handle for a window, it will set the window position at a provided (x, y)
location.
However, in a multi-monitored environment the code below sets the window position to the primary monitor, only. I would like to be able to select which monitor, too.
Can someone please explain how this can be accomplished using SetWindowPos
or perhaps a combination with another user32.dll
function?
[DllImport("user32.dll", SetLastError = true)]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);
private const int SWP_NOSIZE = 0x0001;
private const int SWP_NOZORDER = 0x0004;
private const int SWP_SHOWWINDOW = 0x0040;
public static void SetWindowPosition(Process p, int x, int y)
{
IntPtr handle = p.MainWindowHandle;
if (handle != IntPtr.Zero)
{
SetWindowPos(handle, IntPtr.Zero, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_SHOWWINDOW);
}
}
Solution based upon Jimi's comment.
Here is my monitor configuration:
Observe that I have a secondary monitor to the left of my primary monitor. After reading the Virtual Monitor link that Jimi provided I discovered that to move windows to the secondary monitor that I must use a negative x-value because it is left of the primary monitor's origin (top left corner, or (0, 0)
).
Therefore, if I want to have my window position set to the secondary monitor's <0,0> coordinate, that I must SUBTRACT the x-width of the secondary monitor from the origin of the primary monitor, like this:
(0, 0) - (1920, 0) = (-1920, 0)
Now, when I call SetWindowPosition
in my client code, I call it like this:
SetWindowPosition(Process p, -1920, 0);
Note: I do not know what you would do if the monitors have different resolutions. That is a more complex topic and not a question that I am asking. Also, I did not see a need to explore deeper in to the topic as the simple example above solved all of my issues.
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
If we change, using the System applet, the Primary Screen reference, setting it to \\.\DISPLAY3
, the coordinates will be modified accordingly:
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, callMonitoFromWindow
, 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);