在 WPF 中.如何通过鼠标拖动在ScrollViewer中滚动对象,就像iPhone一样


通过

鼠标滚轮或滚动条种子拖动进行滚动是很好的。但是无法通过在滚动视图上拖动鼠标内容来滚动。如何实现此操作?

        <ScrollViewer x:Name="scrollViewer" Grid.Row="1" HorizontalScrollBarVisibility="Auto" CanContentScroll="True">
            <Grid x:Name="galleryGrid" ShowGridLines="True">
                <Grid.RowDefinitions>
                    <RowDefinition Height="*"></RowDefinition>
                    <RowDefinition Height="*"></RowDefinition>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="500"></ColumnDefinition>
                    <ColumnDefinition Width="500"></ColumnDefinition>
                    <ColumnDefinition Width="500"></ColumnDefinition>
                    <ColumnDefinition Width="500"></ColumnDefinition>
                    <ColumnDefinition Width="500"></ColumnDefinition>
                </Grid.ColumnDefinitions>
                <Button Grid.Column="0" Magin="10,10,10,10">Test</Button>
                <Button Grid.Column="1" Magin="10,10,10,10">Test</Button>
                <Button Grid.Column="2" Magin="10,10,10,10">Test</Button>
                <Button Grid.Column="3" Magin="10,10,10,10">Test</Button>
                <Button Grid.Column="4" Magin="10,10,10,10">Test</Button>
            </Grid>
        </ScrollViewer>

我找到了解决这个问题的方法。它正在以下...

    Point scrollMousePoint = new Point();
    double hOff = 1;
    private void scrollViewer_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        scrollMousePoint = e.GetPosition(scrollViewer);
        hOff = scrollViewer.HorizontalOffset;
        scrollViewer.CaptureMouse();
    }
    private void scrollViewer_PreviewMouseMove(object sender, MouseEventArgs e)
    {
        if(scrollViewer.IsMouseCaptured)
        {
            scrollViewer.ScrollToHorizontalOffset(hOff + (scrollMousePoint.X - e.GetPosition(scrollViewer).X));
        }
    }
    private void scrollViewer_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        scrollViewer.ReleaseMouseCapture();
    }
    private void scrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
    {
        scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset + e.Delta);
    }

谢谢!

我在保留滚动查看器按钮的同时做了这个类。来自 user110777 的代码,但这适用于垂直而不是水平,并且适用于查看器(因为我现在只捕获内容)。另外,我使用MouseLeftButtonDown而不是预览,以便允许用户单击组合框之类的内容而不会引起拖动。(如果您希望标签或文本块拖动设置其IsHitTestVisible=false)

public class ScrollDragger
{
    private readonly ScrollViewer _scrollViewer;
    private readonly UIElement _content;
    private Point _scrollMousePoint;
    private double _hOff = 1;
    public ScrollDragger(UIElement content, ScrollViewer scrollViewer)
    {
        _scrollViewer = scrollViewer;
        _content = content;
        content.MouseLeftButtonDown += scrollViewer_MouseLeftButtonDown;
        content.PreviewMouseMove += scrollViewer_PreviewMouseMove;
        content.PreviewMouseLeftButtonUp += scrollViewer_PreviewMouseLeftButtonUp;
    }
    private void scrollViewer_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        _content.CaptureMouse();
        _scrollMousePoint = e.GetPosition(_scrollViewer);
        _hOff = _scrollViewer.VerticalOffset;
    }
    private void scrollViewer_PreviewMouseMove(object sender, MouseEventArgs e)
    {
        if (_content.IsMouseCaptured)
        {
            var newOffset = _hOff + (_scrollMousePoint.Y - e.GetPosition(_scrollViewer).Y);
            _scrollViewer.ScrollToVerticalOffset(newOffset);
        }
    }
    private void scrollViewer_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        _content.ReleaseMouseCapture();
    } 
}

