diff --git a/src/Controls/src/Core/BindableObject.cs b/src/Controls/src/Core/BindableObject.cs
index 11ef8b8cac64..f205dbe7e611 100644
--- a/src/Controls/src/Core/BindableObject.cs
+++ b/src/Controls/src/Core/BindableObject.cs
@@ -432,25 +432,6 @@ internal bool GetIsBound(BindableProperty targetProperty)
return bpcontext != null && bpcontext.Bindings.Count > 0;
}
- ///
- /// Forces the binding for the specified property to apply immediately.
- /// This is used when one property depends on another and needs the dependent
- /// property's binding to resolve before proceeding.
- /// See https://github.com/dotnet/maui/issues/31939
- ///
- internal void ForceBindingApply(BindableProperty targetProperty)
- {
- if (targetProperty == null)
- throw new ArgumentNullException(nameof(targetProperty));
-
- BindablePropertyContext bpcontext = GetContext(targetProperty);
- if (bpcontext == null || bpcontext.Bindings.Count == 0)
- return;
-
- // Force the binding to apply now
- ApplyBinding(bpcontext, fromBindingContextChanged: false);
- }
-
internal virtual void OnRemoveDynamicResource(BindableProperty property)
{
}
diff --git a/src/Controls/src/Core/BindableProperty.cs b/src/Controls/src/Core/BindableProperty.cs
index ad7eed736eee..ed771a2c94c8 100644
--- a/src/Controls/src/Core/BindableProperty.cs
+++ b/src/Controls/src/Core/BindableProperty.cs
@@ -252,21 +252,6 @@ public sealed class BindableProperty
internal ValidateValueDelegate ValidateValue { get; private set; }
- // Properties that this property depends on - when getting this property's value,
- // if the dependency has a pending binding, return the default value instead.
- // This is used to fix timing issues where one property binding resolves before another.
- // See https://github.com/dotnet/maui/issues/31939
- internal BindableProperty[] Dependencies { get; private set; }
-
- ///
- /// Registers a dependency on another BindableProperty. When this property's value is retrieved,
- /// if the dependency has a binding that hasn't resolved yet (value is null), return null.
- ///
- internal void DependsOn(params BindableProperty[] dependencies)
- {
- Dependencies = dependencies;
- }
-
/// Creates a new instance of the BindableProperty class.
/// The name of the BindableProperty.
/// The type of the property.
diff --git a/src/Controls/src/Core/Button/Button.cs b/src/Controls/src/Core/Button/Button.cs
index cc87be1f60c8..39caa9c6a301 100644
--- a/src/Controls/src/Core/Button/Button.cs
+++ b/src/Controls/src/Core/Button/Button.cs
@@ -465,7 +465,7 @@ void ICommandElement.CanExecuteChanged(object sender, EventArgs e) =>
RefreshIsEnabledProperty();
protected override bool IsEnabledCore =>
- base.IsEnabledCore && CommandElement.GetCanExecute(this, CommandProperty);
+ base.IsEnabledCore && CommandElement.GetCanExecute(this);
bool _wasImageLoading;
diff --git a/src/Controls/src/Core/Button/ButtonElement.cs b/src/Controls/src/Core/Button/ButtonElement.cs
index 99869fa7c3d9..00f5c3ff71d1 100644
--- a/src/Controls/src/Core/Button/ButtonElement.cs
+++ b/src/Controls/src/Core/Button/ButtonElement.cs
@@ -10,27 +10,16 @@ static class ButtonElement
///
/// The backing store for the bindable property.
///
- public static readonly BindableProperty CommandProperty;
+ public static readonly BindableProperty CommandProperty = BindableProperty.Create(
+ nameof(IButtonElement.Command), typeof(ICommand), typeof(IButtonElement), null,
+ propertyChanging: CommandElement.OnCommandChanging, propertyChanged: CommandElement.OnCommandChanged);
///
/// The backing store for the bindable property.
///
- public static readonly BindableProperty CommandParameterProperty;
-
- static ButtonElement()
- {
- CommandParameterProperty = BindableProperty.Create(
- nameof(IButtonElement.CommandParameter), typeof(object), typeof(IButtonElement), null,
- propertyChanged: CommandElement.OnCommandParameterChanged);
-
- CommandProperty = BindableProperty.Create(
- nameof(IButtonElement.Command), typeof(ICommand), typeof(IButtonElement), null,
- propertyChanging: CommandElement.OnCommandChanging, propertyChanged: CommandElement.OnCommandChanged);
-
- // Register dependency: Command depends on CommandParameter for CanExecute evaluation
- // See https://github.com/dotnet/maui/issues/31939
- CommandProperty.DependsOn(CommandParameterProperty);
- }
+ public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(
+ nameof(IButtonElement.CommandParameter), typeof(object), typeof(IButtonElement), null,
+ propertyChanged: CommandElement.OnCommandParameterChanged);
///
/// The string identifier for the pressed visual state of this control.
diff --git a/src/Controls/src/Core/Cells/TextCell.cs b/src/Controls/src/Core/Cells/TextCell.cs
index 0b22d618a9f1..fb976ead8e4b 100644
--- a/src/Controls/src/Core/Cells/TextCell.cs
+++ b/src/Controls/src/Core/Cells/TextCell.cs
@@ -11,28 +11,19 @@ namespace Microsoft.Maui.Controls
public class TextCell : Cell, ICommandElement
{
/// Bindable property for .
- public static readonly BindableProperty CommandProperty;
+ public static readonly BindableProperty CommandProperty =
+ BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(TextCell),
+ propertyChanging: CommandElement.OnCommandChanging,
+ propertyChanged: CommandElement.OnCommandChanged);
/// Bindable property for .
- public static readonly BindableProperty CommandParameterProperty;
-
- static TextCell()
- {
- CommandParameterProperty = BindableProperty.Create(nameof(CommandParameter),
+ public static readonly BindableProperty CommandParameterProperty =
+ BindableProperty.Create(nameof(CommandParameter),
typeof(object),
typeof(TextCell),
null,
propertyChanged: CommandElement.OnCommandParameterChanged);
- CommandProperty = BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(TextCell),
- propertyChanging: CommandElement.OnCommandChanging,
- propertyChanged: CommandElement.OnCommandChanged);
-
- // Register dependency: Command depends on CommandParameter for CanExecute evaluation
- // See https://github.com/dotnet/maui/issues/31939
- CommandProperty.DependsOn(CommandParameterProperty);
- }
-
/// Bindable property for .
public static readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(TextCell), default(string));
@@ -104,7 +95,10 @@ protected internal override void OnTapped()
void ICommandElement.CanExecuteChanged(object sender, EventArgs eventArgs)
{
- IsEnabled = CommandElement.GetCanExecute(this, CommandProperty);
+ if (Command is null)
+ return;
+
+ IsEnabled = Command.CanExecute(CommandParameter);
}
WeakCommandSubscription ICommandElement.CleanupTracker { get; set; }
diff --git a/src/Controls/src/Core/CheckBox/CheckBox.Mapper.cs b/src/Controls/src/Core/CheckBox/CheckBox.Mapper.cs
index 4453b3727b7c..d777dd9c45f1 100644
--- a/src/Controls/src/Core/CheckBox/CheckBox.Mapper.cs
+++ b/src/Controls/src/Core/CheckBox/CheckBox.Mapper.cs
@@ -9,13 +9,7 @@ namespace Microsoft.Maui.Controls
{
public partial class CheckBox
{
- static CheckBox()
- {
- // Register dependency: Command depends on CommandParameter for CanExecute evaluation
- // See https://github.com/dotnet/maui/issues/31939
- CommandProperty.DependsOn(CommandParameterProperty);
- RemapForControls();
- }
+ static CheckBox() => RemapForControls();
private new static void RemapForControls()
{
diff --git a/src/Controls/src/Core/CheckBox/CheckBox.cs b/src/Controls/src/Core/CheckBox/CheckBox.cs
index 60f6d77b23c6..f13ab47ff78e 100644
--- a/src/Controls/src/Core/CheckBox/CheckBox.cs
+++ b/src/Controls/src/Core/CheckBox/CheckBox.cs
@@ -145,7 +145,7 @@ void ICommandElement.CanExecuteChanged(object sender, EventArgs e) =>
RefreshIsEnabledProperty();
protected override bool IsEnabledCore =>
- base.IsEnabledCore && CommandElement.GetCanExecute(this, CommandProperty);
+ base.IsEnabledCore && CommandElement.GetCanExecute(this);
public Paint Foreground => Color?.AsPaint();
bool ICheckBox.IsChecked
diff --git a/src/Controls/src/Core/CommandElement.cs b/src/Controls/src/Core/CommandElement.cs
index 767badd9fbfa..6a4a70de5519 100644
--- a/src/Controls/src/Core/CommandElement.cs
+++ b/src/Controls/src/Core/CommandElement.cs
@@ -37,23 +37,11 @@ public static void OnCommandParameterChanged(BindableObject bo, object o, object
commandElement.CanExecuteChanged(bo, EventArgs.Empty);
}
- public static bool GetCanExecute(ICommandElement commandElement, BindableProperty? commandProperty = null)
+ public static bool GetCanExecute(ICommandElement commandElement)
{
if (commandElement.Command == null)
return true;
- // If there are dependencies (e.g., CommandParameter for Command), force their bindings
- // to apply before evaluating CanExecute. This fixes timing issues where Command binding
- // resolves before CommandParameter binding during reparenting.
- // See https://github.com/dotnet/maui/issues/31939
- if (commandProperty?.Dependencies is not null && commandElement is BindableObject bo)
- {
- foreach (var dependency in commandProperty.Dependencies)
- {
- bo.ForceBindingApply(dependency);
- }
- }
-
return commandElement.Command.CanExecute(commandElement.CommandParameter);
}
}
diff --git a/src/Controls/src/Core/ImageButton/ImageButton.cs b/src/Controls/src/Core/ImageButton/ImageButton.cs
index 6262771ca9e3..73ecfed62810 100644
--- a/src/Controls/src/Core/ImageButton/ImageButton.cs
+++ b/src/Controls/src/Core/ImageButton/ImageButton.cs
@@ -244,7 +244,7 @@ bool IImageElement.IsAnimationPlaying
bool IImageController.GetLoadAsAnimation() => false;
protected override bool IsEnabledCore =>
- base.IsEnabledCore && CommandElement.GetCanExecute(this, CommandProperty);
+ base.IsEnabledCore && CommandElement.GetCanExecute(this);
void ICommandElement.CanExecuteChanged(object sender, EventArgs e) =>
RefreshIsEnabledProperty();
diff --git a/src/Controls/src/Core/Menu/MenuItem.cs b/src/Controls/src/Core/Menu/MenuItem.cs
index 59577062ed00..fd7f8260b0fc 100644
--- a/src/Controls/src/Core/Menu/MenuItem.cs
+++ b/src/Controls/src/Core/Menu/MenuItem.cs
@@ -12,26 +12,15 @@ namespace Microsoft.Maui.Controls
public partial class MenuItem : BaseMenuItem, IMenuItemController, ICommandElement, IMenuElement, IPropertyPropagationController
{
/// Bindable property for .
- public static readonly BindableProperty CommandProperty;
+ public static readonly BindableProperty CommandProperty = BindableProperty.Create(
+ nameof(Command), typeof(ICommand), typeof(MenuItem), null,
+ propertyChanging: CommandElement.OnCommandChanging,
+ propertyChanged: CommandElement.OnCommandChanged);
/// Bindable property for .
- public static readonly BindableProperty CommandParameterProperty;
-
- static MenuItem()
- {
- CommandParameterProperty = BindableProperty.Create(
- nameof(CommandParameter), typeof(object), typeof(MenuItem), null,
- propertyChanged: CommandElement.OnCommandParameterChanged);
-
- CommandProperty = BindableProperty.Create(
- nameof(Command), typeof(ICommand), typeof(MenuItem), null,
- propertyChanging: CommandElement.OnCommandChanging,
- propertyChanged: CommandElement.OnCommandChanged);
-
- // Register dependency: Command depends on CommandParameter for CanExecute evaluation
- // See https://github.com/dotnet/maui/issues/31939
- CommandProperty.DependsOn(CommandParameterProperty);
- }
+ public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(
+ nameof(CommandParameter), typeof(object), typeof(MenuItem), null,
+ propertyChanged: CommandElement.OnCommandParameterChanged);
/// Bindable property for .
public static readonly BindableProperty IsDestructiveProperty = BindableProperty.Create(nameof(IsDestructive), typeof(bool), typeof(MenuItem), false);
@@ -133,7 +122,7 @@ static object CoerceIsEnabledProperty(BindableObject bindable, object value)
return false;
}
- var canExecute = CommandElement.GetCanExecute(menuItem, CommandProperty);
+ var canExecute = CommandElement.GetCanExecute(menuItem);
if (!canExecute)
{
return false;
diff --git a/src/Controls/src/Core/RefreshView/RefreshView.Mapper.cs b/src/Controls/src/Core/RefreshView/RefreshView.Mapper.cs
index 038f56fb9277..41329181883d 100644
--- a/src/Controls/src/Core/RefreshView/RefreshView.Mapper.cs
+++ b/src/Controls/src/Core/RefreshView/RefreshView.Mapper.cs
@@ -6,13 +6,6 @@ namespace Microsoft.Maui.Controls
{
public partial class RefreshView
{
- static RefreshView()
- {
- // Register dependency: Command depends on CommandParameter for CanExecute evaluation
- // See https://github.com/dotnet/maui/issues/31939
- CommandProperty.DependsOn(CommandParameterProperty);
- }
-
internal static new void RemapForControls()
{
// Adjust the mappings to preserve Controls.RefreshView legacy behaviors
diff --git a/src/Controls/src/Core/RefreshView/RefreshView.cs b/src/Controls/src/Core/RefreshView/RefreshView.cs
index 564806dbeccb..ff2de69c5e81 100644
--- a/src/Controls/src/Core/RefreshView/RefreshView.cs
+++ b/src/Controls/src/Core/RefreshView/RefreshView.cs
@@ -121,7 +121,7 @@ static object CoerceIsRefreshEnabledProperty(BindableObject bindable, object val
if (bindable is RefreshView refreshView)
{
refreshView._isRefreshEnabledExplicit = (bool)value;
- return refreshView._isRefreshEnabledExplicit && CommandElement.GetCanExecute(refreshView, CommandProperty);
+ return refreshView._isRefreshEnabledExplicit && CommandElement.GetCanExecute(refreshView);
}
return false;
diff --git a/src/Controls/src/Core/SearchBar/SearchBar.Mapper.cs b/src/Controls/src/Core/SearchBar/SearchBar.Mapper.cs
index 3278ba05f637..0bfd6cc6806f 100644
--- a/src/Controls/src/Core/SearchBar/SearchBar.Mapper.cs
+++ b/src/Controls/src/Core/SearchBar/SearchBar.Mapper.cs
@@ -6,13 +6,6 @@ namespace Microsoft.Maui.Controls
{
public partial class SearchBar
{
- static SearchBar()
- {
- // Register dependency: SearchCommand depends on SearchCommandParameter for CanExecute evaluation
- // See https://github.com/dotnet/maui/issues/31939
- SearchCommandProperty.DependsOn(SearchCommandParameterProperty);
- }
-
internal static new void RemapForControls()
{
// Adjust the mappings to preserve Controls.SearchBar legacy behaviors
diff --git a/src/Controls/src/Core/SearchBar/SearchBar.cs b/src/Controls/src/Core/SearchBar/SearchBar.cs
index 2b7e09dd629a..fbc3b649e5f3 100644
--- a/src/Controls/src/Core/SearchBar/SearchBar.cs
+++ b/src/Controls/src/Core/SearchBar/SearchBar.cs
@@ -164,7 +164,7 @@ private void OnRequestedThemeChanged(object sender, AppThemeChangedEventArgs e)
object ICommandElement.CommandParameter => SearchCommandParameter;
protected override bool IsEnabledCore =>
- base.IsEnabledCore && CommandElement.GetCanExecute(this, SearchCommandProperty);
+ base.IsEnabledCore && CommandElement.GetCanExecute(this);
void ICommandElement.CanExecuteChanged(object sender, EventArgs e) =>
RefreshIsEnabledProperty();
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue13537.xaml.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue13537.xaml.cs
index 3674f3b4b8b4..474e6c9b7482 100644
--- a/src/Controls/tests/TestCases.HostApp/Issues/Issue13537.xaml.cs
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue13537.xaml.cs
@@ -15,7 +15,6 @@ public Issue13537()
{
InitializeComponent();
Routing.RegisterRoute("NewPage", typeof(Issue13537InnerPage));
- Application.Current.Windows[0].Page = new NavigationPage(new Issue13537HomePage());
}
}
diff --git a/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/ShouldFlyoutBeVisibleAfterMaximizingWindow.png b/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/ShouldFlyoutBeVisibleAfterMaximizingWindow.png
index b07c35c40a21..742c7c90c6cf 100644
Binary files a/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/ShouldFlyoutBeVisibleAfterMaximizingWindow.png and b/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/ShouldFlyoutBeVisibleAfterMaximizingWindow.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ShouldFlyoutBeVisibleAfterMaximizingWindow.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ShouldFlyoutBeVisibleAfterMaximizingWindow.png
index 8af59788dc1d..715a5e019386 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ShouldFlyoutBeVisibleAfterMaximizingWindow.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ShouldFlyoutBeVisibleAfterMaximizingWindow.png differ
diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui31939.xaml b/src/Controls/tests/Xaml.UnitTests/Issues/Maui31939.xaml
deleted file mode 100644
index 80a671ace710..000000000000
--- a/src/Controls/tests/Xaml.UnitTests/Issues/Maui31939.xaml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui31939.xaml.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Maui31939.xaml.cs
deleted file mode 100644
index 91d712d9a975..000000000000
--- a/src/Controls/tests/Xaml.UnitTests/Issues/Maui31939.xaml.cs
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-using System;
-using System.Collections.Generic;
-using System.Windows.Input;
-using Microsoft.Maui.Controls.Core.UnitTests;
-using Microsoft.Maui.Dispatching;
-using Microsoft.Maui.UnitTests;
-using Xunit;
-
-namespace Microsoft.Maui.Controls.Xaml.UnitTests;
-
-// Regression test for https://github.com/dotnet/maui/issues/31939
-// CommandParameter TemplateBinding is lost during ControlTemplate reparenting,
-// causing CanExecute to be called with null parameter before CommandParameter is resolved.
-public partial class Maui31939 : ContentPage
-{
- public Maui31939() => InitializeComponent();
-
- [Collection("Issue")]
- public class Tests : IDisposable
- {
- public Tests()
- {
- Application.SetCurrentApplication(new MockApplication());
- DispatcherProvider.SetCurrent(new DispatcherProviderStub());
- }
-
- public void Dispose()
- {
- DispatcherProvider.SetCurrent(null);
- }
-
- [Theory]
- [XamlInflatorData]
- internal void CommandParameterTemplateBindingShouldNotBeNullWhenCanExecuteIsCalled(XamlInflator inflator)
- {
- // Verify initial template binding works correctly: CommandParameter should be resolved
- // before CanExecute is called when template is first applied.
- var viewModel = new Maui31939ViewModel();
- var page = new Maui31939(inflator);
- page.BindingContext = viewModel;
-
- Assert.False(viewModel.CanExecuteCalledWithNullParameter);
-
- var button = (Button)page.TestControl.GetTemplateChild("TestButton");
- Assert.NotNull(button);
- Assert.Equal("TestValue", button.CommandParameter);
- Assert.NotNull(button.Command);
- }
-
- [Theory]
- [XamlInflatorData]
- internal void CommandParameterTemplateBindingWorksAfterReparenting(XamlInflator inflator)
- {
- // Regression test: when elements are reparented within a ControlTemplate, bindings
- // are re-applied. Due to the async void ApplyRelativeSourceBinding path, Command may
- // be applied before CommandParameter, causing CanExecute(null) to be called.
- var viewModel = new Maui31939ViewModel();
- var page = new Maui31939(inflator);
- page.BindingContext = viewModel;
-
- var grid = (Grid)page.TestControl.GetTemplateChild("MainLayout");
- var button = (Button)page.TestControl.GetTemplateChild("TestButton");
-
- Assert.NotNull(button);
- Assert.Equal("TestValue", button.CommandParameter);
-
- // Simulate reparenting operation (like the issue describes)
- viewModel.ResetCanExecuteTracking();
- grid.Children.Clear();
- grid.Children.Add(button);
-
- // After reparenting, CommandParameter should still be bound correctly
- // and CanExecute should not have been called with null
- Assert.False(viewModel.CanExecuteCalledWithNullParameter);
- Assert.Equal("TestValue", button.CommandParameter);
- }
- }
-}
-
-///
-/// Custom control with Command and CommandParameter bindable properties
-/// for testing TemplateBinding scenarios.
-///
-public class Maui31939Control : ContentView
-{
- public static readonly BindableProperty TestCommandProperty =
- BindableProperty.Create(nameof(TestCommand), typeof(ICommand), typeof(Maui31939Control), null);
-
- public static readonly BindableProperty TestCommandParameterProperty =
- BindableProperty.Create(nameof(TestCommandParameter), typeof(object), typeof(Maui31939Control), null);
-
- public ICommand TestCommand
- {
- get => (ICommand)GetValue(TestCommandProperty);
- set => SetValue(TestCommandProperty, value);
- }
-
- public object TestCommandParameter
- {
- get => GetValue(TestCommandParameterProperty);
- set => SetValue(TestCommandParameterProperty, value);
- }
-}
-
-///
-/// ViewModel with a Command that tracks whether CanExecute was called with null parameter.
-/// This simulates the real-world scenario where apps have commands that expect non-null parameters.
-///
-public class Maui31939ViewModel
-{
- public bool CanExecuteCalledWithNullParameter { get; private set; }
- private bool _isEnabled = true;
-
- public Maui31939ViewModel()
- {
- TestCommand = new Maui31939Command(
- execute: parameter => { /* Do nothing */ },
- canExecute: parameter =>
- {
- if (parameter is null)
- {
- CanExecuteCalledWithNullParameter = true;
- }
- return _isEnabled;
- });
- }
-
- public ICommand TestCommand { get; }
-
- public void ResetCanExecuteTracking()
- {
- CanExecuteCalledWithNullParameter = false;
- }
-}
-
-///
-/// Custom command implementation that allows tracking CanExecute calls.
-///
-public class Maui31939Command : ICommand
-{
- private readonly Action