ContentPresenter: Propagate binding context to children with explicit TemplateBinding#30880
Conversation
|
Hey there @@HarishwaranVijayakumar! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed. |
There was a problem hiding this comment.
Pull Request Overview
This PR fixes a binding context inheritance issue in ContentPresenter when using explicit TemplateBinding in ControlTemplates. The problem occurred when ContentPresenter with explicit template bindings failed to properly propagate binding context to child elements, causing data bindings like Commands to fail.
Key Changes:
- Modified
SetChildInheritedBindingContextmethod in ContentPresenter to ensure proper binding context propagation - Added comprehensive UI test coverage to validate the fix across all platforms
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/Controls/src/Core/ContentPresenter.cs |
Core fix: Updated SetChildInheritedBindingContext to call SetInheritedBindingContext for proper context propagation |
src/Controls/tests/TestCases.HostApp/Issues/Issue23797.cs |
Added UI test page demonstrating ContentPresenter binding context issue with custom control and command binding |
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue23797.cs |
Added automated NUnit test to verify binding context propagation works correctly |
Comments suppressed due to low confidence (2)
src/Controls/tests/TestCases.HostApp/Issues/Issue23797.cs:10
- [nitpick] The property name 'Issue23797_ViewModel' is redundant and unclear. Consider renaming it to 'ViewModel' or 'SharedViewModel' for better readability.
public static Issue23797_ViewModel Issue23797_ViewModel { get; } = new Issue23797_ViewModel();
src/Controls/tests/TestCases.HostApp/Issues/Issue23797.cs:52
- [nitpick] The class name 'CustomControlWithCustomContent_Issue23797' is overly verbose and unclear. Consider renaming it to 'CustomContentControl' or 'TestContentControl' for better readability.
public class CustomControlWithCustomContent_Issue23797 : ContentView
|
/azp run |
| internal override void SetChildInheritedBindingContext(Element child, object context) | ||
| { | ||
| // We never want to use the standard inheritance mechanism, we will get this set by our parent | ||
| SetInheritedBindingContext(child, context); |
There was a problem hiding this comment.
Do we know why this comment is the exact opposite of this PR?
|
Azure Pipelines successfully started running 3 pipeline(s). |
|
/rebase |
59b1c0b to
965a2c7
Compare
965a2c7 to
7c9297b
Compare
🤖 AI Summary📊 Expand Full Review🔍 Pre-Flight — Context & Validation📝 Review Session — Modified the testcase ·
|
| File:Line | Reviewer | Comment | Status |
|---|---|---|---|
ContentPresenter.cs:107 |
mattleibow | "Do we know why this comment is the exact opposite of this PR?" |
Key concern: The original comment explicitly said NOT to use standard inheritance. PR #26072 (referenced in the PR description) was a related fix for binding context propagation in ContentView → ControlTemplate, merged in .NET 9 SR2. The current PR extends that fix to the SetChildInheritedBindingContext path.
Note: The current working directory already has the PR's fix applied (the method calls SetInheritedBindingContext).
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #30880 | Call SetInheritedBindingContext in SetChildInheritedBindingContext |
⏳ PENDING (Gate) | ContentPresenter.cs (+1/-1) |
Original PR |
🚦 Gate — Test Verification
📝 Review Session — Modified the testcase · 7c9297b
Result: ✅ PASSED
Platform: android
Mode: Full Verification
Test Filter: Issue23797
| Check | Expected | Actual | Result |
|---|---|---|---|
| Tests WITHOUT fix | FAIL | FAIL | ✅ |
| Tests WITH fix | PASS | PASS | ✅ |
- Tests FAIL without fix ✅ (bug is present)
- Tests PASS with fix ✅ (bug is fixed)
Conclusion: The tests properly validate the fix and catch the bug when it's present.
🔧 Fix — Analysis & Comparison
📝 Review Session — Modified the testcase · 7c9297b
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix (manual) | Remove SetChildInheritedBindingContext override entirely |
✅ PASS | ContentPresenter.cs |
Equivalent to PR's fix - base Element class already calls SetInheritedBindingContext |
| 2 | try-fix (claude-opus-4.6) | Override OnBindingContextChanged + keep no-op SetChildInheritedBindingContext |
❌ FAIL | ContentPresenter.cs |
base.OnBindingContextChanged iterates logical children and calls the no-op, creating a dead-end |
| 3 | try-fix (manual) | Add SetInheritedBindingContext(newView, self.BindingContext) in OnContentChanged + keep no-op |
✅ PASS (partial) | ContentPresenter.cs |
Test passes but incomplete - doesn't handle BindingContext changes after content is set |
| PR | PR #30880 | Call SetInheritedBindingContext(child, context) in SetChildInheritedBindingContext |
✅ PASS (Gate) | ContentPresenter.cs (+1/-1) |
Original PR - minimal, complete fix |
Cross-Pollination Summary
| Model | Round 2 Response |
|---|---|
| gpt-5.2-codex | NEW IDEA (already covered by existing AddLogicalChild calls) |
| gemini-3-pro-preview | NO NEW IDEAS |
| claude-sonnet-4.5 | NEW IDEA (OnPropertyChanged for BC - same coverage gap as Attempt 3) |
| claude-opus-4.6 | NO NEW IDEAS (analysis: solution space exhausted, PR fix is optimal) |
| gpt-5.2 | NO NEW IDEAS |
Exhausted: Yes - all 5 models confirmed no new complete ideas
Selected Fix: PR's fix - The PR's single-line change is the minimal, complete, and correct solution. It fixes SetChildInheritedBindingContext which is the single hook called by the framework for all inherited binding context scenarios. Attempt 1 (removing the override) is functionally equivalent but the PR's explicit override is better for documentation purposes. Attempt 3 is incomplete (misses BC changes after content set).
Root Cause Analysis
ContentPresenter.SetChildInheritedBindingContext was intentionally overridden to be a no-op with the comment: "We never want to use the standard inheritance mechanism, we will get this set by our parent". This was designed for the default ContentPresenter behavior where ContentConverter handles binding context propagation.
When using explicit TemplateBinding on ContentPresenter.ContentProperty, the ContentConverter is bypassed. This left children of ContentPresenter with no binding context propagation path, causing Commands bound to the ViewModel to fail.
The fix correctly enables standard inheritance by calling SetInheritedBindingContext(child, context), aligning ContentPresenter with its sibling ContentView which does the same thing.
Note on Try-Fix Models
Note: 3 of 5 model invocations via task agent failed due to API rate limiting (429). Those attempts were run manually within the main context.
📋 Report — Final Recommendation
📝 Review Session — Modified the testcase · 7c9297b
✅ Final Recommendation: APPROVE
Summary
PR #30880 fixes a binding context inheritance bug in ContentPresenter when using explicit TemplateBinding for ContentProperty. The fix is a minimal 1-line change that aligns ContentPresenter's behavior with ContentView and the base Element class. Gate testing confirms tests fail without the fix and pass with it. Multi-model try-fix exploration found no better alternative — the PR's approach is the minimal complete solution.
Root Cause
ContentPresenter.SetChildInheritedBindingContext was historically a no-op (with comment: "We never want to use the standard inheritance mechanism, we will get this set by our parent"). This was originally designed for the default ContentPresenter behavior where ContentConverter handled binding context propagation.
When users create multiple ContentPresenter slots with explicit TemplateBinding (bypassing ContentConverter), the standard inheritance path is not triggered. This left child views with no binding context, silently breaking all ViewModel-bound Commands.
This fix continues the direction of PR #26072 (merged in .NET 9 SR2), which changed ContentView to always propagate binding context to ControlTemplate.
Fix Quality
✅ Minimal and correct: The 1-line change (SetInheritedBindingContext(child, context)) is the only required fix. It makes ContentPresenter behave identically to:
ContentView.SetChildInheritedBindingContext(same 1-line implementation)Element.SetChildInheritedBindingContext(the base class method)
✅ Tests pass verification: Gate confirmed tests FAIL without fix, PASS with fix on Android.
✅ Independent validation: Try-fix found that removing the override entirely (Attempt 1) is functionally equivalent, confirming the PR's fix is correct.
Reviewer Question Addressed
Reviewer mattleibow asked: "Do we know why this comment is the exact opposite of this PR?"
Answer: The original "We never want..." comment reflected Xamarin.Forms-era behavior where ContentConverter exclusively managed binding context for ContentPresenter. With PR #26072 changing the binding context propagation model to be more consistent (always propagate to ControlTemplate), the original no-op is now incorrect for the explicit-TemplateBinding use case. The fix is consistent with the direction established by PR #26072.
Fix Candidates Summary
| # | Approach | Result | Notes |
|---|---|---|---|
| 1 | Remove override entirely | ✅ PASS | Equivalent to PR's fix; less explicit |
| 2 | OnBindingContextChanged + no-op SetChildInheritedBindingContext | ❌ FAIL | No propagation path |
| 3 | SetInheritedBindingContext in OnContentChanged + no-op | ✅ PASS (partial) | Misses BC changes after content is set |
| PR | SetInheritedBindingContext in SetChildInheritedBindingContext | ✅ PASS | Selected — minimal, complete, explicit |
Selected Fix: PR's fix — it's the minimal, complete, and most self-documenting solution.
Minor Issues (Non-Blocking)
-
Static ViewModel in test (
public static Issue23797_ViewModel): If this test is ever expanded with multiple test methods, the shared static state could cause flakiness. For a single test method, this is acceptable. -
Missing newline at end of both test files:
Issue23797.csand the test file are missing a trailing newline. Minor code style issue. -
Reviewer question unanswered: The PR discussion has an unanswered inline comment from
mattleibow. The PR author should respond to explain why the original comment was intentionally reversed (context from PR Always propagate the BC to the ControlTemplate #26072).
Title & Description
- Title "Fix for Binding context in ContentPresenter" — Accurate but could be more precise (e.g., "Fix binding context inheritance in ContentPresenter for explicit TemplateBinding"). Acceptable as-is.
- Description — Has the required NOTE block, clear root cause analysis, and references PR Always propagate the BC to the ControlTemplate #26072 and issue Binding context in ContentPresenter #23797. Well-written.
📋 Expand PR Finalization Review
Title: ⚠️ Needs Update
Current: Fix for Binding context in ContentPresenter
Issues:
- Uses vague "Fix for" prefix (noise)
- Doesn't describe the specific behavior being fixed
- Doesn't capture what changed in the code
Recommended: ContentPresenter: Propagate binding context to children with explicit TemplateBinding
Description: ⚠️ Needs Update
- Uses vague "Fix for" prefix (noise)
- Doesn't describe the specific behavior being fixed
- Doesn't capture what changed in the code
✨ Suggested PR Description
[!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
When using a ContentPresenter with an explicit TemplateBinding to a custom BindableProperty in a ControlTemplate, child elements inside the ContentPresenter do not inherit the binding context. This causes data bindings (e.g. Command) to fail silently.
Repro: Create a ContentView subclass with a custom BindableProperty of type View (e.g. MyContent), use it in a ControlTemplate with ContentPresenter.ContentProperty explicitly bound to that property via TemplateBinding, and place a Button with a Command binding inside. The command will not fire.
Root Cause
ContentPresenter overrides SetChildInheritedBindingContext with an intentional no-op:
// We never want to use the standard inheritance mechanism, we will get this set by our parentThis design came from the Xamarin.Forms era where the binding context was assumed to be set through a different propagation path. With the default ContentPresenter behavior (content bound via ContentConverter on IContentView.Content), the binding context eventually propagates correctly because the ControlTemplate's content tree is wired up through ParentOverride and the templated parent.
However, with an explicit TemplateBinding to a custom BindableProperty, the binding resolves directly without going through the templated parent propagation path. The SetChildInheritedBindingContext override silently discards the inherited context, so child elements never receive the BindingContext from the templated parent.
Note: ContentConverter does NOT handle binding context — it only forwards text/font properties from ancestor elements.
Description of Change
src/Controls/src/Core/ContentPresenter.cs
Removed the no-op override in SetChildInheritedBindingContext and replaced it with an actual call to SetInheritedBindingContext(child, context).
This ensures that when binding context flows down the element tree to a ContentPresenter, its children receive the inherited context — matching the standard MAUI/WinUI behavior where DataContext always propagates through the template tree.
This is consistent with the direction taken in PR #26072, which committed to always propagating BindingContext through ControlTemplate hierarchies.
Key Technical Details
SetChildInheritedBindingContextis called by the element tree's inherited binding context propagation mechanism when a parent'sBindingContextchanges.- The
ContentPresenterconstructor sets up a default binding onContentPropertyusingContentConverterandRelativeBindingSource.TemplatedParent. This default path is NOT affected by this change. ContentConverteronly handles text/font property propagation — it does not participate inBindingContextinheritance.- The fix aligns
ContentPresenterwith standardSetInheritedBindingContextbehavior used by all other layout types.
Issues Fixed
Fixes #23797
Platforms Tested
- Windows
- Android
- iOS
- Mac
Code Review: ⚠️ Issues Found
Code Review — PR #30880
🔴 Critical Issues
Inaccurate Root Cause in Description (Description Correctness)
- File: PR description
- Problem: The description claims the
ContentConverterwas "responsible for preserving binding context inheritance" and that explicitTemplateBinding"bypasses this converter." This is factually incorrect.ContentConverteronly handles text/font property propagation (seeContentConverter.cs—ConfigureView/BindTextProperties/BindFontProperties). It has nothing to do withBindingContextinheritance. This misunderstanding could mislead future developers. - Recommendation: Update description to correctly explain that the
SetChildInheritedBindingContextno-op was the actual suppressor of binding context propagation.
Unaddressed Open Reviewer Concern
- File: PR review thread
- Problem: Reviewer
mattleibowasked "Do we know why this comment is the exact opposite of this PR?" This question is still open and unresolved. The original comment was intentional Xamarin.Forms-era design. The PR should explicitly explain why this design decision is now safe to reverse, especially with context from PR Always propagate the BC to the ControlTemplate #26072. - Recommendation: Author should respond to this review comment with an explanation, and that explanation should be reflected in the PR description.
🟡 Suggestions
Missing end-of-file newlines in test files
- Files:
src/Controls/tests/TestCases.HostApp/Issues/Issue23797.cssrc/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue23797.cs
- Problem: Both new files are missing a trailing newline (
\ No newline at end of filein the diff). - Recommendation: Add newlines at the end of both files for consistency with the rest of the codebase.
Test category may not be the most specific
- File:
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue23797.cs(line 16) - Problem:
[Category(UITestCategories.Layout)]is used, but the test is specifically aboutContentPresenterbinding context in aControlTemplate. A more specific category likeControlTemplateorBindingContextmight exist. - Recommendation: Verify available
UITestCategoriesvalues and use the most specific one.Layoutis acceptable if no better match exists.
Static ViewModel on the page class
- File:
src/Controls/tests/TestCases.HostApp/Issues/Issue23797.cs(line 11) - Problem:
public static Issue23797_ViewModel Issue23797_ViewModel { get; } = new Issue23797_ViewModel();— using astaticproperty on aContentPagesubclass is unusual for test pages. If tests run in a shared process or multiple test runs re-use the page type, the static ViewModel state (Message = "failure") would persist and could cause false negatives on a second run. - Recommendation: Move the ViewModel instantiation to the constructor (non-static), or reset state in a setup method.
✅ Looks Good
- Single focused change: The core fix is minimal (1 line changed) and targeted — replaces the intentional no-op with the correct propagation call.
- Test coverage: Both a
HostAppUI page and aTestCases.Shared.TestsNUnit test are included, following the two-project requirement for UI tests. - C# only HostApp test: The HostApp test correctly uses pure C# (no XAML) as preferred.
- AutomationId usage:
AutomationIdvalues (Issue23797Btn,CurrentMessageLabel) are consistently defined in the host page and referenced in the NUnit test. - Issue attribute:
[Issue(IssueTracker.Github, 23797, ...)]correctly links to the bug report. - NOTE block: The PR description includes the required NOTE block at the top.
- Cross-platform testing: All four platforms (Windows, Android, iOS, Mac) are checked in the tested platforms list.
… TemplateBinding (#30880) <!-- 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! ### Issue Details When using a `ContentPresenter` with an **explicit** `TemplateBinding` to a custom `BindableProperty` in a `ControlTemplate`, child elements inside the `ContentPresenter` do not inherit the binding context. This causes data bindings (e.g. `Command`) to fail silently. **Repro:** Create a `ContentView` subclass with a custom `BindableProperty` of type `View` (e.g. `MyContent`), use it in a `ControlTemplate` with `ContentPresenter.ContentProperty` explicitly bound to that property via `TemplateBinding`, and place a `Button` with a `Command` binding inside. The command will not fire. ### Root Cause `ContentPresenter` overrides `SetChildInheritedBindingContext` with an intentional no-op: ```csharp // We never want to use the standard inheritance mechanism, we will get this set by our parent ``` This design came from the Xamarin.Forms era where the binding context was assumed to be set through a different propagation path. With the **default** `ContentPresenter` behavior (content bound via `ContentConverter` on `IContentView.Content`), the binding context eventually propagates correctly because the ControlTemplate's content tree is wired up through `ParentOverride` and the templated parent. However, with an **explicit** `TemplateBinding` to a custom `BindableProperty`, the binding resolves directly without going through the templated parent propagation path. The `SetChildInheritedBindingContext` override silently discards the inherited context, so child elements never receive the `BindingContext` from the templated parent. Note: `ContentConverter` does NOT handle binding context — it only forwards text/font properties from ancestor elements. ### Description of Change **`src/Controls/src/Core/ContentPresenter.cs`** Removed the no-op override in `SetChildInheritedBindingContext` and replaced it with an actual call to `SetInheritedBindingContext(child, context)`. This ensures that when binding context flows down the element tree to a `ContentPresenter`, its children receive the inherited context — matching the standard MAUI/WinUI behavior where `DataContext` always propagates through the template tree. This is consistent with the direction taken in PR #26072, which committed to always propagating `BindingContext` through `ControlTemplate` hierarchies. ### Key Technical Details - `SetChildInheritedBindingContext` is called by the element tree's inherited binding context propagation mechanism when a parent's `BindingContext` changes. - The `ContentPresenter` constructor sets up a default binding on `ContentProperty` using `ContentConverter` and `RelativeBindingSource.TemplatedParent`. This default path is NOT affected by this change. - `ContentConverter` only handles text/font property propagation — it does not participate in `BindingContext` inheritance. - The fix aligns `ContentPresenter` with standard `SetInheritedBindingContext` behavior used by all other layout types. ### Issues Fixed Fixes #23797 ### Platforms Tested - [x] Windows - [x] Android - [x] iOS - [x] Mac
… TemplateBinding (#30880) <!-- 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! ### Issue Details When using a `ContentPresenter` with an **explicit** `TemplateBinding` to a custom `BindableProperty` in a `ControlTemplate`, child elements inside the `ContentPresenter` do not inherit the binding context. This causes data bindings (e.g. `Command`) to fail silently. **Repro:** Create a `ContentView` subclass with a custom `BindableProperty` of type `View` (e.g. `MyContent`), use it in a `ControlTemplate` with `ContentPresenter.ContentProperty` explicitly bound to that property via `TemplateBinding`, and place a `Button` with a `Command` binding inside. The command will not fire. ### Root Cause `ContentPresenter` overrides `SetChildInheritedBindingContext` with an intentional no-op: ```csharp // We never want to use the standard inheritance mechanism, we will get this set by our parent ``` This design came from the Xamarin.Forms era where the binding context was assumed to be set through a different propagation path. With the **default** `ContentPresenter` behavior (content bound via `ContentConverter` on `IContentView.Content`), the binding context eventually propagates correctly because the ControlTemplate's content tree is wired up through `ParentOverride` and the templated parent. However, with an **explicit** `TemplateBinding` to a custom `BindableProperty`, the binding resolves directly without going through the templated parent propagation path. The `SetChildInheritedBindingContext` override silently discards the inherited context, so child elements never receive the `BindingContext` from the templated parent. Note: `ContentConverter` does NOT handle binding context — it only forwards text/font properties from ancestor elements. ### Description of Change **`src/Controls/src/Core/ContentPresenter.cs`** Removed the no-op override in `SetChildInheritedBindingContext` and replaced it with an actual call to `SetInheritedBindingContext(child, context)`. This ensures that when binding context flows down the element tree to a `ContentPresenter`, its children receive the inherited context — matching the standard MAUI/WinUI behavior where `DataContext` always propagates through the template tree. This is consistent with the direction taken in PR #26072, which committed to always propagating `BindingContext` through `ControlTemplate` hierarchies. ### Key Technical Details - `SetChildInheritedBindingContext` is called by the element tree's inherited binding context propagation mechanism when a parent's `BindingContext` changes. - The `ContentPresenter` constructor sets up a default binding on `ContentProperty` using `ContentConverter` and `RelativeBindingSource.TemplatedParent`. This default path is NOT affected by this change. - `ContentConverter` only handles text/font property propagation — it does not participate in `BindingContext` inheritance. - The fix aligns `ContentPresenter` with standard `SetInheritedBindingContext` behavior used by all other layout types. ### Issues Fixed Fixes #23797 ### Platforms Tested - [x] Windows - [x] Android - [x] iOS - [x] Mac
… TemplateBinding (#30880) <!-- 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! ### Issue Details When using a `ContentPresenter` with an **explicit** `TemplateBinding` to a custom `BindableProperty` in a `ControlTemplate`, child elements inside the `ContentPresenter` do not inherit the binding context. This causes data bindings (e.g. `Command`) to fail silently. **Repro:** Create a `ContentView` subclass with a custom `BindableProperty` of type `View` (e.g. `MyContent`), use it in a `ControlTemplate` with `ContentPresenter.ContentProperty` explicitly bound to that property via `TemplateBinding`, and place a `Button` with a `Command` binding inside. The command will not fire. ### Root Cause `ContentPresenter` overrides `SetChildInheritedBindingContext` with an intentional no-op: ```csharp // We never want to use the standard inheritance mechanism, we will get this set by our parent ``` This design came from the Xamarin.Forms era where the binding context was assumed to be set through a different propagation path. With the **default** `ContentPresenter` behavior (content bound via `ContentConverter` on `IContentView.Content`), the binding context eventually propagates correctly because the ControlTemplate's content tree is wired up through `ParentOverride` and the templated parent. However, with an **explicit** `TemplateBinding` to a custom `BindableProperty`, the binding resolves directly without going through the templated parent propagation path. The `SetChildInheritedBindingContext` override silently discards the inherited context, so child elements never receive the `BindingContext` from the templated parent. Note: `ContentConverter` does NOT handle binding context — it only forwards text/font properties from ancestor elements. ### Description of Change **`src/Controls/src/Core/ContentPresenter.cs`** Removed the no-op override in `SetChildInheritedBindingContext` and replaced it with an actual call to `SetInheritedBindingContext(child, context)`. This ensures that when binding context flows down the element tree to a `ContentPresenter`, its children receive the inherited context — matching the standard MAUI/WinUI behavior where `DataContext` always propagates through the template tree. This is consistent with the direction taken in PR #26072, which committed to always propagating `BindingContext` through `ControlTemplate` hierarchies. ### Key Technical Details - `SetChildInheritedBindingContext` is called by the element tree's inherited binding context propagation mechanism when a parent's `BindingContext` changes. - The `ContentPresenter` constructor sets up a default binding on `ContentProperty` using `ContentConverter` and `RelativeBindingSource.TemplatedParent`. This default path is NOT affected by this change. - `ContentConverter` only handles text/font property propagation — it does not participate in `BindingContext` inheritance. - The fix aligns `ContentPresenter` with standard `SetInheritedBindingContext` behavior used by all other layout types. ### Issues Fixed Fixes #23797 ### Platforms Tested - [x] Windows - [x] Android - [x] iOS - [x] Mac
… TemplateBinding (#30880) <!-- 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! ### Issue Details When using a `ContentPresenter` with an **explicit** `TemplateBinding` to a custom `BindableProperty` in a `ControlTemplate`, child elements inside the `ContentPresenter` do not inherit the binding context. This causes data bindings (e.g. `Command`) to fail silently. **Repro:** Create a `ContentView` subclass with a custom `BindableProperty` of type `View` (e.g. `MyContent`), use it in a `ControlTemplate` with `ContentPresenter.ContentProperty` explicitly bound to that property via `TemplateBinding`, and place a `Button` with a `Command` binding inside. The command will not fire. ### Root Cause `ContentPresenter` overrides `SetChildInheritedBindingContext` with an intentional no-op: ```csharp // We never want to use the standard inheritance mechanism, we will get this set by our parent ``` This design came from the Xamarin.Forms era where the binding context was assumed to be set through a different propagation path. With the **default** `ContentPresenter` behavior (content bound via `ContentConverter` on `IContentView.Content`), the binding context eventually propagates correctly because the ControlTemplate's content tree is wired up through `ParentOverride` and the templated parent. However, with an **explicit** `TemplateBinding` to a custom `BindableProperty`, the binding resolves directly without going through the templated parent propagation path. The `SetChildInheritedBindingContext` override silently discards the inherited context, so child elements never receive the `BindingContext` from the templated parent. Note: `ContentConverter` does NOT handle binding context — it only forwards text/font properties from ancestor elements. ### Description of Change **`src/Controls/src/Core/ContentPresenter.cs`** Removed the no-op override in `SetChildInheritedBindingContext` and replaced it with an actual call to `SetInheritedBindingContext(child, context)`. This ensures that when binding context flows down the element tree to a `ContentPresenter`, its children receive the inherited context — matching the standard MAUI/WinUI behavior where `DataContext` always propagates through the template tree. This is consistent with the direction taken in PR #26072, which committed to always propagating `BindingContext` through `ControlTemplate` hierarchies. ### Key Technical Details - `SetChildInheritedBindingContext` is called by the element tree's inherited binding context propagation mechanism when a parent's `BindingContext` changes. - The `ContentPresenter` constructor sets up a default binding on `ContentProperty` using `ContentConverter` and `RelativeBindingSource.TemplatedParent`. This default path is NOT affected by this change. - `ContentConverter` only handles text/font property propagation — it does not participate in `BindingContext` inheritance. - The fix aligns `ContentPresenter` with standard `SetInheritedBindingContext` behavior used by all other layout types. ### Issues Fixed Fixes #23797 ### Platforms Tested - [x] Windows - [x] Android - [x] iOS - [x] Mac
…explicit TemplateBinding (dotnet#30880)" This reverts commit 266cc03.
… TemplateBinding (dotnet#30880) <!-- 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! ### Issue Details When using a `ContentPresenter` with an **explicit** `TemplateBinding` to a custom `BindableProperty` in a `ControlTemplate`, child elements inside the `ContentPresenter` do not inherit the binding context. This causes data bindings (e.g. `Command`) to fail silently. **Repro:** Create a `ContentView` subclass with a custom `BindableProperty` of type `View` (e.g. `MyContent`), use it in a `ControlTemplate` with `ContentPresenter.ContentProperty` explicitly bound to that property via `TemplateBinding`, and place a `Button` with a `Command` binding inside. The command will not fire. ### Root Cause `ContentPresenter` overrides `SetChildInheritedBindingContext` with an intentional no-op: ```csharp // We never want to use the standard inheritance mechanism, we will get this set by our parent ``` This design came from the Xamarin.Forms era where the binding context was assumed to be set through a different propagation path. With the **default** `ContentPresenter` behavior (content bound via `ContentConverter` on `IContentView.Content`), the binding context eventually propagates correctly because the ControlTemplate's content tree is wired up through `ParentOverride` and the templated parent. However, with an **explicit** `TemplateBinding` to a custom `BindableProperty`, the binding resolves directly without going through the templated parent propagation path. The `SetChildInheritedBindingContext` override silently discards the inherited context, so child elements never receive the `BindingContext` from the templated parent. Note: `ContentConverter` does NOT handle binding context — it only forwards text/font properties from ancestor elements. ### Description of Change **`src/Controls/src/Core/ContentPresenter.cs`** Removed the no-op override in `SetChildInheritedBindingContext` and replaced it with an actual call to `SetInheritedBindingContext(child, context)`. This ensures that when binding context flows down the element tree to a `ContentPresenter`, its children receive the inherited context — matching the standard MAUI/WinUI behavior where `DataContext` always propagates through the template tree. This is consistent with the direction taken in PR dotnet#26072, which committed to always propagating `BindingContext` through `ControlTemplate` hierarchies. ### Key Technical Details - `SetChildInheritedBindingContext` is called by the element tree's inherited binding context propagation mechanism when a parent's `BindingContext` changes. - The `ContentPresenter` constructor sets up a default binding on `ContentProperty` using `ContentConverter` and `RelativeBindingSource.TemplatedParent`. This default path is NOT affected by this change. - `ContentConverter` only handles text/font property propagation — it does not participate in `BindingContext` inheritance. - The fix aligns `ContentPresenter` with standard `SetInheritedBindingContext` behavior used by all other layout types. ### Issues Fixed Fixes dotnet#23797 ### Platforms Tested - [x] Windows - [x] Android - [x] iOS - [x] Mac
… TemplateBinding (#30880) <!-- 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! ### Issue Details When using a `ContentPresenter` with an **explicit** `TemplateBinding` to a custom `BindableProperty` in a `ControlTemplate`, child elements inside the `ContentPresenter` do not inherit the binding context. This causes data bindings (e.g. `Command`) to fail silently. **Repro:** Create a `ContentView` subclass with a custom `BindableProperty` of type `View` (e.g. `MyContent`), use it in a `ControlTemplate` with `ContentPresenter.ContentProperty` explicitly bound to that property via `TemplateBinding`, and place a `Button` with a `Command` binding inside. The command will not fire. ### Root Cause `ContentPresenter` overrides `SetChildInheritedBindingContext` with an intentional no-op: ```csharp // We never want to use the standard inheritance mechanism, we will get this set by our parent ``` This design came from the Xamarin.Forms era where the binding context was assumed to be set through a different propagation path. With the **default** `ContentPresenter` behavior (content bound via `ContentConverter` on `IContentView.Content`), the binding context eventually propagates correctly because the ControlTemplate's content tree is wired up through `ParentOverride` and the templated parent. However, with an **explicit** `TemplateBinding` to a custom `BindableProperty`, the binding resolves directly without going through the templated parent propagation path. The `SetChildInheritedBindingContext` override silently discards the inherited context, so child elements never receive the `BindingContext` from the templated parent. Note: `ContentConverter` does NOT handle binding context — it only forwards text/font properties from ancestor elements. ### Description of Change **`src/Controls/src/Core/ContentPresenter.cs`** Removed the no-op override in `SetChildInheritedBindingContext` and replaced it with an actual call to `SetInheritedBindingContext(child, context)`. This ensures that when binding context flows down the element tree to a `ContentPresenter`, its children receive the inherited context — matching the standard MAUI/WinUI behavior where `DataContext` always propagates through the template tree. This is consistent with the direction taken in PR #26072, which committed to always propagating `BindingContext` through `ControlTemplate` hierarchies. ### Key Technical Details - `SetChildInheritedBindingContext` is called by the element tree's inherited binding context propagation mechanism when a parent's `BindingContext` changes. - The `ContentPresenter` constructor sets up a default binding on `ContentProperty` using `ContentConverter` and `RelativeBindingSource.TemplatedParent`. This default path is NOT affected by this change. - `ContentConverter` only handles text/font property propagation — it does not participate in `BindingContext` inheritance. - The fix aligns `ContentPresenter` with standard `SetInheritedBindingContext` behavior used by all other layout types. ### Issues Fixed Fixes #23797 ### Platforms Tested - [x] Windows - [x] Android - [x] iOS - [x] Mac
## What's Coming .NET MAUI inflight/candidate introduces significant improvements across all platforms with focus on quality, performance, and developer experience. This release includes 46 commits with various improvements, bug fixes, and enhancements. ## Button - [Android] Implemented material3 support for Button by @Dhivya-SF4094 in #33173 <details> <summary>🔧 Fixes</summary> - [Implement Material3 support for Button](#33172) </details> ## CollectionView - [Android] Fix RemainingItemsThresholdReachedCommand not firing when CollectionView has Header and Footer both defined by @SuthiYuvaraj in #29618 <details> <summary>🔧 Fixes</summary> - [Android : RemainingItemsThresholdReachedCommand not firing when CollectionVew has Header and Footer both defined](#29588) </details> - [iOS/MacCatalyst] Fix CollectionView ScrollTo for horizontal layouts by @Shalini-Ashokan in #33853 <details> <summary>🔧 Fixes</summary> - [[iOS/MacCatalyst] CollectionView ScrollTo does not work with horizontal Layout](#33852) </details> - [iOS & Mac] Fixed IndicatorView Size doesnt update dynamically by @SubhikshaSf4851 in #31129 <details> <summary>🔧 Fixes</summary> - [[iOS, Catalyst] IndicatorView.IndicatorSize does not update dynamically at runtime](#31064) </details> - [Android] Fix for CollectionView Scrolled event is triggered on the initial app load. by @BagavathiPerumal in #33558 <details> <summary>🔧 Fixes</summary> - [[Android] CollectionView Scrolled event is triggered on the initial app load.](#33333) </details> - [iOS, Android] Fix for CollectionView IsEnabled=false allows touch interactions by @praveenkumarkarunanithi in #31403 <details> <summary>🔧 Fixes</summary> - [More issues with CollectionView IsEnabled, InputTransparent, Opacity via Styles and code behind](#19771) </details> - [iOS] Fix VerticalOffset Update When Modifying CollectionView.ItemsSource While Scrolled by @devanathan-vaithiyanathan in #34153 <details> <summary>🔧 Fixes</summary> - [[iOS]VerticalOffset Not Reset to Zero After Clearing ItemSource in CollectionView](#26798) </details> ## DateTimePicker - [Android] Fix DatePicker MinimumDate/MaximumDate not updating dynamically by @HarishwaranVijayakumar in #33687 <details> <summary>🔧 Fixes</summary> - [[regression/8.0.3] [Android] DatePicker control minimum date issue](#19256) - [[Android] DatePicker does not update MinimumDate / MaximumDate in the Popup when set in the viewmodel after first opening](#33583) </details> ## Drawing - Android drawable perf by @albyrock87 in #31567 ## Editor - [Android] Implemented material3 support for Editor by @SyedAbdulAzeemSF4852 in #33478 <details> <summary>🔧 Fixes</summary> - [Implement Material3 Support for Editor](#33476) </details> ## Entry - [iOS, Mac] Fix for CursorPosition not updating when typing into Entry control by @SyedAbdulAzeemSF4852 in #30505 <details> <summary>🔧 Fixes</summary> - [Entry control CursorPosition does not update on TextChanged event [iOS Maui 8.0.7] ](#20911) - [CursorPosition not calculated correctly on behaviors events for iOS devices](#32483) </details> ## Flyoutpage - [Android, Windows] Fix for FlyoutPage toolbar button not updating on orientation change by @praveenkumarkarunanithi in #31962 <details> <summary>🔧 Fixes</summary> - [Flyout page in Android does not show flyout button (burger) consistently](#24468) </details> - Fix for First Item in CollectionView Overlaps in FlyoutPage.Flyout on iOS by @praveenkumarkarunanithi in #29265 <details> <summary>🔧 Fixes</summary> - [[iOS] CollectionView not rendering first item correctly in FlyoutPage.Flyout](#29170) </details> ## Image - [Android] Fix excessive memory usage for stream and resource-based image loading by @Shalini-Ashokan in #33590 <details> <summary>🔧 Fixes</summary> - [[Android] Unexpected high Bitmap.ByteCount when loading image via ImageSource.FromResource() or ImageSource.FromStream() in .NET MAUI](#33239) </details> - [Android] Fix for Resize method returns an image that has already been disposed by @SyedAbdulAzeemSF4852 in #29964 <details> <summary>🔧 Fixes</summary> - [In GraphicsView, the Resize method returns an image that has already been disposed](#29961) - [IIMage.Resize bugged behaviour](#31103) </details> ## Label - Fixed Label Span font property inheritance when applied via Style by @SubhikshaSf4851 in #34110 <details> <summary>🔧 Fixes</summary> - [`Span` does not inherit text styling from `Label` if that styling is applied using `Style` ](#21326) </details> - [Android] Implemented material3 support for Label by @SyedAbdulAzeemSF4852 in #33599 <details> <summary>🔧 Fixes</summary> - [Implement Material3 Support for Label](#33598) </details> ## Map - [Android] Fix Circle Stroke color is incorrectly updated as Fill color. by @NirmalKumarYuvaraj in #33643 <details> <summary>🔧 Fixes</summary> - [[Android] Circle Stroke color is incorrectly updated as Fill color.](#33642) </details> ## Mediapicker - [iOS] Fix: invoke MediaPicker completion handler after DismissViewController by @yuriikyry4enko in #34250 <details> <summary>🔧 Fixes</summary> - [[iOS] Media Picker UIImagePickerController closing issue](#21996) </details> ## Navigation - Fix ContentPage memory leak on Android when using NavigationPage modally (fixes #33918) by @brunck in #34117 <details> <summary>🔧 Fixes</summary> - [[Android] Modal TabbedPage whose tabs are NavigationPage(ContentPage) is retained after PopModalAsync()](#33918) </details> ## Picker - [Android] Implement material3 support for TimePicker by @HarishwaranVijayakumar in #33646 <details> <summary>🔧 Fixes</summary> - [Implement Material3 support for TimePicker](#33645) </details> - [Android] Implemented Material3 support for Picker by @SyedAbdulAzeemSF4852 in #33668 <details> <summary>🔧 Fixes</summary> - [Implement Material3 support for Picker](#33665) </details> ## RadioButton - [Android] Implemented material3 support for RadioButton by @SyedAbdulAzeemSF4852 in #33468 <details> <summary>🔧 Fixes</summary> - [Implement Material3 Support for RadioButton](#33467) </details> ## Setup - Clarify MA003 error message by @jeremy-visionaid in #34067 <details> <summary>🔧 Fixes</summary> - [MA003 false positive with 9.0.21](#26599) </details> ## Shell - [Android] Fix TabBar FlowDirection not updating dynamically by @SubhikshaSf4851 in #33091 <details> <summary>🔧 Fixes</summary> - [[Android, iOS] FlowDirection RTL is not updated dynamically on Shell TabBar](#32993) </details> - [Android] Fix page not disposed on Shell replace navigation by @Vignesh-SF3580 in #33426 <details> <summary>🔧 Fixes</summary> - [[Android] [Shell] replace navigation leaks current page](#25134) </details> - [Android] Fixed Shell flyout does not disable scrolling when FlyoutVerticalScrollMode is set to Disabled by @NanthiniMahalingam in #32734 <details> <summary>🔧 Fixes</summary> - [[Android] Shell.FlyoutVerticalScrollMode="Disabled" does not disable scrolling](#32477) </details> ## Single Project - Fix: Throw a clear error when an SVG lacks dimensions instead of a NullReferenceException by @Shalini-Ashokan in #33194 <details> <summary>🔧 Fixes</summary> - [MAUI Fails To Convert Valid SVG Files Into PNG Files (Object reference not set to an instance of an object)](#32460) </details> ## SwipeView - [iOS] Fix SwipeView stays open on iOS after updating content by @devanathan-vaithiyanathan in #31248 <details> <summary>🔧 Fixes</summary> - [[iOS] - Swipeview with collectionview issue](#19541) </details> ## TabbedPage - [Windows] Fixed IsEnabled Property not works on Tabs by @NirmalKumarYuvaraj in #26728 <details> <summary>🔧 Fixes</summary> - [ShellContent IsEnabledProperty does not work](#5161) - [[Windows] Shell Tab IsEnabled Not Working](#32996) </details> - [Android] Fix NavigationBar overlapping StatusBar when NavigationBar visibility changes by @Vignesh-SF3580 in #33359 <details> <summary>🔧 Fixes</summary> - [[Android] NavigationBar overlaps with StatusBar when mixing HasNavigationBar=true/false in TabbedPage on Android 15 (API 35)](#33340) </details> ## Templates - Fix for unable to open task using keyboard navigation on windows platform by @SuthiYuvaraj in #33647 <details> <summary>🔧 Fixes</summary> - [Unable to open task using keyboard: A11y_.NET maui_User can get all the insights of Dashboard_Keyboard](#30787) </details> ## TitleView - Fix for NavigationPage.TitleView does not expand with host window in iPadOS 26+ by @SuthiYuvaraj in #33088 ## Toolbar - [iOS] Fix toolbar items ignoring BarTextColor on iOS/MacCatalyst 26+ by @Shalini-Ashokan in #34036 <details> <summary>🔧 Fixes</summary> - [[iOS 26] ToolbarItem color with custom BarTextColor not working](#33970) </details> - [Android] Fix for ToolbarItem retaining the icon from the previous page on Android when using NavigationPage. by @BagavathiPerumal in #32311 <details> <summary>🔧 Fixes</summary> - [Toolbaritem keeps the icon of the previous page on Android, using NavigationPage (not shell)](#31727) </details> ## WebView - [Android] Fix WebView in a grid expands beyond it's cell by @devanathan-vaithiyanathan in #32145 <details> <summary>🔧 Fixes</summary> - [Android - WebView in a grid expands beyond it's cell](#32030) </details> ## Xaml - ContentPresenter: Propagate binding context to children with explicit TemplateBinding by @HarishwaranVijayakumar in #30880 <details> <summary>🔧 Fixes</summary> - [Binding context in ContentPresenter](#23797) </details> <details> <summary>🔧 Infrastructure (1)</summary> - [Revert] ContentPresenter: Propagate binding context to children with explicit TemplateBinding by @Ahamed-Ali in #34332 </details> <details> <summary>🧪 Testing (6)</summary> - [Testing] Feature Matrix UITest Cases for Shell Flyout Page by @NafeelaNazhir in #32525 - [Testing] Feature Matrix UITest Cases for Brushes by @LogishaSelvarajSF4525 in #31833 - [Testing] Feature Matrix UITest Cases for BindableLayout by @LogishaSelvarajSF4525 in #33108 - [Android] Add UI tests for Material 3 CheckBox by @HarishwaranVijayakumar in #34126 <details> <summary>🔧 Fixes</summary> - [[Android] Add UI tests for Material 3 CheckBox](#34125) </details> - [Testing] Feature Matrix UITest Cases for Shell Tabbed Page by @NafeelaNazhir in #33159 - [Testing] Fixed Test case failure in PR 34294 - [03/2/2026] Candidate - 1 by @TamilarasanSF4853 in #34334 </details> <details> <summary>📦 Other (2)</summary> - Bumps Syncfusion.Maui.Toolkit dependency to version 1.0.9 by @PaulAndersonS in #34178 - Fix crash when closing Windows based app when using TitleBar by @MFinkBK in #34032 <details> <summary>🔧 Fixes</summary> - [Unhandled exception "Value does not fall within the expected range" when closing Windows app](#32194) </details> </details> **Full Changelog**: main...inflight/candidate
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
When using a
ContentPresenterwith an explicitTemplateBindingto a customBindablePropertyin aControlTemplate, child elements inside theContentPresenterdo not inherit the binding context. This causes data bindings (e.g.Command) to fail silently.Repro: Create a
ContentViewsubclass with a customBindablePropertyof typeView(e.g.MyContent), use it in aControlTemplatewithContentPresenter.ContentPropertyexplicitly bound to that property viaTemplateBinding, and place aButtonwith aCommandbinding inside. The command will not fire.Root Cause
ContentPresenteroverridesSetChildInheritedBindingContextwith an intentional no-op:// We never want to use the standard inheritance mechanism, we will get this set by our parentThis design came from the Xamarin.Forms era where the binding context was assumed to be set through a different propagation path. With the default
ContentPresenterbehavior (content bound viaContentConverteronIContentView.Content), the binding context eventually propagates correctly because the ControlTemplate's content tree is wired up throughParentOverrideand the templated parent.However, with an explicit
TemplateBindingto a customBindableProperty, the binding resolves directly without going through the templated parent propagation path. TheSetChildInheritedBindingContextoverride silently discards the inherited context, so child elements never receive theBindingContextfrom the templated parent.Note:
ContentConverterdoes NOT handle binding context — it only forwards text/font properties from ancestor elements.Description of Change
src/Controls/src/Core/ContentPresenter.csRemoved the no-op override in
SetChildInheritedBindingContextand replaced it with an actual call toSetInheritedBindingContext(child, context).This ensures that when binding context flows down the element tree to a
ContentPresenter, its children receive the inherited context — matching the standard MAUI/WinUI behavior whereDataContextalways propagates through the template tree.This is consistent with the direction taken in PR #26072, which committed to always propagating
BindingContextthroughControlTemplatehierarchies.Key Technical Details
SetChildInheritedBindingContextis called by the element tree's inherited binding context propagation mechanism when a parent'sBindingContextchanges.ContentPresenterconstructor sets up a default binding onContentPropertyusingContentConverterandRelativeBindingSource.TemplatedParent. This default path is NOT affected by this change.ContentConverteronly handles text/font property propagation — it does not participate inBindingContextinheritance.ContentPresenterwith standardSetInheritedBindingContextbehavior used by all other layout types.Issues Fixed
Fixes #23797
Platforms Tested