ScrollViewer.cs 7.4 KB

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