Skip to content

Fix RadioButtonGroup not working with ContentView#34781

Merged
kubaflo merged 4 commits intodotnet:inflight/currentfrom
Dhivya-SF4094:fix-34759
Apr 12, 2026
Merged

Fix RadioButtonGroup not working with ContentView#34781
kubaflo merged 4 commits intodotnet:inflight/currentfrom
Dhivya-SF4094:fix-34759

Conversation

@Dhivya-SF4094
Copy link
Copy Markdown
Contributor

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Issue Detail

RadioButtonGroup is not functioning correctly when RadioButton controls are placed inside a ContentView in .NET 10.

Root Cause

PR #32640 (fixing #32466) removed the coerceValue and Value = this initialiser from RadioButton, so RadioButton.Value now defaults to null. This introduced two distinct bugs in the ContentView+ControlTemplate scenario:

  • AddRadioButton guard object.Equals(radioButton.Value, this.SelectedValue) evaluates to object.Equals(null, null) == true, causing every button added via a ControlTemplate to be auto-checked immediately.
  • When the layout is not yet attached to a Page (common during ControlTemplate inflation), GetVisualRoot() returns null. The original fallback radioButton.Parent resolves to the button's immediate parent (Border), which only contains that single button — so other RadioButtons in the same group are never found and never unchecked.

Description of Change

  • Updated the equality check in AddRadioButton to ensure comparison only happens when SelectedValue is not null. This avoids unintended selection during initialization.
  • Improved the root resolution logic in UncheckOtherRadioButtonsInScope. If no visual root is found, fallback to the RadioButtonGroupController’s layout (group container).

Issue Fixed

Fixes #34759

Screenshots

Before  After 
 
34759_BeforeFix.mov
  
34759_AfterFix.mov

Dhivya-SF4094 and others added 3 commits April 1, 2026 11:01
Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com>
Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 2, 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://github.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34781

Or

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

@dotnet-policy-service dotnet-policy-service Bot added the partner/syncfusion Issues / PR's with Syncfusion collaboration label Apr 2, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 2, 2026

🧪 PR Test Evaluation

Overall Verdict: ⚠️ Tests need improvement

The unit test covers the primary regression scenario well, but it lacks explicit assertions for the layout's SelectedValue property and doesn't exercise the positive auto-check path through the ContentView scenario.

👍 / 👎 — Was this evaluation helpful? React to let us know!

📊 Expand Full Evaluation

PR Test Evaluation Report

PR: #34781 — Fix RadioButtonGroup not working with ContentView
Test files evaluated: 1 (RadioButtonTests.cs)
Fix files: 2 (RadioButtonGroup.cs, RadioButtonGroupController.cs)


Overall Verdict

⚠️ Tests need improvement

The new unit test adequately covers the core regression (RadioButtons inside ContentView ControlTemplates not mutually excluding each other), but it only implicitly covers the null SelectedValue guard fix and doesn't assert SelectedValue state on the layout after selections.


1. Fix Coverage — ⚠️

The fix addresses two distinct bugs:

  1. RadioButtonGroup.cs: GetVisualRoot returns null when a ContentView's ControlTemplate is applied before the ContentView is attached to a Page, so UncheckOtherRadioButtonsInScope silently bailed out. Fixed by falling back to the controller's layout element.

  2. RadioButtonGroupController.cs: In AddRadioButton, object.Equals(radioButton.Value, this.SelectedValue) was true when both are null, causing RadioButtons to be spuriously auto-checked on add when SelectedValue was unset. Fixed by guarding with this.SelectedValue is not null.

The test covers Bug #1 directly (mutual exclusion works across ContentViews). It covers Bug #2 implicitly — the Assert.False(radioButton1.IsChecked) and Assert.False(radioButton2.IsChecked) assertions after layout.Add() would fail before the null guard was added (since both buttons have Value = null and SelectedValue = null). However, there is no explicit comment or assertion tying this to Bug #2, making the coverage of that fix hard to see.

2. Edge Cases & Gaps — ⚠️

Covered:

  • Initial state: both buttons unchecked after ContentViews added (implicitly covers null/null SelectedValue/Value auto-check bug)
  • Mutual exclusion: checking button 1 → button 2 stays unchecked
  • Bidirectional exclusion: checking button 2 → button 1 becomes unchecked