这就是我的做法,XAML:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="20px"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <TextBlock Grid.Row="0" Text="this is not in the scrollviewer" Name="tb"/>
    <ScrollViewer Name="sv" 
                  HorizontalScrollBarVisibility="Auto" 
                  Grid.Row="1">
        <StackPanel Name="sp" Width="500" Height="500"
                    MouseMove="sp_MouseMove" 
                    Background="Transparent">
            <Ellipse Height="50" Width="50" Fill="Green"/>
        </StackPanel>
    </ScrollViewer>
</Grid>

C#:

private void sp_MouseMove(object sender, MouseEventArgs e)
    {
        Point newMousePosition = Mouse.GetPosition((StackPanel)sender);
        tb.Text = newMousePosition.X + " | " + newMousePosition.Y;
        if (Mouse.LeftButton == MouseButtonState.Pressed)
        {
            if (newMousePosition.Y < oldMousePosition.Y)
                sv.ScrollToVerticalOffset(sv.VerticalOffset + 1);
            if (newMousePosition.Y > oldMousePosition.Y)
                sv.ScrollToVerticalOffset(sv.VerticalOffset - 1);
            if (newMousePosition.X < oldMousePosition.X)
                sv.ScrollToHorizontalOffset(sv.HorizontalOffset + 1);
            if (newMousePosition.X > oldMousePosition.X)
                sv.ScrollToHorizontalOffset(sv.HorizontalOffset - 1);
        }
        else
        {
            oldMousePosition = newMousePosition;
        }
    }

其中Point oldMousePosition;是窗口的成员。

UWP 变体:

    Pointer pointer;
    PointerPoint scrollMousePoint ;
    double hOff = 1;
    private void MainScrollviewer_PointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
    {
        pointer = e.Pointer;
        scrollMousePoint = e.GetCurrentPoint(scrollviewer);
        hOff = scrollviewer.HorizontalOffset;
        scrollviewer.CapturePointer(pointer);
    }
    private void MainScrollviewer_PointerReleased(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
    {
        scrollviewer.ReleasePointerCaptures();
    }
    private void MainScrollviewer_PointerMoved(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
    {
        if (scrollviewer.PointerCaptures!= null&& scrollviewer.PointerCaptures.Count>0)
        {
          scrollviewer.ChangeView(hOff + (scrollMousePoint.Position.X - e.GetCurrentPoint(scrollviewer).Position.X),null,null);
        }
    }

我知道这个问题是针对 WPF 的,但这是我搜索 UWP 解决方案的最佳结果。

使用这个

用于水平滚动

private void ScrollViewer_OnPreviewMouseWheel(object sender, MouseWheelEventArgs e)     
{             
    ScrollViewer scv = (ScrollViewer)sender;               
    scv.ScrollToHorizontalOffset(scv.HorizontalOffset - e.Delta);
    e.Handled = true;    
}

您可以在 C# WPF 中像这样执行此操作。请注意,有两种类型的点类。从他们那里,您应该使用System.Windows.Point来获得这项工作。这些是 ScrollViewer 中与鼠标相关的事件。下面的代码将在 ScrollViewer 内将对象滚动到水平和垂直。

   System.Windows.Point ScrollMousePoint1 = new System.Windows.Point();
   double HorizontalOff1 = 1; double VerticalOff1 = 1;
   private void ScrollViewer1_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            ScrollMousePoint1 = e.GetPosition(ScrollViewer1);
            HorizontalOff1 = ScrollViewer1.HorizontalOffset;
            VerticalOff1 = ScrollViewer1.VerticalOffset;
            ScrollViewer1.CaptureMouse();
        }
        private void ScrollViewer1_PreviewMouseMove(object sender, System.Windows.Input.MouseEventArgs e)
        {
            if (ScrollViewer1.IsMouseCaptured)
            {
                ScrollViewer1.ScrollToHorizontalOffset(HorizontalOff1 + (ScrollMousePoint1.X - e.GetPosition(ScrollViewer1).X));
                ScrollViewer1.ScrollToVerticalOffset(VerticalOff1 + (ScrollMousePoint1.Y - e.GetPosition(ScrollViewer1).Y));
            }
        }
        private void ScrollViewer1_PreviewMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            ScrollViewer1.ReleaseMouseCapture();
        }
        private void ScrollViewer1_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
        {
            ScrollViewer1.ScrollToHorizontalOffset(ScrollViewer1.HorizontalOffset + e.Delta);
            ScrollViewer1.ScrollToVerticalOffset(ScrollViewer1.VerticalOffset + e.Delta);
        }

