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 #31939

When a Button inside a ControlTemplate has both Command and CommandParameter with TemplateBinding, the async binding application path can cause Command to be evaluated before CommandParameter resolves, resulting in CanExecute being called with a null parameter.

Solution

Add a BindableProperty dependency mechanism via DependsOn() method. When CommandProperty.DependsOn(CommandParameterProperty) is registered, the CommandElement.GetCanExecute() method checks if CommandParameter has a pending binding with null value. If so, it returns true (assumes command can execute) instead of calling CanExecute(null). When the binding resolves, OnCommandParameterChanged fires and CanExecute is properly re-evaluated.

Changes

  • BindableProperty: Add Dependencies property and DependsOn() method
  • BindableObject: Add HasPendingBindingWithNullValue() helper
  • CommandElement: Check dependencies before calling CanExecute
  • ButtonElement, CheckBox, SearchBar: Register Command → CommandParameter dependency
  • Button, ImageButton, CheckBox, SearchBar: Pass CommandProperty to GetCanExecute()

Testing

Added unit tests that verify:

  1. Initial template binding works correctly
  2. CommandParameter is preserved after reparenting (the bug scenario)

Verified: Tests fail without fix, pass with fix.

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 PR fixes issue #31939 where CommandParameter TemplateBinding is lost during control template reparenting, causing CanExecute to be called with a null parameter before CommandParameter resolves. The fix introduces a BindableProperty dependency mechanism that delays CanExecute evaluation until dependent bindings have resolved.

Key changes:

  • Added a dependency mechanism via BindableProperty.DependsOn() to establish property dependencies
  • Modified CommandElement.GetCanExecute() to check for pending bindings on dependencies before calling CanExecute
  • Registered Command → CommandParameter dependencies in Button, ImageButton, CheckBox, and SearchBar controls
  • Added comprehensive unit tests to verify the fix works for both initial binding and reparenting scenarios

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/Controls/src/Core/BindableProperty.cs Adds Dependencies property and DependsOn() method to establish property dependencies
src/Controls/src/Core/BindableObject.cs Adds HasPendingBindingWithNullValue() helper to detect unresolved bindings
src/Controls/src/Core/CommandElement.cs Updates GetCanExecute() to check dependencies for pending bindings before calling CanExecute
src/Controls/src/Core/Button/ButtonElement.cs Registers Command → CommandParameter dependency in static constructor and restructures property initialization
src/Controls/src/Core/Button/Button.cs Passes CommandProperty to GetCanExecute() for dependency checking
src/Controls/src/Core/ImageButton/ImageButton.cs Passes CommandProperty to GetCanExecute() for dependency checking
src/Controls/src/Core/CheckBox/CheckBox.cs Passes CommandProperty to GetCanExecute() for dependency checking
src/Controls/src/Core/CheckBox/CheckBox.Mapper.cs Registers Command → CommandParameter dependency in static constructor
src/Controls/src/Core/SearchBar/SearchBar.cs Passes SearchCommandProperty to GetCanExecute() for dependency checking
src/Controls/src/Core/SearchBar/SearchBar.Mapper.cs Registers SearchCommand → SearchCommandParameter dependency in static constructor
src/Controls/tests/Xaml.UnitTests/Issues/Maui31939.xaml XAML test page with custom control and ControlTemplate demonstrating the bug scenario
src/Controls/tests/Xaml.UnitTests/Issues/Maui31939.xaml.cs Comprehensive unit tests verifying initial binding and reparenting scenarios, including custom control and command implementations

@StephaneDelcroix StephaneDelcroix marked this pull request as draft December 2, 2025 14:24
@StephaneDelcroix StephaneDelcroix force-pushed the fix/31939-bindableproperty-dependency branch from cd23217 to c5e0099 Compare December 2, 2025 14:30
When a Button inside a ControlTemplate has both Command and CommandParameter
with TemplateBinding, the async binding application path can cause Command
to be evaluated before CommandParameter resolves, resulting in CanExecute
being called with null parameter.

