Unfortunately, the answer of user3225503 seems not to work (anymore?)
My scenario: WIN10 20H2, WPF-App with dpi-awareness "PerMonitor", Framework 4.7.2, 2 Monitors with different resolutions and different screen scalings ("Horror scenario"):
the dm.dmPelsWidth member of the DEVMODE structure has always the physical resolution of my monitors, so the scaling is always 1.0.
All what we want is to restore our program and its windows like we left it in the last session right? This seems to be incredibly hard, thanks to MS!
But a different approach seems to work:
Switch on per-monitor dpi-awareness in the manifest file of your application:
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<!-- The combination of below two tags have the following effect :
1) Per-Monitor for >= Windows 10 Anniversary Update
2) System < Windows 10 Anniversary Update -->
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings"> PerMonitor</dpiAwareness>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</windowsSettings>
Always use GetPlacement and SetPlacement win32-api calls for storing/restoring window placements
SetPlacement will set the wrong dialog width/height if the dialog is on a secondary display and each display has different scalings. So we need a new factor depending on scaling factors of each display to correct this in the Loading-event of the window:
event code:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
if (string.IsNullOrWhiteSpace(Properties.Settings.Default.Placement))
return;
ScreenExtensions.WINDOWPLACEMENT placement = new ScreenExtensions.WINDOWPLACEMENT();
placement.ReadFromBase64String(Properties.Settings.Default.Placement);
System.Windows.Interop.HwndSource shwnd = System.Windows.Interop.HwndSource.FromVisual(this) as System.Windows.Interop.HwndSource;
double PrimaryMonitorScaling = ScreenExtensions.GetScalingForPoint(new System.Drawing.Point(1, 1));
double CurrentMonitorScaling = ScreenExtensions.GetScalingForPoint(new System.Drawing.Point(placement.rcNormalPosition.left, placement.rcNormalPosition.top));
double RescaleFactor = CurrentMonitorScaling / PrimaryMonitorScaling;
double width = placement.rcNormalPosition.right - placement.rcNormalPosition.left;
double height = placement.rcNormalPosition.bottom - placement.rcNormalPosition.top;
placement.rcNormalPosition.right = placement.rcNormalPosition.left + (int)(width / RescaleFactor + 0.5);
placement.rcNormalPosition.bottom = placement.rcNormalPosition.top + (int)(height / RescaleFactor + 0.5);
ScreenExtensions.SetPlacement(shwnd.Handle, placement);
}
- There are some more goodies in the code example, e.g. serialization of the WINDOWPLACEMENT structure. don't forget to create a member "Placement" in your application settings! Tell me if this works for you:
Example code:
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Windows;
namespace DpiApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
System.Windows.Interop.HwndSource shwnd = System.Windows.Interop.HwndSource.FromVisual(this) as System.Windows.Interop.HwndSource;
var plc = ScreenExtensions.GetPlacement(shwnd.Handle);
Properties.Settings.Default.Placement = plc.ToString();
Properties.Settings.Default.Save();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
if (string.IsNullOrWhiteSpace(Properties.Settings.Default.Placement))
return;
ScreenExtensions.WINDOWPLACEMENT placement = new ScreenExtensions.WINDOWPLACEMENT();
placement.ReadFromBase64String(Properties.Settings.Default.Placement);
System.Windows.Interop.HwndSource shwnd = System.Windows.Interop.HwndSource.FromVisual(this) as System.Windows.Interop.HwndSource;
double PrimaryMonitorScaling = ScreenExtensions.GetScalingForPoint(new System.Drawing.Point(1, 1));
double CurrentMonitorScaling = ScreenExtensions.GetScalingForPoint(new System.Drawing.Point(placement.rcNormalPosition.left, placement.rcNormalPosition.top));
double RescaleFactor = CurrentMonitorScaling / PrimaryMonitorScaling;
double width = placement.rcNormalPosition.right - placement.rcNormalPosition.left;
double height = placement.rcNormalPosition.bottom - placement.rcNormalPosition.top;
placement.rcNormalPosition.right = placement.rcNormalPosition.left + (int)(width / RescaleFactor + 0.5);
placement.rcNormalPosition.bottom = placement.rcNormalPosition.top + (int)(height / RescaleFactor + 0.5);
ScreenExtensions.SetPlacement(shwnd.Handle, placement);
}
}
public static class ScreenExtensions
{
public const string User32 = "user32.dll";
public const string shcore = "Shcore.dll";
public static void GetDpi(this System.Windows.Forms.Screen screen, DpiType dpiType, out uint dpiX, out uint dpiY)
{
var pnt = new System.Drawing.Point(screen.Bounds.Left + 1, screen.Bounds.Top + 1);
var mon = MonitorFromPoint(pnt, 2/*MONITOR_DEFAULTTONEAREST*/);
GetDpiForMonitor(mon, dpiType, out dpiX, out dpiY);
}
public static double GetScalingForPoint(System.Drawing.Point aPoint)
{
var mon = MonitorFromPoint(aPoint, 2/*MONITOR_DEFAULTTONEAREST*/);
uint dpiX, dpiY;
GetDpiForMonitor(mon, DpiType.Effective, out dpiX, out dpiY);
return (double)dpiX / 96.0;
}
[DllImport(User32)]
private static extern IntPtr MonitorFromPoint([In] System.Drawing.Point pt, [In] uint dwFlags);
[DllImport(shcore)]
private static extern IntPtr GetDpiForMonitor([In] IntPtr hmonitor, [In] DpiType dpiType, [Out] out uint dpiX, [Out] out uint dpiY);
[DllImport(User32, CharSet = CharSet.Auto)]
[ResourceExposure(ResourceScope.None)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
[DllImport(User32, CharSet = CharSet.Auto, SetLastError = true)]
[ResourceExposure(ResourceScope.None)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl);
public enum DpiType
{
Effective = 0,
Angular = 1,
Raw = 2,
}
public static WINDOWPLACEMENT GetPlacement(IntPtr hWnd)
{
WINDOWPLACEMENT placement = new WINDOWPLACEMENT();
placement.length = Marshal.SizeOf(placement);
GetWindowPlacement(hWnd, ref placement);
return placement;
}
public static bool SetPlacement(IntPtr hWnd, WINDOWPLACEMENT aPlacement)
{
bool erg = SetWindowPlacement(hWnd, ref aPlacement);
return erg;
}
[StructLayout(LayoutKind.Sequential)]
public struct POINTSTRUCT
{
public int x;
public int y;
public POINTSTRUCT(int x, int y)
{
this.x = x;
this.y = y;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
public RECT(int left, int top, int right, int bottom)
{
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
public RECT(Rect r)
{
this.left = (int)r.Left;
this.top = (int)r.Top;
this.right = (int)r.Right;
this.bottom = (int)r.Bottom;
}
public static RECT FromXYWH(int x, int y, int width, int height)
{
return new RECT(x, y, x + width, y + height);
}
public Size Size
{
get { return new Size(this.right - this.left, this.bottom - this.top); }
}
}
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPLACEMENT
{
public int length;
public uint flags;
public uint showCmd;
public POINTSTRUCT ptMinPosition;
public POINTSTRUCT ptMaxPosition;
public RECT rcNormalPosition;
public override string ToString()
{
byte[] StructBytes = RawSerialize(this);
return System.Convert.ToBase64String(StructBytes);
}
public void ReadFromBase64String(string aB64)
{
byte[] b64 = System.Convert.FromBase64String(aB64);
var NewWP = ReadStruct<WINDOWPLACEMENT>(b64, 0);
length = NewWP.length;
flags = NewWP.flags;
showCmd = NewWP.showCmd;
ptMinPosition.x = NewWP.ptMinPosition.x;
ptMinPosition.y = NewWP.ptMinPosition.y;
ptMaxPosition.x = NewWP.ptMaxPosition.x;
ptMaxPosition.y = NewWP.ptMaxPosition.y;
rcNormalPosition.left = NewWP.rcNormalPosition.left;
rcNormalPosition.top = NewWP.rcNormalPosition.top;
rcNormalPosition.right = NewWP.rcNormalPosition.right;
rcNormalPosition.bottom = NewWP.rcNormalPosition.bottom;
}
static public T ReadStruct<T>(byte[] aSrcBuffer, int aOffset)
{
byte[] buffer = new byte[Marshal.SizeOf(typeof(T))];
Buffer.BlockCopy(aSrcBuffer, aOffset, buffer, 0, Marshal.SizeOf(typeof(T)));
GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
T temp = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
return temp;
}
static public T ReadStruct<T>(Stream fs)
{
byte[] buffer = new byte[Marshal.SizeOf(typeof(T))];
fs.Read(buffer, 0, Marshal.SizeOf(typeof(T)));
GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
T temp = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
return temp;
}
public static byte[] RawSerialize(object anything)
{
int rawsize = Marshal.SizeOf(anything);
byte[] rawdata = new byte[rawsize];
GCHandle handle = GCHandle.Alloc(rawdata, GCHandleType.Pinned);
Marshal.StructureToPtr(anything, handle.AddrOfPinnedObject(), false);
handle.Free();
return rawdata;
}
}
}
}