我的经验:关注用户110777更改了以下代码

private void scrollViewer_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
       scrollMousePoint = e.GetPosition(scrollViewer);
        hOff = scrollViewer.HorizontalOffset;
        scrollViewer.CaptureMouse();
    }
更新了

水平和垂直组合拖动

从 https://stackoverflow.com/a/42288914/3169805 开始,我修改了它以也进行水平拖动,并修复了一些质量问题,例如在边缘附近跳来跳去,以及当您的窗口未最大化时超出可滚动区域而导致的死区。在拖动操作期间还将光标设置为手形。

public class ScrollDragger
{
    private readonly ScrollViewer _scrollViewer;
    private readonly UIElement _content;
    private readonly Cursor _dragCursor = Cursors.Hand;
    private double _scrollMouseX;
    private double _scrollMouseY;
    private int _updateCounter = 0;
    public ScrollDragger(UIElement content, ScrollViewer scrollViewer)
    {
        _scrollViewer = scrollViewer;
        _content = content;
        content.MouseLeftButtonDown += scrollViewer_MouseLeftButtonDown;
        content.PreviewMouseMove += scrollViewer_PreviewMouseMove;
        content.PreviewMouseLeftButtonUp += scrollViewer_PreviewMouseLeftButtonUp;
    }
    private void scrollViewer_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        // Capture the mouse, reset counter, switch to hand cursor to indicate dragging
        _content.CaptureMouse();
        _updateCounter = 0;
        _scrollViewer.Cursor = _dragCursor;
    }
    private void scrollViewer_PreviewMouseMove(object sender, MouseEventArgs e)
    {
        if (_content.IsMouseCaptured)
        {
            _updateCounter++;
            // Skip dragging on the first PreviewMouseMove event after the left mouse button goes down. It actually triggers two of these and this ignores both, preventing jumping.
            if (_updateCounter <= 1)
            {
                // Grab starting mouse offset relative to scroll viewer, used to calculate first delta
                _scrollMouseY = e.GetPosition(_scrollViewer).Y;
                _scrollMouseX = e.GetPosition(_scrollViewer).X;
                return;
            }
            // Calculate new vertical offset then scroll to it
            var newVOff = HandleMouseMoveAxisUpdateScroll(_scrollViewer.VerticalOffset, ref _scrollMouseY, e.GetPosition(_scrollViewer).Y, _scrollViewer.ScrollableHeight);
            _scrollViewer.ScrollToVerticalOffset(newVOff);
            // Calculate new horizontal offset and scroll to it
            var newHOff = HandleMouseMoveAxisUpdateScroll(_scrollViewer.HorizontalOffset, ref _scrollMouseX, e.GetPosition(_scrollViewer).X, _scrollViewer.ScrollableWidth);
            _scrollViewer.ScrollToHorizontalOffset(newHOff);
        }
    }
    private double HandleMouseMoveAxisUpdateScroll(double offsetStart, ref double oldScrollMouse, double newScrollMouse, double scrollableMax)
    {
        // How far does the user want to drag since the last update?
        var mouseDelta = newScrollMouse - oldScrollMouse;
        // Add mouse delta to current scroll offset to get the new expected scroll offset
        var newScrollOffset = offsetStart + mouseDelta;
        // Keep the scroll offset from going off the screen
        var newScrollOffsetClamped = newScrollOffset.Clamp(0, scrollableMax);
        // Save the current mouse position in scroll coordinates so that we'll have it for next update
        oldScrollMouse = newScrollMouse;
        return newScrollOffsetClamped;
    }
    private void scrollViewer_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        _content.ReleaseMouseCapture();
        _updateCounter = 0; // Reset counter, used to prevent jumping at start of drag
        _scrollViewer.Cursor = null;
    }
    public void Unload()
    {
        _content.MouseLeftButtonDown -= scrollViewer_MouseLeftButtonDown;
        _content.PreviewMouseMove -= scrollViewer_PreviewMouseMove;
        _content.PreviewMouseLeftButtonUp -= scrollViewer_PreviewMouseLeftButtonUp;
    }
}
public static class MathExtensions
{
    // Clamp the value between the min and max. Value returned will be min or max if it's below min or above max
    public static double Clamp(this Double value, double min, double max)
    {
        return Math.Min(Math.Max(value, min), max);
    }
}

