Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

enter image description here

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.

like image 317
sapbucket Avatar asked Oct 26 '18 16:10

sapbucket


1 Answers

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);
like image 110
Jimi Avatar answered Sep 19 '22 12:09

Jimi