终端一体化运控平台
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

226 lines
7.8 KiB

  1. using BPASmartClient.MilkWithTea.Data;
  2. using System;
  3. using System.Windows;
  4. using System.Windows.Controls;
  5. using System.Windows.Input;
  6. using System.Windows.Media;
  7. using System.Windows.Media.Animation;
  8. namespace BPASmartClient.MilkWithTea.Control;
  9. public class ScrollViewer : System.Windows.Controls.ScrollViewer
  10. {
  11. private double _totalVerticalOffset;
  12. private double _totalHorizontalOffset;
  13. private bool _isRunning;
  14. /// <summary>
  15. /// 是否响应鼠标滚轮操作
  16. /// </summary>
  17. public static readonly DependencyProperty CanMouseWheelProperty = DependencyProperty.Register(
  18. "CanMouseWheel", typeof(bool), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.TrueBox));
  19. /// <summary>
  20. /// 是否响应鼠标滚轮操作
  21. /// </summary>
  22. public bool CanMouseWheel
  23. {
  24. get => (bool) GetValue(CanMouseWheelProperty);
  25. set => SetValue(CanMouseWheelProperty, ValueBoxes.BooleanBox(value));
  26. }
  27. protected override void OnMouseWheel(MouseWheelEventArgs e)
  28. {
  29. if (!CanMouseWheel) return;
  30. if (!IsInertiaEnabled)
  31. {
  32. if (ScrollViewerAttach.GetOrientation(this) == Orientation.Vertical)
  33. {
  34. base.OnMouseWheel(e);
  35. }
  36. else
  37. {
  38. _totalHorizontalOffset = HorizontalOffset;
  39. CurrentHorizontalOffset = HorizontalOffset;
  40. _totalHorizontalOffset = Math.Min(Math.Max(0, _totalHorizontalOffset - e.Delta), ScrollableWidth);
  41. CurrentHorizontalOffset = _totalHorizontalOffset;
  42. }
  43. return;
  44. }
  45. e.Handled = true;
  46. if (ScrollViewerAttach.GetOrientation(this) == Orientation.Vertical)
  47. {
  48. if (!_isRunning)
  49. {
  50. _totalVerticalOffset = VerticalOffset;
  51. CurrentVerticalOffset = VerticalOffset;
  52. }
  53. _totalVerticalOffset = Math.Min(Math.Max(0, _totalVerticalOffset - e.Delta), ScrollableHeight);
  54. ScrollToVerticalOffsetWithAnimation(_totalVerticalOffset);
  55. }
  56. else
  57. {
  58. if (!_isRunning)
  59. {
  60. _totalHorizontalOffset = HorizontalOffset;
  61. CurrentHorizontalOffset = HorizontalOffset;
  62. }
  63. _totalHorizontalOffset = Math.Min(Math.Max(0, _totalHorizontalOffset - e.Delta), ScrollableWidth);
  64. ScrollToHorizontalOffsetWithAnimation(_totalHorizontalOffset);
  65. }
  66. }
  67. internal void ScrollToTopInternal(double milliseconds = 500)
  68. {
  69. if (!_isRunning)
  70. {
  71. _totalVerticalOffset = VerticalOffset;
  72. CurrentVerticalOffset = VerticalOffset;
  73. }
  74. ScrollToVerticalOffsetWithAnimation(0, milliseconds);
  75. }
  76. public void ScrollToVerticalOffsetWithAnimation(double offset, double milliseconds = 500)
  77. {
  78. var animation = CreateAnimation(offset, milliseconds);
  79. animation.EasingFunction = new CubicEase
  80. {
  81. EasingMode = EasingMode.EaseOut
  82. };
  83. animation.FillBehavior = FillBehavior.Stop;
  84. animation.Completed += (s, e1) =>
  85. {
  86. CurrentVerticalOffset = offset;
  87. _isRunning = false;
  88. };
  89. _isRunning = true;
  90. BeginAnimation(CurrentVerticalOffsetProperty, animation, HandoffBehavior.Compose);
  91. }
  92. public void ScrollToHorizontalOffsetWithAnimation(double offset, double milliseconds = 500)
  93. {
  94. var animation = CreateAnimation(offset, milliseconds);
  95. animation.EasingFunction = new CubicEase
  96. {
  97. EasingMode = EasingMode.EaseOut
  98. };
  99. animation.FillBehavior = FillBehavior.Stop;
  100. animation.Completed += (s, e1) =>
  101. {
  102. CurrentHorizontalOffset = offset;
  103. _isRunning = false;
  104. };
  105. _isRunning = true;
  106. BeginAnimation(CurrentHorizontalOffsetProperty, animation, HandoffBehavior.Compose);
  107. }
  108. protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) =>
  109. IsPenetrating ? null : base.HitTestCore(hitTestParameters);
  110. /// <summary>
  111. /// 是否支持惯性
  112. /// </summary>
  113. public static readonly DependencyProperty IsInertiaEnabledProperty = DependencyProperty.RegisterAttached(
  114. "IsInertiaEnabled", typeof(bool), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.FalseBox));
  115. public static void SetIsInertiaEnabled(DependencyObject element, bool value) => element.SetValue(IsInertiaEnabledProperty, ValueBoxes.BooleanBox(value));
  116. public static bool GetIsInertiaEnabled(DependencyObject element) => (bool) element.GetValue(IsInertiaEnabledProperty);
  117. /// <summary>
  118. /// 是否支持惯性
  119. /// </summary>
  120. public bool IsInertiaEnabled
  121. {
  122. get => (bool) GetValue(IsInertiaEnabledProperty);
  123. set => SetValue(IsInertiaEnabledProperty, ValueBoxes.BooleanBox(value));
  124. }
  125. /// <summary>
  126. /// 控件是否可以穿透点击
  127. /// </summary>
  128. public static readonly DependencyProperty IsPenetratingProperty = DependencyProperty.RegisterAttached(
  129. "IsPenetrating", typeof(bool), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.FalseBox));
  130. /// <summary>
  131. /// 控件是否可以穿透点击
  132. /// </summary>
  133. public bool IsPenetrating
  134. {
  135. get => (bool) GetValue(IsPenetratingProperty);
  136. set => SetValue(IsPenetratingProperty, ValueBoxes.BooleanBox(value));
  137. }
  138. public static void SetIsPenetrating(DependencyObject element, bool value) => element.SetValue(IsPenetratingProperty, ValueBoxes.BooleanBox(value));
  139. public static bool GetIsPenetrating(DependencyObject element) => (bool) element.GetValue(IsPenetratingProperty);
  140. /// <summary>
  141. /// 当前垂直滚动偏移
  142. /// </summary>
  143. internal static readonly DependencyProperty CurrentVerticalOffsetProperty = DependencyProperty.Register(
  144. "CurrentVerticalOffset", typeof(double), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.Double0Box, OnCurrentVerticalOffsetChanged));
  145. private static void OnCurrentVerticalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  146. {
  147. if (d is ScrollViewer ctl && e.NewValue is double v)
  148. {
  149. ctl.ScrollToVerticalOffset(v);
  150. }
  151. }
  152. /// <summary>
  153. /// 当前垂直滚动偏移
  154. /// </summary>
  155. internal double CurrentVerticalOffset
  156. {
  157. // ReSharper disable once UnusedMember.Local
  158. get => (double) GetValue(CurrentVerticalOffsetProperty);
  159. set => SetValue(CurrentVerticalOffsetProperty, value);
  160. }
  161. /// <summary>
  162. /// 当前水平滚动偏移
  163. /// </summary>
  164. internal static readonly DependencyProperty CurrentHorizontalOffsetProperty = DependencyProperty.Register(
  165. "CurrentHorizontalOffset", typeof(double), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.Double0Box, OnCurrentHorizontalOffsetChanged));
  166. private static void OnCurrentHorizontalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  167. {
  168. if (d is ScrollViewer ctl && e.NewValue is double v)
  169. {
  170. ctl.ScrollToHorizontalOffset(v);
  171. }
  172. }
  173. /// <summary>
  174. /// 当前水平滚动偏移
  175. /// </summary>
  176. internal double CurrentHorizontalOffset
  177. {
  178. get => (double) GetValue(CurrentHorizontalOffsetProperty);
  179. set => SetValue(CurrentHorizontalOffsetProperty, value);
  180. }
  181. /// <summary>
  182. /// 创建一个Double动画
  183. /// </summary>
  184. /// <param name="toValue"></param>
  185. /// <param name="milliseconds"></param>
  186. /// <returns></returns>
  187. public DoubleAnimation CreateAnimation(double toValue, double milliseconds = 200)
  188. {
  189. return new(toValue, new Duration(TimeSpan.FromMilliseconds(milliseconds)))
  190. {
  191. EasingFunction = new PowerEase { EasingMode = EasingMode.EaseInOut }
  192. };
  193. }
  194. }