Missing:

  • Positive auto-check path via ContentView: When SelectedValue IS set to a non-null value before adding RadioButtons (e.g., layout.SetValue(RadioButtonGroup.SelectedValueProperty, "Choice1")), the RadioButton with a matching Value should be auto-checked. This path exists in AddRadioButton and is modified by the fix but isn't tested in the ContentView scenario.
  • Layout SelectedValue assertion: The test only asserts IsChecked on the RadioButton objects. Asserting layout.GetValue(RadioButtonGroup.SelectedValueProperty) after each selection would confirm the controller's HandleRadioButtonGroupSelectionChanged path works correctly through the ContentView hierarchy.
  • Explicit null Value + null SelectedValue case: A small dedicated test that specifically sets radioButton.Value = null and layout.SetValue(SelectedValueProperty, null) before add, and asserts the button remains unchecked, would make Bug Update README.md #2 coverage explicit and easier to trace.

3. Test Type Appropriateness — ✅

Current: Unit Test (Core.UnitTests)
Recommendation: Same — correct choice. The fix is in pure C# logic in RadioButtonGroup.cs and RadioButtonGroupController.cs with no platform-specific rendering involved. A unit test is the lightest and most appropriate test type here.

4. Convention Compliance — ✅

Automated checks found no issues:

  • Uses [Fact] attribute ✅
  • Located in the appropriate RadioButtonTests.cs file ✅
  • No anti-patterns detected ✅

5. Flakiness Risk — ✅ Low

Pure synchronous unit test with no async operations, platform dependencies, timing, or external state. Extremely low flakiness risk.

6. Duplicate Coverage — ✅ No duplicates

Existing tests in RadioButtonTests.cs cover group selection, null selection clearing, and nested descendants — but none simulate the AddLogicalChild-before-parent pattern specific to ContentView ControlTemplates. The new test covers a distinct scenario.

7. Platform Scope — ✅

Unit test runs on all platforms via the standard Core.UnitTests project. The fix is in cross-platform code, so this is appropriate.

8. Assertion Quality — ⚠️

  • Assert.False(radioButton1.IsChecked) / Assert.True(radioButton2.IsChecked) — specific and correct ✅
  • Missing: Assert.Null(layout.GetValue(RadioButtonGroup.SelectedValueProperty)) after initial add — would confirm SelectedValue tracking works ⚠️
  • Missing: Assert.Equal("1", layout.GetValue(RadioButtonGroup.SelectedValueProperty)) after check — would verify the controller propagates selection through the ContentView hierarchy ⚠️

9. Fix-Test Alignment — ✅

The test directly simulates the problematic scenario from issue #34759: ControlTemplate applied via AddLogicalChild before the ContentView is parented. The test exercises both code paths changed by the fix.


Recommendations

  1. Add explicit SelectedValue assertions — After radioButton1.IsChecked = true, assert that layout.GetValue(RadioButtonGroup.SelectedValueProperty) equals radioButton1.Value. This closes the assertion gap and confirms the controller works through ContentView boundaries.

  2. Add explicit test for Bug Update README.md #2 — Add a focused [Fact] that sets SelectedValue = null on the layout, then adds a RadioButton with Value = null, and asserts IsChecked remains false. This makes the null guard fix self-documenting and prevents future regressions from reverting the guard.

  3. Test the positive auto-check path — Add a case where layout.SetValue(RadioButtonGroup.SelectedValueProperty, "Choice1") is set before adding a ContentView that wraps a RadioButton with Value = "Choice1". Assert it gets auto-checked. This validates the AddRadioButton path in the ContentView scenario end-to-end.

Note

🔒 Integrity filtering filtered 2 items

Integrity filtering activated and filtered the following items during workflow execution.
This happens when a tool call accesses a resource that does not meet the required integrity or secrecy level of the workflow.

🧪 Test evaluation by Evaluate PR Tests

@sheiksyedm sheiksyedm added community ✨ Community Contribution area-controls-radiobutton RadioButton, RadioButtonGroup labels Apr 4, 2026
@sheiksyedm sheiksyedm added this to the .NET 10 SR7 milestone Apr 6, 2026
@sheiksyedm sheiksyedm marked this pull request as ready for review April 6, 2026 06:39
Copilot AI review requested due to automatic review settings April 6, 2026 06:39
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

