diff --git a/src/Controls/src/Core/Slider/Slider.cs b/src/Controls/src/Core/Slider/Slider.cs index 5575636cf511..ec73d6cd7bb6 100644 --- a/src/Controls/src/Core/Slider/Slider.cs +++ b/src/Controls/src/Core/Slider/Slider.cs @@ -11,19 +11,19 @@ namespace Microsoft.Maui.Controls public partial class Slider : View, ISliderController, IElementConfiguration, ISlider { /// Bindable property for . - public static readonly BindableProperty MinimumProperty = BindableProperty.Create(nameof(Minimum), typeof(double), typeof(Slider), 0d, coerceValue: (bindable, value) => + public static readonly BindableProperty MinimumProperty = BindableProperty.Create(nameof(Minimum), typeof(double), typeof(Slider), 0d, propertyChanged: (bindable, oldValue, newValue) => { var slider = (Slider)bindable; - slider.Value = slider.Value.Clamp((double)value, slider.Maximum); - return value; + // Re-coerce Value to ensure it stays within valid range + slider.Value = slider.Value.Clamp((double)newValue, slider.Maximum); }); /// Bindable property for . - public static readonly BindableProperty MaximumProperty = BindableProperty.Create(nameof(Maximum), typeof(double), typeof(Slider), 1d, coerceValue: (bindable, value) => + public static readonly BindableProperty MaximumProperty = BindableProperty.Create(nameof(Maximum), typeof(double), typeof(Slider), 1d, propertyChanged: (bindable, oldValue, newValue) => { var slider = (Slider)bindable; - slider.Value = slider.Value.Clamp(slider.Minimum, (double)value); - return value; + // Re-coerce Value to ensure it stays within valid range + slider.Value = slider.Value.Clamp(slider.Minimum, (double)newValue); }); /// Bindable property for . diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue32903.xaml b/src/Controls/tests/TestCases.HostApp/Issues/Issue32903.xaml new file mode 100644 index 000000000000..aeec94425127 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue32903.xaml @@ -0,0 +1,53 @@ + + + + + diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue32903.xaml.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue32903.xaml.cs new file mode 100644 index 000000000000..f21087e17d26 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue32903.xaml.cs @@ -0,0 +1,82 @@ +using System.ComponentModel; + +namespace Maui.Controls.Sample.Issues +{ + [Issue(IssueTracker.Github, 32903, "Slider Binding Initialization Order Causes Incorrect Value Assignment in XAML", PlatformAffected.All)] + public partial class Issue32903 : ContentPage + { + public Issue32903() + { + InitializeComponent(); + + var viewModel = new SliderViewModel(); + BindingContext = viewModel; + + // Log initial values for debugging + Console.WriteLine($"[Issue32903] ViewModel initialized - Min: {viewModel.ValueMin}, Max: {viewModel.ValueMax}, Value: {viewModel.Value}"); + + // Use Dispatcher to log actual slider values after bindings are applied + Dispatcher.DispatchDelayed(TimeSpan.FromMilliseconds(100), () => + { + Console.WriteLine($"[Issue32903] After bindings - Slider.Minimum: {TestSlider.Minimum}, Slider.Maximum: {TestSlider.Maximum}, Slider.Value: {TestSlider.Value}"); + Console.WriteLine($"[Issue32903] ViewModel.Value: {viewModel.Value}"); + }); + } + } + + public class SliderViewModel : INotifyPropertyChanged + { + private double _valueMin = 10; + private double _valueMax = 100; + private double _value = 50; + + public double ValueMin + { + get => _valueMin; + set + { + if (_valueMin != value) + { + _valueMin = value; + Console.WriteLine($"[SliderViewModel] ValueMin set to: {value}"); + OnPropertyChanged(nameof(ValueMin)); + } + } + } + + public double ValueMax + { + get => _valueMax; + set + { + if (_valueMax != value) + { + _valueMax = value; + Console.WriteLine($"[SliderViewModel] ValueMax set to: {value}"); + OnPropertyChanged(nameof(ValueMax)); + } + } + } + + public double Value + { + get => _value; + set + { + if (_value != value) + { + Console.WriteLine($"[SliderViewModel] Value changing from {_value} to {value}"); + _value = value; + OnPropertyChanged(nameof(Value)); + } + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue32903.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue32903.cs new file mode 100644 index 000000000000..ce4b4178e179 --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue32903.cs @@ -0,0 +1,46 @@ +using System; +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues +{ + public class Issue32903 : _IssuesUITest + { + public Issue32903(TestDevice device) : base(device) { } + + public override string Issue => "Slider Binding Initialization Order Causes Incorrect Value Assignment in XAML"; + + [Test] + [Category(UITestCategories.Slider)] + public void SliderValueShouldInitializeCorrectlyWithBindings() + { + // Wait for the page to load + App.WaitForElement("ValueLabel"); + + // Get the actual slider value displayed + var actualValueText = App.FindElement("ActualValueLabel").GetText(); + + // The bug: Value should be 50, but due to binding order it becomes 10 (or 1) + // Extract the numeric value from "Slider.Value: X" + if (actualValueText == null) + { + Assert.Fail("ActualValueLabel text is null"); + return; + } + + var valueStr = actualValueText.Replace("Slider.Value:", string.Empty, StringComparison.Ordinal).Trim(); + var actualValue = double.Parse(valueStr); + + // The bug manifests as Value being clamped to Minimum (10) or default Maximum (1) + // Expected: 50 + // Actual (with bug): 10 or 1 + Console.WriteLine($"[Test] Slider.Value is: {actualValue}"); + + // This test will FAIL with the bug (Value will be 10 or 1 instead of 50) + // After the fix, this test should PASS (Value will be 50) + Assert.That(actualValue, Is.EqualTo(50).Within(0.1), + $"Slider Value should initialize to 50, but was {actualValue}. This indicates the binding initialization order bug."); + } + } +}