RangeTrack.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. // Adapted from https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Primitives/Track.cs
  2. using System;
  3. using System.Windows;
  4. using System.Windows.Controls;
  5. using System.Windows.Controls.Primitives;
  6. using System.Windows.Input;
  7. using System.Windows.Media;
  8. using HandyControl.Data;
  9. namespace HandyControl.Controls;
  10. public class RangeTrack : FrameworkElement
  11. {
  12. private RepeatButton _increaseButton;
  13. private RepeatButton _centerButton;
  14. private RepeatButton _decreaseButton;
  15. private RangeThumb _thumbStart;
  16. private RangeThumb _thumbEnd;
  17. private Visual[] _visualChildren;
  18. private double Density { get; set; } = double.NaN;
  19. public RepeatButton DecreaseRepeatButton
  20. {
  21. get => _decreaseButton;
  22. set
  23. {
  24. if (Equals(_increaseButton, value) || Equals(_centerButton, value))
  25. {
  26. throw new NotSupportedException("SameButtons");
  27. }
  28. UpdateComponent(_decreaseButton, value);
  29. _decreaseButton = value;
  30. if (_decreaseButton != null)
  31. {
  32. CommandManager.InvalidateRequerySuggested(); // Should post an idle queue item to update IsEnabled on button
  33. }
  34. }
  35. }
  36. public RepeatButton CenterRepeatButton
  37. {
  38. get => _centerButton;
  39. set
  40. {
  41. if (Equals(_increaseButton, value) || Equals(_decreaseButton, value))
  42. {
  43. throw new NotSupportedException("SameButtons");
  44. }
  45. UpdateComponent(_centerButton, value);
  46. _centerButton = value;
  47. if (_centerButton != null)
  48. {
  49. CommandManager.InvalidateRequerySuggested(); // Should post an idle queue item to update IsEnabled on button
  50. }
  51. }
  52. }
  53. public RepeatButton IncreaseRepeatButton
  54. {
  55. get => _increaseButton;
  56. set
  57. {
  58. if (Equals(_decreaseButton, value) || Equals(_centerButton, value))
  59. {
  60. throw new NotSupportedException("SameButtons");
  61. }
  62. UpdateComponent(_increaseButton, value);
  63. _increaseButton = value;
  64. if (_increaseButton != null)
  65. {
  66. CommandManager.InvalidateRequerySuggested(); // Should post an idle queue item to update IsEnabled on button
  67. }
  68. }
  69. }
  70. public RangeThumb ThumbStart
  71. {
  72. get => _thumbStart;
  73. set
  74. {
  75. UpdateComponent(_thumbStart, value);
  76. _thumbStart = value;
  77. }
  78. }
  79. public RangeThumb ThumbEnd
  80. {
  81. get => _thumbEnd;
  82. set
  83. {
  84. UpdateComponent(_thumbEnd, value);
  85. _thumbEnd = value;
  86. }
  87. }
  88. static RangeTrack()
  89. {
  90. IsEnabledProperty.OverrideMetadata(typeof(RangeTrack), new UIPropertyMetadata(OnIsEnabledChanged));
  91. }
  92. public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
  93. nameof(Orientation), typeof(Orientation), typeof(RangeTrack),
  94. new FrameworkPropertyMetadata(default(Orientation), FrameworkPropertyMetadataOptions.AffectsMeasure));
  95. public Orientation Orientation
  96. {
  97. get => (Orientation) GetValue(OrientationProperty);
  98. set => SetValue(OrientationProperty, value);
  99. }
  100. public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register(
  101. nameof(Minimum), typeof(double), typeof(RangeTrack),
  102. new FrameworkPropertyMetadata(ValueBoxes.Double0Box, FrameworkPropertyMetadataOptions.AffectsArrange));
  103. public double Minimum
  104. {
  105. get => (double) GetValue(MinimumProperty);
  106. set => SetValue(MinimumProperty, value);
  107. }
  108. public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register(
  109. nameof(Maximum), typeof(double), typeof(RangeTrack),
  110. new FrameworkPropertyMetadata(ValueBoxes.Double1Box, FrameworkPropertyMetadataOptions.AffectsArrange));
  111. public double Maximum
  112. {
  113. get => (double) GetValue(MaximumProperty);
  114. set => SetValue(MaximumProperty, value);
  115. }
  116. public static readonly DependencyProperty ValueStartProperty = DependencyProperty.Register(
  117. nameof(ValueStart), typeof(double), typeof(RangeTrack),
  118. new FrameworkPropertyMetadata(ValueBoxes.Double0Box,
  119. FrameworkPropertyMetadataOptions.BindsTwoWayByDefault |
  120. FrameworkPropertyMetadataOptions.AffectsArrange));
  121. public double ValueStart
  122. {
  123. get => (double) GetValue(ValueStartProperty);
  124. set => SetValue(ValueStartProperty, value);
  125. }
  126. public static readonly DependencyProperty ValueEndProperty = DependencyProperty.Register(
  127. nameof(ValueEnd), typeof(double), typeof(RangeTrack),
  128. new FrameworkPropertyMetadata(ValueBoxes.Double0Box,
  129. FrameworkPropertyMetadataOptions.BindsTwoWayByDefault |
  130. FrameworkPropertyMetadataOptions.AffectsArrange));
  131. public double ValueEnd
  132. {
  133. get => (double) GetValue(ValueEndProperty);
  134. set => SetValue(ValueEndProperty, value);
  135. }
  136. public static readonly DependencyProperty IsDirectionReversedProperty = DependencyProperty.Register(
  137. nameof(IsDirectionReversed), typeof(bool), typeof(RangeTrack), new PropertyMetadata(ValueBoxes.FalseBox));
  138. public bool IsDirectionReversed
  139. {
  140. get => (bool) GetValue(IsDirectionReversedProperty);
  141. set => SetValue(IsDirectionReversedProperty, ValueBoxes.BooleanBox(value));
  142. }
  143. protected override Visual GetVisualChild(int index)
  144. {
  145. if (_visualChildren?[index] == null)
  146. {
  147. // ReSharper disable once UseNameofExpression
  148. // ReSharper disable once LocalizableElement
  149. throw new ArgumentOutOfRangeException("index", index, "ArgumentOutOfRange");
  150. }
  151. return _visualChildren[index];
  152. }
  153. protected override Size MeasureOverride(Size availableSize)
  154. {
  155. var desiredSize = new Size();
  156. // Only measure thumb
  157. // Repeat buttons will be sized based on thumb
  158. if (_thumbStart != null)
  159. {
  160. _thumbStart.Measure(availableSize);
  161. desiredSize = _thumbStart.DesiredSize;
  162. }
  163. if (_thumbEnd != null)
  164. {
  165. _thumbEnd.Measure(availableSize);
  166. desiredSize = new Size(Math.Max(_thumbEnd.DesiredSize.Width, desiredSize.Width),
  167. Math.Max(_thumbEnd.DesiredSize.Height, desiredSize.Height));
  168. }
  169. return desiredSize;
  170. }
  171. private static void CoerceLength(ref double componentLength, double trackLength)
  172. {
  173. if (componentLength < 0)
  174. {
  175. componentLength = 0;
  176. }
  177. else if (componentLength > trackLength || double.IsNaN(componentLength))
  178. {
  179. componentLength = trackLength;
  180. }
  181. }
  182. protected override Size ArrangeOverride(Size arrangeSize)
  183. {
  184. var isVertical = Orientation == Orientation.Vertical;
  185. ComputeLengths(arrangeSize, isVertical, out var decreaseButtonLength, out var centerButtonLength,
  186. out var increaseButtonLength, out var thumbStartLength, out var thumbEndLength);
  187. var offset = new Point();
  188. var pieceSize = arrangeSize;
  189. var isDirectionReversed = IsDirectionReversed;
  190. if (isVertical)
  191. {
  192. CoerceLength(ref decreaseButtonLength, arrangeSize.Height);
  193. CoerceLength(ref centerButtonLength, arrangeSize.Height);
  194. CoerceLength(ref increaseButtonLength, arrangeSize.Height);
  195. CoerceLength(ref thumbStartLength, arrangeSize.Height);
  196. CoerceLength(ref thumbEndLength, arrangeSize.Height);
  197. offset.Y = isDirectionReversed ? decreaseButtonLength + thumbEndLength + centerButtonLength + thumbStartLength : 0;
  198. pieceSize.Height = increaseButtonLength;
  199. IncreaseRepeatButton?.Arrange(new Rect(offset, pieceSize));
  200. offset.Y = isDirectionReversed ? decreaseButtonLength + thumbEndLength : increaseButtonLength + thumbStartLength;
  201. pieceSize.Height = centerButtonLength;
  202. CenterRepeatButton?.Arrange(new Rect(offset, pieceSize));
  203. offset.Y = isDirectionReversed ? 0 : increaseButtonLength + thumbStartLength + centerButtonLength + thumbEndLength;
  204. pieceSize.Height = decreaseButtonLength;
  205. DecreaseRepeatButton?.Arrange(new Rect(offset, pieceSize));
  206. offset.Y = isDirectionReversed
  207. ? decreaseButtonLength + thumbEndLength + centerButtonLength
  208. : increaseButtonLength + thumbStartLength + centerButtonLength;
  209. pieceSize.Height = thumbStartLength;
  210. ArrangeThumb(isDirectionReversed, false, offset, pieceSize);
  211. offset.Y = isDirectionReversed ? decreaseButtonLength : increaseButtonLength;
  212. pieceSize.Height = thumbEndLength;
  213. ArrangeThumb(isDirectionReversed, true, offset, pieceSize);
  214. }
  215. else
  216. {
  217. CoerceLength(ref decreaseButtonLength, arrangeSize.Width);
  218. CoerceLength(ref centerButtonLength, arrangeSize.Width);
  219. CoerceLength(ref increaseButtonLength, arrangeSize.Width);
  220. CoerceLength(ref thumbStartLength, arrangeSize.Width);
  221. CoerceLength(ref thumbEndLength, arrangeSize.Width);
  222. offset.X = isDirectionReversed ? 0 : decreaseButtonLength + thumbEndLength + centerButtonLength + thumbStartLength;
  223. pieceSize.Width = increaseButtonLength;
  224. IncreaseRepeatButton?.Arrange(new Rect(offset, pieceSize));
  225. offset.X = isDirectionReversed ? increaseButtonLength + thumbStartLength : decreaseButtonLength + thumbEndLength;
  226. pieceSize.Width = centerButtonLength;
  227. CenterRepeatButton?.Arrange(new Rect(offset, pieceSize));
  228. offset.X = isDirectionReversed ? increaseButtonLength + thumbStartLength + centerButtonLength + thumbEndLength : 0;
  229. pieceSize.Width = decreaseButtonLength;
  230. DecreaseRepeatButton?.Arrange(new Rect(offset, pieceSize));
  231. offset.X = isDirectionReversed ? increaseButtonLength : decreaseButtonLength;
  232. pieceSize.Width = thumbStartLength;
  233. ArrangeThumb(isDirectionReversed, false, offset, pieceSize);
  234. offset.X = isDirectionReversed
  235. ? increaseButtonLength + thumbStartLength + centerButtonLength
  236. : decreaseButtonLength + thumbEndLength + centerButtonLength;
  237. pieceSize.Width = thumbEndLength;
  238. ArrangeThumb(isDirectionReversed, true, offset, pieceSize);
  239. }
  240. return arrangeSize;
  241. }
  242. private void ArrangeThumb(bool isDirectionReversed, bool isStart, Point offset, Size pieceSize)
  243. {
  244. if (isStart)
  245. {
  246. if (isDirectionReversed)
  247. {
  248. ThumbStart?.Arrange(new Rect(offset, pieceSize));
  249. }
  250. else
  251. {
  252. ThumbEnd?.Arrange(new Rect(offset, pieceSize));
  253. }
  254. }
  255. else
  256. {
  257. if (isDirectionReversed)
  258. {
  259. ThumbEnd?.Arrange(new Rect(offset, pieceSize));
  260. }
  261. else
  262. {
  263. ThumbStart?.Arrange(new Rect(offset, pieceSize));
  264. }
  265. }
  266. }
  267. private void ComputeLengths(Size arrangeSize, bool isVertical, out double decreaseButtonLength,
  268. out double centerButtonLength, out double increaseButtonLength, out double thumbStartLength,
  269. out double thumbEndLength)
  270. {
  271. var min = Minimum;
  272. var range = Math.Max(0.0, Maximum - min);
  273. var offsetStart = Math.Min(range, ValueStart - min);
  274. var offsetEnd = Math.Min(range, ValueEnd - min);
  275. double trackLength;
  276. // Compute thumb size
  277. if (isVertical)
  278. {
  279. trackLength = arrangeSize.Height;
  280. thumbStartLength = _thumbStart?.DesiredSize.Height ?? 0;
  281. thumbEndLength = _thumbEnd?.DesiredSize.Height ?? 0;
  282. }
  283. else
  284. {
  285. trackLength = arrangeSize.Width;
  286. thumbStartLength = _thumbStart?.DesiredSize.Width ?? 0;
  287. thumbEndLength = _thumbEnd?.DesiredSize.Width ?? 0;
  288. }
  289. CoerceLength(ref thumbStartLength, trackLength);
  290. CoerceLength(ref thumbEndLength, trackLength);
  291. var remainingTrackLength = trackLength - thumbStartLength - thumbEndLength;
  292. decreaseButtonLength = remainingTrackLength * offsetStart / range;
  293. CoerceLength(ref decreaseButtonLength, remainingTrackLength);
  294. centerButtonLength = remainingTrackLength * offsetEnd / range - decreaseButtonLength;
  295. CoerceLength(ref centerButtonLength, remainingTrackLength);
  296. increaseButtonLength = remainingTrackLength - decreaseButtonLength - centerButtonLength;
  297. CoerceLength(ref increaseButtonLength, remainingTrackLength);
  298. Density = range / remainingTrackLength;
  299. }
  300. protected override int VisualChildrenCount
  301. {
  302. get
  303. {
  304. if (_visualChildren == null) return 0;
  305. for (var i = 0; i < _visualChildren.Length; i++)
  306. {
  307. if (_visualChildren[i] == null) return i;
  308. }
  309. return _visualChildren.Length;
  310. }
  311. }
  312. private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  313. {
  314. if ((bool) e.NewValue)
  315. {
  316. Mouse.Synchronize();
  317. }
  318. }
  319. public virtual double ValueFromPoint(Point pt)
  320. {
  321. return Orientation == Orientation.Horizontal
  322. ? !IsDirectionReversed
  323. ? pt.X / RenderSize.Width * Maximum
  324. : (1 - pt.X / RenderSize.Width) * Maximum
  325. : !IsDirectionReversed
  326. ? pt.Y / RenderSize.Height * Maximum
  327. : (1 - pt.X / RenderSize.Height) * Maximum;
  328. }
  329. public virtual double ValueFromDistance(double horizontal, double vertical)
  330. {
  331. double scale = IsDirectionReversed ? -1 : 1;
  332. return Orientation == Orientation.Horizontal
  333. ? scale * horizontal * Density
  334. : -1 * scale * vertical * Density;
  335. }
  336. private void UpdateComponent(Control oldValue, Control newValue)
  337. {
  338. if (oldValue != newValue)
  339. {
  340. _visualChildren ??= new Visual[5];
  341. if (oldValue != null)
  342. {
  343. // notify the visual layer that the old component has been removed.
  344. RemoveVisualChild(oldValue);
  345. }
  346. // Remove the old value from our z index list and add new value to end
  347. int i = 0;
  348. while (i < 5)
  349. {
  350. // Array isn't full, break
  351. if (_visualChildren[i] == null)
  352. break;
  353. // found the old value
  354. if (_visualChildren[i] == oldValue)
  355. {
  356. // Move values down until end of array or a null element
  357. while (i < 4 && _visualChildren[i + 1] != null)
  358. {
  359. _visualChildren[i] = _visualChildren[i + 1];
  360. i++;
  361. }
  362. }
  363. else
  364. {
  365. i++;
  366. }
  367. }
  368. // Add newValue at end of z-order
  369. _visualChildren[i] = newValue;
  370. AddVisualChild(newValue);
  371. InvalidateMeasure();
  372. InvalidateArrange();
  373. }
  374. }
  375. }