Fixes a .NET 10 regression where RadioButtonGroup fails to enforce single-selection when RadioButtons are hosted inside a ContentView control template (often before the visual tree is attached to a Page), and prevents unintended auto-checking when both SelectedValue and RadioButton.Value are null.

Changes:

  • Prevent auto-checking newly discovered RadioButtons when the group’s SelectedValue is null.
  • Improve uncheck scope resolution by falling back to the group controller’s layout when no Page visual root exists.
  • Add a unit test covering the ContentView + inline ControlTemplate scenario to prevent regressions.

Reviewed changes

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

File Description
src/Controls/src/Core/RadioButton/RadioButtonGroupController.cs Guards the initial equality-based auto-check behavior and exposes the controller’s layout for scope resolution.
src/Controls/src/Core/RadioButton/RadioButtonGroup.cs Uses the controller’s layout as the fallback uncheck root when no Page ancestor exists.
src/Controls/tests/Core.UnitTests/RadioButtonTests.cs Adds a regression test reproducing the ContentView control-template initialization scenario.

@Dhivya-SF4094
Copy link
Copy Markdown
Contributor Author

I have updated the unit test to include explicit assertions for the layout's SelectedValue property and added coverage for the positive auto-check path through the ContentView scenario.

@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Apr 10, 2026

🚦 Gate — Test Before and After Fix

👋 @Dhivya-SF4094 — new gate results are available. Please review the latest session below.

🚦 Gate Sessiond85b251 · Improve RadioButtonGroup ContentView tests: add SelectedValue assertions and positive auto-check path · 2026-04-11 12:24 UTC

Gate Result: ✅ PASSED

Platform: ANDROID · Base: main · Merge base: b43bdad1

Test Without Fix (expect FAIL) With Fix (expect PASS)
🧪 RadioButtonTests RadioButtonTests ✅ FAIL — 118s ✅ PASS — 82s
🔴 Without fix — 🧪 RadioButtonTests: FAIL ✅ · 118s
  Determining projects to restore...
  Restored /home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj (in 4.73 sec).
  Restored /home/vsts/work/1/s/src/Controls/src/Xaml/Controls.Xaml.csproj (in 2.71 sec).
  Restored /home/vsts/work/1/s/src/Controls/Maps/src/Controls.Maps.csproj (in 8.29 sec).
  Restored /home/vsts/work/1/s/src/TestUtils/src/TestUtils/TestUtils.csproj (in 6 ms).
  Restored /home/vsts/work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 11 ms).
  Restored /home/vsts/work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 65 ms).
  Restored /home/vsts/work/1/s/src/Essentials/src/Essentials.csproj (in 33 ms).
  Restored /home/vsts/work/1/s/src/Core/maps/src/Maps.csproj (in 50 ms).
  Restored /home/vsts/work/1/s/src/Core/src/Core.csproj (in 85 ms).
  1 of 10 projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13810030
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13810030
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13810030
  Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13810030
  Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0/Microsoft.Maui.Maps.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13810030
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13810030
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0/Microsoft.Maui.Controls.Xaml.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13810030
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0/Microsoft.Maui.Controls.Maps.dll
  TestUtils -> /home/vsts/work/1/s/artifacts/bin/TestUtils/Debug/netstandard2.0/Microsoft.Maui.TestUtils.dll
  Controls.Core.UnitTests -> /home/vsts/work/1/s/artifacts/bin/Controls.Core.UnitTests/Debug/net10.0/Microsoft.Maui.Controls.Core.UnitTests.dll
