Skip to content

Fix how the toggle switch draws itself - #1

Merged
Skorinn merged 4 commits into
masterfrom
develop
Sep 8, 2026
Merged

Skorinn merged 4 commits into
masterfrom
develop

Conversation

@Skorinn

@Skorinn Skorinn commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Three faults in how ToggleButton draws itself, all found by giving the switch a colour other than the black
it defaults to. The first two had been there since the original implementation and were invisible while the
switch was black; the third was introduced fixing the first, in this same branch.

Black corners

OnPaint cleared the control with this.Parent.BackColor. That was wrong three ways: it ignored the
control's own BackColor, which is the property a consumer sets to say what the switch is sitting on, so
setting it did nothing; it required a parent to exist at paint time, so rendering an unparented instance
threw; and it turned the corners the rounded switch does not reach black whenever the parent's
BackColor was Color.Transparent, because clearing with a transparent colour writes it into the double
buffer rather than letting anything through.

The clear now comes from the control's own BackColor. SupportsTransparentBackColor is added alongside, so
a consumer can set BackColor = Color.Transparent and have whatever the control sits on show through
properly.

A notch off the corner, and a toggle that looked to have escaped

The background stopped two pixels short of the control's right edge, leaving a sliver of the control
uncovered beside the rounded end, which showed as a notch hanging off the corner.

Separately, the toggle's size and its position came from expressions that did not agree: it was inset two
pixels from the top and the left, but its diameter left it four pixels short of the right end of the
background, and the checked position put it two pixels from that end instead. The gap on the side the toggle
had travelled to came out narrower than the others, and a gap of a pixel or two, once antialiasing has taken
its share, reads as the toggle breaking out of the background rather than sitting in it.

Both the diameter and the two positions now come from one inset, so the gap is the same on every side and
the two states are mirror images of each other.

A fringe of the previous frame

Removing the clear in the first commit went one step too far. The reasoning was that AllPaintingInWmPaint
has the background painted before OnPaint runs — true of Control, but not of ButtonBase, which paints
its own background from OnPaint, by way of the renderer it runs there. This control's override replaces
that, so nothing was left clearing the surface.

The previous frame therefore stayed underneath, and every repaint blended the new switch's antialiased edge
over the old one. Switching the toggle on left a grey fringe of the off state's track hanging outside the
blue, one pixel clear of it, following the curve of the rounded end.

Verification

There are no tests in this repository, so this was verified by driving the consumer that uncovered it. Read
straight out of the bitmap, along the middle row of a 71-pixel-wide control:

State Track before Toggle Track after
Off 4 px 17 px 50 px
On 50 px 17 px 4 px

Exact mirror images, and no neutral-grey pixel anywhere in the switch — the switch's own antialiased edges
blend toward the colour behind them, so on a white surface anything neutral had come from somewhere else.

The disabled state was checked separately for any blue left over from the previous frame: none.

Version

AssemblyVersion and AssemblyFileVersion are bumped to 1.0.1.0. The release workflow requires the tag to
be reachable from master, so a release can be cut once this lands.

🤖 Generated with Claude Code

https://claude.ai/code/session_0153VVkWg7DQmdaNLcvtY37w

Skorinn and others added 3 commits September 8, 2026 08:14
OnPaint cleared the control with this.Parent.BackColor. That was wrong in
three ways.

It ignored the control's own BackColor, which is the property a consumer sets
to say what the switch is sitting on, so setting it did nothing.

It required a parent to exist at paint time, so rendering an unparented
instance - DrawToBitmap, or some designer hosting - threw.

And it blacked out the corners the rounded switch does not reach whenever the
parent's BackColor was Color.Transparent, because clearing with a transparent
colour writes it into the double buffer rather than letting anything through.
That went unnoticed while the switch itself defaulted to black, since the
black corners and the black switch were indistinguishable; it appears the
moment a consumer gives the switch a colour of its own.

