Skip to content

Add a per-state stroke model for Net11 editable-control borders - #14919

Closed
ricardobossan wants to merge 22 commits into
dotnet:mainfrom
ricardobossan:Issue_14906_Add_Per_State_Stroke_Net11
Closed

Add a per-state stroke model for Net11 editable-control borders#14919
ricardobossan wants to merge 22 commits into
dotnet:mainfrom
ricardobossan:Issue_14906_Add_Per_State_Stroke_Net11

Conversation

@ricardobossan

@ricardobossan ricardobossan commented Aug 22, 2026

Copy link
Copy Markdown
Member

Fixes #14906

Proposed changes

For VisualStylesMode.Net11, replace the simple ForeColor-based border and basic focus ring used by editable controls (TextBox, MaskedTextBox, and RichTextBox through TextBoxBase) with a state-resolved stroke design.

  • Introduce ModernFieldStrokeState with Rest, Hover, Focused, Disabled, and ReadOnly values; the ModernFieldStroke record holds the side/top and bottom colors, surface, DIP widths, and focus-indicator setting. ModernFieldStrokeResolver.GetStroke is the single resolution point, applying Disabled > Focused > ReadOnly > Hover > Rest. Rendering consumes its resolved stroke rather than selecting colors or states itself.
  • Add linear-light compositing to ModernControlColorMath because the semi-transparent WinUI control-stroke overlays require it. PopupButtonColorMath.Blend cannot serve this purpose because it blends encoded sRGB values. Stroke colors use the effective background and do not depend on ForeColor.
  • Have TextBoxBase.OnNcPaint render every state over the control's own BackColor, eliminating state-specific interior fills so a padded custom background remains seamless. State changes are communicated by border color or weight and, when focused, the accent. For rounded borders, CreateVisualStylesBottomEdgePath creates the tapered bottom edge so it joins the corner rounds without extending up their arcs. The common bottom-edge shape is used for every rounded state, with only its color and thickness varying. Focus makes that edge accent-colored and heavier at about 3 DIP while keeping the other sides at the subtle resting color, rather than drawing a heavy surround. FixedSingle and None retain a straight focus underline. Pointer tracking in OnMouseEnter and OnMouseLeave repaints the non-client frame for hover, which is distinguished only through border color and contrast.
  • Keep ReadOnly in the resolver's precedence model, but give it the Rest appearance, including the same surface and strokes. The visually differentiated appearances are Rest, Hover, Focused, and Disabled.
  • Since High Contrast disables Net11 VisualStyles automatically, this drawing route is never selected in HC. The pre-existing system-color legacy border handles that case, leaving no High Contrast logic in the new model.
  • In light mode, the resting lower edge delivers roughly 3:1 WCAG 1.4.11 non-text contrast on white, unlike the approximately 1.74:1 WinUI measurement that Accessibility Insights rejected. Retaining that 3:1 minimum also keeps it less prominent than the accent focus edge. In dark mode, the side/top overlay alpha is reduced from the WinUI approximately 0x12 to near-invisible 0x03, matching the subtle light and classic treatment. Both themes use the visible bottom edge to define the field.
  • Limit this behavior to Net11; the < Net11 rendering path remains as it was.

The work covers TextBoxBase. ComboBox, which uses a WM_PAINT client-area adapter, and UpDown remain follow-up items under #14906. Leaf also identified the Windows 11 flat scrollbar behavior where its up/down arrows appear only on hover under the Net11 appearance. That scrollbar change is deliberately deferred to a separate follow-up so these border-rendering changes can merge first. The design direction and state values are from the #14906 discussion.

Customer Impact

When VisualStylesMode.Net11 is active, every TextBoxBase control receives a modern border that is unaffected by ForeColor, addressing #14847 across the control family. Rest, hover, focus, and disabled have their respective treatments, while read-only intentionally looks like rest.

Regression?

No regressions are expected. This is a Net11-gated change, leaving Classic and the legacy fallback, including the High Contrast VisualStyles opt-out path, unchanged.

Risk

The risk level is low to medium. It affects the newly introduced Net11 editable-control border surface only, is contained behind that mode gate, and has resolver-level unit coverage.