Test run for /home/vsts/work/1/s/artifacts/bin/Controls.Core.UnitTests/Debug/net10.0/Microsoft.Maui.Controls.Core.UnitTests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.13]   Discovering: Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:01.39]   Discovered:  Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:01.40]   Starting:    Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:01.61]     RadioButtonGroupWorksWithContentViewControlTemplate [FAIL]
[xUnit.net 00:00:01.61]       Assert.False() Failure
[xUnit.net 00:00:01.61]       Expected: False
[xUnit.net 00:00:01.61]       Actual:   True
[xUnit.net 00:00:01.61]       Stack Trace:
[xUnit.net 00:00:01.61]         /_/src/Controls/tests/Core.UnitTests/RadioButtonTests.cs(455,0): at Microsoft.Maui.Controls.Core.UnitTests.RadioButtonTests.RadioButtonGroupWorksWithContentViewControlTemplate()
[xUnit.net 00:00:01.61]            at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
[xUnit.net 00:00:01.61]            at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
  Passed RadioButtonGroupLayoutShouldNotLeak [94 ms]
  Passed GroupControllerSelectionIsNullWhenSelectedButtonRemoved [13 ms]
  Passed RadioButtonShouldNotLeak [19 ms]
  Passed LayoutGroupNameAppliesToExistingRadioButtons [< 1 ms]
  Passed GroupSelectedValueUpdatesWhenSelectedButtonValueUpdates [< 1 ms]
  Passed GroupNullSelectionClearsAnySelection [< 1 ms]
  Passed ValuePropertyCanBeSetToNull [< 1 ms]
  Passed ImpliedGroupDoesNotIncludeExplicitGroups [< 1 ms]
  Failed RadioButtonGroupWorksWithContentViewControlTemplate [5 ms]
  Error Message:
   Assert.False() Failure
Expected: False
Actual:   True
  Stack Trace:
     at Microsoft.Maui.Controls.Core.UnitTests.RadioButtonTests.RadioButtonGroupWorksWithContentViewControlTemplate() in /_/src/Controls/tests/Core.UnitTests/RadioButtonTests.cs:line 455
   at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
  Passed RadioButtonAddedToGroupGetsGroupName [< 1 ms]
  Passed RadioButtonGroupWorksWithDynamicallyAddedDescendants [1 ms]
  Passed RadioButtonGroupSelectedValueBindingWorksWithNestedDescendants [< 1 ms]
  Passed ImpliedGroup [< 1 ms]
  Passed ThereCanBeOnlyOne [< 1 ms]
  Passed NestedRadioButtonAddedToGroupGetsGroupName [< 1 ms]
  Passed UpdatedGroupNameAppliesToRadioButtonsWithOldGroupName [< 1 ms]
  Passed RadioButtonAddedToGroupKeepsGroupName [< 1 ms]
  Passed RemovingSelectedButtonFromGroupClearsSelection [< 1 ms]
[xUnit.net 00:00:01.63]   Finished:    Microsoft.Maui.Controls.Core.UnitTests
  Passed RadioButtonGroupAutoChecksMatchingButtonInContentViewWhenSelectedValuePreset [< 1 ms]

Test Run Failed.
Total tests: 19
     Passed: 18
     Failed: 1
 Total time: 2.1226 Seconds

🟢 With fix — 🧪 RadioButtonTests: PASS ✅ · 82s
  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13810030
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13810030
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13810030
  Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13810030
  Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0/Microsoft.Maui.Maps.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13810030
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13810030
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13810030
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0/Microsoft.Maui.Controls.Maps.dll
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0/Microsoft.Maui.Controls.Xaml.dll
  TestUtils -> /home/vsts/work/1/s/artifacts/bin/TestUtils/Debug/netstandard2.0/Microsoft.Maui.TestUtils.dll
  Controls.Core.UnitTests -> /home/vsts/work/1/s/artifacts/bin/Controls.Core.UnitTests/Debug/net10.0/Microsoft.Maui.Controls.Core.UnitTests.dll
