Skip to content

Fix CheckBox/RadioButton Appearance.Button + FlatStyle.Standard invisible in dark mode#14397

Open
ricardobossan wants to merge 2 commits intodotnet:mainfrom
ricardobossan:Issue_14347_CheckBox_And_RadioButton_Invisible_In_Dark_Mode
Open

Fix CheckBox/RadioButton Appearance.Button + FlatStyle.Standard invisible in dark mode#14397
ricardobossan wants to merge 2 commits intodotnet:mainfrom
ricardobossan:Issue_14347_CheckBox_And_RadioButton_Invisible_In_Dark_Mode

Conversation

@ricardobossan
Copy link
Copy Markdown
Member

@ricardobossan ricardobossan commented Mar 16, 2026

Fixes #14347

Root Cause

CheckBox and RadioButton with Appearance.Button + FlatStyle.Standard intentionally disabled owner-draw in dark mode to work around a VisualStyleRenderer HighDPI issue:

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.

Proposed changes

  • 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.

Customer Impact

CheckBox and RadioButton controls with Appearance.Button + FlatStyle.Standard are now visible and correctly styled in dark mode. Previously they were completely invisible.

Regression?

No.

Risk

Minimal.

Screenshots

Before

Screenshot 2026-03-15 205030

After

Screenshot 2026-03-15 215221

Test methodology

Manual.

Test environment(s)

  • 11.0.100-preview.3.26161.119
Microsoft Reviewers: Open in CodeFlow

…ible in dark mode

Fixes dotnet#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
@ricardobossan ricardobossan self-assigned this Mar 16, 2026
@ricardobossan ricardobossan requested a review from a team as a code owner March 16, 2026 01:32
@ricardobossan ricardobossan added the waiting-review This item is waiting on review by one or more members of team label Mar 16, 2026
@github-actions github-actions bot added the area-DarkMode Issues relating to Dark Mode feature label Mar 16, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes dark-mode invisibility for CheckBox/RadioButton when using Appearance.Button + FlatStyle.Standard by restoring owner-draw in dark mode and routing “button-like” rendering through a dark-mode-capable adapter.

Changes:

  • Remove dark-mode special-casing from OwnerDraw in CheckBox and RadioButton (now always base.OwnerDraw).
  • Use DarkModeAdapterFactory.CreateStandardAdapter(...) for the Appearance.Button rendering path in the Standard adapters.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/RadioButton.cs Restores owner-draw in dark mode by removing the special OwnerDraw condition.
src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/CheckBox.cs Restores owner-draw in dark mode by removing the special OwnerDraw condition.
src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/RadioButtonStandardAdapter.cs Routes Appearance.Button painting through the dark-mode adapter factory.
src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/CheckBoxStandardAdapter.cs Routes Appearance.Button painting and sizing through the dark-mode adapter factory.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +176 to 177
private protected override bool OwnerDraw => base.OwnerDraw;

Comment on lines 109 to 111

protected override ButtonBaseAdapter CreateButtonAdapter() => new ButtonStandardAdapter(Control);
protected override ButtonBaseAdapter CreateButtonAdapter() => DarkModeAdapterFactory.CreateStandardAdapter(Control);

Comment on lines +53 to 56
private new ButtonBaseAdapter ButtonAdapter => base.ButtonAdapter;

protected override ButtonBaseAdapter CreateButtonAdapter() => new ButtonStandardAdapter(Control);
protected override ButtonBaseAdapter CreateButtonAdapter() => DarkModeAdapterFactory.CreateStandardAdapter(Control);

|| Appearance != Appearance.Button
|| FlatStyle != FlatStyle.Standard)
&& base.OwnerDraw;
private protected override bool OwnerDraw => base.OwnerDraw;
@LeafShi1
Copy link
Copy Markdown
Member

Should we enable OwnerDraw in Standard mode? @KlausLoeffelmann

@SimonZhao888
Copy link
Copy Markdown
Member

Review Notes for PR #14397

Thanks for the fix, @ricardobossan. Making these controls visible in dark mode is important. A few observations:

1. CheckState visual fidelity concern

When Appearance.Button CheckBox/RadioButton is routed through ButtonDarkModeAdapter via DarkModeAdapterFactory.CreateStandardAdapter, the adapter's PaintOver renders the Hot state unconditionally, ignoring the passed CheckState. This means:

  • A checked toggle-button may lose its "pressed/toggled" visual on hover.
  • CheckState.Indeterminate renders identically to Hot, losing its distinct visual cue.

This could trade the invisibility bug for a subtler visual-state bug. Could you verify that checked and indeterminate states still look correct on hover in dark mode? If ButtonDarkModeAdapter needs adjustment, it may be worth a follow-up or addressing in this PR.

2. Preferred size consistency

GetPreferredSizeCore in CheckBoxStandardAdapter now also routes through DarkModeAdapterFactory. If ButtonDarkModeAdapter computes different preferred sizes than ButtonStandardAdapter, there could be layout shifts when switching between light and dark modes. Worth a quick manual check.

3. Missing automated tests

The PR description mentions manual testing only. Since this is a regression fix for a specific combination (Appearance.Button + FlatStyle.Standard in dark mode), a targeted unit test would be valuable to prevent future regressions — e.g., asserting OwnerDraw == true under SystemColorMode.Dark for this combination.

4. LeafShi1's question

@LeafShi1 raised a good question about whether we should enable OwnerDraw in Standard mode — it would be great to get @KlausLoeffelmann's input on that before merging.

Overall the approach looks sound. The main risk is point #1 above.

(Copilot co-authored on Simon Zhao.)

@ricardobossan
Copy link
Copy Markdown
Member Author

@SimonZhao888, thanks for the thorough review!

1. CheckState visual fidelity: fixed

You're right. PaintOver was unconditionally passing PushButtonState.Hot, dropping the checked state on hover. Fixed in the latest commit: when CheckState.Checked, PaintOver now passes PushButtonState.Pressed so the toggle-button remains visually pressed while hovered. GetButtonBackColor and GetButtonTextColor are updated accordingly.

2. Preferred size consistency: non-issue

ButtonDarkModeAdapter doesn't override GetPreferredSizeCore; it inherits the same implementation from ButtonBaseAdapter as ButtonStandardAdapter does. Sizes are identical between light and dark mode for this combination.

3. Automated tests: added

Added a regression test to both CheckBoxTests and RadioButtonTests that enables dark mode via Application.SetColorMode(SystemColorMode.Dark) and asserts GetStyle(ControlStyles.UserPaint) == true for Appearance.Button + FlatStyle.Standard. This locks in the owner-draw path and will catch any future reversion.

4. @LeafShi1's question

Agreed. It would be great to get @KlausLoeffelmann's input before merging. He added the original OwnerDraw workaround, so he's best placed to confirm whether enabling owner-draw unconditionally for FlatStyle.Standard (the approach taken here) is the right long-term direction, or whether a narrower scope is preferred.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-DarkMode Issues relating to Dark Mode feature waiting-review This item is waiting on review by one or more members of team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[DarkMode] the checked/unchecked items cannot be seen after clicking the CustomComCtl32Button button in DarkMode for WinformsControlTest project

4 participants