Skip to content

Refactor DarkMode management for Controls as per API Review outcome #13582

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

KlausLoeffelmann
Copy link
Member

@KlausLoeffelmann KlausLoeffelmann commented Jun 11, 2025

For a Control to indicate that it wants to participate in DarkMode theming, it needs to indicate that by setting the style flag ApplyThemingImplicitly.

The original architectural plan was to introduce a dedicate protected virtual method InitializeControl that would be called by the constructor of Control, which would then set the ApplyThemingImplicitly flag and give also the control the chance to learn about the initial DeviceAPI, with that replacing the original internal method.

This approach was not approved by the API Review board (see #13550 (comment)).

Consequently, this PR builds the DarkMode handling back to the original approach and also refactors the flaw of the approach how controls are internally provided the initial DPI setting.
(Also pointed outby the API Review.)

Because of the general architecture of the Controls inheritance hierarchy, Controls need to make sure to opt into (or opt out) DarkMode handling before their own constructor runs, which means they have to override CreateParams and set the respective flags before calling the base implementation.

Microsoft Reviewers: Open in CodeFlow

@KlausLoeffelmann KlausLoeffelmann requested a review from a team as a code owner June 11, 2025 07:13
@github-actions github-actions bot added the area-DarkMode Issues relating to Dark Mode feature label Jun 11, 2025
Copy link
Contributor

@Copilot 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

This PR reverts a previously unapproved architecture for DarkMode theming in controls and refactors DPI handling to use a PropertyStore‐based approach. Key changes include removing early SetStyle calls from constructors in favor of setting them within CreateParams overrides, replacing the removed InitializeConstantsForInitialDpi method with a standardized InitializeControl override, and migrating from internal DPI field usage to the new DeviceDpiInternal/FormerDeviceDpi properties.

Reviewed Changes

Copilot reviewed 36 out of 36 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
TabControl.cs, Splitter.cs, ProgressBar.cs, PictureBox.cs, MonthCalendar.cs, ListBox.cs, Label.cs Moved the ApplyThemingImplicitly flag from the constructor to the CreateParams getter and removed redundant pragmas where needed.
PropertyGrid.cs, DataGridView.cs, Control.cs Updated DPI handling code to use the new PropertyStore keys and properties (DeviceDpiInternal and FormerDeviceDpi), and removed the obsolete InitializeConstantsForInitialDpi.
DataGridViewColumn.cs, ComboBox.FlatComboAdapter.cs Updated DPI retrieval calls to reference the new properties.
RadioButton.cs, CheckBox.cs, ButtonBase.cs, Button.cs Replaced the old DPI initialization methods with InitializeControl and changed the accessibility or virtuality of OwnerDraw where applicable.
PublicAPI.Unshipped.txt, PublicAPI.Shipped.txt Updated and removed API entries to align with the refactored theming and DPI handling changes.
Comments suppressed due to low confidence (1)

src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonBase.cs:620

  • Changing the accessibility and making OwnerDraw virtual may affect how derived controls override this property. Verify that the new visibility and virtual nature align with the intended usage across the control hierarchy.
private protected virtual bool OwnerDraw => FlatStyle != FlatStyle.System;

Copy link

codecov bot commented Jun 11, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 97.40072%. Comparing base (f3bc39d) to head (4ed52ca).
Report is 13 commits behind head on main.

Additional details and impacted files
@@                 Coverage Diff                  @@
##                main      #13582          +/-   ##
====================================================
+ Coverage   76.60070%   97.40072%   +20.80002%     
====================================================
  Files           3236        1182        -2054     
  Lines         639344      352713      -286631     
  Branches       47314        5368       -41946     
====================================================
- Hits          489742      343545      -146197     
+ Misses        146026        8412      -137614     
+ Partials        3576         756        -2820     
Flag Coverage Δ
Debug 97.40072% <ø> (+20.80002%) ⬆️
integration ?
production ?
test 97.40072% <ø> (+0.00004%) ⬆️
unit ?

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@KlausLoeffelmann KlausLoeffelmann force-pushed the NewApi_0_DarkModeControl branch from a8a909a to 53aaa95 Compare June 11, 2025 21:37
@KlausLoeffelmann KlausLoeffelmann force-pushed the NewApi_0_DarkModeControl branch from 53aaa95 to b3118f9 Compare June 12, 2025 06:38
@KlausLoeffelmann KlausLoeffelmann force-pushed the NewApi_0_DarkModeControl branch from 67539e5 to f7cbf17 Compare June 13, 2025 01:48
Copy link
Member

@JeremyKuhne JeremyKuhne left a comment

Choose a reason for hiding this comment

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

Nothing blocking, but the comments should be followed up on.

s_deviceDpiInternal,
ScaleHelper.InitialSystemDpi);
}

Copy link
Member

Choose a reason for hiding this comment

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

nit: don't leave a blank line between getter and setter blocks, also these should be get =>

| ControlStyles.CacheText
| ControlStyles.StandardClick,
true);

// This class overrides GetPreferredSizeCore, let Control automatically cache the result
SetExtendedState(ExtendedStates.UserPreferredSizeCache, true);

// Be aware that OwnerDraw is effective calling CreateParams which in turn can be overwritten
// by derived classes. So, it's important to set certain flags already in CreateParams -
// setting them in the derived control's constructor would already be too late!
Copy link
Member

Choose a reason for hiding this comment

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

nit: avoid exclamation points

// value when the Handle is created. We use the static infra to ensure that the DPI is set
// correctly in the PropertyStore, and that it is not set to 0.
// So, this is available before any Constructor of any inheriting control runs.
Properties.AddValue(
Copy link
Member

Choose a reason for hiding this comment

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

When we always create a value, putting it in the Properties has unnecessary overhead and as such the general recommendation is to not do this for things that aren't optional or are in inner loops.

@KlausLoeffelmann
Copy link
Member Author

I will merge this, so it will not get stale, and will follow up in the next PR.

@KlausLoeffelmann KlausLoeffelmann merged commit 380e851 into dotnet:main Jun 14, 2025
8 checks passed
@KlausLoeffelmann KlausLoeffelmann deleted the NewApi_0_DarkModeControl branch June 14, 2025 15:16
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
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants