Skip to content

DarkMode (b) DarkModeButtonRenderer #13408

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

Conversation

KlausLoeffelmann
Copy link
Member

@KlausLoeffelmann KlausLoeffelmann commented May 5, 2025

Add correlating DarkMode Button renderers to the existing Button renderers.

This way, we are becoming completely independent of the XP-based VisualStyle rendering (and most of all incomplete theming for certain situations, like the Button rendering for 96DPI/100% scaling scenarios).

Fixes #11949.

DarkModeButtonRenderers

Note:
For Preview 5, we are very defensively activating the new Renderers, and do a more intensive testing for Preview 6. This means:

  • Nothing changes, if we're in LightMode. We're not picking up any new Win11 compatible styles for now.

  • In contrast to the documentation, we're rendering EVERY FlatMode ourselves (and have so all along) - also the default FlatMode. That means, if VisualStyles are enabled, we're still controlling the paint-pipeline (handling OnPaint), we're just delegating certain parts down to the VisualStyleRenderer (which does not work reliably for dark mode and does not work in 96 DPI altogether.) Our initial assumption that the native Win32 System button doesn't render dark mode correctly is wrong: It does, and it does so in every HighDpi mode (scenario tested by CTI), even with animation support. We are just not using it (and never have), when the user is not explicitly opting in by setting the FlatStyle to System. This is what the docs need to be updated for.

  • For that reason, for DarkMode and FlatStyle.Standard, we're picking up the internal SystemRenderer, except for cases when we're rendering with images, as the SystemRenderer is NOT able to render images (and never was). In case of using Images, we're using the new DarkMode FlatRenderer, because that is style-compatible with the SystemRenderer.

  • For the other styles, we're using the new DarkMode renderers respectively (Again, LightMode is not affected), we're not introducing any new Win11 styles for Preview 5.

@KlausLoeffelmann KlausLoeffelmann self-assigned this May 5, 2025
@KlausLoeffelmann KlausLoeffelmann added area-DarkMode Issues relating to Dark Mode feature and removed needs-area-label labels May 5, 2025
@dotnet-policy-service dotnet-policy-service bot added the draft draft PR label May 5, 2025
@KlausLoeffelmann KlausLoeffelmann force-pushed the DarkMode_b_DarkModeButtonRenderer branch 2 times, most recently from 57f4d2f to 3b45170 Compare May 9, 2025 22:12
Copy link

codecov bot commented May 9, 2025

Codecov Report

Attention: Patch coverage is 30.54711% with 1371 lines in your changes missing coverage. Please review.

Project coverage is 76.47110%. Comparing base (f2060df) to head (b1bfd78).

Additional details and impacted files
@@                 Coverage Diff                 @@
##                main      #13408         +/-   ##
===================================================
+ Coverage   75.42791%   76.47110%   +1.04319%     
===================================================
  Files           3230        3240         +10     
  Lines         639213      640319       +1106     
  Branches       47303       47410        +107     
===================================================
+ Hits          482145      489659       +7514     
+ Misses        148044      147077        -967     
+ Partials        9024        3583       -5441     
Flag Coverage Δ
Debug 76.47110% <30.54711%> (+1.04319%) ⬆️
integration 18.73064% <18.23708%> (?)
production 50.82504% <30.54711%> (+2.42533%) ⬆️
test 97.40114% <ø> (ø)
unit 48.21959% <28.97670%> (-0.18013%) ⬇️

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 DarkMode_b_DarkModeButtonRenderer branch 6 times, most recently from 918e60d to b83e93b Compare May 16, 2025 22:10
@KlausLoeffelmann KlausLoeffelmann marked this pull request as ready for review May 17, 2025 00:41
@KlausLoeffelmann KlausLoeffelmann requested a review from a team as a code owner May 17, 2025 00:41
@dotnet-policy-service dotnet-policy-service bot removed the draft draft PR label May 17, 2025
@KlausLoeffelmann KlausLoeffelmann changed the base branch from main to klaus/preview5/A May 17, 2025 04:26
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 introduces a new dark-mode–aware button rendering system alongside minor tweaks to the toolstrip renderer.

  • Adds four sub-renderers (Standard, Flat, Popup, System) under ButtonDarkModeRenderer for WinForms buttons in dark mode.
  • Updates ButtonBaseAdapter/ButtonBase to select and use the new dark-mode adapter when dark mode is enabled.
  • Toggles a flag in ToolStripSystemRenderer to use the alternative dark-mode path and consolidates pen creation in its dark-mode border renderer.

Reviewed Changes

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

