Skip to content

Add InvalidateStyle() and VisualStateManager.InvalidateVisualStates() APIs#34723

Merged
jfversluis merged 3 commits intonet11.0from
fix/invalidate-style-34721
Apr 2, 2026
Merged

Add InvalidateStyle() and VisualStateManager.InvalidateVisualStates() APIs#34723
jfversluis merged 3 commits intonet11.0from
fix/invalidate-style-34721

Conversation

@StephaneDelcroix
Copy link
Copy Markdown
Contributor

@StephaneDelcroix StephaneDelcroix commented Mar 28, 2026

Description

Adds public APIs to force reapplication of in-place-mutated styles and visual states, primarily for Hot Reload scenarios (including XAML Incremental Hot Reload).

Problem

When a Style or VisualState object is mutated in-place (e.g., a Setter's Value is changed), the framework does not reapply it:

  • MergedStyle uses reference equality (_style == value) to skip reapplication
  • VisualStateManager.GoToState short-circuits when CurrentState.Name == name

The only workaround was to create new object instances and reassign them.

Solution

StyleableElement.InvalidateStyle() — unconditionally unapplies and reapplies the current merged style (implicit + class + explicit layers):

element.InvalidateStyle();

Also added on Span and ImageSource which independently own their own MergedStyle.

VisualStateManager.InvalidateVisualStates(VisualElement) — unapplies and reapplies current state setters for all groups:

VisualStateManager.InvalidateVisualStates(element);

Both APIs are caller-driven (no automatic change tracking) and have no effect on elements without styles/VSM groups.

New Public API

// Style reapplication
Microsoft.Maui.Controls.StyleableElement.InvalidateStyle() -> void
Microsoft.Maui.Controls.Span.InvalidateStyle() -> void
Microsoft.Maui.Controls.ImageSource.InvalidateStyle() -> void

// VSM reapplication
static Microsoft.Maui.Controls.VisualStateManager.InvalidateVisualStates(VisualElement) -> void

Testing

6 new unit tests covering:

  • Mutated setter values are reapplied after InvalidateStyle()
  • Multiple setters are reapplied correctly
  • No-op when no style is set
  • Mutated VSM setter values are reapplied after InvalidateVisualStates()
  • No-op when no VSM groups exist
  • No-op when no current state is set

Fixes #34721
Fixes #34722
Fixes #618

Add public APIs to force reapplication of in-place-mutated styles and
visual states, primarily for Hot Reload scenarios.

- StyleableElement.InvalidateStyle() - forces unapply+reapply of merged style
- Span.InvalidateStyle() - same for Span (owns its own MergedStyle)
- ImageSource.InvalidateStyle() - same for ImageSource
- VisualStateManager.InvalidateVisualStates(VisualElement) - forces
  unapply+reapply of current visual state setters for all groups

Fixes #34721
Fixes #34722

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 28, 2026 20:56
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 28, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34723

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34723"

Copy link
Copy Markdown
Contributor

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 adds public “invalidate” APIs to force reapplication of styles and visual states after in-place mutation (primarily to support Hot Reload / XAML Incremental Hot Reload scenarios where reference-equality short-circuits prevent reapplying).

Changes:

  • Added StyleableElement.InvalidateStyle() (and equivalent APIs on Span and ImageSource) to unapply+reapply the current merged style layers.
  • Added VisualStateManager.InvalidateVisualStates(VisualElement) to unapply+reapply setters for each group’s current state.
  • Added unit tests covering style and VSM invalidation/no-op cases, and updated PublicAPI.Unshipped entries across TFMs.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/Controls/src/Core/StyleableElement/StyleableElement.cs Adds public InvalidateStyle() forwarding to merged style reapply.
src/Controls/src/Core/MergedStyle.cs Adds internal Reapply() helper which unapplies/applies style layers.
src/Controls/src/Core/Span.cs Adds public InvalidateStyle() for Span-owned merged style.
src/Controls/src/Core/ImageSource.cs Adds public InvalidateStyle() for ImageSource-owned merged style.
src/Controls/src/Core/VisualStateManager.cs Adds InvalidateVisualStates(VisualElement) to reapply current visual state setters.
src/Controls/tests/Core.UnitTests/StyleTests.cs Adds unit tests validating style invalidation behavior and no-op safety.
src/Controls/tests/Core.UnitTests/VisualStateManagerTests.cs Adds unit tests validating VSM invalidation behavior and no-op safety.
src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt Declares new public APIs (netstandard).
src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt Declares new public APIs (net).
src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt Declares new public APIs (Android).
src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt Declares new public APIs (iOS).
src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt Declares new public APIs (MacCatalyst).
src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt Declares new public APIs (Windows).
src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt Declares new public APIs (Tizen).

return;
}