Test run for /home/vsts/work/1/s/artifacts/bin/Controls.Core.UnitTests/Debug/net10.0/Microsoft.Maui.Controls.Core.UnitTests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.14]   Discovering: Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:01.41]   Discovered:  Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:01.42]   Starting:    Microsoft.Maui.Controls.Core.UnitTests
  Passed RadioButtonGroupLayoutShouldNotLeak [79 ms]
  Passed GroupControllerSelectionIsNullWhenSelectedButtonRemoved [12 ms]
  Passed RadioButtonShouldNotLeak [18 ms]
  Passed LayoutGroupNameAppliesToExistingRadioButtons [< 1 ms]
  Passed GroupSelectedValueUpdatesWhenSelectedButtonValueUpdates [< 1 ms]
  Passed GroupNullSelectionClearsAnySelection [< 1 ms]
  Passed ValuePropertyCanBeSetToNull [< 1 ms]
  Passed ImpliedGroupDoesNotIncludeExplicitGroups [< 1 ms]
  Passed RadioButtonGroupWorksWithContentViewControlTemplate [5 ms]
  Passed RadioButtonAddedToGroupGetsGroupName [< 1 ms]
  Passed RadioButtonGroupWorksWithDynamicallyAddedDescendants [1 ms]
  Passed RadioButtonGroupSelectedValueBindingWorksWithNestedDescendants [< 1 ms]
  Passed ImpliedGroup [< 1 ms]
  Passed ThereCanBeOnlyOne [< 1 ms]
  Passed NestedRadioButtonAddedToGroupGetsGroupName [< 1 ms]
  Passed UpdatedGroupNameAppliesToRadioButtonsWithOldGroupName [< 1 ms]
  Passed RadioButtonAddedToGroupKeepsGroupName [< 1 ms]
  Passed RemovingSelectedButtonFromGroupClearsSelection [< 1 ms]
[xUnit.net 00:00:01.61]   Finished:    Microsoft.Maui.Controls.Core.UnitTests
  Passed RadioButtonGroupAutoChecksMatchingButtonInContentViewWhenSelectedValuePreset [< 1 ms]

Test Run Successful.
Total tests: 19
     Passed: 19
 Total time: 2.1254 Seconds

📁 Fix files reverted (3 files)
  • eng/pipelines/ci-copilot.yml
  • src/Controls/src/Core/RadioButton/RadioButtonGroup.cs
  • src/Controls/src/Core/RadioButton/RadioButtonGroupController.cs

@MauiBot MauiBot added s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels Apr 10, 2026
@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Apr 11, 2026

🤖 AI Summary

👋 @Dhivya-SF4094 — new AI review results are available. Please review the latest session below.

📊 Review Sessiond85b251 · Improve RadioButtonGroup ContentView tests: add SelectedValue assertions and positive auto-check path · 2026-04-11 12:57 UTC
🔍 Pre-Flight — Context & Validation

Issue: #34759 - RadioButtonGroup not working with ContentView
PR: #34781 - Fix RadioButtonGroup not working with ContentView
Platforms Affected: Android, Windows, iOS, macOS (regression in .NET 10)
Files Changed: 2 implementation, 1 test

Key Findings

  • Regression introduced by PR SourceGen="false" #32640 (fixing [Regression] RadioButton should support Null Value #32466) which removed coerceValue and Value = this initializer from RadioButton — RadioButton.Value now defaults to null
  • Bug 1 (auto-check): In RadioButtonGroupController.AddRadioButton, object.Equals(null, null) evaluates to true, causing every RadioButton added via ControlTemplate to be immediately auto-checked
  • Bug 2 (uncheck scope): In RadioButtonGroup.UncheckOtherRadioButtonsInScope, when a layout is not yet attached to a Page, GetVisualRoot() returns null. The old fallback radioButton.Parent was the immediate Border parent, which contains only that single button — so sibling RadioButtons are never found/unchecked
  • Fix is entirely in unit-testable logic — no platform-specific code changes
  • Tests use AddLogicalChild to simulate ControlTemplate inflation before the ContentView is added to the layout

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #34781 (1) Guard AddRadioButton equality check with SelectedValue is not null; (2) fallback to RadioButtonGroupController.GetGroupController(radioButton)?.Layout in UncheckOtherRadioButtonsInScope ✅ PASSED (Gate) RadioButtonGroup.cs, RadioButtonGroupController.cs, RadioButtonTests.cs Original PR