Nothing needs to be cleared here. AllPaintingInWmPaint has the background
painted from BackColor immediately before OnPaint runs. BackColor is ambient,
so a consumer that has not set one still gets the parent's colour and nothing
changes for them; one that has set it now gets what it asked for.

SupportsTransparentBackColor is added alongside, so BackColor can be set to
Color.Transparent and have whatever the control sits on show through the
corners properly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0153VVkWg7DQmdaNLcvtY37w
…ggle

Two faults in the geometry, both of which only became visible once the switch
was given a colour of its own.

The background stopped two pixels short of the control's right edge, so a
sliver of the control was left uncovered beside the rounded end and showed as
a notch hanging off the corner. The rounded ends are now as wide as the
control is tall and the right one ends on the last pixel, so the background
fills the control.

The toggle's size and its position were set from separate expressions that
did not agree: it was inset two pixels from the top and the left, but its
diameter left it four pixels short of the right end of the background, and
the checked position put it two pixels from that end instead. The gap on the
side the toggle had travelled to came out narrower than the others, and a gap
of a pixel or two, once antialiasing has taken its share, reads as the toggle
breaking out of the background rather than sitting in it.

Both the diameter and the two positions now come from one inset, so the gap
is the same on every side and the two states are mirror images of each other.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0153VVkWg7DQmdaNLcvtY37w
Removing the clear from OnPaint went one step too far. The reasoning was that
AllPaintingInWmPaint has the background painted before OnPaint runs, which is
true of Control but not of ButtonBase: ButtonBase paints its own background
from OnPaint, by way of the renderer it runs from there, and this override
replaces that. Nothing was left clearing the surface.

The previous frame therefore stayed underneath, and every repaint blended the
new switch's antialiased edge over the old one. Switching the toggle on left
a grey fringe of the off state's track hanging outside the blue, one pixel
clear of it, following the curve of the rounded end. It was measurable as
neutral grey - the switch's own edges blend toward the colour behind them, so
anything neutral on a white surface came from somewhere else.

The surface is cleared again, from the control's own BackColor rather than
the parent's, which is what the original was doing wrong. A consumer that
wants what is behind the control to show through sets BackColor to
Color.Transparent, and that is handed to the framework to paint.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0153VVkWg7DQmdaNLcvtY37w
Copilot AI lite review requested due to automatic review settings September 8, 2026 17:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

AssemblyInformationalVersion remains at 1.0.0, leaving the user-visible/release-tag version inconsistent with the bumped 1.0.1.0 version.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR fixes rendering issues in ToggleButton when using non-default colors by properly clearing/painting the background surface (including transparent scenarios) and by making the track/toggle geometry consistent so the toggle sits evenly inside the background in both states.

Changes:

  • Paint/clear the control surface using the control’s own BackColor, enabling proper transparent-corner behavior via SupportsTransparentBackColor.
  • Adjust background path geometry to reach the right edge and unify toggle sizing/positioning via a single inset.
  • Bump assembly version/file version to 1.0.1.0.
File summaries
File Description
ToggleButton.cs Fixes background clearing behavior and corrects track/toggle geometry so drawing is consistent with non-black colors and transparent backgrounds.
Properties/AssemblyInfo.cs Updates assembly versioning for the 1.0.1.0 release.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread Properties/AssemblyInfo.cs Outdated
[assembly: AssemblyFileVersion("1.0.1.0")]

// The version reported to users, and the version the release tag must match
[assembly: AssemblyInformationalVersion("1.0.0")]
The release workflow reads AssemblyInformationalVersion, not AssemblyVersion,
and refuses to build a tag that disagrees with it. Only the other two were
bumped with the fix, so a v1.0.1 tag would have been rejected as not matching
the assembly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0153VVkWg7DQmdaNLcvtY37w
@Skorinn
Skorinn merged commit 5b04482 into master Sep 8, 2026
2 checks passed
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.

2 participants