From b66339008f4b6b43c917b231093a9bd86f97be22 Mon Sep 17 00:00:00 2001 From: "Ricardo Bossan (BEYONDSOFT CONSULTING INC) (from Dev Box)" Date: Sun, 15 Mar 2026 22:26:59 -0300 Subject: [PATCH] Fix CheckBox/RadioButton Appearance.Button + FlatStyle.Standard invisible in dark mode Fixes #14347 `CheckBox` and `RadioButton` with `Appearance.Button + FlatStyle.Standard` intentionally disabled owner-draw in dark mode to work around a VisualStyleRenderer HighDPI issue: ```csharp private protected override bool OwnerDraw => (!Application.IsDarkModeEnabled || Appearance != Appearance.Button || FlatStyle != FlatStyle.Standard) && base.OwnerDraw; ``` With `OwnerDraw = false`, WinForms delegates painting to the native ComCtl32 button (`BS_3STATE | BS_PUSHLIKE`). The native control renders with light-mode colors (or transparent) on a dark form background, making the controls completely invisible. - **`CheckBox.cs` / `RadioButton.cs`**: Remove the dark mode exception from `OwnerDraw`. Both controls now return `base.OwnerDraw` (i.e., `FlatStyle != FlatStyle.System`) unconditionally, restoring owner-draw for `Appearance.Button + FlatStyle.Standard` in dark mode. - **`CheckBoxStandardAdapter.cs` / `RadioButtonStandardAdapter.cs`**: Change `CreateButtonAdapter()` from `new ButtonStandardAdapter(Control)` to `DarkModeAdapterFactory.CreateStandardAdapter(Control)`. In dark mode this returns `ButtonDarkModeAdapter`, which renders using explicit dark mode colors (`#333333` background, `#9B9B9B` border, `#F0F0F0` text) and does not use VisualStyleRenderers, resolving both the visibility issue and the HighDPI concern that motivated the original workaround. `CheckBox` and `RadioButton` controls with `Appearance.Button + FlatStyle.Standard` are now visible and correctly styled in dark mode. Previously they were completely invisible. No. Minimal. Manual. - 11.0.100-preview.3.26161.119 --- .../ButtonInternal/CheckBoxStandardAdapter.cs | 6 +++--- .../ButtonInternal/RadioButtonStandardAdapter.cs | 4 ++-- .../Windows/Forms/Controls/Buttons/CheckBox.cs | 12 +----------- .../Windows/Forms/Controls/Buttons/RadioButton.cs | 13 +------------ 4 files changed, 7 insertions(+), 28 deletions(-) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/CheckBoxStandardAdapter.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/CheckBoxStandardAdapter.cs index 3a7f7d3b0c8..d846da9c067 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/CheckBoxStandardAdapter.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/CheckBoxStandardAdapter.cs @@ -89,7 +89,7 @@ internal override Size GetPreferredSizeCore(Size proposedSize) { if (Control.Appearance == Appearance.Button) { - ButtonStandardAdapter adapter = new(Control); + ButtonBaseAdapter adapter = DarkModeAdapterFactory.CreateStandardAdapter(Control); return adapter.GetPreferredSizeCore(proposedSize); } else @@ -105,9 +105,9 @@ internal override Size GetPreferredSizeCore(Size proposedSize) } } - private new ButtonStandardAdapter ButtonAdapter => (ButtonStandardAdapter)base.ButtonAdapter; + private new ButtonBaseAdapter ButtonAdapter => base.ButtonAdapter; - protected override ButtonBaseAdapter CreateButtonAdapter() => new ButtonStandardAdapter(Control); + protected override ButtonBaseAdapter CreateButtonAdapter() => DarkModeAdapterFactory.CreateStandardAdapter(Control); protected override LayoutOptions Layout(PaintEventArgs e) { diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/RadioButtonStandardAdapter.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/RadioButtonStandardAdapter.cs index 0181286c263..4a75be1ebf2 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/RadioButtonStandardAdapter.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/RadioButtonStandardAdapter.cs @@ -50,9 +50,9 @@ internal override void PaintOver(PaintEventArgs e, CheckState state) } } - private new ButtonStandardAdapter ButtonAdapter => (ButtonStandardAdapter)base.ButtonAdapter; + private new ButtonBaseAdapter ButtonAdapter => base.ButtonAdapter; - protected override ButtonBaseAdapter CreateButtonAdapter() => new ButtonStandardAdapter(Control); + protected override ButtonBaseAdapter CreateButtonAdapter() => DarkModeAdapterFactory.CreateStandardAdapter(Control); protected override LayoutOptions Layout(PaintEventArgs e) { diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/CheckBox.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/CheckBox.cs index 57526b2644b..e3139311bca 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/CheckBox.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/CheckBox.cs @@ -96,17 +96,7 @@ public Appearance Appearance } } - private protected override bool OwnerDraw => - // We want NO owner draw ONLY when we're - // * In Dark Mode - // * When _then_ the Appearance is Button - // * But then ONLY when we're rendering with FlatStyle.Standard - // (because that would let us usually let us draw with the VisualStyleRenderers, - // which cause HighDPI issues in Dark Mode). - (!Application.IsDarkModeEnabled - || Appearance != Appearance.Button - || FlatStyle != FlatStyle.Standard) - && base.OwnerDraw; + private protected override bool OwnerDraw => base.OwnerDraw; [SRCategory(nameof(SR.CatPropertyChanged))] [SRDescription(nameof(SR.CheckBoxOnAppearanceChangedDescr))] diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/RadioButton.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/RadioButton.cs index a6674f1ea69..f5c190832bc 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/RadioButton.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/RadioButton.cs @@ -173,18 +173,7 @@ public bool Checked } } - private protected override bool OwnerDraw => - // Order is key here - do NOT change! - // We want NO owner draw ONLY when we're - // * in Dark Mode - // * when _then_ the Appearance is Button - // * but then ONLY when we're rendering with FlatStyle.Standard - // (because that would let us usually let us draw with the VisualStyleRenderers, - // which cause HighDPI issues in Dark Mode). - (!Application.IsDarkModeEnabled - || Appearance != Appearance.Button - || FlatStyle != FlatStyle.Standard) - && base.OwnerDraw; + private protected override bool OwnerDraw => base.OwnerDraw; /// [Browsable(false)]