Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/Controls/src/Core/Slider/Slider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@ namespace Microsoft.Maui.Controls
public partial class Slider : View, ISliderController, IElementConfiguration<Slider>, ISlider
{
/// <summary>Bindable property for <see cref="Minimum"/>.</summary>
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);
Copy link
Contributor

Choose a reason for hiding this comment

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

what if newMinimum is higher than Max ?

});

/// <summary>Bindable property for <see cref="Maximum"/>.</summary>
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);
});

/// <summary>Bindable property for <see cref="Value"/>.</summary>
Expand Down
53 changes: 53 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue32903.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Issues.Issue32903"
Title="Issue 32903">
<VerticalStackLayout Padding="20" Spacing="10">
<Label Text="Slider Binding Initialization Order Test"
FontSize="18"
FontAttributes="Bold"
HorizontalOptions="Center"
Margin="0,0,0,20"/>

<Label Text="Expected: Value should be 50 (not 10)"
FontSize="14"
Margin="0,0,0,10"/>

<Label x:Name="ValueLabel"
AutomationId="ValueLabel"
Text="{Binding Value, StringFormat='Current Value: {0:F0}'}"
FontSize="16"
HorizontalOptions="Center"
Margin="0,0,0,10"/>

<Label Text="{Binding ValueMin, StringFormat='Minimum: {0:F0}'}"
FontSize="14"
HorizontalOptions="Center"/>

<Label Text="{Binding ValueMax, StringFormat='Maximum: {0:F0}'}"
FontSize="14"
HorizontalOptions="Center"/>

<Slider x:Name="TestSlider"
AutomationId="TestSlider"
Minimum="{Binding ValueMin}"
Maximum="{Binding ValueMax}"
Value="{Binding Value, Mode=TwoWay}"
Margin="0,20,0,0"/>

<Label x:Name="ActualValueLabel"
AutomationId="ActualValueLabel"
Text="{Binding Source={x:Reference TestSlider}, Path=Value, StringFormat='Slider.Value: {0:F0}'}"
FontSize="16"
HorizontalOptions="Center"
TextColor="Red"
Margin="0,10,0,0"/>

<Label Text="Bug: If Value shows 10 instead of 50, the binding order issue exists"
FontSize="12"
HorizontalOptions="Center"
TextColor="Gray"
Margin="0,10,0,0"/>
</VerticalStackLayout>
</ContentPage>
82 changes: 82 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue32903.xaml.cs
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
Original file line number Diff line number Diff line change
@@ -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.");
}
}
}
Loading