I want a WPF window to cover all monitors.
<Window ... WindowStyle="None" ...
In app.manifest
DPI Awareness is enabled:
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>
The complete area covering all monitors is calculated by finding the outer limits of all Screen.AllScreens
.
Step 1: These screen coordinates are then translated to WPF coordinates using Visual.PointFromScreen
.
var topLeft = PointFromScreen(new Point(o.Left, o.Top));
var bottomRight = PointFromScreen(new Point(o.Right, o.Bottom));
var x1 = Left + topLeft.X;
var y1 = Top + topLeft.Y;
var x2 = Left + bottomRight.X;
var y2 = Top + bottomRight.Y;
var w = x2 - x1;
var h = y2 - y1;
Step 2: Finally the windows is positioned using the calculated coordinates.
Left = x1;
Top = y1;
Width = w;
Height = h;
However this fail with multiple monitors, each having separate DPI scaling.
Eventually I figured out that the output of Visual.PointFromScreen
changes with the monitor the window is located at.
The problem here is in step 2. After setting Left
the window has moved onto another monitor and a new set of coordinates need to be calculated using Visual.PointFromScreen
.
Same goes for Top
, Width
and Height
.
If I only set Left
the left side is placed correctly, but the moment I set Top
the previously value set to Left is now applied to the new scaling monitor and thus the left side also moves even though only Top
was updated.
My rather ugly, but working, solution is to loop through step 1 and 2 until the coordinates from Visual.PointFromScreen
no longer changes.
while (true)
{
var topLeft = PointFromScreen(new Point(o.Left, o.Top));
var bottomRight = PointFromScreen(new Point(o.Right, o.Bottom));
var x1 = Left + topLeft.X;
var y1 = Top + topLeft.Y;
var x2 = Left + bottomRight.X;
var y2 = Top + bottomRight.Y;
var w = x2 - x1;
var h = y2 - y1;
if (Left == x1 && Top == y1 && Width == w && Height == h)
break;
Left = x1;
Top = y1;
Width = w;
Height = h;
}
Are there any better ways, for example to set all location properties at once?
You can skip all these conversion by using SetWindowPos function.
using System.Runtime.InteropServices;
using System.Windows.Interop;
internal class Helper
{
[DllImport("User32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetWindowPos(
nint hWnd,
nint hWndInsertAfter,
int X,
int Y,
int cx,
int cy,
uint uFlags);
public static bool SetWindowPosition(System.Windows.Window window, System.Drawing.Rectangle o)
{
nint handle = new WindowInteropHelper(window).EnsureHandle();
return SetWindowPos(handle, nint.Zero, o.Left, o.Top, o.Width, o.Height, 0);
}
}