Show a summary per file
File Description
src/.../ToolStripSystemRenderer.cs Flip isSystemDefaultAlternative flag to true
src/.../ToolStripSystemDarkModeRenderer.cs Hoist borderPen creation out of branches
src/.../Controls/Buttons/DarkModeRenderer/* New dark-mode renderer classes & factory
src/.../Controls/Buttons/DarkModeRenderer/ButtonDarkModeRenderer.cs Core dispatcher for dark-mode button rendering
src/.../ButtonInternal/ButtonDarkModeAdapter.cs New adapter to route RenderButton calls
src/.../ButtonInternal/ButtonBaseAdapter.cs & ButtonBase.cs Hook up dark-mode adapter via OwnerDraw and CreateDarkModeAdapter
Winforms.sln (Unrelated) added AI-Prompts and GDI solution items
Comments suppressed due to low confidence (1)

Winforms.sln:212

  • Solution file now includes AI-Prompts and GDI entries unrelated to WinForms code. Remove these to avoid polluting the main solution.
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AI-Prompts"...

Comment on lines 19 to 20
private const int DefaultButtonBorderThickness = 0;
private const int NonDefaultButtonBorderThickness = 0;
Copy link
Preview

Copilot AI May 19, 2025

Choose a reason for hiding this comment

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

[nitpick] Both border-thickness constants are zero, making the switch on isDefault a no-op. Either consolidate into one constant or clarify if different values are intended.

Suggested change
private const int DefaultButtonBorderThickness = 0;
private const int NonDefaultButtonBorderThickness = 0;
private const int ButtonBorderThickness = 0;

Copilot uses AI. Check for mistakes.

@KlausLoeffelmann KlausLoeffelmann force-pushed the DarkMode_b_DarkModeButtonRenderer branch from b83e93b to c505a15 Compare May 21, 2025 07:23
@KlausLoeffelmann KlausLoeffelmann force-pushed the DarkMode_b_DarkModeButtonRenderer branch 6 times, most recently from 61aefaa to 23ff881 Compare May 27, 2025 18:02
@KlausLoeffelmann KlausLoeffelmann changed the base branch from klaus/preview5/A to main May 27, 2025 18:58
@KlausLoeffelmann KlausLoeffelmann force-pushed the DarkMode_b_DarkModeButtonRenderer branch from 86d8e0b to b1bfd78 Compare June 8, 2025 04:39
@KlausLoeffelmann KlausLoeffelmann changed the base branch from main to klaus/preview5/A June 8, 2025 05:26
@KlausLoeffelmann KlausLoeffelmann changed the base branch from klaus/preview5/A to klaus/preview6/A June 8, 2025 05:39
@KlausLoeffelmann KlausLoeffelmann force-pushed the DarkMode_b_DarkModeButtonRenderer branch from b1bfd78 to f0e353c Compare June 8, 2025 06:06
@KlausLoeffelmann KlausLoeffelmann force-pushed the DarkMode_b_DarkModeButtonRenderer branch 5 times, most recently from c0170e5 to 1528a35 Compare June 9, 2025 03:45
@KlausLoeffelmann KlausLoeffelmann force-pushed the DarkMode_b_DarkModeButtonRenderer branch 4 times, most recently from b993552 to 4a291d5 Compare June 13, 2025 07:15
@KlausLoeffelmann KlausLoeffelmann force-pushed the DarkMode_b_DarkModeButtonRenderer branch 3 times, most recently from bb6f045 to 6e80645 Compare June 15, 2025 18:52
@KlausLoeffelmann KlausLoeffelmann force-pushed the DarkMode_b_DarkModeButtonRenderer branch from 2b83abf to 8a6ad4a Compare June 15, 2025 19:20
@KlausLoeffelmann KlausLoeffelmann force-pushed the DarkMode_b_DarkModeButtonRenderer branch from 8a6ad4a to 1cc912f Compare June 15, 2025 19:21
@KlausLoeffelmann KlausLoeffelmann force-pushed the DarkMode_b_DarkModeButtonRenderer branch from e9a373a to 08204aa Compare June 16, 2025 01:31
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.

There is more feedback to address (maybe an hour or two of work), but nothing that blocks getting this in for the next preview. Approving with the understanding that we'll address with a follow up issue to track.

/// <term><see cref="FlatStyle.Standard"/></term>
/// <description>
/// The default style. The button is not wrapping the system button. It is rendered using the StandardButton adapter.
/// VisualStyleRenderer from the OS is used for certain parts, which may have issues in high-resolution scenarios.
Copy link
Member

Choose a reason for hiding this comment

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

Cref for VisualStyleRenderer?

/// <term><see cref="FlatStyle.System"/></term>
/// <description>
/// The button wraps the system button and is not owner-drawn.
/// No <c>OnPaint</c>, <c>OnPaintBackground</c>, or adapter is involved.
Copy link
Member

Choose a reason for hiding this comment

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

Would be better as a cref for the events.

/// <description>
/// The default style. The button is not wrapping the system button. It is rendered using the StandardButton adapter.
/// VisualStyleRenderer from the OS is used for certain parts, which may have issues in high-resolution scenarios.
/// Dark mode works to some extent, but improvements are needed.
Copy link
Member

Choose a reason for hiding this comment

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

What improvements are needed? As public documentation it would be better to say something like.

Suggested change
/// Dark mode works to some extent, but improvements are needed.
/// Partially supports dark mode.

And then make sure we have tracking issues around what we might be able to address.

Copy link
Member Author

Choose a reason for hiding this comment

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

/// <description>
/// The button is fully owner-drawn. No rendering is delegated to the OS, not even VisualStyleRenderer.
/// This style works well in dark mode and is fully controlled by the application.
/// 3D effects are expected but may not be rendered; consider revisiting for meaningful styling.
Copy link
Member

Choose a reason for hiding this comment

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

What does this mean for the user? Does it really work well if 3d effects don't render? I don't really understand this, but I would guess you might be saying something like:

"Generally, works well in dark mode, with the exception of ..."

Copy link
Member Author

Choose a reason for hiding this comment

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

Note, that this is describing a status quo, which has been changing over the time up to a point where the docs often no longer reflect the presents. The docs are still stating things like certain Borderstyle effects (not only for buttons), which are no longer true in general. For example, many controls have a BorderStyle 3D, which no longer resemble any 3D effect. But then we have control like the RichTextBox, which still does that in the same way as under Windows 95.

That was the original motivation behind the Visual Styles, which became then strongly connected to the dark mode changes, and the reason, I would argue to revisit the VisualStyles approach, should it come to it (resources, mandate).

/// <description>
/// The button wraps the system button and is not owner-drawn.
/// No <c>OnPaint</c>, <c>OnPaintBackground</c>, or adapter is involved.
/// In dark mode, this style is used as a fallback for Standard-style buttons.
Copy link
Member

Choose a reason for hiding this comment

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

What are standard style buttons? Is there a cref that could be used here? Or another link that users can follow to understand this from docs?

@@ -64,6 +64,9 @@ _ when color.GetBrightness() > 0.5 => ControlPaint.Dark(color, 0.2f),
};

/// <summary>
/// Creates a dark mode compatible brush. Important:
/// Always do: `using var brush = GetDarkModeBrush(color)`,
/// since you're dealing with a cached brush => scope, really!
Copy link
Member

Choose a reason for hiding this comment

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

Comment is duplicated. Please do not use ! in comments.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, damn. My bad. Noted.

@@ -68,7 +68,9 @@ protected override void OnPaint(PaintEventArgs pevent)
{
base.OnPaint(pevent);

if (Application.RenderWithVisualStyles & _useComboBoxTheme)
#pragma warning disable WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
if (!Application.IsDarkModeEnabled
Copy link
Member

Choose a reason for hiding this comment

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

A comment why we're skipping for dark mode would be useful.

@@ -96,6 +96,9 @@ public Appearance Appearance
else
{
UpdateStyles();

// UpdateStyles should also update the UserDraw flag, but it doesn't.
Copy link
Member

Choose a reason for hiding this comment

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

"Should" seems to imply that there is a follow up here. Is there something we should be tracking?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's maybe phrased a bit awkwardly. We need to update the docs rather. They imply that it should. It's now added to the list, and I ping @adegeo for it and talk to him!

graphics.DrawPath(focusPen, focusPath);
}

/// <summary>
Copy link
Member

Choose a reason for hiding this comment

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

nit: Shouldn't need to add comments to overrides unless you need to change the docs. The less copies of documentation we have around, the less likely we are to have some copies lacking updates.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added that rule to the Copilot-Instructions. It should not happen again (if you use that to generate the XML), and I will be working to get that into account for pure refactotoring purposes.

/// <summary>
/// Cache of colors for different button states in dark mode.
/// </summary>
internal class DarkModeButtonColors
Copy link
Member

Choose a reason for hiding this comment

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

Where is this overridden? If it isn't it should be sealed and virtuals removed. In either case, the constructor should not be public.

Copy link
Member Author

Choose a reason for hiding this comment

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

It's internal, and I kept the extensibility for us internally in mind. At that time, I did not know, if sealing a class would be final, but if I understood correctly, it is not. Added to the list!

@KlausLoeffelmann KlausLoeffelmann merged commit 15f1f35 into dotnet:klaus/preview6/A Jun 16, 2025
2 checks passed
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.

[Dark Mode] Button with FlatStyle 'Standard' is not in dark mode on 100% dpi after enabled SystemColorMode.Dark
2 participants