Skip to content

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

Open
ricardobossan wants to merge 13 commits into
dotnet:mainfrom
ricardobossan:Issue_14906_Add_Per_State_Stroke_Net11
Open

Add a per-state stroke model for Net11 editable-control borders#14919
ricardobossan wants to merge 13 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

Replace the flat, ForeColor-driven border and the primitive focus ring on VisualStylesMode.Net11 editable controls (TextBox, MaskedTextBox, RichTextBox, all via TextBoxBase) with a proper per-state stroke model.

  • New model and resolver: a ModernFieldStrokeState (Rest, Hover, Focused, Disabled, ReadOnly), a ModernFieldStroke record (side/top color, bottom color, surface, and DIP thicknesses), and a single ModernFieldStrokeResolver.GetStroke chokepoint with precedence Disabled > Focused > ReadOnly > Hover > Rest. Paint code receives only a completed stroke; all state and color selection stays behind the resolver.
  • Linear-light color math added to ModernControlColorMath. WinUI control-stroke overlays are semi-transparent and must be composited in linear light. The existing PopupButtonColorMath.Blend works in encoded sRGB and cannot, so this adds the correct compositing. Colors derive from the effective background and are independent of ForeColor.
  • TextBoxBase.OnNcPaint now draws from the resolved stroke: a light side/top, a stronger darker bottom elevation edge, and a per-state surface. Focus is expressed by an accent bottom edge at 4 DIP (bottom only), which replaces the former rounded focus ring. Hover tracks pointer-over via OnMouseEnter/OnMouseLeave and repaints the non-client frame the same way focus does.
  • High Contrast automatically opts out of Net11 VisualStyles, so the modern path never runs under HC; the framework's legacy border (which already uses system colors) applies, and the model carries no HC handling.
  • The light-mode bottom edge (the visible resting border) is raised to meet WCAG 1.4.11 (~4.5:1 over a white field) per [Tracking] Modern field stroke state model for Net11 editable controls (TextBoxBase) #14906 feedback; it measured 1.74:1 and failed Accessibility Insights. The subtle side stroke is left at the WinUI value. The exact value is a proposal pending design confirmation.
  • Only runs under Net11. The legacy (< Net11) path is unchanged.

Scope is TextBoxBase. ComboBox (a WM_PAINT client-area adapter) and UpDown are out of scope and tracked as follow-ups in #14906. Design direction and the per-state values come from the discussion in #14906.

Customer Impact

Under VisualStylesMode.Net11, editable-control borders no longer inherit ForeColor (issue #14847, fixed here generally for all TextBoxBase controls), and they gain distinct rest, hover, focus, disabled, and read-only treatments consistent with the modern visual style.

Regression?

No. The change is gated on Net11; Classic, Disabled, and High Contrast (which opts out of VisualStyles) paint exactly as before.

Risk

Low to medium. It changes the appearance of Net11 editable-control borders (a new feature surface), behind the Net11 gate, and is covered by resolver unit tests.

Test methodology

  • Resolver unit tests
  • Manual verification across the five states, all three control types, light and dark mode, accent-color change, and DPI scaling, plus confirming High Contrast falls back to the legacy border (the VisualStyles opt-out)

Known follow-ups (not in this PR)

  • The focus bottom grows symmetrically about the border line rather than strictly outward. Growing it strictly outward without clipping at the control's outer bound needs additional non-client geometry work; it is a visual refinement, not a blocker.
  • ComboBox and UpDown convergence to the same visual target. They paint on a different path (ComboBox overpaints during WM_PAINT, not NC-paint), so they are out of this PR's scope and tracked separately in [Tracking] Modern field stroke state model for Net11 editable controls (TextBoxBase) #14906.

Screenshots

  1. Light mode: cycling the system accent color, switching to High Contrast themes, and interacting with every control across the states:
14906-complete2-light-accent
  1. Dark mode: cycling the system accent color and interacting with every control across the states:
14906-complete-dark-accent

The Classic-mode control visible in both confirms the legacy (< Net11) path is unchanged. DPI scaling was verified manually (the 2 DIP and 4 DIP strokes scale up proportionally); it is absent from the recordings only because changing the host display scale stopped the screen recorder.

@ricardobossan

This comment was marked as outdated.

Ricardo Bossan (BEYONDSOFT CONSULTING INC) (from Dev Box) added 10 commits August 25, 2026 22:53
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.
@ricardobossan
ricardobossan force-pushed the Issue_14906_Add_Per_State_Stroke_Net11 branch from 7fb6b99 to 9fab207 Compare August 26, 2026 02:49
Comment thread src/System.Windows.Forms/System/Windows/Forms/Controls/TextBox/TextBoxBase.cs Outdated
@LeafShi1
LeafShi1 self-requested a review August 27, 2026 01:54

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 on lines 1696 to 1700
protected override void OnHandleDestroyed(EventArgs e)
{
_focusIndicatorRenderer?.Dispose();
_focusIndicatorRenderer = null;
_textBoxFlags[s_modified] = Modified;
_textBoxFlags[s_setSelectionOnHandleCreated] = true;
// Update text selection cached values to be restored when recreating the handle.
Comment on lines +19 to +22
// WinUI control-stroke overlay alphas over the black (light mode) / white (dark mode) pole,
// verified against Common_themeresources_any.xaml, except light-mode Strong, which is raised
// above WinUI so the visible bottom edge meets WCAG 1.4.11 (#14906). Composited in linear light.
private const int StrokeDefaultAlphaLight = 0x0F; // ControlStrokeColorDefault
Comment on lines +2678 to +2679
int sideThickness = Math.Max(1, (int)MathF.Round(stroke.SideTopThicknessDip * DeviceDpi / 96f));
int bottomThickness = Math.Max(1, (int)MathF.Round(stroke.BottomThicknessDip * DeviceDpi / 96f));
Comment on lines 2664 to +2668
int borderThickness = Math.Max(focusBorderMetrics.Width, focusBorderMetrics.Height);
int focusBandHeight = GetVisualStylesFocusBandHeight();

Color clientBackColor = BackColor;
ModernFieldStrokeContext strokeContext = new(
BackColor: BackColor,
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.

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

3 participants