using BPASmartClient.MilkWithTea.Data; using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; namespace BPASmartClient.MilkWithTea.Control; public class ScrollViewer : System.Windows.Controls.ScrollViewer { private double _totalVerticalOffset; private double _totalHorizontalOffset; private bool _isRunning; /// /// 是否响应鼠标滚轮操作 /// public static readonly DependencyProperty CanMouseWheelProperty = DependencyProperty.Register( "CanMouseWheel", typeof(bool), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.TrueBox)); /// /// 是否响应鼠标滚轮操作 /// public bool CanMouseWheel { get => (bool) GetValue(CanMouseWheelProperty); set => SetValue(CanMouseWheelProperty, ValueBoxes.BooleanBox(value)); } protected override void OnMouseWheel(MouseWheelEventArgs e) { if (!CanMouseWheel) return; if (!IsInertiaEnabled) { if (ScrollViewerAttach.GetOrientation(this) == Orientation.Vertical) { base.OnMouseWheel(e); } else { _totalHorizontalOffset = HorizontalOffset; CurrentHorizontalOffset = HorizontalOffset; _totalHorizontalOffset = Math.Min(Math.Max(0, _totalHorizontalOffset - e.Delta), ScrollableWidth); CurrentHorizontalOffset = _totalHorizontalOffset; } return; } e.Handled = true; if (ScrollViewerAttach.GetOrientation(this) == Orientation.Vertical) { if (!_isRunning) { _totalVerticalOffset = VerticalOffset; CurrentVerticalOffset = VerticalOffset; } _totalVerticalOffset = Math.Min(Math.Max(0, _totalVerticalOffset - e.Delta), ScrollableHeight); ScrollToVerticalOffsetWithAnimation(_totalVerticalOffset); } else { if (!_isRunning) { _totalHorizontalOffset = HorizontalOffset; CurrentHorizontalOffset = HorizontalOffset; } _totalHorizontalOffset = Math.Min(Math.Max(0, _totalHorizontalOffset - e.Delta), ScrollableWidth); ScrollToHorizontalOffsetWithAnimation(_totalHorizontalOffset); } } internal void ScrollToTopInternal(double milliseconds = 500) { if (!_isRunning) { _totalVerticalOffset = VerticalOffset; CurrentVerticalOffset = VerticalOffset; } ScrollToVerticalOffsetWithAnimation(0, milliseconds); } public void ScrollToVerticalOffsetWithAnimation(double offset, double milliseconds = 500) { var animation = CreateAnimation(offset, milliseconds); animation.EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }; animation.FillBehavior = FillBehavior.Stop; animation.Completed += (s, e1) => { CurrentVerticalOffset = offset; _isRunning = false; }; _isRunning = true; BeginAnimation(CurrentVerticalOffsetProperty, animation, HandoffBehavior.Compose); } public void ScrollToHorizontalOffsetWithAnimation(double offset, double milliseconds = 500) { var animation = CreateAnimation(offset, milliseconds); animation.EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }; animation.FillBehavior = FillBehavior.Stop; animation.Completed += (s, e1) => { CurrentHorizontalOffset = offset; _isRunning = false; }; _isRunning = true; BeginAnimation(CurrentHorizontalOffsetProperty, animation, HandoffBehavior.Compose); } protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) => IsPenetrating ? null : base.HitTestCore(hitTestParameters); /// /// 是否支持惯性 /// public static readonly DependencyProperty IsInertiaEnabledProperty = DependencyProperty.RegisterAttached( "IsInertiaEnabled", typeof(bool), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.FalseBox)); public static void SetIsInertiaEnabled(DependencyObject element, bool value) => element.SetValue(IsInertiaEnabledProperty, ValueBoxes.BooleanBox(value)); public static bool GetIsInertiaEnabled(DependencyObject element) => (bool) element.GetValue(IsInertiaEnabledProperty); /// /// 是否支持惯性 /// public bool IsInertiaEnabled { get => (bool) GetValue(IsInertiaEnabledProperty); set => SetValue(IsInertiaEnabledProperty, ValueBoxes.BooleanBox(value)); } /// /// 控件是否可以穿透点击 /// public static readonly DependencyProperty IsPenetratingProperty = DependencyProperty.RegisterAttached( "IsPenetrating", typeof(bool), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.FalseBox)); /// /// 控件是否可以穿透点击 /// public bool IsPenetrating { get => (bool) GetValue(IsPenetratingProperty); set => SetValue(IsPenetratingProperty, ValueBoxes.BooleanBox(value)); } public static void SetIsPenetrating(DependencyObject element, bool value) => element.SetValue(IsPenetratingProperty, ValueBoxes.BooleanBox(value)); public static bool GetIsPenetrating(DependencyObject element) => (bool) element.GetValue(IsPenetratingProperty); /// /// 当前垂直滚动偏移 /// internal static readonly DependencyProperty CurrentVerticalOffsetProperty = DependencyProperty.Register( "CurrentVerticalOffset", typeof(double), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.Double0Box, OnCurrentVerticalOffsetChanged)); private static void OnCurrentVerticalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is ScrollViewer ctl && e.NewValue is double v) { ctl.ScrollToVerticalOffset(v); } } /// /// 当前垂直滚动偏移 /// internal double CurrentVerticalOffset { // ReSharper disable once UnusedMember.Local get => (double) GetValue(CurrentVerticalOffsetProperty); set => SetValue(CurrentVerticalOffsetProperty, value); } /// /// 当前水平滚动偏移 /// internal static readonly DependencyProperty CurrentHorizontalOffsetProperty = DependencyProperty.Register( "CurrentHorizontalOffset", typeof(double), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.Double0Box, OnCurrentHorizontalOffsetChanged)); private static void OnCurrentHorizontalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is ScrollViewer ctl && e.NewValue is double v) { ctl.ScrollToHorizontalOffset(v); } } /// /// 当前水平滚动偏移 /// internal double CurrentHorizontalOffset { get => (double) GetValue(CurrentHorizontalOffsetProperty); set => SetValue(CurrentHorizontalOffsetProperty, value); } /// /// 创建一个Double动画 /// /// /// /// public DoubleAnimation CreateAnimation(double toValue, double milliseconds = 200) { return new(toValue, new Duration(TimeSpan.FromMilliseconds(milliseconds))) { EasingFunction = new PowerEase { EasingMode = EasingMode.EaseInOut } }; } }