sources/Google.Solutions.Mvvm/Theme/DpiAwareness.cs (118 lines of code) (raw):

// // Copyright 2024 Google LLC // // Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. // using Google.Solutions.Common.Runtime; using System; using System.Collections.Specialized; using System.ComponentModel; using System.Configuration; using System.Drawing; using System.Runtime.InteropServices; namespace Google.Solutions.Mvvm.Theme { public enum DpiAwarenessMode { DpiUnaware = 0, SystemAware = 1, PerMonitor = 2, PerMonitorV2 = 3, DpiUnawareGdiScaled = 4, } public static class DpiAwareness { private static DpiAwarenessMode currentMode = DpiAwarenessMode.DpiUnaware; private static IntPtr ToDpiAwarenessContext(DpiAwarenessMode mode) { var contextValue = mode switch { DpiAwarenessMode.DpiUnaware => NativeMethods.DPI_AWARENESS_CONTEXT.UNAWARE, DpiAwarenessMode.SystemAware => NativeMethods.DPI_AWARENESS_CONTEXT.SYSTEM_AWARE, DpiAwarenessMode.PerMonitor => NativeMethods.DPI_AWARENESS_CONTEXT.PER_MONITOR_AWARE, DpiAwarenessMode.PerMonitorV2 => NativeMethods.DPI_AWARENESS_CONTEXT.PER_MONITOR_AWARE_V2, DpiAwarenessMode.DpiUnawareGdiScaled => NativeMethods.DPI_AWARENESS_CONTEXT.UNAWARE_GDISCALED, _ => throw new ArgumentException(nameof(mode)), }; return new IntPtr((int)contextValue); } /// <summary> /// Windows uses 96 DPI by default. /// </summary> public static readonly SizeF DefaultDpi = new SizeF(96, 96); /// <summary> /// Default font size used by the Designer. /// </summary> public static readonly SizeF DefaultFontSize = new SizeF(6F, 13F); /// <summary> /// Check if the Windows version supports DPI awareness. /// </summary> public static bool IsSupported { get { // // SetThreadDpiAwarenessContext requires Windows 10 1607, // GDI scaling requires 1703 (= build 15063). // Use that as baseline. // var osVersion = Environment.OSVersion.Version; return osVersion.Major > 10 || (osVersion.Major == 10 && osVersion.Build >= 15063); } } private static void CheckSupported() { if (!IsSupported) { throw new PlatformNotSupportedException( "DPI awareness requires Windows 10 1703 or a later version of Windows"); } } /// <summary> /// Gets or sets the high DPI mode of the process. /// </summary> public static DpiAwarenessMode ProcessMode { get => currentMode; set { CheckSupported(); if (!NativeMethods.SetProcessDpiAwarenessContext(ToDpiAwarenessContext(value))) { throw new Win32Exception(); } // // Some of the .NET 4.7+ specific High-DPI features of WinForms // need to be enabled in app.config. These features include: // // - Control.LogicalToDeviceUnits returning scaled values // - Automatic scaling for toolbars // // Setting the process DPI awareness context alone isn's sufficient. // var winFormsDpiAwareness = value switch { DpiAwarenessMode.SystemAware => "system", DpiAwarenessMode.PerMonitor => "permonitor", DpiAwarenessMode.PerMonitorV2 => "permonitorv2", _ => null }; if (winFormsDpiAwareness != null) { var winFormsConfig = (NameValueCollection)ConfigurationManager .GetSection("System.Windows.Forms.ApplicationConfigurationSection"); winFormsConfig["DpiAwareness"] = winFormsDpiAwareness; } currentMode = value; } } /// <summary> /// Temporarily enter the given mode. Restores the original /// DPI awareness mode when the returned object is disposed. /// </summary> public static IDisposable EnterThreadMode(DpiAwarenessMode mode) { CheckSupported(); var original = NativeMethods.SetThreadDpiAwarenessContext( ToDpiAwarenessContext(mode)); if (original == IntPtr.Zero) { throw new Win32Exception(); } return Disposable.Create(() => { if (NativeMethods.SetThreadDpiAwarenessContext(original) == IntPtr.Zero) { throw new Win32Exception(); } }); } //--------------------------------------------------------------------- // P/Invoke. //--------------------------------------------------------------------- private static class NativeMethods { [DllImport("User32", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool SetProcessDpiAwarenessContext( [In] IntPtr context); [DllImport("User32", SetLastError = true)] public static extern IntPtr GetThreadDpiAwarenessContext(); [DllImport("User32", SetLastError = true)] public static extern IntPtr SetThreadDpiAwarenessContext( [In] IntPtr dpiContext); public enum DPI_AWARENESS_CONTEXT : int { UNAWARE = -1, SYSTEM_AWARE = -2, PER_MONITOR_AWARE = -3, PER_MONITOR_AWARE_V2 = -4, UNAWARE_GDISCALED = -5, } } } }