## Solution
Add a BindableProperty dependency mechanism via DependsOn() method. When
CommandProperty.DependsOn(CommandParameterProperty) is registered, the
CommandElement.GetCanExecute() forces the CommandParameter binding to apply
before calling CanExecute. This ensures the parameter value is available.

## Changes
- BindableProperty: Add Dependencies property and DependsOn() method
- BindableObject: Add ForceBindingApply() to force a binding to apply immediately
- CommandElement: Force dependency bindings to apply before calling CanExecute
- ButtonElement, CheckBox, SearchBar, MenuItem, RefreshView, TextCell: Register
  Command -> CommandParameter dependency
- All ICommandElement implementations: Pass CommandProperty to GetCanExecute()

## Testing
Added unit tests that verify:
1. Initial template binding works correctly
2. CommandParameter is preserved after reparenting (the bug scenario)
@StephaneDelcroix StephaneDelcroix force-pushed the fix/31939-bindableproperty-dependency branch from c5e0099 to 5485a34 Compare December 2, 2025 14:39
@StephaneDelcroix StephaneDelcroix marked this pull request as ready for review December 2, 2025 14:55
Copy link
Contributor

Copilot AI commented Dec 5, 2025

@rmarinho I've opened a new pull request, #33022, to work on those changes. Once the pull request is ready, I'll request review from you.

@rmarinho rmarinho changed the base branch from main to inflight/current December 5, 2025 16:40
@rmarinho rmarinho merged commit fbc5f44 into inflight/current Dec 5, 2025
62 of 64 checks passed
@rmarinho rmarinho deleted the fix/31939-bindableproperty-dependency branch December 5, 2025 19:10
PureWeen pushed a commit that referenced this pull request Dec 8, 2025
…32961)

When a Button inside a ControlTemplate has both Command and CommandParameter
with TemplateBinding, the async binding application path can cause Command
to be evaluated before CommandParameter resolves, resulting in CanExecute
being called with null parameter.

## Solution
Add a BindableProperty dependency mechanism via DependsOn() method. When
CommandProperty.DependsOn(CommandParameterProperty) is registered, the
CommandElement.GetCanExecute() forces the CommandParameter binding to apply
before calling CanExecute. This ensures the parameter value is available.

## Changes
- BindableProperty: Add Dependencies property and DependsOn() method
- BindableObject: Add ForceBindingApply() to force a binding to apply immediately
- CommandElement: Force dependency bindings to apply before calling CanExecute
- ButtonElement, CheckBox, SearchBar, MenuItem, RefreshView, TextCell: Register
  Command -> CommandParameter dependency
- All ICommandElement implementations: Pass CommandProperty to GetCanExecute()

## Testing
Added unit tests that verify:
1. Initial template binding works correctly
2. CommandParameter is preserved after reparenting (the bug scenario)
PureWeen pushed a commit that referenced this pull request Dec 10, 2025
…32961)

When a Button inside a ControlTemplate has both Command and CommandParameter
with TemplateBinding, the async binding application path can cause Command
to be evaluated before CommandParameter resolves, resulting in CanExecute
being called with null parameter.

## Solution
Add a BindableProperty dependency mechanism via DependsOn() method. When
CommandProperty.DependsOn(CommandParameterProperty) is registered, the
CommandElement.GetCanExecute() forces the CommandParameter binding to apply
before calling CanExecute. This ensures the parameter value is available.

## Changes
- BindableProperty: Add Dependencies property and DependsOn() method
- BindableObject: Add ForceBindingApply() to force a binding to apply immediately
- CommandElement: Force dependency bindings to apply before calling CanExecute
- ButtonElement, CheckBox, SearchBar, MenuItem, RefreshView, TextCell: Register
  Command -> CommandParameter dependency
- All ICommandElement implementations: Pass CommandProperty to GetCanExecute()

## Testing
Added unit tests that verify:
1. Initial template binding works correctly
2. CommandParameter is preserved after reparenting (the bug scenario)
PureWeen pushed a commit that referenced this pull request Dec 16, 2025
…32961)

