diff --git a/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/DpiHelper.cs b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/DpiHelper.cs index f944b0faf6d..83bd34cf2ec 100644 --- a/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/DpiHelper.cs +++ b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/DpiHelper.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Drawing.Drawing2D; +using Microsoft.Win32; using static Interop; namespace System.Windows.Forms @@ -171,6 +172,45 @@ public static Bitmap CreateScaledBitmap(Bitmap logicalImage, int deviceDpi = 0) /// public static bool IsScalingRequired => DeviceDpi != LogicalDpi; + /// + /// Retrieve the text scale factor, which is set via Settings > Display > Make Text Bigger. + /// The settings are stored in the registry under HKCU\Software\Microsoft\Accessibility in (DWORD)TextScaleFactor. + /// + /// The scaling factor in the range [1.0, 2.25]. + /// Windows Text scaling + public static float GetTextScaleFactor() + { + // The default(100) and max(225) text scale factor is value what Settings display text scale + // applies and also clamps the text scale factor value between 100 and 225 value. + const short MinTextScaleValue = 100; + const short MaxTextScaleValue = 225; + + short textScaleValue = MinTextScaleValue; + try + { + RegistryKey? key = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Accessibility"); + if (key is not null && key.GetValue("TextScaleFactor") is int _textScaleValue) + { + textScaleValue = (short)_textScaleValue; + } + } + catch + { + // Failed to read the registry for whatever reason. +#if DEBUG + throw; +#endif + } + + // Restore the text scale if it isn't the default value in the valid text scale factor value + if (textScaleValue > MinTextScaleValue && textScaleValue <= MaxTextScaleValue) + { + return (float)textScaleValue / MinTextScaleValue; + } + + return 1.0f; + } + /// /// scale logical pixel to the factor /// diff --git a/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/OsVersion.cs b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/OsVersion.cs index b96ecce4dc8..33fe048b501 100644 --- a/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/OsVersion.cs +++ b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/OsVersion.cs @@ -20,6 +20,12 @@ private static NtDll.RTL_OSVERSIONINFOEX InitVersion() return info; } + /// + /// Is Windows 10 first release or later. (Threshold 1, build 10240, version 1507) + /// + public static bool IsWindows10_1507OrGreater + => s_versionInfo.dwMajorVersion >= 10 && s_versionInfo.dwBuildNumber >= 10240; + /// /// Is Windows 10 Anniversary Update or later. (Redstone 1, build 14393, version 1607) /// diff --git a/src/System.Windows.Forms/src/PublicAPI.Unshipped.txt b/src/System.Windows.Forms/src/PublicAPI.Unshipped.txt index 3c6deb494bf..0657001a1b1 100644 --- a/src/System.Windows.Forms/src/PublicAPI.Unshipped.txt +++ b/src/System.Windows.Forms/src/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ -~override System.Windows.Forms.MonthCalendar.OnResize(System.EventArgs e) -> void \ No newline at end of file +~override System.Windows.Forms.MonthCalendar.OnResize(System.EventArgs e) -> void +~static System.Windows.Forms.Application.SetDefaultFont(System.Drawing.Font font) -> void diff --git a/src/System.Windows.Forms/src/Resources/SR.resx b/src/System.Windows.Forms/src/Resources/SR.resx index e5bc1b57b65..c051093b38b 100644 --- a/src/System.Windows.Forms/src/Resources/SR.resx +++ b/src/System.Windows.Forms/src/Resources/SR.resx @@ -6495,7 +6495,7 @@ Stack trace where the illegal operation occurred was: Width must be greater than MinWidth. - SetCompatibleTextRenderingDefault must be called before the first IWin32Window object is created in the application. + {0} must be called before the first IWin32Window object is created in the application. Setting event '{0}' diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.cs.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.cs.xlf index 362c198fb45..90a8146930b 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.cs.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.cs.xlf @@ -11052,8 +11052,8 @@ Trasování zásobníku, kde došlo k neplatné operaci: - SetCompatibleTextRenderingDefault must be called before the first IWin32Window object is created in the application. - Před vytvořením prvního objektu IWin32Window v aplikaci je nutné volat metodu SetCompatibleTextRenderingDefault. + {0} must be called before the first IWin32Window object is created in the application. + {0} must be called before the first IWin32Window object is created in the application. diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.de.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.de.xlf index 12c2592c646..ff73b87403b 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.de.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.de.xlf @@ -11052,8 +11052,8 @@ Stapelüberwachung, in der der unzulässige Vorgang auftrat: - SetCompatibleTextRenderingDefault must be called before the first IWin32Window object is created in the application. - SetCompatibleTextRenderingDefault muss aufgerufen werden, bevor das erste IWin32Window-Objekt in der Anwendung erstellt wird. + {0} must be called before the first IWin32Window object is created in the application. + {0} must be called before the first IWin32Window object is created in the application. diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.es.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.es.xlf index ac435b8567c..e622e06e7b7 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.es.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.es.xlf @@ -11052,8 +11052,8 @@ El seguimiento de la pila donde tuvo lugar la operación no válida fue: - SetCompatibleTextRenderingDefault must be called before the first IWin32Window object is created in the application. - SetCompatibleTextRenderingDefault se debe llamar antes de crear el primer objeto IWin32Window en la aplicación. + {0} must be called before the first IWin32Window object is created in the application. + {0} must be called before the first IWin32Window object is created in the application. diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.fr.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.fr.xlf index 99405e21ac0..a82887a98c5 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.fr.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.fr.xlf @@ -11052,8 +11052,8 @@ Cette opération non conforme s'est produite sur la trace de la pile : - SetCompatibleTextRenderingDefault must be called before the first IWin32Window object is created in the application. - SetCompatibleTextRenderingDefault doit être appelé avant la création du premier objet IWin32Window dans l'application. + {0} must be called before the first IWin32Window object is created in the application. + {0} must be called before the first IWin32Window object is created in the application. diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.it.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.it.xlf index 6eaf3f69621..85987eff1c5 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.it.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.it.xlf @@ -11052,8 +11052,8 @@ Traccia dello stack da cui si è verificata l'operazione non valida: - SetCompatibleTextRenderingDefault must be called before the first IWin32Window object is created in the application. - SetCompatibleTextRenderingDefault deve essere chiamato prima della creazione del primo oggetto IWin32Window nell'applicazione. + {0} must be called before the first IWin32Window object is created in the application. + {0} must be called before the first IWin32Window object is created in the application. diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.ja.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.ja.xlf index ee9ee6a80d6..cec37c236a1 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.ja.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.ja.xlf @@ -11052,8 +11052,8 @@ Stack trace where the illegal operation occurred was: - SetCompatibleTextRenderingDefault must be called before the first IWin32Window object is created in the application. - 最初の IWin32Window オブジェクトがアプリケーションで作成される前に、SetCompatibleTextRenderingDefault が呼び出されなければなりません。 + {0} must be called before the first IWin32Window object is created in the application. + {0} must be called before the first IWin32Window object is created in the application. diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.ko.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.ko.xlf index ceaaface994..8bd45205569 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.ko.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.ko.xlf @@ -11052,8 +11052,8 @@ Stack trace where the illegal operation occurred was: - SetCompatibleTextRenderingDefault must be called before the first IWin32Window object is created in the application. - SetCompatibleTextRenderingDefault는 애플리케이션에 첫 번째 IWin32Window 개체가 만들어지기 전에 호출해야 합니다. + {0} must be called before the first IWin32Window object is created in the application. + {0} must be called before the first IWin32Window object is created in the application. diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.pl.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.pl.xlf index 9093a31118f..5b3e6f28cb0 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.pl.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.pl.xlf @@ -11052,8 +11052,8 @@ Stos śledzenia, w którym wystąpiła zabroniona operacja: - SetCompatibleTextRenderingDefault must be called before the first IWin32Window object is created in the application. - Należy wywołać element SetCompatibleTextRenderingDefault, aby pierwszy obiekt IWin32Window został utworzony w aplikacji. + {0} must be called before the first IWin32Window object is created in the application. + {0} must be called before the first IWin32Window object is created in the application. diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.pt-BR.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.pt-BR.xlf index a94a498efa5..491baf72140 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.pt-BR.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.pt-BR.xlf @@ -11052,8 +11052,8 @@ Rastreamento de pilha em que a operação ilegal ocorreu: - SetCompatibleTextRenderingDefault must be called before the first IWin32Window object is created in the application. - É necessário chamar SetCompatibleTextRenderingDefault antes da criação do primeiro objeto IWin32Window no aplicativo. + {0} must be called before the first IWin32Window object is created in the application. + {0} must be called before the first IWin32Window object is created in the application. diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.ru.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.ru.xlf index 99c4d85470e..9bacf4d3f4c 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.ru.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.ru.xlf @@ -11053,8 +11053,8 @@ Stack trace where the illegal operation occurred was: - SetCompatibleTextRenderingDefault must be called before the first IWin32Window object is created in the application. - До создания первого объекта IWin32Window в приложении необходимо вызвать SetCompatibleTextRenderingDefault. + {0} must be called before the first IWin32Window object is created in the application. + {0} must be called before the first IWin32Window object is created in the application. diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.tr.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.tr.xlf index 66b07e9f414..e379cbcda73 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.tr.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.tr.xlf @@ -11052,8 +11052,8 @@ Geçersiz işlemin gerçekleştiği yığın izi: - SetCompatibleTextRenderingDefault must be called before the first IWin32Window object is created in the application. - Uygulamada ilk IWin32Window nesnesi oluşturulmadan önce SetCompatibleTextRenderingDefault çağrılmalıdır. + {0} must be called before the first IWin32Window object is created in the application. + {0} must be called before the first IWin32Window object is created in the application. diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hans.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hans.xlf index ece6a375883..99e50ea4134 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hans.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hans.xlf @@ -11052,8 +11052,8 @@ Stack trace where the illegal operation occurred was: - SetCompatibleTextRenderingDefault must be called before the first IWin32Window object is created in the application. - 在应用程序中创建第一个 IWin32Window 对象之前,必须调用 SetCompatibleTextRenderingDefault。 + {0} must be called before the first IWin32Window object is created in the application. + {0} must be called before the first IWin32Window object is created in the application. diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hant.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hant.xlf index 6668599a0b6..6749634b321 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hant.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hant.xlf @@ -11052,8 +11052,8 @@ Stack trace where the illegal operation occurred was: - SetCompatibleTextRenderingDefault must be called before the first IWin32Window object is created in the application. - 在應用程式中建立第一個 IWin32Window 物件之前,必須先呼叫 SetCompatibleTextRenderingDefault。 + {0} must be called before the first IWin32Window object is created in the application. + {0} must be called before the first IWin32Window object is created in the application. diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs index b855665ab5f..a828226e093 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs @@ -6,6 +6,7 @@ using System.ComponentModel; using System.Diagnostics; +using System.Drawing; using System.Globalization; using System.IO; using System.Reflection; @@ -30,6 +31,8 @@ public sealed partial class Application /// Hash table for our event list /// private static EventHandlerList s_eventHandlers; + private static Font s_defaultFont; + private static Font s_defaultFontScaled; private static string s_startupPath; private static string s_executablePath; private static object s_appFileVersion; @@ -279,6 +282,8 @@ public static InputLanguage CurrentInputLanguage internal static bool CustomThreadExceptionHandlerAttached => ThreadContext.FromCurrent().CustomThreadExceptionHandlerAttached; + internal static Font DefaultFont => s_defaultFontScaled ?? s_defaultFont; + /// /// Gets the path for the executable file that started the application. /// @@ -1218,6 +1223,31 @@ public static void Run(ApplicationContext context) internal static void RunDialog(Form form) => ThreadContext.FromCurrent().RunMessageLoop(Interop.Mso.msoloop.ModalForm, new ModalApplicationContext(form)); + /// + /// Scale the default font (if it is set) as per the Settings display text scale settings. + /// + internal static void ScaleDefaultFont() + { + if (s_defaultFont is null || !OsVersion.IsWindows10_1507OrGreater) + { + return; + } + + float textScaleValue = DpiHelper.GetTextScaleFactor(); + + if (s_defaultFontScaled is not null) + { + s_defaultFontScaled.Dispose(); + s_defaultFontScaled = null; + } + + // Restore the text scale if it isn't the default value in the valid text scale factor value + if (textScaleValue > 1.0f) + { + s_defaultFontScaled = new Font(s_defaultFont.FontFamily, s_defaultFont.Size * textScaleValue); + } + } + /// /// Sets the static UseCompatibleTextRenderingDefault field on Control to the value passed in. /// This switch determines the default text rendering engine to use by some controls that support @@ -1227,12 +1257,60 @@ public static void SetCompatibleTextRenderingDefault(bool defaultValue) { if (NativeWindow.AnyHandleCreated) { - throw new InvalidOperationException(SR.Win32WindowAlreadyCreated); + throw new InvalidOperationException(string.Format(SR.Win32WindowAlreadyCreated, nameof(SetCompatibleTextRenderingDefault))); } Control.UseCompatibleTextRenderingDefault = defaultValue; } + /// + /// Sets the default for process. + /// + /// The font to be used as a default across the application. + /// is . + /// + /// You can only call this method before the first window is created by your Windows Forms application. + /// + /// + /// + /// The system text scale factor will be applied to the font, i.e. if the default font is set to "Calibri, 11f" + /// and the text scale factor is set to 150% the resulting default font will be set to "Calibri, 16.5f". + /// + /// + /// Users can adjust text scale with the Make text bigger slider on the Settings -> Ease of Access -> Vision/Display screen. + /// + /// + /// Windows Text scaling + public static void SetDefaultFont(Font font) + { + if (font is null) + throw new ArgumentNullException(nameof(font)); + + if (NativeWindow.AnyHandleCreated) + throw new InvalidOperationException(string.Format(SR.Win32WindowAlreadyCreated, nameof(SetDefaultFont))); + + // If user made a prior call to this API with a different custom fonts, we want to clean it up. + if (s_defaultFont is not null) + { + s_defaultFont?.Dispose(); + s_defaultFont = null; + s_defaultFontScaled?.Dispose(); + s_defaultFontScaled = null; + } + + if (font.IsSystemFont) + { + // The system font is managed the .NET runtime, and it is already scaled to the current text scale factor. + // We need to clone it because our reference will no longer be scaled by the .NET runtime. + s_defaultFont = (Font)font.Clone(); + } + else + { + s_defaultFont = font; + ScaleDefaultFont(); + } + } + /// /// Sets the mode for process. /// diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs index 02fb8d840b0..36fc961ddf9 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs @@ -1810,7 +1810,7 @@ public static Font DefaultFont { if (s_defaultFont is null) { - s_defaultFont = SystemFonts.MessageBoxFont; + s_defaultFont = Application.DefaultFont ?? SystemFonts.MessageBoxFont; Debug.Assert(s_defaultFont is not null, "defaultFont wasn't set!"); } @@ -11603,6 +11603,7 @@ private void UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs if (pref.Category == UserPreferenceCategory.Color) { s_defaultFont = null; + Application.ScaleDefaultFont(); OnSystemColorsChanged(EventArgs.Empty); } } diff --git a/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/MainForm.Designer.cs b/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/MainForm.Designer.cs index 33426fd4c48..2e3e6bbc79b 100644 --- a/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/MainForm.Designer.cs +++ b/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/MainForm.Designer.cs @@ -47,7 +47,7 @@ private void InitializeComponent() // MainForm // this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(570, 30); this.Controls.Add(this.flowLayoutPanelUITypeEditors); this.Name = "MainForm"; diff --git a/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/MainForm.cs b/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/MainForm.cs index f30dacfb62d..02ed97ff49b 100644 --- a/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/MainForm.cs +++ b/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/MainForm.cs @@ -4,10 +4,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Drawing; using System.Runtime.InteropServices; using System.Windows.Forms; using System.Windows.Forms.IntegrationTests.Common; +using Microsoft.Win32; using WindowsFormsApp1; using WinformsControlsTest.UserControls; @@ -29,8 +31,8 @@ public MainForm() InitInfo info = buttonsInitInfo[item]; Button button = new Button { + AutoSizeMode = AutoSizeMode.GrowAndShrink, Name = info.Name, - Size = new Size(259, 23), TabIndex = (int)item, Text = info.Name, UseVisualStyleBackColor = true @@ -40,15 +42,17 @@ public MainForm() flowLayoutPanelUITypeEditors.Controls.Add(button); } - // Calculate the form size. - ClientSize = new Size(545, 18 + (mainFormControlsTabOrderItems.Length + 1) / 2 * 29); - MinimumSize = Size; - - // Force the panel to show all buttons - flowLayoutPanelUITypeEditors.PerformLayout(); - flowLayoutPanelUITypeEditors.Controls[(int)MainFormControlsTabOrder.ButtonsButton].Focus(); - Text = RuntimeInformation.FrameworkDescription; + + SystemEvents.UserPreferenceChanged += (s, e) => + { + // The default font gets reset for UserPreferenceCategory.Color + // though perhaps it should've been done for UserPreferenceCategory.Window + if (e.Category == UserPreferenceCategory.Color) + { + UpdateLayout(); + } + }; } private IReadOnlyDictionary GetButtonsInitInfo() => new Dictionary @@ -159,6 +163,70 @@ public MainForm() } }; + protected override void OnShown(EventArgs e) + { + base.OnShown(e); + + UpdateLayout(); + flowLayoutPanelUITypeEditors.Controls[(int)MainFormControlsTabOrder.ButtonsButton].Focus(); + } + + private void UpdateLayout() + { + MinimumSize = default; + Debug.WriteLine($"MessageBoxFont: {SystemFonts.MessageBoxFont}", nameof(MainForm)); + Debug.WriteLine($"Default font: {Control.DefaultFont}", nameof(MainForm)); + + // 1. Auto-size all buttons + flowLayoutPanelUITypeEditors.SuspendLayout(); + foreach (Control c in flowLayoutPanelUITypeEditors.Controls) + { + if (c is Button button) + { + button.AutoSize = true; + } + } + + flowLayoutPanelUITypeEditors.ResumeLayout(true); + + // 2. Find the biggest button + Size biggestButton = default; + foreach (Control c in flowLayoutPanelUITypeEditors.Controls) + { + if (c is Button button) + { + if (button.Width > biggestButton.Width) + { + biggestButton = button.Size; + } + } + } + + Debug.WriteLine($"Biggest button size: {biggestButton}", nameof(MainForm)); + + // 3. Size all buttons to the biggest button + flowLayoutPanelUITypeEditors.SuspendLayout(); + foreach (Control c in flowLayoutPanelUITypeEditors.Controls) + { + if (c is Button button) + { + button.AutoSize = false; + button.Size = biggestButton; + } + } + + flowLayoutPanelUITypeEditors.ResumeLayout(true); + + // 4. Calculate the new form size showing all buttons in two vertical columns + int padding = flowLayoutPanelUITypeEditors.Controls[0].Margin.All; + ClientSize = new Size( + (biggestButton.Width + padding * 2) * 2 + padding * 2, + (int)(flowLayoutPanelUITypeEditors.Controls.Count / 2 * (biggestButton.Height + padding * 2) + padding * 2) + ); + MinimumSize = Size; + Debug.WriteLine($"Minimum form size: {MinimumSize}", nameof(MainForm)); + } + private struct InitInfo { public InitInfo(string name, EventHandler handler) diff --git a/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/MultipleControls.Designer.cs b/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/MultipleControls.Designer.cs index 9ebd685d46b..cede22d53a3 100644 --- a/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/MultipleControls.Designer.cs +++ b/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/MultipleControls.Designer.cs @@ -83,6 +83,9 @@ private void InitializeComponent() // // label1 // + this.label1.AccessibleDescription = "Test Label AccessibleDescription"; + this.label1.AccessibleName = "Test Label AccessibleName"; + this.label1.AccessibleRole = System.Windows.Forms.AccessibleRole.Indicator; this.label1.AutoSize = true; this.label1.FlatStyle = System.Windows.Forms.FlatStyle.System; this.label1.Location = new System.Drawing.Point(13, 73); @@ -90,9 +93,6 @@ private void InitializeComponent() this.label1.Size = new System.Drawing.Size(35, 13); this.label1.TabIndex = 2; this.label1.Text = "label1"; - this.label1.AccessibleName = "Test Label AccessibleName"; - this.label1.AccessibleDescription = "Test Label AccessibleDescription"; - this.label1.AccessibleRole = System.Windows.Forms.AccessibleRole.Indicator; // // maskedTextBox1 // @@ -196,6 +196,9 @@ private void InitializeComponent() // // groupBox1 // + this.groupBox1.AccessibleDescription = "Test GroupBox AccessibleDescription"; + this.groupBox1.AccessibleName = "Test GroupBox AccessibleName"; + this.groupBox1.AccessibleRole = System.Windows.Forms.AccessibleRole.Table; this.groupBox1.Controls.Add(this.radioButton2); this.groupBox1.Controls.Add(this.radioButton1); this.groupBox1.Location = new System.Drawing.Point(125, 171); @@ -204,9 +207,6 @@ private void InitializeComponent() this.groupBox1.TabIndex = 7; this.groupBox1.TabStop = false; this.groupBox1.Text = "groupBox1"; - this.groupBox1.AccessibleName = "Test GroupBox AccessibleName"; - this.groupBox1.AccessibleDescription = "Test GroupBox AccessibleDescription"; - this.groupBox1.AccessibleRole = System.Windows.Forms.AccessibleRole.Table; // // checkedListBox1 // @@ -236,12 +236,12 @@ private void InitializeComponent() this.domainUpDown1.Size = new System.Drawing.Size(120, 20); this.domainUpDown1.TabIndex = 10; this.domainUpDown1.Text = "domainUpDown1"; - // - // Test3 - // - this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; - this.ClientSize = new System.Drawing.Size(629, 269); + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(639, 397); this.Controls.Add(this.domainUpDown1); this.Controls.Add(this.numericUpDown1); this.Controls.Add(this.checkedListBox1); @@ -253,7 +253,7 @@ private void InitializeComponent() this.Controls.Add(this.label1); this.Controls.Add(this.button1); this.Controls.Add(this.progressBar1); - this.Name = "Test3"; + this.Name = "Form1"; this.Text = "These look ok"; this.Load += new System.EventHandler(this.Test3_Load); this.tabControl1.ResumeLayout(false); diff --git a/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/MultipleControls.cs b/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/MultipleControls.cs index c6620c65173..546a4f7e37d 100644 --- a/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/MultipleControls.cs +++ b/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/MultipleControls.cs @@ -4,6 +4,7 @@ using System; using System.ComponentModel; +using System.Drawing; using System.Threading; using System.Windows.Forms; @@ -14,6 +15,107 @@ public partial class MultipleControls : Form public MultipleControls() { InitializeComponent(); + CreateMyListView(); + } + + private void CreateMyListView() + { + // Create a new ListView control. + ListView listView2 = new ListView + { + Bounds = new Rectangle(new Point(0, 0), new Size(400, 200)), + + // Set the view to show details. + View = View.Details, + // Allow the user to edit item text. + LabelEdit = true, + // Allow the user to rearrange columns. + AllowColumnReorder = true, + // Display check boxes. + CheckBoxes = true, + // Select the item and subitems when selection is made. + FullRowSelect = true, + // Display grid lines. + GridLines = true, + // Sort the items in the list in ascending order. + Sorting = SortOrder.Ascending, + + VirtualMode = true, + VirtualListSize = 3, + }; + + ListViewGroup listViewGroup1 = new("ListViewGroup", HorizontalAlignment.Left) + { + Header = "ListViewGroup", + Name = "listViewGroup1" + }; + listView2.Groups.AddRange(new ListViewGroup[] { listViewGroup1 }); + + // Create three items and three sets of subitems for each item. + ListViewItem item1 = new("item1", 0) + { + // Place a check mark next to the item. + Checked = true + }; + item1.SubItems.Add("sub1"); + item1.SubItems.Add("sub2"); + item1.SubItems.Add("sub3"); + ListViewItem item2 = new("item2", 1); + item2.SubItems.Add("sub4"); + item2.SubItems.Add("sub5"); + item2.SubItems.Add("sub6"); + ListViewItem item3 = new("item3", 0) + { + // Place a check mark next to the item. + Checked = true + }; + item3.SubItems.Add("sub7"); + item3.SubItems.Add("sub8"); + item3.SubItems.Add("sub9"); + + // Add the items to the ListView, but because the listview is in Virtual Mode, we have to manage items ourselves + // and thus, we can't call the following: + // listView2.Items.AddRange(new ListViewItem[] { item1, item2, item3 }); + listView2.RetrieveVirtualItem += (s, e) => + { + e.Item = e.ItemIndex switch + { + 0 => item1, + 1 => item2, + 2 => item3, + _ => throw new IndexOutOfRangeException(), + }; + }; + + // Create columns for the items and subitems. + // Width of -2 indicates auto-size. + listView2.Columns.Add("column1", "Item Column", -2, HorizontalAlignment.Left, 0); + listView2.Columns.Add("Column 2", -2, HorizontalAlignment.Left); + listView2.Columns.Add("Column 3", -2, HorizontalAlignment.Left); + listView2.Columns.Add("Column 4", -2, HorizontalAlignment.Center); + listView2.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize); + + // Create two ImageList objects. + ImageList imageListSmall = new(); + ImageList imageListLarge = new(); + + // Initialize the ImageList objects with bitmaps. + imageListSmall.Images.Add(Bitmap.FromFile("Images\\SmallA.bmp")); + imageListSmall.Images.Add(Bitmap.FromFile("Images\\SmallABlue.bmp")); + imageListLarge.Images.Add(Bitmap.FromFile("Images\\LargeA.bmp")); + imageListLarge.Images.Add(Bitmap.FromFile("Images\\LargeABlue.bmp")); + + // Assign the ImageList objects to the ListView. + listView2.LargeImageList = imageListLarge; + listView2.SmallImageList = imageListSmall; + + // Add the ListView to the control collection. + Controls.Add(listView2); + listView2.Dock = DockStyle.Bottom; + + // Change a ListViewGroup's header. + listView2.Groups[0].HeaderAlignment = HorizontalAlignment.Center; + listView2.Groups[0].Header = "NewText"; } private void Test3_Load(object sender, EventArgs e) diff --git a/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/Program.cs b/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/Program.cs index 9c9b7d0f5cb..d93e5bf5770 100644 --- a/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/Program.cs +++ b/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/Program.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. using System; -using System.Globalization; +using System.Drawing; using System.Threading; using System.Windows.Forms; @@ -18,6 +18,12 @@ static class Program static void Main() { Application.EnableVisualStyles(); + Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + + //Application.SetDefaultFont(new Font(new FontFamily("Microsoft Sans Serif"), 8f)); + //Application.SetDefaultFont(new Font(new FontFamily("Chiller"), 12f)); + Application.SetDefaultFont(new Font(new FontFamily("Calibri"), 11f)); + Application.SetCompatibleTextRenderingDefault(false); Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException); //UnhandledExceptionMode.ThrowException Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture; diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ApplicationTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ApplicationTests.cs index 1a03bd95d29..055f8a3d566 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ApplicationTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ApplicationTests.cs @@ -1,9 +1,10 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.ComponentModel; +using System.Drawing; using System.Globalization; using System.IO; using System.Threading; @@ -151,6 +152,121 @@ public void Application_EnableVisualStyles_ManifestResourceExists() Assert.NotNull(stream); } + [WinFormsFact] + public void Application_DefaultFont_ReturnsNull_IfNoFontSet() + { + var applicationTestAccessor = typeof(Application).TestAccessor().Dynamic; + Assert.Null(applicationTestAccessor.s_defaultFont); + Assert.Null(applicationTestAccessor.s_defaultFontScaled); + Assert.Null(Application.DefaultFont); + } + + [WinFormsFact] + public void Application_DefaultFont_Returns_DefaultFont_IfNotScaled() + { + var applicationTestAccessor = typeof(Application).TestAccessor().Dynamic; + Assert.Null(applicationTestAccessor.s_defaultFont); + Assert.Null(applicationTestAccessor.s_defaultFontScaled); + + Font customFont = (Font)SystemFonts.CaptionFont.Clone(); + try + { + applicationTestAccessor.s_defaultFont = customFont; + + AreFontEqual(customFont, Application.DefaultFont); + } + finally + { + customFont.Dispose(); + applicationTestAccessor.s_defaultFont = null; + applicationTestAccessor.s_defaultFontScaled?.Dispose(); + applicationTestAccessor.s_defaultFontScaled = null; + } + } + + [WinFormsFact] + public void Application_DefaultFont_Returns_ScaledDefaultFont_IfScaled() + { + var applicationTestAccessor = typeof(Application).TestAccessor().Dynamic; + Assert.Null(applicationTestAccessor.s_defaultFont); + Assert.Null(applicationTestAccessor.s_defaultFontScaled); + + Font font = new Font(new FontFamily("Arial"), 12f); + Font scaled = new Font(new FontFamily("Arial"), 16f); + try + { + applicationTestAccessor.s_defaultFont = font; + applicationTestAccessor.s_defaultFontScaled = scaled; + + AreFontEqual(scaled, Application.DefaultFont); + } + finally + { + applicationTestAccessor.s_defaultFont = null; + applicationTestAccessor.s_defaultFontScaled = null; + font.Dispose(); + scaled.Dispose(); + } + } + + private static void AreFontEqual(Font expected, Font actual) + { + Assert.Equal(expected.Name, actual.Name); + Assert.Equal(expected.SizeInPoints, actual.SizeInPoints); + Assert.Equal(expected.GdiCharSet, actual.GdiCharSet); + Assert.Equal(expected.Style, actual.Style); + } + + [WinFormsFact] + public void Application_SetDefaultFont_SetNull_ThrowsArgumentNullException() + { + Assert.Throws("font", () => Application.SetDefaultFont(null)); + } + + [WinFormsFact] + public void Application_SetDefaultFont_AfterHandleCreated_InvalidOperationException() + { + using var control = new Control(); + var window = new NativeWindow(); + window.AssignHandle(control.Handle); + + Assert.Throws(() => Application.SetDefaultFont(SystemFonts.CaptionFont)); + } + + [WinFormsFact] + public void Application_SetDefaultFont_MustCloseSystemFont() + { + var applicationTestAccessor = typeof(Application).TestAccessor().Dynamic; + Assert.Null(applicationTestAccessor.s_defaultFont); + Assert.Null(applicationTestAccessor.s_defaultFontScaled); + + Assert.True(SystemFonts.CaptionFont.IsSystemFont); + + // This a unholy, but generally at this stage NativeWindow.AnyHandleCreated=true, + // And we won't be able to set the font, unless we flip the bit + var nativeWindowTestAccessor = typeof(NativeWindow).TestAccessor().Dynamic; + bool currentAnyHandleCreated = nativeWindowTestAccessor.t_anyHandleCreated; + + try + { + nativeWindowTestAccessor.t_anyHandleCreated = false; + + Application.SetDefaultFont(SystemFonts.CaptionFont); + + Assert.False(applicationTestAccessor.s_defaultFont.IsSystemFont); + } + finally + { + // Flip the bit back + nativeWindowTestAccessor.t_anyHandleCreated = currentAnyHandleCreated; + + applicationTestAccessor.s_defaultFont.Dispose(); + applicationTestAccessor.s_defaultFontScaled?.Dispose(); + applicationTestAccessor.s_defaultFont = null; + applicationTestAccessor.s_defaultFontScaled = null; + } + } + [WinFormsTheory] [CommonMemberData(nameof(CommonTestHelper.GetEnumTypeTheoryDataInvalid), typeof(HighDpiMode))] public void Application_SetHighDpiMode_SetInvalidValue_ThrowsInvalidEnumArgumentException(HighDpiMode value)