From dd5d7a2653dcce3aa8006c9a029204678c2d912c Mon Sep 17 00:00:00 2001 From: Lamparter <71598437+Lamparter@users.noreply.github.com> Date: Sun, 1 Dec 2024 08:49:42 +0000 Subject: [PATCH 1/9] Fix issue --- components/RangeSelector/src/RangeSelector.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/RangeSelector/src/RangeSelector.cs b/components/RangeSelector/src/RangeSelector.cs index 7cf3bdbb..0b527284 100644 --- a/components/RangeSelector/src/RangeSelector.cs +++ b/components/RangeSelector/src/RangeSelector.cs @@ -34,7 +34,6 @@ public partial class RangeSelector : Control internal const string MinPressedState = "MinPressed"; internal const string MaxPressedState = "MaxPressed"; - private const double Epsilon = 0.01; private const double DefaultMinimum = 0.0; private const double DefaultMaximum = 10.0; private const double DefaultStepFrequency = 1; @@ -168,7 +167,7 @@ private void VerifyValues() if (Minimum == Maximum) { - Maximum += Epsilon; + throw new ArgumentException("Maximum and Minimum values cannot be equal."); } if (!_maxSet) From c28bc00151f247142eda16cce5b9cc82249f3b6b Mon Sep 17 00:00:00 2001 From: Arlo Date: Fri, 12 Dec 2025 19:24:39 -0600 Subject: [PATCH 2/9] Remove Minimum/Maximum Epsilon delta property and usage, fixed issue where Maximum values less than Minimum would erroneously overwrite Minimum --- .../src/RangeSelector.Properties.cs | 30 ------------------- components/RangeSelector/src/RangeSelector.cs | 8 +++-- 2 files changed, 5 insertions(+), 33 deletions(-) diff --git a/components/RangeSelector/src/RangeSelector.Properties.cs b/components/RangeSelector/src/RangeSelector.Properties.cs index 6377df37..b25112d3 100644 --- a/components/RangeSelector/src/RangeSelector.Properties.cs +++ b/components/RangeSelector/src/RangeSelector.Properties.cs @@ -131,21 +131,6 @@ private static void MinimumChangedCallback(DependencyObject d, DependencyPropert var newValue = (double)e.NewValue; var oldValue = (double)e.OldValue; - if (rangeSelector.Maximum < newValue) - { - rangeSelector.Maximum = newValue + Epsilon; - } - - if (rangeSelector.RangeStart < newValue) - { - rangeSelector.RangeStart = newValue; - } - - if (rangeSelector.RangeEnd < newValue) - { - rangeSelector.RangeEnd = newValue; - } - if (newValue != oldValue) { rangeSelector.SyncThumbs(); @@ -164,21 +149,6 @@ private static void MaximumChangedCallback(DependencyObject d, DependencyPropert var newValue = (double)e.NewValue; var oldValue = (double)e.OldValue; - if (rangeSelector.Minimum > newValue) - { - rangeSelector.Minimum = newValue - Epsilon; - } - - if (rangeSelector.RangeEnd > newValue) - { - rangeSelector.RangeEnd = newValue; - } - - if (rangeSelector.RangeStart > newValue) - { - rangeSelector.RangeStart = newValue; - } - if (newValue != oldValue) { rangeSelector.SyncThumbs(); diff --git a/components/RangeSelector/src/RangeSelector.cs b/components/RangeSelector/src/RangeSelector.cs index 0b527284..8b1402ac 100644 --- a/components/RangeSelector/src/RangeSelector.cs +++ b/components/RangeSelector/src/RangeSelector.cs @@ -161,13 +161,13 @@ private void VerifyValues() { if (Minimum > Maximum) { - Minimum = Maximum; - Maximum = Maximum; + Maximum = Minimum; } if (Minimum == Maximum) { - throw new ArgumentException("Maximum and Minimum values cannot be equal."); + RangeStart = Minimum; + RangeEnd = Maximum; } if (!_maxSet) @@ -241,6 +241,8 @@ private void SyncThumbs(bool fromMinKeyDown = false, bool fromMaxKeyDown = false return; } + VerifyValues(); + var relativeLeft = ((RangeStart - Minimum) / (Maximum - Minimum)) * DragWidth(); var relativeRight = ((RangeEnd - Minimum) / (Maximum - Minimum)) * DragWidth(); From 8b092b8379eb0e15cec71033ed19aa778a5815da Mon Sep 17 00:00:00 2001 From: Arlo Date: Fri, 12 Dec 2025 19:49:08 -0600 Subject: [PATCH 3/9] Fixed issue where Minimum values greater than Maximum would erroneously overwrite minimum, cleaned up duplicate logic Amends c28bc00151f247142eda16cce5b9cc82249f3b6b --- .../src/RangeSelector.Properties.cs | 44 ------------------- components/RangeSelector/src/RangeSelector.cs | 3 +- tooling | 2 +- 3 files changed, 3 insertions(+), 46 deletions(-) diff --git a/components/RangeSelector/src/RangeSelector.Properties.cs b/components/RangeSelector/src/RangeSelector.Properties.cs index b25112d3..80395dfc 100644 --- a/components/RangeSelector/src/RangeSelector.Properties.cs +++ b/components/RangeSelector/src/RangeSelector.Properties.cs @@ -171,29 +171,7 @@ private static void RangeMinChangedCallback(DependencyObject d, DependencyProper return; } - var newValue = (double)e.NewValue; rangeSelector.RangeMinToStepFrequency(); - - if (rangeSelector._valuesAssigned) - { - if (newValue < rangeSelector.Minimum) - { - rangeSelector.RangeStart = rangeSelector.Minimum; - } - else if (newValue > rangeSelector.Maximum) - { - rangeSelector.RangeStart = rangeSelector.Maximum; - } - - rangeSelector.SyncActiveRectangle(); - - // If the new value is greater than the old max, move the max also - if (newValue > rangeSelector.RangeEnd) - { - rangeSelector.RangeEnd = newValue; - } - } - rangeSelector.SyncThumbs(); } @@ -213,29 +191,7 @@ private static void RangeMaxChangedCallback(DependencyObject d, DependencyProper return; } - var newValue = (double)e.NewValue; rangeSelector.RangeMaxToStepFrequency(); - - if (rangeSelector._valuesAssigned) - { - if (newValue < rangeSelector.Minimum) - { - rangeSelector.RangeEnd = rangeSelector.Minimum; - } - else if (newValue > rangeSelector.Maximum) - { - rangeSelector.RangeEnd = rangeSelector.Maximum; - } - - rangeSelector.SyncActiveRectangle(); - - // If the new max is less than the old minimum then move the minimum - if (newValue < rangeSelector.RangeStart) - { - rangeSelector.RangeStart = newValue; - } - } - rangeSelector.SyncThumbs(); } } diff --git a/components/RangeSelector/src/RangeSelector.cs b/components/RangeSelector/src/RangeSelector.cs index 8b1402ac..6b7de792 100644 --- a/components/RangeSelector/src/RangeSelector.cs +++ b/components/RangeSelector/src/RangeSelector.cs @@ -161,7 +161,8 @@ private void VerifyValues() { if (Minimum > Maximum) { - Maximum = Minimum; + Maximum = Math.Min(Minimum, Maximum); + Minimum = Math.Min(Minimum, Maximum); } if (Minimum == Maximum) diff --git a/tooling b/tooling index c137363c..cdf9aa9a 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit c137363ca757a8b69576fed31bbe80115ffba765 +Subproject commit cdf9aa9a8dfde68ec2dfb7c3ae278b0cdbb60459 From 82b58563166fdf60780b03a23d71c9448fc07ac4 Mon Sep 17 00:00:00 2001 From: Arlo Date: Thu, 18 Dec 2025 14:36:28 -0600 Subject: [PATCH 4/9] Ensure RangeStart/RangeEnd stay on valid steps when constrained by each other MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When RangeStart is set to a value that snaps beyond RangeEnd (or vice versa), the control now snaps to the nearest valid step that respects the constraint, rather than being clamped to a non-step value. Problem: With StepFrequency=40, RangeStart=40, RangeEnd=60, setting RangeStart=60 would: 1. Snap 60 → 80 (nearest step via Math.Round) 2. VerifyValues() clamps 80 → 60 (to not exceed RangeEnd) 3. Result: RangeStart=60, which is not on a valid step (valid steps: 0, 40, 80, 100) Solution: RangeMinToStepFrequency and RangeMaxToStepFrequency now handle the range constraint directly after step snapping: • RangeMinToStepFrequency: If snapped value exceeds RangeEnd, snap down to previous valid step using Math.Floor • RangeMaxToStepFrequency: If snapped value is below RangeStart, snap up to next valid step using Math.Ceiling This keeps MoveToStepFrequency as a pure step-snapping utility, while the range constraint logic lives in the methods that understand the relationship between the two range values. Note: The interaction between VerifyValues() (bounds clamping) and step frequency snapping remains complex and should be refactored in a future PR to clarify responsibilities. --- components/RangeSelector/src/RangeSelector.cs | 20 +++++++++++++++++-- .../RangeSelector/tests/Test_RangeSelector.cs | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/components/RangeSelector/src/RangeSelector.cs b/components/RangeSelector/src/RangeSelector.cs index 6b7de792..cea11a7e 100644 --- a/components/RangeSelector/src/RangeSelector.cs +++ b/components/RangeSelector/src/RangeSelector.cs @@ -209,12 +209,28 @@ private void VerifyValues() private void RangeMinToStepFrequency() { - RangeStart = MoveToStepFrequency(RangeStart); + var newValue = MoveToStepFrequency(RangeStart); + + // If snapped value exceeds RangeEnd, snap down to previous step + if (newValue > RangeEnd) + { + newValue = Minimum + (((int)Math.Floor((RangeEnd - Minimum) / StepFrequency)) * StepFrequency); + } + + RangeStart = newValue; } private void RangeMaxToStepFrequency() { - RangeEnd = MoveToStepFrequency(RangeEnd); + var newValue = MoveToStepFrequency(RangeEnd); + + // If snapped value is below RangeStart, snap up to next step + if (newValue < RangeStart) + { + newValue = Minimum + (((int)Math.Ceiling((RangeStart - Minimum) / StepFrequency)) * StepFrequency); + } + + RangeEnd = newValue; } private double MoveToStepFrequency(double rangeValue) diff --git a/components/RangeSelector/tests/Test_RangeSelector.cs b/components/RangeSelector/tests/Test_RangeSelector.cs index 20ac7559..17c114db 100644 --- a/components/RangeSelector/tests/Test_RangeSelector.cs +++ b/components/RangeSelector/tests/Test_RangeSelector.cs @@ -241,7 +241,7 @@ public Task SetRangeStart(double propInput, double expectedRangeStart, double ex [DataRow(30, 60, 70, 76, 100, 100)] [DataRow(40, 40, 60, 20, 0, 60)] [DataRow(40, 40, 60, 50, 40, 60)] - [DataRow(40, 40, 60, 60, 100, 100)] + [DataRow(40, 40, 60, 60, 40, 60)] // When RangeStart is set equal to RangeEnd but RangeEnd doesn't align with original RangeStart + StepFrequency public Task SetRangeStart_StepTest(double stepFrequency, double rangeStart, double rangeEnd, double propInput, double expectedRangeStart, double expectedRangeEnd) => SetProp(stepFrequency, 0, rangeStart, rangeEnd, 100, Property.RangeStart, propInput, stepFrequency, 0, expectedRangeStart, expectedRangeEnd, 100); From 3ff04f14a66468fce079ba0cb1be5bf51128f424 Mon Sep 17 00:00:00 2001 From: Arlo Date: Thu, 18 Dec 2025 15:00:42 -0600 Subject: [PATCH 5/9] Fixed order of operations for bound correction in VerifyValues --- components/RangeSelector/src/RangeSelector.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/components/RangeSelector/src/RangeSelector.cs b/components/RangeSelector/src/RangeSelector.cs index cea11a7e..a73f97a0 100644 --- a/components/RangeSelector/src/RangeSelector.cs +++ b/components/RangeSelector/src/RangeSelector.cs @@ -167,8 +167,9 @@ private void VerifyValues() if (Minimum == Maximum) { - RangeStart = Minimum; + // Move RangeEnd before RangeStart to avoid code paths which correct for RangeStart being greater than RangeEnd RangeEnd = Maximum; + RangeStart = Minimum; } if (!_maxSet) @@ -181,24 +182,26 @@ private void VerifyValues() RangeStart = Minimum; } - if (RangeStart < Minimum) + // RangeEnd bound correction + if (RangeEnd < Minimum) { - RangeStart = Minimum; + RangeEnd = Minimum; } - if (RangeEnd < Minimum) + if (RangeEnd > Maximum) { - RangeEnd = Minimum; + RangeEnd = Maximum; } - if (RangeStart > Maximum) + // RangeStart bound correction + if (RangeStart < Minimum) { - RangeStart = Maximum; + RangeStart = Minimum; } - if (RangeEnd > Maximum) + if (RangeStart > Maximum) { - RangeEnd = Maximum; + RangeStart = Maximum; } if (RangeEnd < RangeStart) From 4ada66ce9e237d91227c90de2240816d13b5452f Mon Sep 17 00:00:00 2001 From: Arlo Date: Thu, 18 Dec 2025 15:28:15 -0600 Subject: [PATCH 6/9] Fixed many tests to correct for intended vs observed behavior --- .../RangeSelector/tests/Test_RangeSelector.cs | 73 +++++++++---------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/components/RangeSelector/tests/Test_RangeSelector.cs b/components/RangeSelector/tests/Test_RangeSelector.cs index 17c114db..f97bc609 100644 --- a/components/RangeSelector/tests/Test_RangeSelector.cs +++ b/components/RangeSelector/tests/Test_RangeSelector.cs @@ -113,7 +113,7 @@ public Task Initialize_MinLtMax(double rangeStart, double rangeEnd, double expec // // Then // Minimum will be Maximum - // Maximum will be Maximum + 0.01 + // Maximum will be Maximum // If // Minimum >= Maximum @@ -121,8 +121,6 @@ public Task Initialize_MinLtMax(double rangeStart, double rangeEnd, double expec // RangeEnd > Maximum // // Then - // RangeStart will be Maximum + 0.01 - // Else // RangeStart will be Maximum // If @@ -130,37 +128,35 @@ public Task Initialize_MinLtMax(double rangeStart, double rangeEnd, double expec // RangeEnd > Maximum // // Then - // RangeEnd will be Maximum + 0.01 - // Else // RangeEnd will be Maximum // Input:Start End Expected:Start End [DataRow(0, 0, 0, 0)] - [DataRow(0, 10, 0, 0.01)] + [DataRow(0, 10, 0, 0)] [DataRow(0, -10, 0, 0)] [DataRow(-10, 0, 0, 0)] [DataRow(10, 0, 0, 0)] - [DataRow(10, 90, 0.01, 0.01)] - [DataRow(90, 10, 0.01, 0.01)] + [DataRow(10, 90, 0, 0)] + [DataRow(90, 10, 0, 0)] [DataRow(-90, -10, 0, 0)] [DataRow(-10, -90, 0, 0)] [DataRow(10, -10, 0, 0)] - [DataRow(-10, 10, 0, 0.01)] + [DataRow(-10, 10, 0, 0)] public Task Initialize_MinEqMax(double rangeStart, double rangeEnd, double expectedRangeStart, double expectedRangeEnd) - => Initialize(1, 0, rangeStart, rangeEnd, 0, 1, 0, expectedRangeStart, expectedRangeEnd, 0.01); + => Initialize(1, 0, rangeStart, rangeEnd, 0, 1, 0, expectedRangeStart, expectedRangeEnd, 0); [TestCategory("Initialize")] [TestMethod] - [DataRow(0, 100, 0, 0.01)] - [DataRow(10, 90, 0.01, 0.01)] - [DataRow(50, 50, 0.01, 0.01)] + [DataRow(0, 100, 0, 0)] + [DataRow(10, 90, 0, 0)] + [DataRow(50, 50, 0, 0)] [DataRow(0, 0, 0, 0)] - [DataRow(100, 100, 0.01, 0.01)] + [DataRow(100, 100, 0, 0)] - [DataRow(10, 100, 0.01, 0.01)] + [DataRow(10, 100, 0, 0)] - [DataRow(90, 10, 0.01, 0.01)] - [DataRow(100, 10, 0.01, 0.01)] + [DataRow(90, 10, 0, 0)] + [DataRow(100, 10, 0, 0)] [DataRow(90, 0, 0, 0)] [DataRow(100, 0, 0, 0)] @@ -169,25 +165,25 @@ public Task Initialize_MinEqMax(double rangeStart, double rangeEnd, double expec [DataRow(-10, -90, 0, 0)] [DataRow(-50, -50, 0, 0)] - [DataRow(110, 190, 0.01, 0.01)] - [DataRow(190, 110, 0.01, 0.01)] - [DataRow(150, 150, 0.01, 0.01)] + [DataRow(110, 190, 0, 0)] + [DataRow(190, 110, 0, 0)] + [DataRow(150, 150, 0, 0)] public Task Initialize_MinGtMax(double rangeStart, double rangeEnd, double expectedRangeStart, double expectedRangeEnd) - => Initialize(1, 100, rangeStart, rangeEnd, 0, 1, 0, expectedRangeStart, expectedRangeEnd, 0.01); + => Initialize(1, 100, rangeStart, rangeEnd, 0, 1, 0, expectedRangeStart, expectedRangeEnd, 0); [TestMethod] [TestCategory("Set Prop")] // Set:Min Then:Min Start End Max - [DataRow(0, 0, 10, 90, 100)] - [DataRow(-10, -10, 10, 90, 100)] - [DataRow(10, 10, 10, 90, 100)] - [DataRow(50, 50, 50, 90, 100)] - [DataRow(90, 90, 90, 90, 100)] - [DataRow(100, 100, 100, 100, 100)] - [DataRow(110, 110, 110.01, 110.01, 110.01)] + [DataRow(0, 0, 10, 90, 110)] + [DataRow(-10, -10, 10, 90, 110)] + [DataRow(10, 10, 10, 90, 110)] + [DataRow(50, 50, 50, 90, 110)] + [DataRow(90, 90, 90, 90, 110)] + [DataRow(100, 100, 100, 100, 110)] + [DataRow(110, 110, 110, 110, 110)] public Task SetMinimum(double setMinimumValue, double expectedMinimum, double expectedRangeStart, double expectedRangeEnd, double expectedMaximum) - => SetProp(1, 0, 10, 90, 100, Property.Minimum, setMinimumValue, 1, expectedMinimum, expectedRangeStart, expectedRangeEnd, expectedMaximum); + => SetProp(1, 0, 10, 90, 110, Property.Minimum, setMinimumValue, 1, expectedMinimum, expectedRangeStart, expectedRangeEnd, expectedMaximum); [TestMethod] [TestCategory("Set Prop")] @@ -199,7 +195,7 @@ public Task SetMinimum(double setMinimumValue, double expectedMinimum, double ex [DataRow(50, 0, 10, 50, 50)] [DataRow(10, 0, 10, 10, 10)] [DataRow(0, 0, 0, 0, 0)] - [DataRow(-10, -10.01, -10, -10, -10)] + [DataRow(-10, -10, -10, -10, -10)] public Task SetMaximum(double propInput, double expectedMinimum, double expectedRangeStart, double expectedRangeEnd, double expectedMaximum) => SetProp(1, 0, 10, 90, 100, Property.Maximum, propInput, 1, expectedMinimum, expectedRangeStart, expectedRangeEnd, expectedMaximum); @@ -212,9 +208,10 @@ public Task SetMaximum(double propInput, double expectedMinimum, double expected [DataRow(0, 0, 90)] [DataRow(-10, 0, 90)] [DataRow(90, 90, 90)] - [DataRow(95, 95, 95)] - [DataRow(100, 100, 100)] - [DataRow(110, 100, 100)] + // RangeStart should not move beyond RangeEnd + [DataRow(95, 90, 90)] + [DataRow(100, 90, 90)] + [DataRow(110, 90, 90)] public Task SetRangeStart(double propInput, double expectedRangeStart, double expectedRangeEnd) => SetProp(1, 0, 10, 90, 100, Property.RangeStart, propInput, 1, 0, expectedRangeStart, expectedRangeEnd, 100); @@ -232,13 +229,13 @@ public Task SetRangeStart(double propInput, double expectedRangeStart, double ex [DataRow(5, 10, 90, 4, 5, 90)] [DataRow(5, 10, 90, 6, 5, 90)] [DataRow(5, 10, 90, 9, 10, 90)] - [DataRow(5, 10, 90, 100, 100, 100)] + [DataRow(5, 10, 90, 100, 90, 90)] // RangeStart should not exceed RangeEnd [DataRow(5, 10, 90, 89, 90, 90)] [DataRow(5, 10, 90, 91, 90, 90)] - [DataRow(30, 60, 70, 80, 100, 100)] - [DataRow(30, 60, 70, 74, 60, 60)] - [DataRow(30, 60, 70, 75, 60, 60)] - [DataRow(30, 60, 70, 76, 100, 100)] + [DataRow(30, 60, 70, 80, 60, 70)] + [DataRow(30, 60, 70, 74, 60, 70)] + [DataRow(30, 60, 70, 75, 60, 70)] + [DataRow(30, 60, 70, 76, 60, 70)] [DataRow(40, 40, 60, 20, 0, 60)] [DataRow(40, 40, 60, 50, 40, 60)] [DataRow(40, 40, 60, 60, 40, 60)] // When RangeStart is set equal to RangeEnd but RangeEnd doesn't align with original RangeStart + StepFrequency From 897d832747548951464c7a8bf9507d67cea9d3cc Mon Sep 17 00:00:00 2001 From: Arlo Date: Thu, 18 Dec 2025 17:26:38 -0600 Subject: [PATCH 7/9] Revert "Remove Minimum/Maximum Epsilon delta property and usage, fixed issue where Maximum values less than Minimum would erroneously overwrite Minimum" This reverts commit c28bc00151f247142eda16cce5b9cc82249f3b6b. --- .../src/RangeSelector.Properties.cs | 74 ++++++++++++++++++ components/RangeSelector/src/RangeSelector.cs | 48 ++++-------- .../RangeSelector/tests/Test_RangeSelector.cs | 75 ++++++++++--------- tooling | 2 +- 4 files changed, 127 insertions(+), 72 deletions(-) diff --git a/components/RangeSelector/src/RangeSelector.Properties.cs b/components/RangeSelector/src/RangeSelector.Properties.cs index 80395dfc..6377df37 100644 --- a/components/RangeSelector/src/RangeSelector.Properties.cs +++ b/components/RangeSelector/src/RangeSelector.Properties.cs @@ -131,6 +131,21 @@ private static void MinimumChangedCallback(DependencyObject d, DependencyPropert var newValue = (double)e.NewValue; var oldValue = (double)e.OldValue; + if (rangeSelector.Maximum < newValue) + { + rangeSelector.Maximum = newValue + Epsilon; + } + + if (rangeSelector.RangeStart < newValue) + { + rangeSelector.RangeStart = newValue; + } + + if (rangeSelector.RangeEnd < newValue) + { + rangeSelector.RangeEnd = newValue; + } + if (newValue != oldValue) { rangeSelector.SyncThumbs(); @@ -149,6 +164,21 @@ private static void MaximumChangedCallback(DependencyObject d, DependencyPropert var newValue = (double)e.NewValue; var oldValue = (double)e.OldValue; + if (rangeSelector.Minimum > newValue) + { + rangeSelector.Minimum = newValue - Epsilon; + } + + if (rangeSelector.RangeEnd > newValue) + { + rangeSelector.RangeEnd = newValue; + } + + if (rangeSelector.RangeStart > newValue) + { + rangeSelector.RangeStart = newValue; + } + if (newValue != oldValue) { rangeSelector.SyncThumbs(); @@ -171,7 +201,29 @@ private static void RangeMinChangedCallback(DependencyObject d, DependencyProper return; } + var newValue = (double)e.NewValue; rangeSelector.RangeMinToStepFrequency(); + + if (rangeSelector._valuesAssigned) + { + if (newValue < rangeSelector.Minimum) + { + rangeSelector.RangeStart = rangeSelector.Minimum; + } + else if (newValue > rangeSelector.Maximum) + { + rangeSelector.RangeStart = rangeSelector.Maximum; + } + + rangeSelector.SyncActiveRectangle(); + + // If the new value is greater than the old max, move the max also + if (newValue > rangeSelector.RangeEnd) + { + rangeSelector.RangeEnd = newValue; + } + } + rangeSelector.SyncThumbs(); } @@ -191,7 +243,29 @@ private static void RangeMaxChangedCallback(DependencyObject d, DependencyProper return; } + var newValue = (double)e.NewValue; rangeSelector.RangeMaxToStepFrequency(); + + if (rangeSelector._valuesAssigned) + { + if (newValue < rangeSelector.Minimum) + { + rangeSelector.RangeEnd = rangeSelector.Minimum; + } + else if (newValue > rangeSelector.Maximum) + { + rangeSelector.RangeEnd = rangeSelector.Maximum; + } + + rangeSelector.SyncActiveRectangle(); + + // If the new max is less than the old minimum then move the minimum + if (newValue < rangeSelector.RangeStart) + { + rangeSelector.RangeStart = newValue; + } + } + rangeSelector.SyncThumbs(); } } diff --git a/components/RangeSelector/src/RangeSelector.cs b/components/RangeSelector/src/RangeSelector.cs index a73f97a0..0b527284 100644 --- a/components/RangeSelector/src/RangeSelector.cs +++ b/components/RangeSelector/src/RangeSelector.cs @@ -161,15 +161,13 @@ private void VerifyValues() { if (Minimum > Maximum) { - Maximum = Math.Min(Minimum, Maximum); - Minimum = Math.Min(Minimum, Maximum); + Minimum = Maximum; + Maximum = Maximum; } if (Minimum == Maximum) { - // Move RangeEnd before RangeStart to avoid code paths which correct for RangeStart being greater than RangeEnd - RangeEnd = Maximum; - RangeStart = Minimum; + throw new ArgumentException("Maximum and Minimum values cannot be equal."); } if (!_maxSet) @@ -182,26 +180,24 @@ private void VerifyValues() RangeStart = Minimum; } - // RangeEnd bound correction - if (RangeEnd < Minimum) + if (RangeStart < Minimum) { - RangeEnd = Minimum; + RangeStart = Minimum; } - if (RangeEnd > Maximum) + if (RangeEnd < Minimum) { - RangeEnd = Maximum; + RangeEnd = Minimum; } - // RangeStart bound correction - if (RangeStart < Minimum) + if (RangeStart > Maximum) { - RangeStart = Minimum; + RangeStart = Maximum; } - if (RangeStart > Maximum) + if (RangeEnd > Maximum) { - RangeStart = Maximum; + RangeEnd = Maximum; } if (RangeEnd < RangeStart) @@ -212,28 +208,12 @@ private void VerifyValues() private void RangeMinToStepFrequency() { - var newValue = MoveToStepFrequency(RangeStart); - - // If snapped value exceeds RangeEnd, snap down to previous step - if (newValue > RangeEnd) - { - newValue = Minimum + (((int)Math.Floor((RangeEnd - Minimum) / StepFrequency)) * StepFrequency); - } - - RangeStart = newValue; + RangeStart = MoveToStepFrequency(RangeStart); } private void RangeMaxToStepFrequency() { - var newValue = MoveToStepFrequency(RangeEnd); - - // If snapped value is below RangeStart, snap up to next step - if (newValue < RangeStart) - { - newValue = Minimum + (((int)Math.Ceiling((RangeStart - Minimum) / StepFrequency)) * StepFrequency); - } - - RangeEnd = newValue; + RangeEnd = MoveToStepFrequency(RangeEnd); } private double MoveToStepFrequency(double rangeValue) @@ -261,8 +241,6 @@ private void SyncThumbs(bool fromMinKeyDown = false, bool fromMaxKeyDown = false return; } - VerifyValues(); - var relativeLeft = ((RangeStart - Minimum) / (Maximum - Minimum)) * DragWidth(); var relativeRight = ((RangeEnd - Minimum) / (Maximum - Minimum)) * DragWidth(); diff --git a/components/RangeSelector/tests/Test_RangeSelector.cs b/components/RangeSelector/tests/Test_RangeSelector.cs index f97bc609..20ac7559 100644 --- a/components/RangeSelector/tests/Test_RangeSelector.cs +++ b/components/RangeSelector/tests/Test_RangeSelector.cs @@ -113,7 +113,7 @@ public Task Initialize_MinLtMax(double rangeStart, double rangeEnd, double expec // // Then // Minimum will be Maximum - // Maximum will be Maximum + // Maximum will be Maximum + 0.01 // If // Minimum >= Maximum @@ -121,6 +121,8 @@ public Task Initialize_MinLtMax(double rangeStart, double rangeEnd, double expec // RangeEnd > Maximum // // Then + // RangeStart will be Maximum + 0.01 + // Else // RangeStart will be Maximum // If @@ -128,35 +130,37 @@ public Task Initialize_MinLtMax(double rangeStart, double rangeEnd, double expec // RangeEnd > Maximum // // Then + // RangeEnd will be Maximum + 0.01 + // Else // RangeEnd will be Maximum // Input:Start End Expected:Start End [DataRow(0, 0, 0, 0)] - [DataRow(0, 10, 0, 0)] + [DataRow(0, 10, 0, 0.01)] [DataRow(0, -10, 0, 0)] [DataRow(-10, 0, 0, 0)] [DataRow(10, 0, 0, 0)] - [DataRow(10, 90, 0, 0)] - [DataRow(90, 10, 0, 0)] + [DataRow(10, 90, 0.01, 0.01)] + [DataRow(90, 10, 0.01, 0.01)] [DataRow(-90, -10, 0, 0)] [DataRow(-10, -90, 0, 0)] [DataRow(10, -10, 0, 0)] - [DataRow(-10, 10, 0, 0)] + [DataRow(-10, 10, 0, 0.01)] public Task Initialize_MinEqMax(double rangeStart, double rangeEnd, double expectedRangeStart, double expectedRangeEnd) - => Initialize(1, 0, rangeStart, rangeEnd, 0, 1, 0, expectedRangeStart, expectedRangeEnd, 0); + => Initialize(1, 0, rangeStart, rangeEnd, 0, 1, 0, expectedRangeStart, expectedRangeEnd, 0.01); [TestCategory("Initialize")] [TestMethod] - [DataRow(0, 100, 0, 0)] - [DataRow(10, 90, 0, 0)] - [DataRow(50, 50, 0, 0)] + [DataRow(0, 100, 0, 0.01)] + [DataRow(10, 90, 0.01, 0.01)] + [DataRow(50, 50, 0.01, 0.01)] [DataRow(0, 0, 0, 0)] - [DataRow(100, 100, 0, 0)] + [DataRow(100, 100, 0.01, 0.01)] - [DataRow(10, 100, 0, 0)] + [DataRow(10, 100, 0.01, 0.01)] - [DataRow(90, 10, 0, 0)] - [DataRow(100, 10, 0, 0)] + [DataRow(90, 10, 0.01, 0.01)] + [DataRow(100, 10, 0.01, 0.01)] [DataRow(90, 0, 0, 0)] [DataRow(100, 0, 0, 0)] @@ -165,25 +169,25 @@ public Task Initialize_MinEqMax(double rangeStart, double rangeEnd, double expec [DataRow(-10, -90, 0, 0)] [DataRow(-50, -50, 0, 0)] - [DataRow(110, 190, 0, 0)] - [DataRow(190, 110, 0, 0)] - [DataRow(150, 150, 0, 0)] + [DataRow(110, 190, 0.01, 0.01)] + [DataRow(190, 110, 0.01, 0.01)] + [DataRow(150, 150, 0.01, 0.01)] public Task Initialize_MinGtMax(double rangeStart, double rangeEnd, double expectedRangeStart, double expectedRangeEnd) - => Initialize(1, 100, rangeStart, rangeEnd, 0, 1, 0, expectedRangeStart, expectedRangeEnd, 0); + => Initialize(1, 100, rangeStart, rangeEnd, 0, 1, 0, expectedRangeStart, expectedRangeEnd, 0.01); [TestMethod] [TestCategory("Set Prop")] // Set:Min Then:Min Start End Max - [DataRow(0, 0, 10, 90, 110)] - [DataRow(-10, -10, 10, 90, 110)] - [DataRow(10, 10, 10, 90, 110)] - [DataRow(50, 50, 50, 90, 110)] - [DataRow(90, 90, 90, 90, 110)] - [DataRow(100, 100, 100, 100, 110)] - [DataRow(110, 110, 110, 110, 110)] + [DataRow(0, 0, 10, 90, 100)] + [DataRow(-10, -10, 10, 90, 100)] + [DataRow(10, 10, 10, 90, 100)] + [DataRow(50, 50, 50, 90, 100)] + [DataRow(90, 90, 90, 90, 100)] + [DataRow(100, 100, 100, 100, 100)] + [DataRow(110, 110, 110.01, 110.01, 110.01)] public Task SetMinimum(double setMinimumValue, double expectedMinimum, double expectedRangeStart, double expectedRangeEnd, double expectedMaximum) - => SetProp(1, 0, 10, 90, 110, Property.Minimum, setMinimumValue, 1, expectedMinimum, expectedRangeStart, expectedRangeEnd, expectedMaximum); + => SetProp(1, 0, 10, 90, 100, Property.Minimum, setMinimumValue, 1, expectedMinimum, expectedRangeStart, expectedRangeEnd, expectedMaximum); [TestMethod] [TestCategory("Set Prop")] @@ -195,7 +199,7 @@ public Task SetMinimum(double setMinimumValue, double expectedMinimum, double ex [DataRow(50, 0, 10, 50, 50)] [DataRow(10, 0, 10, 10, 10)] [DataRow(0, 0, 0, 0, 0)] - [DataRow(-10, -10, -10, -10, -10)] + [DataRow(-10, -10.01, -10, -10, -10)] public Task SetMaximum(double propInput, double expectedMinimum, double expectedRangeStart, double expectedRangeEnd, double expectedMaximum) => SetProp(1, 0, 10, 90, 100, Property.Maximum, propInput, 1, expectedMinimum, expectedRangeStart, expectedRangeEnd, expectedMaximum); @@ -208,10 +212,9 @@ public Task SetMaximum(double propInput, double expectedMinimum, double expected [DataRow(0, 0, 90)] [DataRow(-10, 0, 90)] [DataRow(90, 90, 90)] - // RangeStart should not move beyond RangeEnd - [DataRow(95, 90, 90)] - [DataRow(100, 90, 90)] - [DataRow(110, 90, 90)] + [DataRow(95, 95, 95)] + [DataRow(100, 100, 100)] + [DataRow(110, 100, 100)] public Task SetRangeStart(double propInput, double expectedRangeStart, double expectedRangeEnd) => SetProp(1, 0, 10, 90, 100, Property.RangeStart, propInput, 1, 0, expectedRangeStart, expectedRangeEnd, 100); @@ -229,16 +232,16 @@ public Task SetRangeStart(double propInput, double expectedRangeStart, double ex [DataRow(5, 10, 90, 4, 5, 90)] [DataRow(5, 10, 90, 6, 5, 90)] [DataRow(5, 10, 90, 9, 10, 90)] - [DataRow(5, 10, 90, 100, 90, 90)] // RangeStart should not exceed RangeEnd + [DataRow(5, 10, 90, 100, 100, 100)] [DataRow(5, 10, 90, 89, 90, 90)] [DataRow(5, 10, 90, 91, 90, 90)] - [DataRow(30, 60, 70, 80, 60, 70)] - [DataRow(30, 60, 70, 74, 60, 70)] - [DataRow(30, 60, 70, 75, 60, 70)] - [DataRow(30, 60, 70, 76, 60, 70)] + [DataRow(30, 60, 70, 80, 100, 100)] + [DataRow(30, 60, 70, 74, 60, 60)] + [DataRow(30, 60, 70, 75, 60, 60)] + [DataRow(30, 60, 70, 76, 100, 100)] [DataRow(40, 40, 60, 20, 0, 60)] [DataRow(40, 40, 60, 50, 40, 60)] - [DataRow(40, 40, 60, 60, 40, 60)] // When RangeStart is set equal to RangeEnd but RangeEnd doesn't align with original RangeStart + StepFrequency + [DataRow(40, 40, 60, 60, 100, 100)] public Task SetRangeStart_StepTest(double stepFrequency, double rangeStart, double rangeEnd, double propInput, double expectedRangeStart, double expectedRangeEnd) => SetProp(stepFrequency, 0, rangeStart, rangeEnd, 100, Property.RangeStart, propInput, stepFrequency, 0, expectedRangeStart, expectedRangeEnd, 100); diff --git a/tooling b/tooling index cdf9aa9a..c137363c 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit cdf9aa9a8dfde68ec2dfb7c3ae278b0cdbb60459 +Subproject commit c137363ca757a8b69576fed31bbe80115ffba765 From 8cc1a305151239a7aec5cc7662cd7a91e601857c Mon Sep 17 00:00:00 2001 From: Arlo Date: Thu, 18 Dec 2025 17:27:12 -0600 Subject: [PATCH 8/9] Remove exception for equal Minimum and Maximum values in RangeSelector --- components/RangeSelector/src/RangeSelector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/RangeSelector/src/RangeSelector.cs b/components/RangeSelector/src/RangeSelector.cs index 0b527284..49591e13 100644 --- a/components/RangeSelector/src/RangeSelector.cs +++ b/components/RangeSelector/src/RangeSelector.cs @@ -167,7 +167,7 @@ private void VerifyValues() if (Minimum == Maximum) { - throw new ArgumentException("Maximum and Minimum values cannot be equal."); + // Do nothing } if (!_maxSet) From 8e6918a45602161e86435c94d54c10f5254e7609 Mon Sep 17 00:00:00 2001 From: Arlo Date: Thu, 18 Dec 2025 17:32:27 -0600 Subject: [PATCH 9/9] Restore missing Epsilon field from revert --- components/RangeSelector/src/RangeSelector.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/components/RangeSelector/src/RangeSelector.cs b/components/RangeSelector/src/RangeSelector.cs index 49591e13..1df11048 100644 --- a/components/RangeSelector/src/RangeSelector.cs +++ b/components/RangeSelector/src/RangeSelector.cs @@ -34,6 +34,7 @@ public partial class RangeSelector : Control internal const string MinPressedState = "MinPressed"; internal const string MaxPressedState = "MaxPressed"; + private const double Epsilon = 0.01; private const double DefaultMinimum = 0.0; private const double DefaultMaximum = 10.0; private const double DefaultStepFrequency = 1;