Skip to content

Conversation

@StephaneDelcroix
Copy link
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!

Description

Fixes #29459

When dynamically switching bindings on a TwoWay bindable property (e.g., switching between binding to property A and property B), the propertyChanged callback was not being fired if the new binding's value matched a previously manually-set value that was still cached in the property context.

Reproduction scenario (from issue):

  1. Bind control to property A (value = 0)
  2. Switch binding to property B (value = 100)
  3. Modify B via control's internal ViewModel (value = 101) - TwoWay binding syncs back
  4. Switch back to property A (value = 0)
  5. Switch back to property B (value = 101) - BUG: propertyChanged not called, internal ViewModel stays at 0

Root cause

In BindableObject.SetBinding(), when a new binding is applied:

  1. The current value is moved from its current specificity to FromBinding specificity
  2. BUT stale ManualValueSetter entries (from code like control.Value = newValue) were NOT cleared
  3. When SetValueActual compared values, it used the highest-specificity value (the stale ManualValueSetter)
  4. If the new binding's value equaled this stale value, sameValue was true and propertyChanged didn't fire

Fix

Clear ManualValueSetter entries when switching bindings to ensure proper value comparison.

Changes

  • src/Controls/src/Core/BindableObject.cs: Added code to remove stale ManualValueSetter entries when setting a new binding
  • src/Controls/tests/Xaml.UnitTests/Issues/Maui29459.xaml/.cs: Added XAML unit test reproducing the issue
  • src/Controls/tests/Core.UnitTests/BindingUnitTests.cs: Added Core unit tests for the fix

Testing

  • All 5422 Controls.Core.UnitTests pass
  • All 1770 Controls.Xaml.UnitTests pass
  • Manually verified with the original reproduction project updated to .NET 10

When dynamically switching bindings on a TwoWay bindable property, the
propertyChanged callback was not being fired if the new binding's value
matched a previously manually-set value that was still cached.

Root cause: In SetBinding(), when moving the current value to FromBinding
specificity, stale ManualValueSetter entries were not cleared. This caused
SetValueActual to compare against the wrong baseline value.

Fix: Clear ManualValueSetter entries when switching bindings to ensure
proper value comparison.

Added unit tests in both Xaml.UnitTests and Core.UnitTests.
Copilot AI review requested due to automatic review settings November 29, 2025 05:31
@StephaneDelcroix StephaneDelcroix added the area-xaml XAML, CSS, Triggers, Behaviors label Nov 29, 2025
@StephaneDelcroix StephaneDelcroix changed the title Fix #29459: Switching bindings now triggers propertyChanged correctly [C] Fix #29459: Switching bindings now triggers propertyChanged correctly Nov 29, 2025
Copilot finished reviewing on behalf of StephaneDelcroix November 29, 2025 05:35
Copy link
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 pull request fixes issue #29459 where dynamically switching TwoWay bindings on a bindable property failed to trigger propertyChanged callbacks when the new binding's value matched a stale ManualValueSetter entry.

Key Changes

  • Core fix: Modified BindableObject.SetBinding() to explicitly clear ManualValueSetter entries when setting a new binding, ensuring clean state and proper value comparison
  • Comprehensive test coverage: Added both XAML-based and Core unit tests that reproduce the exact scenario from the bug report
  • Test validation: All existing tests pass (5422 Controls.Core.UnitTests, 1770 Controls.Xaml.UnitTests)

Reviewed changes

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

File Description
src/Controls/src/Core/BindableObject.cs Added defensive code to remove stale ManualValueSetter entries when switching bindings, preventing incorrect value comparison
src/Controls/tests/Xaml.UnitTests/Issues/Maui29459.xaml Added minimal XAML page with custom control for test infrastructure
src/Controls/tests/Xaml.UnitTests/Issues/Maui29459.xaml.cs Added comprehensive XAML unit tests reproducing the issue with internal ViewModel synchronization pattern
src/Controls/tests/Core.UnitTests/BindingUnitTests.cs Added Core unit tests mirroring the XAML tests to ensure fix works at the framework level

Comment on lines 240 to 241
// Note: PropertyChanged may or may not fire when values are equal - this is implementation-dependent
// The key assertion is that the Value is correct
Copy link

Copilot AI Nov 29, 2025

Choose a reason for hiding this comment

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

[nitpick] The comment on line 240 states "PropertyChanged may or may not fire when values are equal", but then the test only asserts that the value is correct without checking the PropertyChangedCount behavior. This makes the test's assertion incomplete for documenting the actual behavior. Consider either:

  1. Removing the comment and keeping just the value assertion, OR
  2. Adding an explicit assertion about PropertyChangedCount behavior (e.g., // PropertyChangedCount behavior is implementation-defined when values are equal)
Suggested change
// Note: PropertyChanged may or may not fire when values are equal - this is implementation-dependent
// The key assertion is that the Value is correct
// PropertyChangedCount behavior is implementation-defined when values are equal; no assertion is made.

Copilot uses AI. Check for mistakes.
@StephaneDelcroix StephaneDelcroix added this to the .NET 10.0 SR3 milestone Nov 29, 2025
Add the #pragma warning disable CS0219 line to expected output that was
added to the source generator in commit 9b3b826.
mattleibow
mattleibow previously approved these changes Dec 2, 2025
page.MyControl.SetBinding(Maui29459CustomControl.ValueProperty, nameof(Maui29459ViewModel.B));

Assert.That(page.MyControl.Value, Is.EqualTo(50), "Value should remain 50");
// Note: PropertyChanged may or may not fire when values are equal - this is implementation-dependent
Copy link
Member

Choose a reason for hiding this comment

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

The test name is "..DoesNotTriggerPropertyChanged" yet this comment says "may or may not fire". Which is the correct one?

// FROM:
public void SwitchingBindingToSameValueDoesNotTriggerPropertyChanged([Values] XamlInflator inflator)

// TO:
public void SwitchingBindingToSameValueMaintainsCorrectValue([Values] XamlInflator inflator)
// FROM:
// PropertyChanged should NOT fire (optimization)

// TO:
// The value should be correct regardless of PropertyChanged firing behavior

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

Labels

area-xaml XAML, CSS, Triggers, Behaviors

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dynamically switching binding to a custom control does not trigger property changed event of the control

3 participants