Test methodology

  • Unit coverage for the resolver, including its precedence rules, state colors, shared surface behavior, and focus-indicator setting
  • Hands-on checks of all five states for each of the three control types in light and dark themes, across accent-color updates and DPI scaling, including confirmation that High Contrast chooses the legacy border through the VisualStyles opt-out
  • QA test-team validation (Testing result of Pull Request #14919: Add a per-state stroke model for Net11 editable-control borders #14997), whose feedback informed the resting-bottom and focus-corner weights and the hover border contrast

Screenshots

  1. Light mode: interact with each control in every state:
14906_20260904_light
  1. Dark mode: interact with each control in every state:
14906_20260904_dark

The Classic control shown in both recordings demonstrates that the legacy < Net11 branch is unaffected. Manual DPI testing confirmed proportional scaling of the 2 DIP and 4 DIP strokes; the recordings omit it because changing the host display scale halted the screen recorder.

@ricardobossan

This comment was marked as outdated.

Comment thread src/System.Windows.Forms/System/Windows/Forms/Controls/TextBox/TextBoxBase.cs Outdated
Comment thread src/System.Windows.Forms/System/Windows/Forms/Controls/TextBox/TextBoxBase.cs Outdated

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.

🟡 Changes recommended

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

Pull request overview

This PR updates the Net11 (modern visual styles) non-client rendering for editable text controls (via TextBoxBase) by introducing a per-state stroke model and routing border/focus rendering through a single resolver, including new linear-light compositing for WinUI-style overlays.

Changes:

  • Introduces a ModernFieldStroke* model (State, Context, Stroke) plus ModernFieldStrokeResolver.GetStroke() with explicit precedence for Disabled/Focused/ReadOnly/Hover/Rest.
  • Adds linear-light overlay compositing utilities to ModernControlColorMath for modern stroke/surface colors derived from effective background (not ForeColor).
  • Updates TextBoxBase NC painting to use the resolved stroke (including hover tracking) and removes the old focus-indicator animation path; adds resolver unit tests.
File summaries
File Description
src/System.Windows.Forms/System/Windows/Forms/Controls/TextBox/TextBoxBase.cs Replaces ForeColor-driven/animated focus rendering with resolved per-state stroke + hover tracking; updates NC paint focus/bottom edge logic.
src/System.Windows.Forms/System/Windows/Forms/Rendering/ModernControlColorMath.cs Adds linear-light compositing helpers and new field-stroke/surface color APIs (default/hover/strong/read-only + disabled strong border).
src/System.Windows.Forms/System/Windows/Forms/Rendering/ModernFieldStroke.cs Adds resolved stroke record used by paint paths.
src/System.Windows.Forms/System/Windows/Forms/Rendering/ModernFieldStrokeContext.cs Adds resolver input context record (enabled/read-only/focused/hovered/dark/accent/etc.).
src/System.Windows.Forms/System/Windows/Forms/Rendering/ModernFieldStrokeResolver.cs Adds the single chokepoint resolver to compute strokes from context + precedence rules.
src/System.Windows.Forms/System/Windows/Forms/Rendering/ModernFieldStrokeState.cs Adds internal enum describing stroke interaction states.
src/test/unit/System.Windows.Forms/System/Windows/Forms/Rendering/ModernFieldStrokeResolverTests.cs Adds unit tests covering precedence, thickness DIPs, opacity, and color-math invariants.
src/test/unit/System.Windows.Forms/TextBoxBaseTests.cs Removes tests tied to the old AnimatedFocusIndicatorRenderer behavior that no longer applies.
Review details

Suppressed comments (1)

src/System.Windows.Forms/System/Windows/Forms/Controls/TextBox/TextBoxBase.cs:2789

  • PaintRoundedBorderRegionMitigation still uses borderThickness from focus metrics, but the border now being drawn is sideThickness. If these differ, the mitigation can trace too wide/narrow and either leave artifacts or overpaint. Pass sideThickness to keep mitigation aligned with the actual stroke width.
        // Bottom (elevation and focus) edge. The rounded focus indicator grows out of the bottom border
        // as a tapered fill, leaving the left, top, and right of the rounded frame untouched, so the
        // corners do not become heavy (#14997). Non-focus states keep a resting bottom edge clipped to a
        // band; flat styles draw a straight focus underline (see #14906).
        if (BorderStyle == BorderStyle.Fixed3D && canRenderRoundedChrome)
  • Files reviewed: 8/8 changed files
  • Comments generated: 4
  • 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 src/System.Windows.Forms/System/Windows/Forms/Rendering/ModernControlColorMath.cs Outdated
@ricardobossan
ricardobossan marked this pull request as draft September 4, 2026 00:30
@KlausLoeffelmann

Copy link
Copy Markdown
Member

@ricardobossan, do you feel confident to take this out of draft and get it merged, after Olina tested this?

Or is there still work to do form your perspective?

@dotnet-policy-service dotnet-policy-service Bot added the draft draft PR label Sep 4, 2026
@ricardobossan
ricardobossan marked this pull request as ready for review September 4, 2026 18:08
@codecov

codecov Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 37.24166%. Comparing base (b0a7c95) to head (7f5b9a3).
⚠️ Report is 6 commits behind head on main.

Additional details and impacted files
@@              Coverage Diff              @@
##                main      #14919   +/-   ##
=============================================
  Coverage   37.24166%   37.24166%           
=============================================
  Files            246         246           
  Lines           9774        9774           
  Branches        1029        1029           
=============================================
  Hits            3640        3640           
  Misses          5970        5970           
  Partials         164         164           
Flag Coverage Δ
Debug 37.24166% <ø> (ø)
production 39.36526% <ø> (ø)
test 20.64923% <ø> (ø)
unit 39.36526% <ø> (ø)

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.

@ricardobossan

Copy link
Copy Markdown
Member Author

@ricardobossan, do you feel confident to take this out of draft and get it merged, after Olina tested this?

Or is there still work to do form your perspective?

@KlausLoeffelmann Hi Klaus, the border drawing itself is finished and solid. A few minor cosmetic refinements were the only reason the PR sat in draft, and I have deliberately parked those for a later follow-up rather than let them delay finished work, so the PR is now out of draft and ready for review and merge. Two rounds of QA feedback have already been addressed, tracked in #14997. There is also a final re-test pass on the latest commit running with QA for completeness, which should not hold anything up. Thanks for checking in.

@dotnet-policy-service dotnet-policy-service Bot added the waiting-author-feedback The team requires more information from the author label Sep 5, 2026
Ricardo Bossan (BEYONDSOFT CONSULTING INC) (from Dev Box) added 16 commits September 7, 2026 16:16
OnNcPaint now builds a ModernFieldStrokeContext and draws the resolved
ModernFieldStroke (per-state side/top color, bottom color, surface, and
DIP thicknesses) instead of deriving the border from ForeColor, so text
color and border are finally independent.

x Focus is expressed by the accent bottom edge alone; the former rounded
focus ring is removed. The bottom-edge band is clamped so the accent
stays on the bottom and its corners rather than wrapping up the sides.
Track pointer-over via OnMouseEnter/OnMouseLeave under the Net11 guard,
repainting the non-client frame through InvalidateVisualStylesFrame (the
same path focus uses), and feed the hovered flag into the stroke context
so the resolver applies the Hover treatment.
@KlausLoeffelmann
KlausLoeffelmann force-pushed the Issue_14906_Add_Per_State_Stroke_Net11 branch from d8f0650 to 125c254 Compare September 7, 2026 23:17
@dotnet-policy-service dotnet-policy-service Bot removed the waiting-author-feedback The team requires more information from the author label Sep 8, 2026
Ricardo Bossan (BEYONDSOFT CONSULTING INC) (from Dev Box) added 5 commits September 8, 2026 21:38
- Uses the lighter composed field stroke for the modern enabled UpDown
border while keeping the full rounded frame needed for the spin buttons.
- Aligns the UpDown corner radius with the modern text field and removes
the unused standalone value.
- Draws the focus accent on the shared bottom-edge taper so it stays on
the flat lower edge and keeps the fade behavior, with the shared base
covering NumericUpDown, DomainUpDown, and related controls.
Uses a shared offscreen surface for the modern frame, border, and focus
line.
This keeps the classic path untouched while avoiding redraw flicker on
the live DC.
@KlausLoeffelmann KlausLoeffelmann added the NewApi-Net11 Tracks issues for public APIs targeted for .NET 11. label Sep 10, 2026
@KlausLoeffelmann

Copy link
Copy Markdown
Member

Hey @ricardobossan ,

great job - that looks really good!

I will trigger a final Copilot review - see what issues it find, and address those, which make sense to you.

Once you addressed those and resolved the conflicts, commit, push it and ping me, so I can take a final look, before we merge it to main.

After you merge it, please cherry pick/dual check-in the PR into 'integration/net11RC2' and have anybody from CTI approve it based on my approval into main.

@LeafShi1 FYI.

@ricardobossan

Copy link
Copy Markdown
Member Author

Closed in favor of #15080

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

Labels

NewApi-Net11 Tracks issues for public APIs targeted for .NET 11.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Tracking] Modern field stroke state model for Net11 editable controls (TextBoxBase)

4 participants