RangeSlider.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. // Adapted from https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Slider.cs
  2. using System;
  3. using System.ComponentModel;
  4. using System.Globalization;
  5. using System.Windows;
  6. using System.Windows.Controls;
  7. using System.Windows.Controls.Primitives;
  8. using System.Windows.Input;
  9. using System.Windows.Media;
  10. using HandyControl.Data;
  11. using HandyControl.Expression.Drawing;
  12. using HandyControl.Tools;
  13. namespace HandyControl.Controls;
  14. [DefaultEvent("ValueChanged"), DefaultProperty("Value")]
  15. [TemplatePart(Name = ElementTrack, Type = typeof(Track))]
  16. public class RangeSlider : TwoWayRangeBase
  17. {
  18. private const string ElementTrack = "PART_Track";
  19. private RangeTrack _track;
  20. private readonly ToolTip _autoToolTipStart = null;
  21. private readonly ToolTip _autoToolTipEnd = null;
  22. private RangeThumb _thumbCurrent;
  23. private object _thumbOriginalToolTip;
  24. private Point _originThumbPoint;
  25. private Point _previousScreenCoordPosition;
  26. static RangeSlider()
  27. {
  28. InitializeCommands();
  29. MinimumProperty.OverrideMetadata(typeof(RangeSlider), new FrameworkPropertyMetadata(ValueBoxes.Double0Box, FrameworkPropertyMetadataOptions.AffectsMeasure));
  30. MaximumProperty.OverrideMetadata(typeof(RangeSlider), new FrameworkPropertyMetadata(ValueBoxes.Double10Box, FrameworkPropertyMetadataOptions.AffectsMeasure));
  31. ValueStartProperty.OverrideMetadata(typeof(RangeSlider), new FrameworkPropertyMetadata(ValueBoxes.Double0Box, FrameworkPropertyMetadataOptions.AffectsMeasure));
  32. ValueEndProperty.OverrideMetadata(typeof(RangeSlider), new FrameworkPropertyMetadata(ValueBoxes.Double0Box, FrameworkPropertyMetadataOptions.AffectsMeasure));
  33. // Register Event Handler for the Thumb
  34. EventManager.RegisterClassHandler(typeof(RangeSlider), Thumb.DragStartedEvent, new DragStartedEventHandler(OnThumbDragStarted));
  35. EventManager.RegisterClassHandler(typeof(RangeSlider), Thumb.DragDeltaEvent, new DragDeltaEventHandler(OnThumbDragDelta));
  36. EventManager.RegisterClassHandler(typeof(RangeSlider), Thumb.DragCompletedEvent, new DragCompletedEventHandler(OnThumbDragCompleted));
  37. // Listen to MouseLeftButtonDown event to determine if slide should move focus to itself
  38. EventManager.RegisterClassHandler(typeof(RangeSlider), Mouse.MouseDownEvent, new MouseButtonEventHandler(OnMouseLeftButtonDown), true);
  39. }
  40. public RangeSlider()
  41. {
  42. CommandBindings.Add(new CommandBinding(IncreaseLarge, OnIncreaseLarge));
  43. CommandBindings.Add(new CommandBinding(IncreaseSmall, OnIncreaseSmall));
  44. CommandBindings.Add(new CommandBinding(DecreaseLarge, OnDecreaseLarge));
  45. CommandBindings.Add(new CommandBinding(DecreaseSmall, OnDecreaseSmall));
  46. CommandBindings.Add(new CommandBinding(CenterLarge, OnCenterLarge));
  47. CommandBindings.Add(new CommandBinding(CenterSmall, OnCenterSmall));
  48. }
  49. private void OnIncreaseLarge(object sender, ExecutedRoutedEventArgs e) => (sender as RangeSlider)?.OnIncreaseLarge();
  50. private void OnIncreaseSmall(object sender, ExecutedRoutedEventArgs e) => (sender as RangeSlider)?.OnIncreaseSmall();
  51. private void OnDecreaseLarge(object sender, ExecutedRoutedEventArgs e) => (sender as RangeSlider)?.OnDecreaseLarge();
  52. private void OnDecreaseSmall(object sender, ExecutedRoutedEventArgs e) => (sender as RangeSlider)?.OnDecreaseSmall();
  53. private void OnCenterLarge(object sender, ExecutedRoutedEventArgs e) => (sender as RangeSlider)?.OnCenterLarge(e.Parameter);
  54. private void OnCenterSmall(object sender, ExecutedRoutedEventArgs e) => (sender as RangeSlider)?.OnCenterSmall(e.Parameter);
  55. protected virtual void OnIncreaseLarge() => MoveToNextTick(LargeChange, false);
  56. protected virtual void OnIncreaseSmall() => MoveToNextTick(SmallChange, false);
  57. protected virtual void OnDecreaseLarge() => MoveToNextTick(-LargeChange, true);
  58. protected virtual void OnDecreaseSmall() => MoveToNextTick(-SmallChange, true);
  59. protected virtual void OnCenterLarge(object parameter) => MoveToNextTick(LargeChange, false, true);
  60. protected virtual void OnCenterSmall(object parameter) => MoveToNextTick(SmallChange, false, true);
  61. public static RoutedCommand IncreaseLarge { get; private set; }
  62. public static RoutedCommand IncreaseSmall { get; private set; }
  63. public static RoutedCommand DecreaseLarge { get; private set; }
  64. public static RoutedCommand DecreaseSmall { get; private set; }
  65. public static RoutedCommand CenterLarge { get; private set; }
  66. public static RoutedCommand CenterSmall { get; private set; }
  67. private static void InitializeCommands()
  68. {
  69. IncreaseLarge = new RoutedCommand(nameof(IncreaseLarge), typeof(RangeSlider));
  70. IncreaseSmall = new RoutedCommand(nameof(IncreaseSmall), typeof(RangeSlider));
  71. DecreaseLarge = new RoutedCommand(nameof(DecreaseLarge), typeof(RangeSlider));
  72. DecreaseSmall = new RoutedCommand(nameof(DecreaseSmall), typeof(RangeSlider));
  73. CenterLarge = new RoutedCommand(nameof(CenterLarge), typeof(RangeSlider));
  74. CenterSmall = new RoutedCommand(nameof(CenterSmall), typeof(RangeSlider));
  75. }
  76. public override void OnApplyTemplate()
  77. {
  78. _thumbCurrent = null;
  79. base.OnApplyTemplate();
  80. _track = GetTemplateChild(ElementTrack) as RangeTrack;
  81. if (_autoToolTipStart != null)
  82. {
  83. _autoToolTipStart.PlacementTarget = _track?.ThumbStart;
  84. }
  85. if (_autoToolTipEnd != null)
  86. {
  87. _autoToolTipEnd.PlacementTarget = _track?.ThumbEnd;
  88. }
  89. }
  90. public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
  91. nameof(Orientation), typeof(Orientation), typeof(RangeSlider), new PropertyMetadata(default(Orientation)));
  92. public Orientation Orientation
  93. {
  94. get => (Orientation) GetValue(OrientationProperty);
  95. set => SetValue(OrientationProperty, value);
  96. }
  97. public static readonly DependencyProperty IsDirectionReversedProperty = DependencyProperty.Register(
  98. nameof(IsDirectionReversed), typeof(bool), typeof(RangeSlider), new PropertyMetadata(ValueBoxes.FalseBox));
  99. public bool IsDirectionReversed
  100. {
  101. get => (bool) GetValue(IsDirectionReversedProperty);
  102. set => SetValue(IsDirectionReversedProperty, ValueBoxes.BooleanBox(value));
  103. }
  104. public static readonly DependencyProperty DelayProperty = RepeatButton.DelayProperty.AddOwner(typeof(RangeSlider), new FrameworkPropertyMetadata(GetKeyboardDelay()));
  105. public int Delay
  106. {
  107. get => (int) GetValue(DelayProperty);
  108. set => SetValue(DelayProperty, value);
  109. }
  110. internal static int GetKeyboardDelay()
  111. {
  112. var delay = SystemParameters.KeyboardDelay;
  113. if (delay < 0 || delay > 3)
  114. delay = 0;
  115. return (delay + 1) * 250;
  116. }
  117. public static readonly DependencyProperty IntervalProperty = RepeatButton.IntervalProperty.AddOwner(typeof(RangeSlider), new FrameworkPropertyMetadata(GetKeyboardSpeed()));
  118. public int Interval
  119. {
  120. get => (int) GetValue(IntervalProperty);
  121. set => SetValue(IntervalProperty, value);
  122. }
  123. internal static int GetKeyboardSpeed()
  124. {
  125. var speed = SystemParameters.KeyboardSpeed;
  126. if (speed < 0 || speed > 31)
  127. speed = 31;
  128. return (31 - speed) * (400 - 1000 / 30) / 31 + 1000 / 30;
  129. }
  130. public static readonly DependencyProperty AutoToolTipPlacementProperty = DependencyProperty.Register(
  131. nameof(AutoToolTipPlacement), typeof(AutoToolTipPlacement), typeof(RangeSlider), new PropertyMetadata(default(AutoToolTipPlacement)));
  132. public AutoToolTipPlacement AutoToolTipPlacement
  133. {
  134. get => (AutoToolTipPlacement) GetValue(AutoToolTipPlacementProperty);
  135. set => SetValue(AutoToolTipPlacementProperty, value);
  136. }
  137. public static readonly DependencyProperty AutoToolTipPrecisionProperty = DependencyProperty.Register(
  138. nameof(AutoToolTipPrecision), typeof(int), typeof(RangeSlider), new PropertyMetadata(ValueBoxes.Int0Box),
  139. ValidateHelper.IsInRangeOfPosIntIncludeZero);
  140. public int AutoToolTipPrecision
  141. {
  142. get => (int) GetValue(AutoToolTipPrecisionProperty);
  143. set => SetValue(AutoToolTipPrecisionProperty, value);
  144. }
  145. public static readonly DependencyProperty IsSnapToTickEnabledProperty = DependencyProperty.Register(
  146. nameof(IsSnapToTickEnabled), typeof(bool), typeof(RangeSlider), new PropertyMetadata(ValueBoxes.FalseBox));
  147. public bool IsSnapToTickEnabled
  148. {
  149. get => (bool) GetValue(IsSnapToTickEnabledProperty);
  150. set => SetValue(IsSnapToTickEnabledProperty, ValueBoxes.BooleanBox(value));
  151. }
  152. public static readonly DependencyProperty TickPlacementProperty = DependencyProperty.Register(
  153. nameof(TickPlacement), typeof(TickPlacement), typeof(RangeSlider), new PropertyMetadata(default(TickPlacement)));
  154. public TickPlacement TickPlacement
  155. {
  156. get => (TickPlacement) GetValue(TickPlacementProperty);
  157. set => SetValue(TickPlacementProperty, value);
  158. }
  159. public static readonly DependencyProperty TickFrequencyProperty = DependencyProperty.Register(
  160. nameof(TickFrequency), typeof(double), typeof(RangeSlider), new PropertyMetadata(ValueBoxes.Double1Box),
  161. ValidateHelper.IsInRangeOfPosDoubleIncludeZero);
  162. public double TickFrequency
  163. {
  164. get => (double) GetValue(TickFrequencyProperty);
  165. set => SetValue(TickFrequencyProperty, value);
  166. }
  167. public static readonly DependencyProperty TicksProperty = DependencyProperty.Register(
  168. nameof(Ticks), typeof(DoubleCollection), typeof(RangeSlider), new PropertyMetadata(new DoubleCollection()));
  169. public DoubleCollection Ticks
  170. {
  171. get => (DoubleCollection) GetValue(TicksProperty);
  172. set => SetValue(TicksProperty, value);
  173. }
  174. public static readonly DependencyProperty IsMoveToPointEnabledProperty = DependencyProperty.Register(
  175. nameof(IsMoveToPointEnabled), typeof(bool), typeof(RangeSlider), new PropertyMetadata(ValueBoxes.FalseBox));
  176. public bool IsMoveToPointEnabled
  177. {
  178. get => (bool) GetValue(IsMoveToPointEnabledProperty);
  179. set => SetValue(IsMoveToPointEnabledProperty, ValueBoxes.BooleanBox(value));
  180. }
  181. protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
  182. {
  183. if (IsMoveToPointEnabled && _track.ThumbStart is { IsMouseOver: false } && _track.ThumbEnd is { IsMouseOver: false })
  184. {
  185. // Here we need to determine whether it's closer to the starting point or the end point.
  186. var pt = e.MouseDevice.GetPosition(_track);
  187. UpdateValue(pt);
  188. e.Handled = true;
  189. }
  190. base.OnPreviewMouseLeftButtonDown(e);
  191. }
  192. private void MoveToNextTick(double direction, bool isStart, bool isCenter = false, object parameter = null)
  193. {
  194. if (MathHelper.AreClose(direction, 0)) return;
  195. if (isCenter)
  196. {
  197. if (parameter == null)
  198. {
  199. var pt = Mouse.GetPosition(_track);
  200. var newValue = _track.ValueFromPoint(pt);
  201. if (ValidateHelper.IsInRangeOfDouble(newValue))
  202. {
  203. isStart = (ValueStart + ValueEnd) / 2 > newValue;
  204. if (!isStart)
  205. {
  206. direction = -Math.Abs(direction);
  207. }
  208. }
  209. }
  210. if (parameter is bool parameterValue)
  211. {
  212. isStart = parameterValue;
  213. }
  214. }
  215. var value = isStart ? ValueStart : ValueEnd;
  216. var next = SnapToTick(Math.Max(Minimum, Math.Min(Maximum, value + direction)));
  217. var greaterThan = direction > 0;
  218. // If the snapping brought us back to value, find the next tick point
  219. if (MathHelper.AreClose(next, value) &&
  220. !(greaterThan && MathHelper.AreClose(value, Maximum)) &&
  221. !(!greaterThan && MathHelper.AreClose(value, Minimum)))
  222. {
  223. // If ticks collection is available, use it.
  224. // Note that ticks may be unsorted.
  225. if (Ticks is { Count: > 0 })
  226. {
  227. foreach (var tick in Ticks)
  228. {
  229. // Find the smallest tick greater than value or the largest tick less than value
  230. if (greaterThan && MathHelper.GreaterThan(tick, value) && (MathHelper.LessThan(tick, next) || MathHelper.AreClose(next, value))
  231. || !greaterThan && MathHelper.LessThan(tick, value) && (MathHelper.GreaterThan(tick, next) || MathHelper.AreClose(next, value)))
  232. {
  233. next = tick;
  234. }
  235. }
  236. }
  237. else if (MathHelper.GreaterThan(TickFrequency, 0.0))
  238. {
  239. // Find the current tick we are at
  240. var tickNumber = Math.Round((value - Minimum) / TickFrequency);
  241. if (greaterThan)
  242. tickNumber += 1.0;
  243. else
  244. tickNumber -= 1.0;
  245. next = Minimum + tickNumber * TickFrequency;
  246. }
  247. }
  248. // Update if we've found a better value
  249. if (!MathHelper.AreClose(next, value))
  250. {
  251. SetCurrentValue(isStart ? ValueStartProperty : ValueEndProperty, next);
  252. }
  253. }
  254. private double SnapToTick(double value)
  255. {
  256. if (!IsSnapToTickEnabled) return value;
  257. var previous = Minimum;
  258. var next = Maximum;
  259. if (Ticks is { Count: > 0 })
  260. {
  261. foreach (var tick in Ticks)
  262. {
  263. if (MathHelper.AreClose(tick, value))
  264. {
  265. return value;
  266. }
  267. if (MathHelper.LessThan(tick, value) && MathHelper.GreaterThan(tick, previous))
  268. {
  269. previous = tick;
  270. }
  271. else if (MathHelper.GreaterThan(tick, value) && MathHelper.LessThan(tick, next))
  272. {
  273. next = tick;
  274. }
  275. }
  276. }
  277. else if (MathHelper.GreaterThan(TickFrequency, 0.0))
  278. {
  279. previous = Minimum + Math.Round((value - Minimum) / TickFrequency) * TickFrequency;
  280. next = Math.Min(Maximum, previous + TickFrequency);
  281. }
  282. return MathHelper.GreaterThanOrClose(value, (previous + next) * 0.5) ? next : previous;
  283. }
  284. private void UpdateValue(Point point)
  285. {
  286. var newValue = _track.ValueFromPoint(point);
  287. if (ValidateHelper.IsInRangeOfDouble(newValue))
  288. {
  289. var isStart = (ValueStart + ValueEnd) / 2 > newValue;
  290. UpdateValue(newValue, isStart);
  291. }
  292. }
  293. private void UpdateValue(double value, bool isStart)
  294. {
  295. var snappedValue = SnapToTick(value);
  296. if (isStart)
  297. {
  298. if (!MathHelper.AreClose(snappedValue, ValueStart))
  299. {
  300. var start = Math.Max(Minimum, Math.Min(Maximum, snappedValue));
  301. if (start > ValueEnd)
  302. {
  303. SetCurrentValue(ValueStartProperty, ValueEnd);
  304. SetCurrentValue(ValueEndProperty, start);
  305. _track.ThumbStart.CancelDrag();
  306. _track.ThumbEnd.StartDrag();
  307. _thumbCurrent = _track.ThumbEnd;
  308. }
  309. else
  310. {
  311. SetCurrentValue(ValueStartProperty, start);
  312. }
  313. }
  314. }
  315. else
  316. {
  317. if (!MathHelper.AreClose(snappedValue, ValueEnd))
  318. {
  319. var end = Math.Max(Minimum, Math.Min(Maximum, snappedValue));
  320. if (end < ValueStart)
  321. {
  322. SetCurrentValue(ValueEndProperty, ValueStart);
  323. SetCurrentValue(ValueStartProperty, end);
  324. _track.ThumbEnd.CancelDrag();
  325. _track.ThumbStart.StartDrag();
  326. _thumbCurrent = _track.ThumbStart;
  327. }
  328. else
  329. {
  330. SetCurrentValue(ValueEndProperty, end);
  331. }
  332. }
  333. }
  334. }
  335. private static void OnThumbDragStarted(object sender, DragStartedEventArgs e) => (sender as RangeSlider)?.OnThumbDragStarted(e);
  336. protected virtual void OnThumbDragStarted(DragStartedEventArgs e)
  337. {
  338. // Show AutoToolTip if needed.
  339. if (e.OriginalSource is not RangeThumb thumb) return;
  340. _thumbCurrent = thumb;
  341. _originThumbPoint = Mouse.GetPosition(_thumbCurrent);
  342. _thumbCurrent.StartDrag();
  343. if (AutoToolTipPlacement == AutoToolTipPlacement.None)
  344. {
  345. return;
  346. }
  347. var isStart = thumb.Equals(_track.ThumbStart);
  348. if (!isStart)
  349. {
  350. if (!thumb.Equals(_track.ThumbEnd)) return;
  351. }
  352. // Save original tooltip
  353. _thumbOriginalToolTip = thumb.ToolTip;
  354. OnThumbDragStarted(isStart ? _autoToolTipStart : _autoToolTipEnd, isStart);
  355. }
  356. private void OnThumbDragStarted(ToolTip toolTip, bool isStart)
  357. {
  358. toolTip ??= new ToolTip
  359. {
  360. Placement = PlacementMode.Custom,
  361. PlacementTarget = isStart ? _track.ThumbStart : _track.ThumbEnd,
  362. CustomPopupPlacementCallback = AutoToolTipCustomPlacementCallback
  363. };
  364. if (isStart)
  365. {
  366. _track.ThumbStart.ToolTip = toolTip;
  367. }
  368. else
  369. {
  370. _track.ThumbEnd.ToolTip = toolTip;
  371. }
  372. toolTip.Content = GetAutoToolTipNumber(isStart);
  373. toolTip.IsOpen = true;
  374. }
  375. private CustomPopupPlacement[] AutoToolTipCustomPlacementCallback(Size popupSize, Size targetSize, Point offset)
  376. {
  377. switch (AutoToolTipPlacement)
  378. {
  379. case AutoToolTipPlacement.TopLeft:
  380. if (Orientation == Orientation.Horizontal)
  381. {
  382. // Place popup at top of thumb
  383. return new[]{new CustomPopupPlacement(
  384. new Point((targetSize.Width - popupSize.Width) * 0.5, -popupSize.Height),
  385. PopupPrimaryAxis.Horizontal)
  386. };
  387. }
  388. else
  389. {
  390. // Place popup at left of thumb
  391. return new[] {
  392. new CustomPopupPlacement(
  393. new Point(-popupSize.Width, (targetSize.Height - popupSize.Height) * 0.5),
  394. PopupPrimaryAxis.Vertical)
  395. };
  396. }
  397. case AutoToolTipPlacement.BottomRight:
  398. if (Orientation == Orientation.Horizontal)
  399. {
  400. // Place popup at bottom of thumb
  401. return new[] {
  402. new CustomPopupPlacement(
  403. new Point((targetSize.Width - popupSize.Width) * 0.5, targetSize.Height) ,
  404. PopupPrimaryAxis.Horizontal)
  405. };
  406. }
  407. else
  408. {
  409. // Place popup at right of thumb
  410. return new[] {
  411. new CustomPopupPlacement(
  412. new Point(targetSize.Width, (targetSize.Height - popupSize.Height) * 0.5),
  413. PopupPrimaryAxis.Vertical)
  414. };
  415. }
  416. default:
  417. return new CustomPopupPlacement[] { };
  418. }
  419. }
  420. private string GetAutoToolTipNumber(bool isStart)
  421. {
  422. var format = (NumberFormatInfo) NumberFormatInfo.CurrentInfo.Clone();
  423. format.NumberDecimalDigits = AutoToolTipPrecision;
  424. return isStart ? ValueStart.ToString("N", format) : ValueEnd.ToString("N", format);
  425. }
  426. private static void OnThumbDragDelta(object sender, DragDeltaEventArgs e) => (sender as RangeSlider)?.OnThumbDragDelta(e);
  427. protected virtual void OnThumbDragDelta(DragDeltaEventArgs e)
  428. {
  429. if (e.OriginalSource is not Thumb thumb) return;
  430. var isStart = thumb.Equals(_track.ThumbStart);
  431. if (!isStart)
  432. {
  433. if (!thumb.Equals(_track.ThumbEnd)) return;
  434. }
  435. // Convert to Track's co-ordinate
  436. OnThumbDragDelta(_track, isStart, e);
  437. }
  438. private void OnThumbDragDelta(RangeTrack track, bool isStart, DragDeltaEventArgs e)
  439. {
  440. if (track == null || track.ThumbStart == null | _track.ThumbEnd == null) return;
  441. var newValue = (isStart ? ValueStart : ValueEnd) + track.ValueFromDistance(e.HorizontalChange, e.VerticalChange);
  442. if (ValidateHelper.IsInRangeOfDouble(newValue))
  443. {
  444. UpdateValue(newValue, isStart);
  445. }
  446. // Show AutoToolTip if needed
  447. if (AutoToolTipPlacement != AutoToolTipPlacement.None)
  448. {
  449. var toolTip = (isStart ? _autoToolTipStart : _autoToolTipEnd) ?? new ToolTip();
  450. toolTip.Content = GetAutoToolTipNumber(isStart);
  451. var thumb = isStart ? _track.ThumbStart : _track.ThumbEnd;
  452. if (!Equals(thumb.ToolTip, toolTip))
  453. {
  454. thumb.ToolTip = toolTip;
  455. }
  456. if (!toolTip.IsOpen)
  457. {
  458. toolTip.IsOpen = true;
  459. }
  460. }
  461. }
  462. private static void OnThumbDragCompleted(object sender, DragCompletedEventArgs e) => (sender as RangeSlider)?.OnThumbDragCompleted(e);
  463. protected virtual void OnThumbDragCompleted(DragCompletedEventArgs e)
  464. {
  465. // Show AutoToolTip if needed.
  466. if (e.OriginalSource is not Thumb thumb || AutoToolTipPlacement == AutoToolTipPlacement.None)
  467. {
  468. return;
  469. }
  470. var isStart = thumb.Equals(_track.ThumbStart);
  471. if (!isStart)
  472. {
  473. if (!thumb.Equals(_track.ThumbEnd)) return;
  474. }
  475. var toolTip = isStart ? _autoToolTipStart : _autoToolTipEnd;
  476. if (toolTip != null)
  477. {
  478. toolTip.IsOpen = false;
  479. }
  480. thumb.ToolTip = _thumbOriginalToolTip;
  481. }
  482. private static void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
  483. {
  484. if (e.ChangedButton != MouseButton.Left) return;
  485. var slider = (RangeSlider) sender;
  486. // When someone click on the Slider's part, and it's not focusable
  487. // Slider need to take the focus in order to process keyboard correctly
  488. if (!slider.IsKeyboardFocusWithin)
  489. {
  490. e.Handled = slider.Focus() || e.Handled;
  491. }
  492. if (slider._track.ThumbStart.IsMouseOver)
  493. {
  494. slider._track.ThumbStart.StartDrag();
  495. slider._thumbCurrent = slider._track.ThumbStart;
  496. }
  497. if (slider._track.ThumbEnd.IsMouseOver)
  498. {
  499. slider._track.ThumbEnd.StartDrag();
  500. slider._thumbCurrent = slider._track.ThumbEnd;
  501. }
  502. }
  503. protected override void OnMouseMove(MouseEventArgs e)
  504. {
  505. if (_thumbCurrent == null) return;
  506. if (e.MouseDevice.LeftButton != MouseButtonState.Pressed) return;
  507. if (!_thumbCurrent.IsDragging) return;
  508. var thumbCoordPosition = e.GetPosition(_thumbCurrent);
  509. var screenCoordPosition = PointFromScreen(thumbCoordPosition);
  510. if (screenCoordPosition != _previousScreenCoordPosition)
  511. {
  512. _previousScreenCoordPosition = screenCoordPosition;
  513. _thumbCurrent.RaiseEvent(new DragDeltaEventArgs(thumbCoordPosition.X - _originThumbPoint.X,
  514. thumbCoordPosition.Y - _originThumbPoint.Y));
  515. }
  516. }
  517. protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
  518. {
  519. base.OnPreviewMouseLeftButtonUp(e);
  520. _thumbCurrent = null;
  521. }
  522. }