When a Button inside a ControlTemplate has both Command and CommandParameter
with TemplateBinding, the async binding application path can cause Command
to be evaluated before CommandParameter resolves, resulting in CanExecute
being called with null parameter.

## Solution
Add a BindableProperty dependency mechanism via DependsOn() method. When
CommandProperty.DependsOn(CommandParameterProperty) is registered, the
CommandElement.GetCanExecute() forces the CommandParameter binding to apply
before calling CanExecute. This ensures the parameter value is available.

## Changes
- BindableProperty: Add Dependencies property and DependsOn() method
- BindableObject: Add ForceBindingApply() to force a binding to apply immediately
- CommandElement: Force dependency bindings to apply before calling CanExecute
- ButtonElement, CheckBox, SearchBar, MenuItem, RefreshView, TextCell: Register
  Command -> CommandParameter dependency
- All ICommandElement implementations: Pass CommandProperty to GetCanExecute()

## Testing
Added unit tests that verify:
1. Initial template binding works correctly
2. CommandParameter is preserved after reparenting (the bug scenario)
github-actions bot pushed a commit that referenced this pull request Dec 22, 2025
…32961)

When a Button inside a ControlTemplate has both Command and CommandParameter
with TemplateBinding, the async binding application path can cause Command
to be evaluated before CommandParameter resolves, resulting in CanExecute
being called with null parameter.

## Solution
Add a BindableProperty dependency mechanism via DependsOn() method. When
CommandProperty.DependsOn(CommandParameterProperty) is registered, the
CommandElement.GetCanExecute() forces the CommandParameter binding to apply
before calling CanExecute. This ensures the parameter value is available.

## Changes
- BindableProperty: Add Dependencies property and DependsOn() method
- BindableObject: Add ForceBindingApply() to force a binding to apply immediately
- CommandElement: Force dependency bindings to apply before calling CanExecute
- ButtonElement, CheckBox, SearchBar, MenuItem, RefreshView, TextCell: Register
  Command -> CommandParameter dependency
- All ICommandElement implementations: Pass CommandProperty to GetCanExecute()

## Testing
Added unit tests that verify:
1. Initial template binding works correctly
2. CommandParameter is preserved after reparenting (the bug scenario)
PureWeen pushed a commit that referenced this pull request Dec 22, 2025
…32961)

When a Button inside a ControlTemplate has both Command and CommandParameter
with TemplateBinding, the async binding application path can cause Command
to be evaluated before CommandParameter resolves, resulting in CanExecute
being called with null parameter.

## Solution
Add a BindableProperty dependency mechanism via DependsOn() method. When
CommandProperty.DependsOn(CommandParameterProperty) is registered, the
CommandElement.GetCanExecute() forces the CommandParameter binding to apply
before calling CanExecute. This ensures the parameter value is available.

## Changes
- BindableProperty: Add Dependencies property and DependsOn() method
- BindableObject: Add ForceBindingApply() to force a binding to apply immediately
- CommandElement: Force dependency bindings to apply before calling CanExecute
- ButtonElement, CheckBox, SearchBar, MenuItem, RefreshView, TextCell: Register
  Command -> CommandParameter dependency
- All ICommandElement implementations: Pass CommandProperty to GetCanExecute()

## Testing
Added unit tests that verify:
1. Initial template binding works correctly
2. CommandParameter is preserved after reparenting (the bug scenario)
github-actions bot pushed a commit that referenced this pull request Dec 24, 2025
…32961)

When a Button inside a ControlTemplate has both Command and CommandParameter
with TemplateBinding, the async binding application path can cause Command
to be evaluated before CommandParameter resolves, resulting in CanExecute
being called with null parameter.

## Solution
Add a BindableProperty dependency mechanism via DependsOn() method. When
CommandProperty.DependsOn(CommandParameterProperty) is registered, the
CommandElement.GetCanExecute() forces the CommandParameter binding to apply
before calling CanExecute. This ensures the parameter value is available.