非常感谢原始回答者和此线程上的其他人让我入门。

在类构造器上使用此扩展方法:

scrollViewer.ScrollHorizontalByDrag();

    public static void ScrollHorizontalByDrag(this ScrollViewer scrollViewer)
    {
        double hOff = 1;
        Point scrollMousePoint = new();
        scrollViewer.PreviewMouseLeftButtonDown += (s, e) =>
        {
            scrollMousePoint = e.GetPosition(scrollViewer);
            hOff = scrollViewer.HorizontalOffset;
            scrollViewer.CaptureMouse();
        };
        scrollViewer.PreviewMouseMove += (s, e) =>
        {
            if (scrollViewer.IsMouseCaptured)
                scrollViewer.ScrollToHorizontalOffset(hOff + (scrollMousePoint.X - e.GetPosition(scrollViewer).X));
        };
        scrollViewer.PreviewMouseLeftButtonUp += (s, e) => scrollViewer.ReleaseMouseCapture();
        //scrollViewer.PreviewMouseWheel += (s, e) => scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset + e.Delta);
    }

鼠标事件无法区分单击和拖动。因此,有必要将点击时间与保持时间进行比较。这样,单击时,无需拖动,您可以访问它包含的对象。按住对象时,对象将变为可拖动对象。在下面的示例中,可以区分单击并按住状态。您可以使用长按持续时间毫秒更改保持时间。

class DraggableScrollViewer : ScrollViewer
{
    private bool isPressed = false;
    private DateTime pressStartTime;
    private const int longPressDurationMilliseconds = 25;
    private Point startPoint;
    private bool isDragging = false;
    protected override void OnPreviewMouseMove(MouseEventArgs e)
    {
        base.OnPreviewMouseMove(e);
        if (isPressed)
        {
            TimeSpan pressDuration = DateTime.Now - pressStartTime;
            if (pressDuration.TotalMilliseconds >= longPressDurationMilliseconds)
            {
                CaptureMouse();
                if (isDragging && e.LeftButton == MouseButtonState.Pressed)
                {
                    Point currentPoint = e.GetPosition(this);
                    Vector offset = startPoint - currentPoint;
                    ScrollToVerticalOffset(VerticalOffset + offset.Y);
                    ScrollToHorizontalOffset(HorizontalOffset + offset.X);
                    startPoint = currentPoint;
                }
            }
        }
    }
    protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
    {
        base.OnPreviewMouseLeftButtonUp(e);
        isDragging = false;
        isPressed = false;
        ReleaseMouseCapture();
    }
    protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
    {
        base.OnPreviewMouseLeftButtonDown(e);
        startPoint = e.GetPosition(this);
        isDragging = true;
        pressStartTime = DateTime.Now;
        isPressed = true;
    }
}

最新更新