I want to shrink the window so it fits on a screen.
Normally my window has size of 1000x800 so there is enough space in most scenarios but I'm also checking screen area to make sure it's not bigger. However my boss gave me his pc where he has screen scale set to 150% and the window becomes bigger then available space. I thought I solved this by dividing screen size by it's current dpi but turns out this does not work in all scenarios.
This is the method I'm using to set the size based on current DPI:
private void setMainWindowDimensions() {
//set window size and position with respect to current screen
var windowInteropHelper = new WindowInteropHelper(nativeWindow);
var screen = System.Windows.Forms.Screen.FromHandle(windowInteropHelper.Handle);
var dpi = screen.getScale();
nativeWindow.Width = Math.Min(screen.WorkingArea.Width / dpi.x, 1000);
nativeWindow.Height = Math.Min(screen.WorkingArea.Height / dpi.y, 800);
}
And here is the getScale()
extension method I'm using to get the scale factor:
//https://stackoverflow.com/questions/29438430/how-to-get-dpi-scale-for-all-screens
public static (uint x, uint y) GetDpi(this S screen, DpiType dpiType = default) {
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 var dpiX, out var dpiY);
return (dpiX, dpiY);
}
//https://msdn.microsoft.com/en-us/library/windows/desktop/dd145062(v=vs.85).aspx
[DllImport("User32.dll")]
private static extern IntPtr MonitorFromPoint([In]System.Drawing.Point pt, [In]uint dwFlags);
//https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510(v=vs.85).aspx
[DllImport("Shcore.dll")]
private static extern IntPtr GetDpiForMonitor([In]IntPtr hmonitor, [In]DpiType dpiType, [Out]out uint dpiX, [Out]out uint dpiY);
This is is form the How to get DPI scale for all screens? answer.
Above code worked when I changed the scale of main monitor on my work pc, it correctly returned dpi
of (1.5, 1.5). Few months later and my boss is back with he's computer saying it still don't work.
I figured out the DPI is taken form main screen, even if the window is opened on the other and here I still can't figure out how to get correct values.
I was following this article But it doesn't work at all. The methods form the NativeHelpers
project are returning some values but the result is the same as with original method. Basically the SetPerMonitorDPIAware
fails and I don't know why. I used slightly modified version and I thought that there is something wrong with my project configuration, but even when I create new WPF project, extending the window from PerMonitorDPIWindow
class, the result is the same - it throws "Enabling Per-monitor DPI Failed."
private void createWindow() {
var c = logCtx("Creating main applicaton window", ONE_TIME_LOG);
var pma = PerMonitorDPIHelper.SetPerMonitorDPIAware();
if (pma == 0) log("Could not set per monitor DPI awareness.", WARNING_LOG);
else log("Per monitor awareness was set.", SUCCESS_LOG);
window = new CWindow();
nativeWindow.AllowDrop = true;
setMainWindowDimensions();
c.close();
}
private void setMainWindowDimensions() {
//set window size and position with respect to current screen
var windowInteropHelper = new WindowInteropHelper(nativeWindow);
var screen = System.Windows.Forms.Screen.FromHandle(windowInteropHelper.Handle);
var dpi = screen.getScale();
var sdpi = PerMonitorDPIHelper.GetSystemDPI();
var wdpi = PerMonitorDPIHelper.GetDpiForWindow(windowInteropHelper.Handle);
log($@"DPI awareness: {PerMonitorDPIHelper.getPerMonitorDPIAware()}");
log($@"system DPI: {sdpi}, window DPI: {wdpi}, screen DPI: {dpi}");
nativeWindow.Width = Math.Min(screen.WorkingArea.Width / dpi.x, 1000);
nativeWindow.Height = Math.Min(screen.WorkingArea.Height / dpi.y, 800);
}
private void onLoaded(object sender, RoutedEventArgs e) {
var c = logCtx("Main window loaded", ONE_TIME_LOG);
setMainWindowDimensions();
c.close();
}
Here you can see returned DPI is 1.5 even if the window is opened on screen that is not scaled.
Since I have a drawing app, I'm also using the following method to determine real scale coordinates:
private (double x, double y) density {
get {
var m = PresentationSource.FromVisual(Application.Current.MainWindow).CompositionTarget
.TransformToDevice;
return (96 / m.M11 / 25.4, 96 / m.M22 / 25.4);
}
}
This solution is from this question, and it also don't work in various dpi scenarios.
I've also read in one of the comments that shcore
is available only since Windows 8. Our PCs have Win 10 but we also want to support Windows 7.
There is also on more strange thing. My boss PC is actually a laptop with only one, main screen, yet it still doesn't get proper DPI. I don't have access to it right now so don't have logs I've just added...
Is there any method which works in all scenarios? It's seem that such such thing should require 2 lines of code yet it's became so convoluted I've already wasted 2 day on this and I'm out of ideas :(