🔧 Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix (claude-opus-4.6) Fix GetVisualRoot to return topmost ancestor instead of null; guard AddRadioButton with radioButton.Value is not null (mirrors SetSelectedValue pattern) ✅ PASS 2 files No new API surface
2 try-fix (claude-sonnet-4.6) Walk up ancestor tree to find GroupName container as first fallback; guard both values non-null in AddRadioButton ✅ PASS 2 files Semantic approach - no controller changes
3 try-fix (gpt-5.3-codex) Controller-driven uncheck via _layout.Descendants(); guard AddRadioButton with radioButton.IsSet(ValueProperty) ✅ PASS 2 files Uses IsSet check instead of null guard
4 try-fix (gpt-5.4) Controller-owned selection sync; explicit SelectedValue reapply replaces direct equality check in AddRadioButton ✅ PASS 2 files Most controller-centric approach
PR PR #34781 Guard AddRadioButton with SelectedValue is not null; fallback root to GetGroupController().Layout then radioButton.Parent ✅ PASSED (Gate) 3 files Adds internal Element Layout property

Cross-Pollination

Model Round New Ideas? Details
claude-sonnet-4.6 2 No NO NEW IDEAS

Exhausted: Yes
Selected Fix: PR #34781 — Focused minimal fix addressing both bugs with clear comments. All alternatives also pass, but PR's fix adds targeted fallback logic at the right level.


📋 Report — Final Recommendation

✅ Final Recommendation: APPROVE

Phase Status

Phase Status Notes
Pre-Flight ✅ COMPLETE 2 bugs identified, 3 files changed
Gate ✅ PASSED android — tests fail without fix, pass with fix
Try-Fix ✅ COMPLETE 4 attempts, all 4 passing
Report ✅ COMPLETE

Summary

PR #34781 fixes a .NET 10 regression in RadioButtonGroup that broke grouping when RadioButton controls are inside ContentView/ControlTemplate. The fix correctly addresses two distinct bugs introduced by PR #32640, adds two well-targeted unit tests, and is confirmed correct by Gate and all 4 independent try-fix attempts.

Root Cause

PR #32640 removed the coerceValue and Value = this initializer from RadioButton, making RadioButton.Value default to null. This introduced two bugs:

  1. Auto-check bug: object.Equals(null, null) == true in AddRadioButton caused every button added via ControlTemplate to be immediately auto-checked, leaving all buttons in a checked state simultaneously.
  2. Root resolution bug: GetVisualRoot() returns null when the layout is not yet attached to a Page (which happens during ControlTemplate inflation). The old radioButton.Parent fallback pointed to the immediate Border container (single button), not the group layout — so sibling buttons were never found to uncheck.

Fix Quality

The fix is minimal, focused, and correct:

  • Bug 1: Guards AddRadioButton with this.SelectedValue is not null before the equality check. This is the right guard — SelectedValue starts as null by default, meaning no pre-selection; only compare when it's been explicitly set.
  • Bug 2: Falls back to RadioButtonGroupController.GetGroupController(radioButton)?.Layout which reliably references the group's container layout (already registered via AddRadioButtonUpdateGroupName). The final (Element)radioButton.Parent fallback preserves any remaining edge-case compatibility.
  • The internal Element Layout => _layout; property exposes a read-only reference that was always available; it adds no behavioral change to the controller, only access.
  • Comments are thorough and clearly explain why each guard is needed.

Try-Fix comparison:

  • 4 alternative approaches all passed — confirms the bug is definitively fixable.
  • Attempt 1 (modify GetVisualRoot): Works but changes GetVisualRoot's contract (now returns non-null even without a Page ancestor), which could affect other callers.
  • Attempt 2 (GroupName ancestor walk): Semantically clean but adds traversal overhead on every uncheck.
  • Attempt 3 (IsSet(ValueProperty) guard): Also correct but IsSet is a slightly stronger contract change.
  • Attempt 4 (controller-owned sync): Most invasive refactor.
  • PR's fix is the narrowest, safest change of all candidates.

Tests: Two new unit tests cover both the primary regression (null/null auto-check through ContentView ControlTemplate) and the positive auto-check path (pre-set SelectedValue correctly auto-checks matching button). Both tests use AddLogicalChild to accurately simulate ControlTemplate inflation ordering, which is the real-world trigger.

Selected Fix: PR #34781 (original PR fix)


@MauiBot MauiBot added s/agent-approved AI agent recommends approval - PR fix is correct and optimal s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates and removed s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) labels Apr 11, 2026
@kubaflo kubaflo changed the base branch from main to inflight/current April 12, 2026 11:49
@kubaflo kubaflo merged commit bbd66a8 into dotnet:inflight/current Apr 12, 2026
26 of 36 checks passed
PureWeen pushed a commit that referenced this pull request Apr 14, 2026
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!
<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Detail

