Skip to content

Fix issue 14807: CheckBox/RadioButton: ToggleSwitch renders black background when BackColor is Transparent - #14813

Open
SimonZhao888 wants to merge 6 commits into
dotnet:mainfrom
SimonZhao888:Fix_Issue_14807
Open

Fix issue 14807: CheckBox/RadioButton: ToggleSwitch renders black background when BackColor is Transparent#14813
SimonZhao888 wants to merge 6 commits into
dotnet:mainfrom
SimonZhao888:Fix_Issue_14807

Conversation

@SimonZhao888

@SimonZhao888 SimonZhao888 commented Jul 27, 2026

Copy link
Copy Markdown
Member

Fixes #14807

Root Cause

AnimatedToggleSwitchRenderer.RenderControl currently clears with graphics.Clear(Control.BackColor). This bypasses the normal WinForms background pipeline (parent background, transparent background, background image/custom-painted parent), which can cause visual regressions on complex backgrounds.
Using the standard WinForms background painting path (e.g., Control.PaintBackground(...) with PaintEventArgs and ClientRectangle) instead of direct Clear. That preserves both:

parent/transparent background semantics, and
proper BackColor blending behavior (including semi-transparent colors).

Proposed changes

Implemented a background-painting optimization in AnimatedToggleSwitchRenderer to balance rendering correctness and animation performance. Replaced the unconditional Control.PaintBackground(...) call with a new helper: PaintControlBackground(Graphics graphics).

  • Added a fast path for the common case (BackgroundImage is null and opaque BackColor) to use graphics.Clear(Control.BackColor) and avoid per-frame PaintEventArgs allocation.
  • Kept a fallback path for complex backgrounds (transparent back color or background image) that uses Control.PaintBackground(...) to preserve correct WinForms background behavior.
  • In the fallback path, painting is limited to ClipBounds & ClientRectangle to reduce overdraw during animation.

Customer Impact

Improves rendering behavior and animation efficiency for ToggleSwitch-style CheckBox/RadioButton controls.

  • Preserves correct background rendering in complex scenarios (transparent backgrounds and background images), reducing visual artifacts.
  • Reduces per-frame overhead in common scenarios (opaque solid background), improving animation smoothness.

Regression?

  • No

Risk

  • Mini

Screenshots

Before

image

After

image

Light:

14807.mp4
2026-07-30.145810.mp4

DarkMode:

2026-07-30.142639.mp4
2026-07-30.145436.mp4

Test methodology

  • Manually

Test environment(s)

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 fixes ToggleSwitch-style CheckBox/RadioButton rendering when BackColor is Transparent by avoiding Graphics.Clear(BackColor) (which bypasses WinForms background painting semantics) and instead routing background painting through the standard Control.PaintBackground(...) pipeline when transparency/background images are involved.

Changes:

  • Replaced unconditional graphics.Clear(Control.BackColor) with a new PaintControlBackground(Graphics) helper.
  • Added a fast path for opaque solid backgrounds (no BackgroundImage) and a correctness path for transparent/background-image scenarios using Control.PaintBackground(...) with a clipped rectangle.

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

@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
@ricardobossan

Copy link
Copy Markdown
Member

Not sure if intentional, but the existing rendering-matrix test always uses the default
opaque BackColor, so it doesn't cover the fallback path this PR adds. Ran this against
the test project and it passes (2/2 cases).

src\test\unit\System.Windows.Forms\System\Windows\Forms\CheckBoxToggleSwitchTests.cs:

[WinFormsTheory]
[InlineData(true)]
[InlineData(false)]
public void CheckBox_ToggleSwitch_TransparentBackColor_DoesNotThrow(bool useBackgroundImage)
{
    using SystemVisualSettingsTestScope settingsScope = new(
        clientAreaAnimationEnabled: false,
        highContrastEnabled: false);
    using Bitmap backgroundImage = new(10, 10);
    using Panel parent = new() { BackColor = Color.LightBlue, Size = new Size(200, 100) };
    using CheckBox checkBox = new()
    {
        Parent = parent,
        Appearance = Appearance.ToggleSwitch,
        VisualStylesMode = VisualStylesMode.Net11,
        BackColor = Color.Transparent,
        BackgroundImage = useBackgroundImage ? backgroundImage : null,
        Size = new Size(140, 36),
        Text = "Matrix"
    };
    parent.CreateControl();
    using Bitmap bitmap = new(checkBox.Width, checkBox.Height);

    checkBox.DrawToBitmap(bitmap, new Rectangle(Point.Empty, checkBox.Size));
}

@LeafShi1 LeafShi1 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There are no automated tests, but the fix looks fine.

@KlausLoeffelmann

Copy link
Copy Markdown
Member

@SimonZhao888, could you do a few more visual tests with different background colors and also in darkmode?

Thanks!

@SimonZhao888

Copy link
Copy Markdown
Member Author

@SimonZhao888, could you do a few more visual tests with different background colors and also in darkmode?

Thanks!

Got it!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (4)

src/test/unit/System.Windows.Forms/System/Windows/Forms/RadioButtonTests.cs:287

  • PaintEventArgs implements IDisposable; this instance should be disposed to avoid leaking GDI resources in longer-running test runs.
        PaintEventArgs e = new(graphics, control.ClientRectangle);

src/test/unit/System.Windows.Forms/System/Windows/Forms/RadioButtonTests.cs:388

  • CountPixels ignores the alpha channel, which can produce false positives (e.g., a newly created Bitmap is often initialized to transparent black, so searching for black may succeed even if nothing was rendered). Consider excluding fully transparent pixels so the color assertions actually validate rendering output.
                Color pixel = bitmap.GetPixel(x, y);
                if (Math.Abs(pixel.R - color.R) <= channelTolerance
                    && Math.Abs(pixel.G - color.G) <= channelTolerance
                    && Math.Abs(pixel.B - color.B) <= channelTolerance)
                {

src/test/unit/System.Windows.Forms/System/Windows/Forms/CheckBoxTests.cs:647

  • PaintEventArgs implements IDisposable; wrap this in a using declaration to ensure any underlying GDI state is released promptly.
        PaintEventArgs e = new(graphics, box.ClientRectangle);

src/System.Windows.Forms/System/Windows/Forms/Rendering/RadioButton/AnimatedRadioGlyphRenderer.cs:131

  • checkedProgress is computed using Math.Max(_dotScaleCurrent, _dotScaleTarget), which will immediately clamp to 1 when transitioning from unchecked to checked (because the target is 1). That prevents the outer circle/border from actually blending "as checked progress advances". Use the current animated value only so the blend follows the animation.
        float checkedProgress = Math.Clamp(
            Math.Max(_dotScaleCurrent, _dotScaleTarget),
            0f,
            1f);

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CheckBox/RadioButton: ToggleSwitch renders black background when BackColor is Transparent

5 participants