Picker: Refresh ItemDisplayBinding when bound item properties change#29922
Picker: Refresh ItemDisplayBinding when bound item properties change#29922devanathan-vaithiyanathan wants to merge 17 commits into
Conversation
|
/azp run MAUI-UITests-public |
|
Azure Pipelines successfully started running 1 pipeline(s). |
| { | ||
| App.WaitForElement("PickerButton"); | ||
| App.Tap("PickerButton"); | ||
| VerifyScreenshot(); |
|
/azp run MAUI-UITests-public |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
@jsuarezruiz , pending snapshots has been added. |
|
/azp run MAUI-UITests-public |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
|
||
| void OnItemsSourceChanged(IList oldValue, IList newValue) | ||
| { | ||
| if (oldValue is not null) |
There was a problem hiding this comment.
This logic is duplicated, can extract it to a method.
There was a problem hiding this comment.
Inside the methods, check items.Count == 0 before iterating to avoid unnecessary loops
6e94f72 to
5961883
Compare
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 29922Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 29922" |
There was a problem hiding this comment.
Pull request overview
This PR adds support for automatically refreshing the Picker display when a bound item's property changes through ItemDisplayBinding. The implementation subscribes to PropertyChanged events on items in the ItemsSource and updates the display when the bound property changes.
Key Changes:
- Event subscription for
INotifyPropertyChangeditems in ItemsSource - Automatic refresh when bound property changes
- Dynamic subscription/unsubscription when items are added/removed
- Comprehensive UI tests with screenshot verification for all platforms
Reviewed changes
Copilot reviewed 3 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/Controls/src/Core/Picker/Picker.cs |
Core implementation: adds PropertyChanged event handling for items, subscription/unsubscription logic in OnItemsSourceChanged and CollectionChanged methods |
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue25634.cs |
NUnit UI test verifying the picker display updates after property change |
src/Controls/tests/TestCases.HostApp/Issues/Issue25634.cs |
Test HostApp page with Picker, ObservableCollection, and INotifyPropertyChanged model |
src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyPickerItemDisplayBindingValue.png |
Android test screenshot baseline |
src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyPickerItemDisplayBindingValue.png |
iOS test screenshot baseline |
src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyPickerItemDisplayBindingValue.png |
Windows test screenshot baseline |
src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/VerifyPickerItemDisplayBindingValue.png |
MacCatalyst test screenshot baseline |
| { | ||
| if (e.PropertyName == binding.Path) | ||
| { | ||
| ResetItems(); |
There was a problem hiding this comment.
Potential performance issue: ResetItems() is called for every property change on any item, even if that item is not currently visible or selected. For large collections where many items change their properties simultaneously, this could cause excessive UI updates.
Consider optimizing to only refresh the display when the selected item's property changes:
void OnPickerItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (ItemDisplayBinding is Binding binding && !string.IsNullOrEmpty(binding.Path))
{
if (e.PropertyName == binding.Path)
{
// Only reset if the changed item is the currently selected item
// or if we need to update all items in the dropdown
if (sender == SelectedItem)
{
// Update just the display text for the selected item
Handler?.UpdateValue(nameof(IPicker.SelectedItem));
}
else
{
// For items in the dropdown list, consider a more targeted update
ResetItems();
}
}
}
}| ResetItems(); | |
| // Only reset if the changed item is not the currently selected item | |
| if (sender == SelectedItem) | |
| { | |
| // Update just the display text for the selected item | |
| Handler?.UpdateValue(nameof(IPicker.SelectedItem)); | |
| } | |
| else | |
| { | |
| // For items in the dropdown list, update the items | |
| ResetItems(); | |
| } |
| if (e.PropertyName == binding.Path) | ||
| { | ||
| ResetItems(); | ||
| } | ||
| } |
There was a problem hiding this comment.
The OnPickerItemPropertyChanged method only handles Binding type but not other BindingBase types like TypedBinding or custom binding implementations. This could cause the feature to silently fail for other binding types.
Consider handling all BindingBase types or documenting the limitation:
void OnPickerItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (ItemDisplayBinding is Binding binding && !string.IsNullOrEmpty(binding.Path))
{
if (e.PropertyName == binding.Path || string.IsNullOrEmpty(e.PropertyName))
{
ResetItems();
}
}
else if (ItemDisplayBinding is not null && ItemDisplayBinding is not Binding)
{
// For other binding types, refresh on any property change
// since we can't determine the specific path
ResetItems();
}
}Note: Also consider checking for string.IsNullOrEmpty(e.PropertyName) which indicates all properties changed.
| if (e.PropertyName == binding.Path) | |
| { | |
| ResetItems(); | |
| } | |
| } | |
| // For Binding, only reset if the relevant property changed, or all properties changed | |
| if (e.PropertyName == binding.Path || string.IsNullOrEmpty(e.PropertyName)) | |
| { | |
| ResetItems(); | |
| } | |
| } | |
| else if (ItemDisplayBinding is not null && ItemDisplayBinding is not Binding) | |
| { | |
| // For other BindingBase types (TypedBinding, custom), reset on any property change | |
| ResetItems(); | |
| } |
| if (oldValue is not null) | ||
| { | ||
| foreach (var item in oldValue) | ||
| { | ||
| if (item is INotifyPropertyChanged npc) | ||
| { | ||
| npc.PropertyChanged -= OnPickerItemPropertyChanged; | ||
| } | ||
| } | ||
| } | ||
| if (newValue is not null && ItemDisplayBinding is not null) | ||
| { | ||
| foreach (var item in newValue) | ||
| { | ||
| if (item is INotifyPropertyChanged npc) | ||
| { | ||
| npc.PropertyChanged += OnPickerItemPropertyChanged; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Missing cleanup when Picker is disposed or removed from the visual tree. The PropertyChanged event handlers attached to items will prevent garbage collection of both the Picker and the items, causing a memory leak.
Consider overriding cleanup methods to unsubscribe from events:
protected override void OnHandlerChanging(HandlerChangingEventArgs args)
{
base.OnHandlerChanging(args);
if (args.NewHandler == null && ItemsSource is not null)
{
// Unsubscribe when being removed from the visual tree
foreach (var item in ItemsSource)
{
if (item is INotifyPropertyChanged npc)
{
npc.PropertyChanged -= OnPickerItemPropertyChanged;
}
}
}
}Alternatively, use weak event patterns to avoid memory leaks.
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 3 findings
See inline comments for details.
|
|
||
| // Unsubscribe when being removed from visual tree to prevent memory leaks | ||
| if (args.NewHandler == null) | ||
| UnsubscribeFromItemPropertyChanges(ItemsSource); |
There was a problem hiding this comment.
[major] Handler Mapper and Property Patterns — This unsubscribes item PropertyChanged handlers when the Picker's handler is removed, but there is no matching re-subscribe path when a handler is attached again. Concrete scenario: a Picker with ItemDisplayBinding is disconnected/reconnected during navigation or Shell tab switching; after reconnect, changing the selected item's display property no longer calls ResetItems(), so the displayed text goes stale. Either avoid tying these item subscriptions to handler lifetime, or re-subscribe in the attach path.
|
|
||
| void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) | ||
| { | ||
| UnsubscribeFromItemPropertyChanges(e.OldItems); |
There was a problem hiding this comment.
[major] Logic and Correctness Verification — NotifyCollectionChangedAction.Reset commonly arrives with OldItems == null and NewItems == null, so this code leaves the old items subscribed and never subscribes the new contents after ResetItems(). Concrete scenario: an ObservableCollection is cleared/reloaded via Reset; removed items keep a reference to the Picker and new items no longer refresh the display when their bound property changes. Reset needs special handling, such as tracking subscribed items or rebuilding subscriptions around the reset.
| } | ||
| void OnPickerItemPropertyChanged(object sender, PropertyChangedEventArgs e) | ||
| { | ||
| if (ItemDisplayBinding is Binding binding && !string.IsNullOrEmpty(binding.Path)) |
There was a problem hiding this comment.
[major] XAML & Bindings — The refresh logic only recognizes runtime Binding and MultiBinding; compiled XAML ItemDisplayBinding is a TypedBindingBase, so item property changes are ignored for compiled bindings. Existing XAML tests show ItemDisplayBinding can compile to TypedBinding<,>, and that common path will still reproduce issue #25634. The fallback should refresh for other BindingBase implementations, or explicitly support typed bindings.
MauiBot
left a comment
There was a problem hiding this comment.
AI Review Summary
@devanathan-vaithiyanathan — new AI review results are available based on this last commit:
41bf371. To request a fresh review after new comments or commits, comment/review rerun.
🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After Fix
Gate Result: ✅ PASSED
Platform: IOS · Base: main · Merge base: 4567a055
| Test | Without Fix (expect FAIL) | With Fix (expect PASS) |
|---|---|---|
🖥️ Issue25634 Issue25634 |
✅ FAIL — 330s | ✅ PASS — 115s |
🔴 Without fix — 🖥️ Issue25634: FAIL ✅ · 330s
Determining projects to restore...
Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 729 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 730 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 5.01 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/Foldable/src/Controls.Foldable.csproj (in 5.17 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/Maps/src/Controls.Maps.csproj (in 5.97 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj (in 5.97 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/Core/maps/src/Maps.csproj (in 5.98 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Xaml/Controls.Xaml.csproj (in 5.98 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 6 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 5.99 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj (in 5.98 sec).
/Users/cloudtest/vss/_work/1/s/.dotnet/packs/Microsoft.iOS.Sdk.net10.0_26.0/26.0.11017/targets/Xamarin.Shared.Sdk.targets(309,3): warning : RuntimeIdentifier was set on the command line, and will override the value for RuntimeIdentifiers set in the project file. [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-ios]
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0-ios26.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0-ios26.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0-ios26.0/Microsoft.Maui.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Maps/Debug/net10.0-ios26.0/Microsoft.Maui.Maps.dll
Controls.BindingSourceGen -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
Microsoft.AspNetCore.Components.WebView.Maui -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-ios26.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
Controls.Foldable -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Foldable.dll
Controls.Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Maps.dll
Controls.Xaml -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Xaml.dll
Detected signing identity:
Code Signing Key: "" (-)
Provisioning Profile: "" () - no entitlements
Bundle Id: com.microsoft.maui.uitests
App Id: com.microsoft.maui.uitests
Controls.TestCases.HostApp -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-ios/iossimulator-arm64/Controls.TestCases.HostApp.dll
Optimizing assemblies for size may change the behavior of the app. Be sure to test after publishing. See: https://aka.ms/dotnet-illink
Optimizing assemblies for size. This process might take a while.
Build succeeded.
/Users/cloudtest/vss/_work/1/s/.dotnet/packs/Microsoft.iOS.Sdk.net10.0_26.0/26.0.11017/targets/Xamarin.Shared.Sdk.targets(309,3): warning : RuntimeIdentifier was set on the command line, and will override the value for RuntimeIdentifiers set in the project file. [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-ios]
1 Warning(s)
0 Error(s)
Time Elapsed 00:03:00.34
Determining projects to restore...
Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 588 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 589 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/tests/CustomAttributes/Controls.CustomAttributes.csproj (in 588 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 588 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.Core/UITest.Core.csproj (in 1 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/VisualTestUtils/VisualTestUtils.csproj (in 1 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 634 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 654 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.NUnit/UITest.NUnit.csproj (in 1.06 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.Appium/UITest.Appium.csproj (in 2.29 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.Analyzers/UITest.Analyzers.csproj (in 4.31 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/VisualTestUtils.MagickNet/VisualTestUtils.MagickNet.csproj (in 5.47 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.iOS.Tests/Controls.TestCases.iOS.Tests.csproj (in 5.49 sec).
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
Controls.CustomAttributes -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
UITest.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
VisualTestUtils -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
UITest.NUnit -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
VisualTestUtils.MagickNet -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
UITest.Appium -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
UITest.Analyzers -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
Controls.TestCases.iOS.Tests -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
Test run for /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (arm64)
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.06] Discovering: Controls.TestCases.iOS.Tests
[xUnit.net 00:00:00.18] Discovered: Controls.TestCases.iOS.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 6/21/2026 1:45:09 PM FixtureSetup for Issue25634(iOS)
>>>>> 6/21/2026 1:45:14 PM VerifyPickerItemDisplayBindingValue Start
>>>>> 6/21/2026 1:45:17 PM VerifyPickerItemDisplayBindingValue Stop
>>>>> 6/21/2026 1:45:17 PM Log types: syslog, crashlog, performance, safariConsole, safariNetwork, server
Failed VerifyPickerItemDisplayBindingValue [3 s]
Error Message:
VisualTestUtils.VisualTestFailedException :
Snapshot different than baseline: VerifyPickerItemDisplayBindingValue.png (1.15% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.
More info: https://aka.ms/visual-test-workflow
Stack Trace:
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, Nullable`1 retryTimeout, Int32 cropLeft, Int32 cropRight, Int32 cropTop, Int32 cropBottom, Double tolerance) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 309
at Microsoft.Maui.TestCases.Tests.Issues.Issue25634.VerifyPickerItemDisplayBindingValue() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue25634.cs:line 20
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
NUnit Adapter 4.5.0.0: Test execution complete
Results File: /Users/cloudtest/vss/_work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue25634.trx
Test Run Failed.
Total tests: 1
Failed: 1
Total time: 1.5564 Minutes
>>> TRX_RESULT_FILE: /Users/cloudtest/vss/_work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue25634.trx
🟢 With fix — 🖥️ Issue25634: PASS ✅ · 115s
Determining projects to restore...
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 405 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 418 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 375 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 466 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 436 ms).
6 of 11 projects are up-to-date for restore.
/Users/cloudtest/vss/_work/1/s/.dotnet/packs/Microsoft.iOS.Sdk.net10.0_26.0/26.0.11017/targets/Xamarin.Shared.Sdk.targets(309,3): warning : RuntimeIdentifier was set on the command line, and will override the value for RuntimeIdentifiers set in the project file. [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-ios]
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0-ios26.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0-ios26.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0-ios26.0/Microsoft.Maui.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Maps/Debug/net10.0-ios26.0/Microsoft.Maui.Maps.dll
Controls.BindingSourceGen -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
Microsoft.AspNetCore.Components.WebView.Maui -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-ios26.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
Controls.Foldable -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Foldable.dll
Controls.Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Maps.dll
Controls.Xaml -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Xaml.dll
Detected signing identity:
Code Signing Key: "" (-)
Provisioning Profile: "" () - no entitlements
Bundle Id: com.microsoft.maui.uitests
App Id: com.microsoft.maui.uitests
Controls.TestCases.HostApp -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-ios/iossimulator-arm64/Controls.TestCases.HostApp.dll
Optimizing assemblies for size may change the behavior of the app. Be sure to test after publishing. See: https://aka.ms/dotnet-illink
Optimizing assemblies for size. This process might take a while.
Build succeeded.
/Users/cloudtest/vss/_work/1/s/.dotnet/packs/Microsoft.iOS.Sdk.net10.0_26.0/26.0.11017/targets/Xamarin.Shared.Sdk.targets(309,3): warning : RuntimeIdentifier was set on the command line, and will override the value for RuntimeIdentifiers set in the project file. [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-ios]
1 Warning(s)
0 Error(s)
Time Elapsed 00:00:57.66
Determining projects to restore...
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 354 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 423 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 424 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 435 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 405 ms).
8 of 13 projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
Controls.CustomAttributes -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14443093
Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
VisualTestUtils -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
UITest.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
UITest.Appium -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
VisualTestUtils.MagickNet -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
UITest.NUnit -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
UITest.Analyzers -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
Controls.TestCases.iOS.Tests -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
Test run for /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (arm64)
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.06] Discovering: Controls.TestCases.iOS.Tests
[xUnit.net 00:00:00.20] Discovered: Controls.TestCases.iOS.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 6/21/2026 1:47:08 PM FixtureSetup for Issue25634(iOS)
>>>>> 6/21/2026 1:47:12 PM VerifyPickerItemDisplayBindingValue Start
>>>>> 6/21/2026 1:47:13 PM VerifyPickerItemDisplayBindingValue Stop
Passed VerifyPickerItemDisplayBindingValue [1 s]
NUnit Adapter 4.5.0.0: Test execution complete
Results File: /Users/cloudtest/vss/_work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue25634.trx
Test Run Successful.
Total tests: 1
Passed: 1
Total time: 23.3328 Seconds
>>> TRX_RESULT_FILE: /Users/cloudtest/vss/_work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue25634.trx
📁 Fix files reverted (8 files)
src/Controls/src/Core/Picker/Picker.cssrc/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txtsrc/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txtsrc/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txtsrc/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txtsrc/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txtsrc/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txtsrc/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt
📋 Pre-Flight — Context & Validation
Issue: #25634 - Picker ItemDisplayBinding doesn't support MVVM properly
PR: #29922 - Fix Picker ItemDisplayBinding MVVM updates
Platforms Affected: All; test platform requested: iOS
Files Changed: 8 implementation/API, 7 test/snapshot
Key Findings
ghauthentication is unavailable, so PR/issue/comments/CI context could not be queried; pre-flight used the local PR branch diff againstorigin/main.- The PR adds a UI regression for
Issue25634and changesPickerto refresh display text whenItemDisplayBindingsource item properties change. - Gate result was supplied as already passed; gate artifacts were not modified.
Code Review Summary
Verdict: NEEDS_CHANGES
Confidence: low
Errors: 3 | Warnings: 0 | Suggestions: 0
Key code review findings:
- ❌
src/Controls/src/Core/Picker/Picker.cs:427Reset collection changes can leave stale item subscriptions whenOldItemsis null. - ❌
src/Controls/src/Core/Picker/Picker.cs:397Handler disconnect unsubscribes item changes with no reconnect re-subscribe. - ❌
src/Controls/src/Core/Picker/Picker.cs:413CompiledItemDisplayBinding/TypedBindingBasechanges are ignored by the PR'sBinding/MultiBindingfilter.
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #29922 | Strong per-item INotifyPropertyChanged subscriptions plus handler cleanup |
✅ PASSED (Gate supplied) | Picker.cs, PublicAPI, UI test/snapshots |
Original PR; expert review found lifecycle/reset/compiled-binding issues |
🔬 Code Review — Deep Analysis
Code Review — PR #29922
Independent Assessment
What this changes: Picker now listens to INotifyPropertyChanged on ItemsSource items so ItemDisplayBinding display text refreshes when item properties change. It also adds UI screenshot coverage and PublicAPI entries for a new Picker.OnHandlerChanging override.
Inferred motivation: Fix stale Picker display text when MVVM item properties mutate after the items are loaded.
Reconciliation with PR Narrative
Author claims: Unavailable — gh authentication is unavailable.
Agreement/disagreement: Could only verify against local origin/main...HEAD diff.
Prior Review Reconciliation
| Prior ❌ Error Finding | Source | Status | Evidence |
|---|---|---|---|
| Could not query prior reviews/comments | gh reviews / inline comments / issue comments |
❓ Unknown | gh requires authentication in this environment. |
Blast Radius Assessment
- Runs for all instances: No — only Pickers with
ItemDisplayBindingandItemsSourceitems implementingINotifyPropertyChanged. - Startup impact: No.
- Static/shared state: No.
CI Status
- Required-check result: undetermined / tool-unavailable
- Classification: undetermined
- Action taken: confidence capped low; no comments posted.
Findings
❌ Error — Reset collection changes leave stale item subscriptions
src/Controls/src/Core/Picker/Picker.cs:427
CollectionChanged only unsubscribes e.OldItems, but NotifyCollectionChangedAction.Reset commonly has OldItems == null (for example ObservableCollection<T>.Clear()). Existing items can remain subscribed to the Picker, retaining the page and causing stale ResetItems() calls after removal. Reset handling needs tracked subscribed items or another way to unsubscribe previous items.
❌ Error — Handler disconnect permanently disables item refresh after reconnect
src/Controls/src/Core/Picker/Picker.cs:397
When args.NewHandler == null, item subscriptions are removed, but no matching re-subscribe occurs when a handler is attached again. A Picker that survives navigation/handler recreation keeps the same ItemsSource and ItemDisplayBinding, but later item property changes no longer refresh display text.
❌ Error — Compiled ItemDisplayBinding is ignored on property changes
src/Controls/src/Core/Picker/Picker.cs:413
OnPickerItemPropertyChanged refreshes only for Binding and MultiBinding. XAML compiled bindings use TypedBinding<TSource,TProperty> (TypedBindingBase : BindingBase), so property changes for compiled ItemDisplayBinding do nothing. Existing tests show Picker can receive typed ItemDisplayBinding, so this fix misses a common XAML path.
Failure-Mode Probing
- Collection reset/clear: old item subscriptions can remain because
OldItemsmay be null. - Handler reconnect: subscriptions are removed on disconnect and not restored.
- Compiled XAML binding: notification arrives, but no branch calls
ResetItems(). - Null/default values: null
ItemsSourceis guarded.
Verdict: NEEDS_CHANGES
Confidence: low
Summary: The main approach is directionally useful, but lifecycle, reset, and compiled-binding gaps leave concrete correctness and leak issues. CI and PR narrative/comments could not be verified due unavailable gh auth.
🛠️ Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix-1 | Binding display proxies using cloned bindings | ❌ Failed expert review; UI test passed | Picker.cs, PublicAPI |
Changed binding target semantics and clone behavior |
| 2 | try-fix-2 | Selected-item weak observer | ❌ Failed expert review; UI test passed | Picker.cs, PublicAPI |
Missed no-selection and non-selected item changes |
| 3 | try-fix-3 | Weak all-items display invalidation through existing GetDisplayMember() |
✅ Passed | Picker.cs, PublicAPI |
Expert accepted; handles reset, reconnect, compiled bindings, and all current items |
| PR | PR #29922 | Strong per-item INotifyPropertyChanged subscriptions with handler cleanup |
✅ PASSED (Gate supplied) | Picker.cs, PublicAPI, UI test/snapshots |
Original PR; expert review found reset/lifecycle/compiled-binding concerns |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| maui-expert-reviewer | 1 | Yes | Binding display proxies; failed expert review due target semantics and clone metadata |
| maui-expert-reviewer | 2 | Yes | Selected-item weak observer; failed expert review due no-selection/non-selected gaps |
| maui-expert-reviewer | 3 | Yes | Weak all-items display invalidation; passed test and expert review |
Exhausted: No — stopped because candidate #3 passed all tests and was demonstrably better than the PR fix.
Selected Fix: Candidate #3 — weak all-items display invalidation preserves existing binding semantics while avoiding the PR's strong subscription, reset, handler reconnect, and compiled-binding issues.
📝 Recommended PR Title & Description
Assessment: ✏️ Recommend updating — the current metadata accurately describes the submitted PR, but the winning fix changes the implementation from strong per-item subscriptions/handler cleanup to weak all-item display invalidation and removes the new public override.
Recommended title
Picker: Refresh ItemDisplayBinding when item display properties change
Recommended description
### Issue Details
Picker does not refresh the displayed item text when an item property used by ItemDisplayBinding changes.
### Description of Change
Added support to automatically refresh Picker display text when a bound item's display property changes. The fix observes current ItemsSource items through weak property-change observers and refreshes through the existing ItemDisplayBinding display pipeline, so runtime bindings, compiled bindings, converters, fallback values, and existing Picker binding target semantics continue to work.
Observer subscriptions are rebuilt when ItemDisplayBinding, ItemsSource, or collection contents change, including collection reset/reload scenarios. The implementation avoids tying item observation to handler disconnect/reconnect and does not add a new Picker public API override.
### Issues Fixed
Fixes #25634
**Tested the behavior in the following platforms.**
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
| Before | After |
|---------|--------|
| **iOS**<br> <video src="https://github.com/user-attachments/assets/289d7a41-163b-4987-a99e-9ebd392abd3b" width="300" height="600"> |**iOS**<br> <video src="https://github.com/user-attachments/assets/c200ee33-c4f6-4374-afea-6ce4e067389a" width="300" height="600"> |
🏁 Report — Final Recommendation
Comparative Fix Report — PR #29922
Ranking
| Rank | Candidate | Regression result | Expert result | Assessment |
|---|---|---|---|---|
| 1 | pr-plus-reviewer |
✅ Pass (inherits try-fix-3 behavior) |
✅ No remaining findings | Best candidate. Applies the expert feedback to the PR by using weak all-items display invalidation while preserving existing GetDisplayMember() binding semantics. |
| 2 | try-fix-3 |
✅ Pass | ✅ No remaining findings | Equivalent technical approach to pr-plus-reviewer; ranked lower only because the requested PR-forward candidate captures the same fix as reviewer feedback applied to the PR. |
| 3 | pr |
✅ Pass | ❌ 3 major findings | Handles the narrow runtime binding regression, but has handler reconnect, collection reset, and compiled-binding correctness gaps. |
| 4 | try-fix-2 |
✅ Pass | ❌ Failed expert review | Preserves binding semantics but observes only the selected item, missing no-selection and non-selected item display updates. |
| 5 | try-fix-1 |
❌ Fail | ❌ Failed expert review | Failed candidate ranking rules and also changed binding target/clone semantics in ways that can regress RelativeSource, TemplateBinding, and compiled-binding metadata. |
Candidate Analysis
pr
The submitted PR adds strong INotifyPropertyChanged subscriptions for ItemsSource items and refreshes Picker display text when the runtime Binding.Path changes. The supplied gate passed, so it covers the reported simple iOS scenario. However, expert review found major issues: subscriptions are removed on handler disconnect without reconnect recovery, Reset collection changes can leave stale subscriptions and miss new items, and compiled XAML TypedBindingBase item-display bindings are ignored.
pr-plus-reviewer
This sandbox candidate applies the expert feedback by replacing the PR's strong subscriptions with weak all-item observers rebuilt from the current ItemsSource. It avoids handler-lifetime coupling, handles reset/reload by rebuilding observers, deduplicates repeated item references, and refreshes via ResetItems()/GetDisplayMember() so all existing BindingBase implementations continue to flow through the established Picker display pipeline. This is the best PR-forward solution.
try-fix-1
The proxy-binding approach passed the narrow UI test but failed expert review and is marked Fail. It changes binding target semantics by applying ItemDisplayBinding to a plain BindableObject proxy instead of the Picker element, and cloning BindingBase risks losing compiled-binding fallback/target-null metadata. Because failed candidates must rank below passing candidates, this cannot win.
try-fix-2
The selected-item weak observer passed the targeted UI regression but failed expert review. It refreshes only when the selected item changes, so picker popup rows and no-selection scenarios can remain stale. It is safer than try-fix-1 but incomplete compared with the all-items observer.
try-fix-3
The weak all-items display invalidation candidate passed the targeted iOS UI regression and expert review. It resolves the PR's lifecycle, reset, and compiled-binding issues while preserving the existing display binding application path. It is technically equivalent to the pr-plus-reviewer sandbox candidate.
Winner
Winner: pr-plus-reviewer
pr-plus-reviewer wins because it keeps the PR's intended behavior and test coverage while applying the expert-reviewed all-items weak observer fix. It ranks above the raw PR due to the three unresolved major findings in the submitted implementation, and above the standalone try-fix-3 because the requested PR-forward candidate incorporates the same accepted approach as reviewer feedback applied to the PR.
🧭 Next Steps — review latest findings
No alternative fix was selected for this run. Review the session findings and CI results before merging.
This comment has been minimized.
This comment has been minimized.
Tests Failure Analysis
Test Failure Review: Likely unrelated - click to expandOverall verdict: Likely unrelated All 5 recent base-branch (main) builds for both
Recommended actionRerun the failing UI test jobs. The pattern is consistent with pre-existing CI instability on main (5 consecutive base-branch failures on both Evidence detailsChecked pipelines:
Base-branch failures confirming pre-existing instability:
PR scope: 15 changed files, 7 test files, all in Picker / ItemDisplayBinding. No WebView, Shell, SafeArea, or CollectionView files changed. Limitations: AzDO authenticated test-run APIs ( |
kubaflo
left a comment
There was a problem hiding this comment.
Could you please resolve conflicts?
41bf371 to
9d133a1
Compare
@kubaflo , I have resolved the conflicts |



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 Details
Picker does not refresh displayed item when bound property changes through ItemDisplayBinding
Description of Change
Added support to automatically refresh the Picker display when a bound item's property changes. Subscribed to property change events in the items source, and updated the display when the property defined in ItemDisplayBinding changes. Also handled item additions and removals by updating the subscriptions accordingly.
Issues Fixed
Fixes #25634
Tested the behavior in the following platforms.
iOS-BeforeFix.mov
iOS-AfterFix.mov