var vsgSpecificity = vsgSpecificityValue.Key;
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

InvalidateVisualStates computes vsgSpecificity from the property context but never assigns it back to groups.Specificity (unlike GoToState). VisualStateGroupsPropertyChanged relies on VisualStateGroupList.Specificity when unapplying old state setters; leaving it stale can prevent proper unapply if the groups are later replaced/cleared after an invalidation. Consider setting groups.Specificity = vsgSpecificity before computing specificity to keep the list consistent with how its setters were applied.

Suggested change
var vsgSpecificity = vsgSpecificityValue.Key;
var vsgSpecificity = vsgSpecificityValue.Key;
groups.Specificity = vsgSpecificity;

Copilot uses AI. Check for mistakes.
}

var vsgSpecificity = vsgSpecificityValue.Key;
var specificity = vsgSpecificity.CopyStyle(1, 0, 0, 0);
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.

The arguments are not at all obvious to me. What do the 1 and 0 mean here? why aren't we un-applying and re-applying everything with the original specificity?

/// Forces unapply and reapply of the current merged style.
/// Use when a <see cref="Style"/> has been mutated in-place and needs to be reflected on the element.
/// </summary>
public void InvalidateStyle() => _mergedStyle.Reapply();
Copy link
Copy Markdown
Member

@simonrozsival simonrozsival Mar 30, 2026

Choose a reason for hiding this comment

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

If this is an API intended just for internal use by hot reload, should we make it at least [EditorBrowseable(Never)]? Should the comment discourage people from using it in their codebases?

StephaneDelcroix and others added 2 commits March 30, 2026 10:17
These APIs are intended for infrastructure use (Hot Reload) and should
not appear in IntelliSense for application developers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…eVisualStates

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jfversluis jfversluis merged commit 4fa7799 into net11.0 Apr 2, 2026
33 of 36 checks passed
@jfversluis jfversluis deleted the fix/invalidate-style-34721 branch April 2, 2026 12:51
PureWeen pushed a commit that referenced this pull request Apr 2, 2026
- Replace non-existent PR numbers (#34000, #33500, #33000, #34100)
  with real merged PRs (#34024, #34727, #31202, #28713, #34723)
- Add "in dotnet/maui" to all prompts to prevent agent asking for repo
- All PRs verified as real merged PRs with actual code changes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
davidortinau added a commit to dotnet/docs-maui that referenced this pull request Apr 10, 2026
Add moniker-gated sections documenting:
- StyleableElement.InvalidateStyle() in the XAML styles article
- VisualStateManager.InvalidateVisualStates() in the visual states article

These caller-driven APIs were added in dotnet/maui#34723 for .NET 11
Preview 3 to support in-place style/visual-state mutation scenarios.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
davidortinau added a commit to dotnet/maui-samples that referenced this pull request Apr 10, 2026
Demonstrates two new .NET MAUI 11 Preview 3 APIs (dotnet/maui#34723):
- StyleableElement.InvalidateStyle() for reapplying mutated styles
- VisualStateManager.InvalidateVisualStates() for reapplying visual states

Includes docregion markers for snippet extraction.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

4 participants