using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; namespace AIStudio.Wpf.ADiagram.Controls { public class ScrollViewer : System.Windows.Controls.ScrollViewer { private double _totalVerticalOffset; private double _totalHorizontalOffset; private bool _isRunning; /// /// 滚动方向 /// public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register( "Orientation", typeof(Orientation), typeof(ScrollViewer), new PropertyMetadata(Orientation.Vertical)); /// /// 滚动方向 /// public Orientation Orientation { get => (Orientation)GetValue(OrientationProperty); set => SetValue(OrientationProperty, value); } /// /// 是否响应鼠标滚轮操作 /// public static readonly DependencyProperty CanMouseWheelProperty = DependencyProperty.Register( "CanMouseWheel", typeof(bool), typeof(ScrollViewer), new PropertyMetadata(true)); /// /// 是否响应鼠标滚轮操作 /// public bool CanMouseWheel { get => (bool)GetValue(CanMouseWheelProperty); set => SetValue(CanMouseWheelProperty, value); } protected override void OnMouseWheel(MouseWheelEventArgs e) { if (!CanMouseWheel) return; if (!IsInertiaEnabled) { if (Orientation == 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 (Orientation == Orientation.Vertical) { if (!_isRunning) { _totalVerticalOffset = VerticalOffset; CurrentVerticalOffset = VerticalOffset; } _totalVerticalOffset = Math.Min(Math.Max(0, _totalVerticalOffset - e.Delta), ScrollableHeight); ScrollToVerticalOffsetInternal(_totalVerticalOffset); } else { if (!_isRunning) { _totalHorizontalOffset = HorizontalOffset; CurrentHorizontalOffset = HorizontalOffset; } _totalHorizontalOffset = Math.Min(Math.Max(0, _totalHorizontalOffset - e.Delta), ScrollableWidth); ScrollToHorizontalOffsetInternal(_totalHorizontalOffset); } } internal void ScrollToTopInternal(double milliseconds = 500) { if (!_isRunning) { _totalVerticalOffset = VerticalOffset; CurrentVerticalOffset = VerticalOffset; } ScrollToVerticalOffsetInternal(0, milliseconds); } internal void ScrollToVerticalOffsetInternal(double offset, double milliseconds = 500) { var animation = AnimationHelper.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); } internal void ScrollToHorizontalOffsetInternal(double offset, double milliseconds = 500) { var animation = AnimationHelper.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(false)); public static void SetIsInertiaEnabled(DependencyObject element, bool value) { element.SetValue(IsInertiaEnabledProperty, value); } public static bool GetIsInertiaEnabled(DependencyObject element) { return (bool)element.GetValue(IsInertiaEnabledProperty); } /// /// 是否支持惯性 /// public bool IsInertiaEnabled { get => (bool)GetValue(IsInertiaEnabledProperty); set => SetValue(IsInertiaEnabledProperty, value); } /// /// 控件是否可以穿透点击 /// public static readonly DependencyProperty IsPenetratingProperty = DependencyProperty.RegisterAttached( "IsPenetrating", typeof(bool), typeof(ScrollViewer), new PropertyMetadata(false)); /// /// 控件是否可以穿透点击 /// public bool IsPenetrating { get => (bool)GetValue(IsPenetratingProperty); set => SetValue(IsPenetratingProperty, value); } public static void SetIsPenetrating(DependencyObject element, bool value) { element.SetValue(IsPenetratingProperty, value); } public static bool GetIsPenetrating(DependencyObject element) { return (bool)element.GetValue(IsPenetratingProperty); } /// /// 当前垂直滚动偏移 /// internal static readonly DependencyProperty CurrentVerticalOffsetProperty = DependencyProperty.Register( "CurrentVerticalOffset", typeof(double), typeof(ScrollViewer), new PropertyMetadata(0d, 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(0d, 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); } } }