## Changes
- BindableProperty: Add Dependencies property and DependsOn() method
- BindableObject: Add ForceBindingApply() to force a binding to apply immediately
- CommandElement: Force dependency bindings to apply before calling CanExecute
- ButtonElement, CheckBox, SearchBar, MenuItem, RefreshView, TextCell: Register
  Command -> CommandParameter dependency
- All ICommandElement implementations: Pass CommandProperty to GetCanExecute()

## Testing
Added unit tests that verify:
1. Initial template binding works correctly
2. CommandParameter is preserved after reparenting (the bug scenario)
PureWeen pushed a commit that referenced this pull request Dec 26, 2025
…32961)

When a Button inside a ControlTemplate has both Command and CommandParameter
with TemplateBinding, the async binding application path can cause Command
to be evaluated before CommandParameter resolves, resulting in CanExecute
being called with null parameter.

## Solution
Add a BindableProperty dependency mechanism via DependsOn() method. When
CommandProperty.DependsOn(CommandParameterProperty) is registered, the
CommandElement.GetCanExecute() forces the CommandParameter binding to apply
before calling CanExecute. This ensures the parameter value is available.

## Changes
- BindableProperty: Add Dependencies property and DependsOn() method
- BindableObject: Add ForceBindingApply() to force a binding to apply immediately
- CommandElement: Force dependency bindings to apply before calling CanExecute
- ButtonElement, CheckBox, SearchBar, MenuItem, RefreshView, TextCell: Register
  Command -> CommandParameter dependency
- All ICommandElement implementations: Pass CommandProperty to GetCanExecute()

## Testing
Added unit tests that verify:
1. Initial template binding works correctly
2. CommandParameter is preserved after reparenting (the bug scenario)
github-actions bot pushed a commit that referenced this pull request Dec 27, 2025
…32961)

When a Button inside a ControlTemplate has both Command and CommandParameter
with TemplateBinding, the async binding application path can cause Command
to be evaluated before CommandParameter resolves, resulting in CanExecute
being called with null parameter.

## Solution
Add a BindableProperty dependency mechanism via DependsOn() method. When
CommandProperty.DependsOn(CommandParameterProperty) is registered, the
CommandElement.GetCanExecute() forces the CommandParameter binding to apply
before calling CanExecute. This ensures the parameter value is available.

## Changes
- BindableProperty: Add Dependencies property and DependsOn() method
- BindableObject: Add ForceBindingApply() to force a binding to apply immediately
- CommandElement: Force dependency bindings to apply before calling CanExecute
- ButtonElement, CheckBox, SearchBar, MenuItem, RefreshView, TextCell: Register
  Command -> CommandParameter dependency
- All ICommandElement implementations: Pass CommandProperty to GetCanExecute()

## Testing
Added unit tests that verify:
1. Initial template binding works correctly
2. CommandParameter is preserved after reparenting (the bug scenario)
PureWeen added a commit that referenced this pull request Dec 29, 2025
## CollectionView
- Fixed the NRE in CarouselViewController on iOS 15.5 & 16.4 by
@Ahamed-Ali in #30838
  <details>
  <summary>🔧 Fixes</summary>