RadioButtonGroup is not functioning correctly when RadioButton controls
are placed inside a ContentView in .NET 10.

### Root Cause

PR [#32640](#32604) (fixing #32466)
removed the coerceValue and Value = this initialiser from RadioButton,
so RadioButton.Value now defaults to null. This introduced two distinct
bugs in the ContentView+ControlTemplate scenario:

- AddRadioButton guard object.Equals(radioButton.Value,
this.SelectedValue) evaluates to object.Equals(null, null) == true,
causing every button added via a ControlTemplate to be auto-checked
immediately.
- When the layout is not yet attached to a Page (common during
ControlTemplate inflation), GetVisualRoot() returns null. The original
fallback radioButton.Parent resolves to the button's immediate parent
(Border), which only contains that single button — so other RadioButtons
in the same group are never found and never unchecked.

### Description of Change

- Updated the equality check in AddRadioButton to ensure comparison only
happens when SelectedValue is not null. This avoids unintended selection
during initialization.
- Improved the root resolution logic in UncheckOtherRadioButtonsInScope.
If no visual root is found, fallback to the RadioButtonGroupController’s
layout (group container).

### Issue Fixed
Fixes #34759 

### Screenshots

| Before  | After |
|---------|--------|
|  <video
src="https://github.com/user-attachments/assets/39e1e45d-dd04-42b7-bb85-aea83124b0cb">
|   <video
src="https://github.com/user-attachments/assets/252587b2-10ae-4eb9-968e-de114756aa0c"> 
|

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
devanathan-vaithiyanathan pushed a commit to Tamilarasan-Paranthaman/maui that referenced this pull request Apr 21, 2026
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!
<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Detail

RadioButtonGroup is not functioning correctly when RadioButton controls
are placed inside a ContentView in .NET 10.

### Root Cause

PR [dotnet#32640](dotnet#32604) (fixing dotnet#32466)
removed the coerceValue and Value = this initialiser from RadioButton,
so RadioButton.Value now defaults to null. This introduced two distinct
bugs in the ContentView+ControlTemplate scenario:

- AddRadioButton guard object.Equals(radioButton.Value,
this.SelectedValue) evaluates to object.Equals(null, null) == true,
causing every button added via a ControlTemplate to be auto-checked
immediately.
- When the layout is not yet attached to a Page (common during
ControlTemplate inflation), GetVisualRoot() returns null. The original
fallback radioButton.Parent resolves to the button's immediate parent
(Border), which only contains that single button — so other RadioButtons
in the same group are never found and never unchecked.

### Description of Change

- Updated the equality check in AddRadioButton to ensure comparison only
happens when SelectedValue is not null. This avoids unintended selection
during initialization.
- Improved the root resolution logic in UncheckOtherRadioButtonsInScope.
If no visual root is found, fallback to the RadioButtonGroupController’s
layout (group container).

### Issue Fixed
Fixes dotnet#34759 

### Screenshots

| Before  | After |
|---------|--------|
|  <video
src="https://github.com/user-attachments/assets/39e1e45d-dd04-42b7-bb85-aea83124b0cb">
|   <video
src="https://github.com/user-attachments/assets/252587b2-10ae-4eb9-968e-de114756aa0c"> 
|

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PureWeen pushed a commit that referenced this pull request Apr 22, 2026
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!
<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Detail

RadioButtonGroup is not functioning correctly when RadioButton controls
are placed inside a ContentView in .NET 10.

### Root Cause

PR [#32640](#32604) (fixing #32466)
removed the coerceValue and Value = this initialiser from RadioButton,
so RadioButton.Value now defaults to null. This introduced two distinct
bugs in the ContentView+ControlTemplate scenario:

- AddRadioButton guard object.Equals(radioButton.Value,
this.SelectedValue) evaluates to object.Equals(null, null) == true,
causing every button added via a ControlTemplate to be auto-checked
immediately.
- When the layout is not yet attached to a Page (common during
ControlTemplate inflation), GetVisualRoot() returns null. The original
fallback radioButton.Parent resolves to the button's immediate parent
(Border), which only contains that single button — so other RadioButtons
in the same group are never found and never unchecked.

### Description of Change

- Updated the equality check in AddRadioButton to ensure comparison only
happens when SelectedValue is not null. This avoids unintended selection
during initialization.
- Improved the root resolution logic in UncheckOtherRadioButtonsInScope.
If no visual root is found, fallback to the RadioButtonGroupController’s
layout (group container).

### Issue Fixed
Fixes #34759 

### Screenshots

| Before  | After |
|---------|--------|
|  <video
src="https://github.com/user-attachments/assets/39e1e45d-dd04-42b7-bb85-aea83124b0cb">
|   <video
src="https://github.com/user-attachments/assets/252587b2-10ae-4eb9-968e-de114756aa0c"> 
|

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PureWeen pushed a commit that referenced this pull request Apr 28, 2026
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!
<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Detail

RadioButtonGroup is not functioning correctly when RadioButton controls
are placed inside a ContentView in .NET 10.

### Root Cause

PR [#32640](#32604) (fixing #32466)
removed the coerceValue and Value = this initialiser from RadioButton,
so RadioButton.Value now defaults to null. This introduced two distinct
bugs in the ContentView+ControlTemplate scenario:

- AddRadioButton guard object.Equals(radioButton.Value,
this.SelectedValue) evaluates to object.Equals(null, null) == true,
causing every button added via a ControlTemplate to be auto-checked
immediately.
- When the layout is not yet attached to a Page (common during
ControlTemplate inflation), GetVisualRoot() returns null. The original
fallback radioButton.Parent resolves to the button's immediate parent
(Border), which only contains that single button — so other RadioButtons
in the same group are never found and never unchecked.

### Description of Change

- Updated the equality check in AddRadioButton to ensure comparison only
happens when SelectedValue is not null. This avoids unintended selection
during initialization.
- Improved the root resolution logic in UncheckOtherRadioButtonsInScope.
If no visual root is found, fallback to the RadioButtonGroupController’s
layout (group container).

### Issue Fixed
Fixes #34759 

### Screenshots

| Before  | After |
|---------|--------|
|  <video
src="https://github.com/user-attachments/assets/39e1e45d-dd04-42b7-bb85-aea83124b0cb">
|   <video
src="https://github.com/user-attachments/assets/252587b2-10ae-4eb9-968e-de114756aa0c"> 
|

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PureWeen pushed a commit that referenced this pull request Apr 29, 2026
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!
<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Detail

RadioButtonGroup is not functioning correctly when RadioButton controls
are placed inside a ContentView in .NET 10.

### Root Cause

PR [#32640](#32604) (fixing #32466)
removed the coerceValue and Value = this initialiser from RadioButton,
so RadioButton.Value now defaults to null. This introduced two distinct
bugs in the ContentView+ControlTemplate scenario:

- AddRadioButton guard object.Equals(radioButton.Value,
this.SelectedValue) evaluates to object.Equals(null, null) == true,
causing every button added via a ControlTemplate to be auto-checked
immediately.
- When the layout is not yet attached to a Page (common during
ControlTemplate inflation), GetVisualRoot() returns null. The original
fallback radioButton.Parent resolves to the button's immediate parent
(Border), which only contains that single button — so other RadioButtons
in the same group are never found and never unchecked.

### Description of Change

- Updated the equality check in AddRadioButton to ensure comparison only
happens when SelectedValue is not null. This avoids unintended selection
during initialization.
- Improved the root resolution logic in UncheckOtherRadioButtonsInScope.
If no visual root is found, fallback to the RadioButtonGroupController’s
layout (group container).

### Issue Fixed
Fixes #34759 

### Screenshots

| Before  | After |
|---------|--------|
|  <video
src="https://github.com/user-attachments/assets/39e1e45d-dd04-42b7-bb85-aea83124b0cb">
|   <video
src="https://github.com/user-attachments/assets/252587b2-10ae-4eb9-968e-de114756aa0c"> 
|

---------

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

area-controls-radiobutton RadioButton, RadioButtonGroup community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration s/agent-approved AI agent recommends approval - PR fix is correct and optimal s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RadioButtonGroup not working with ContentView

5 participants