- [NRE in CarouselViewController on iOS 15.5 &
16.4](#28557)
  </details>

- [iOS, macOS] Fixed CollectionView group header size changes with
ItemSizingStrategy by @NanthiniMahalingam in
#33161
  <details>
  <summary>🔧 Fixes</summary>

- [[NET 10] I6_Grouping - Grouping_with_variable_sized_items changing
the 'ItemSizingStrategy' also changes the header
size.](#33130)
  </details>

## Flyout
- Add unit tests for TabBar and FlyoutItem navigation
ApplyQueryAttributes (#25663) by @StephaneDelcroix in
#33006

## Flyoutpage
- Fixed the FlyoutPage.Flyout Disappearing When Maximizing the Window on
Mac Platform by @NanthiniMahalingam in
#26701
  <details>
  <summary>🔧 Fixes</summary>

- [FlyoutPage.Flyout - navigation corrupted when running om mac , on
window ok](#22719)
  </details>

## Mediapicker
- [Windows] Fix for PickPhotosAsync throws exception if image is
modified by @HarishwaranVijayakumar in
#32952
  <details>
  <summary>🔧 Fixes</summary>

- [PickPhotosAsync throws exception if image is
modified.](#32408)
  </details>

## Navigation
- Fix for TabBar Navigation does not invoke its
IQueryAttributable.ApplyQueryAttributes(query) by @SuthiYuvaraj in
#25663
  <details>
  <summary>🔧 Fixes</summary>

- [Tabs defined in AppShell.xaml does not invoke its view model's
IQueryAttributable.ApplyQueryAttributes(query)
implementaion](#13537)
- [`ShellContent` routes do not call
`ApplyQueryAttributes`](#28453)
  </details>

## ScrollView
- Fix ScrollToPosition.Center behavior in ScrollView on iOS and
MacCatalyst by @devanathan-vaithiyanathan in
#26825
  <details>
  <summary>🔧 Fixes</summary>

- [ScrollToPosition.Center Centers the First Item too in iOS and
Catalyst](#26760)
- [On iOS - ScrollView.ScrollToAsync Element,
ScrollToPosition.MakeVisible shifts view to the right, instead of just
scrolling vertically](#28965)
  </details>

## Searchbar
- [iOS, Mac, Windows] Fixed CharacterSpacing for SearchBar text and
placeholder text by @Dhivya-SF4094 in
#30407
  <details>
  <summary>🔧 Fixes</summary>

- [[iOS, Mac, Windows] SearchBar CharacterSpacing property is not
working as expected](#30366)
  </details>

## Shell
- Update logic for large title display mode on iOS - shell by @kubaflo
in #33039

## TitleView
- [iOS] Fixed memory leak with PopToRootAsync when using TitleView by
@Vignesh-SF3580 in #28547
  <details>
  <summary>🔧 Fixes</summary>

- [NavigationPage.TitleView causes memory leak with
PopToRootAsync](#28201)
  </details>

## Xaml
- [C] Fix binding to interface-inherited properties like
IReadOnlyList<T>.Count by @StephaneDelcroix in
#32912
  <details>
  <summary>🔧 Fixes</summary>

- [Compiled Binding to Array.Count provides no
result](#13872)
  </details>

- Fix #31939: CommandParameter TemplateBinding lost during reparenting
by @StephaneDelcroix in #32961
  <details>
  <summary>🔧 Fixes</summary>

- [CommandParameter TemplateBinding Lost During ControlTemplate
Reparenting](#31939)
  </details>


<details>
<summary>🧪 Testing (4)</summary>

- [Testing] Fixed Test case failure in PR 33185 - [12/22/2025] Candidate
by @TamilarasanSF4853 in #33257
- [Testing] Re-saved ShouldFlyoutBeVisibleAfterMaximizingWindow test
case images in PR 33185 - [12/22/2025] Candidate by @TamilarasanSF4853
in #33271
- [Testing] Fixed Test case failure in PR 33185 - [12/22/2025] Candidate
- 2 by @TamilarasanSF4853 in #33299
- [Testing] Fixed Test case failure in PR 33185 - [12/22/2025] Candidate
- 3 by @TamilarasanSF4853 in #33311

</details>

<details>
<summary>📦 Other (2)</summary>

- [XSG][BindingSourceGen] Add support for RelayCommand to compiled
bindings by @simonrozsival via @Copilot in
#32954
  <details>
  <summary>🔧 Fixes</summary>

  - [Issue #25818](#25818)
  </details>
- Revert "Update logic for large title display mode on iOS - shell
(#33039)" in cff7f35

</details>
**Full Changelog**:
main...inflight/candidate
@github-actions github-actions bot locked and limited conversation to collaborators Jan 5, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CommandParameter TemplateBinding Lost During ControlTemplate Reparenting

3 participants