user110777
user110777

Reputation: 421

in WPF. How to scroll Objects in ScrollViewer by mouse-dragging, like as iPhone?

it's done well to scroll by mouse-wheel or scrollbar seed-dragging. but scrolling by mouse-dragging contents on scroll view is not done. How can i implement this action?

        <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>

Upvotes: 8

Views: 21581

Answers (11)

GatitoCode
GatitoCode

Reputation: 1

I use this code and it works wonders for me without errors. To enable the effect of dragging the content of the ScrollViewer with the mouse pressed in both X and Y, We can wrap the content of the ScrollViewer in a Grid and add a PreviewMouseDown event to that Grid to determine if the mouse click occurred on the scroll bars to avoid errors.:

In the CS

private bool isDragging = false;
private Point lastMousePosition;

private void Grid_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
    var scrollViewer = (ScrollViewer)sender;
    var horizontalScrollBar = scrollViewer.Template.FindName("PART_HorizontalScrollBar", scrollViewer) as ScrollBar;
    var verticalScrollBar = scrollViewer.Template.FindName("PART_VerticalScrollBar", scrollViewer) as ScrollBar;

    if (horizontalScrollBar != null && horizontalScrollBar.IsMouseOver ||
        verticalScrollBar != null && verticalScrollBar.IsMouseOver)
    {
        isDragging = false;
        return;
    }

    isDragging = true;
    lastMousePosition = e.GetPosition(scrollViewer);
    scrollViewer.CaptureMouse();
}

private void Grid_PreviewMouseMove(object sender, MouseEventArgs e)
{
    if (isDragging)
    {
        var scrollViewer = (ScrollViewer)sender;
        Point currentMousePosition = e.GetPosition(scrollViewer);
        double horizontalOffset = scrollViewer.HorizontalOffset - (currentMousePosition.X - lastMousePosition.X);
        double verticalOffset = scrollViewer.VerticalOffset - (currentMousePosition.Y - lastMousePosition.Y);

        scrollViewer.ScrollToHorizontalOffset(horizontalOffset);
        scrollViewer.ScrollToVerticalOffset(verticalOffset);

        lastMousePosition = currentMousePosition;
    }
}

private void Grid_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
    isDragging = false;
    var scrollViewer = (ScrollViewer)sender;
    scrollViewer.ReleaseMouseCapture();
}

This code handles dragging of the ScrollViewer by using the Grid's PreviewMouseDown, PreviewMouseMove, and PreviewMouseUp events. The Grid must have these events assigned to it and will be used as the control that captures the mouse drag.

When the Grid is clicked, it checks whether the mouse is over the horizontal or vertical scroll bars. If so, isDragging is set to false to prevent dragging. Otherwise, isDragging is set to true, the initial mouse position is captured, and the mouse is captured in the ScrollViewer.

During mouse movement (PreviewMouseMove), the difference between the current mouse position and the previous mouse position is calculated to determine how much the ScrollViewer should scroll. Then, the ScrollToHorizontalOffset and ScrollToVerticalOffset method is used to adjust the scroll position.

When the mouse button is released (PreviewMouseUp), isDragging is set to false and mouse capture is released in the ScrollViewer.

Make sure you assign these events to your corresponding ScrollViewer:

<ScrollViewer x:Name="scrollViewer" 
      HorizontalScrollBarVisibility="Auto" 
      VerticalScrollBarVisibility="Auto"
      PreviewMouseDown="Grid_PreviewMouseDown"
      PreviewMouseMove="Grid_PreviewMouseMove"
      PreviewMouseUp="Grid_PreviewMouseUp">
    <Grid>
        <!-- Contenido del ScrollViewer -->
    </Grid>
</ScrollViewer>     

With this, you should be able to drag the contents of the ScrollViewer while holding down the left mouse button.

Upvotes: 0

metin Altıkardes
metin Altıkardes

Reputation: 11

Mouse events fail to distinguish between click and drag. Therefore, it is necessary to compare the click time with the holding time. In this way, when clicking, there is no dragging and you can access the objects it contains. Objects become draggable when you hold them down. In the example below, the click and hold state can be distinguished. You can change your hold time with longPressDurationMilliseconds.

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;
    }
}

Upvotes: 0

Reza Karimnia
Reza Karimnia

Reputation: 57

Use this extension method on class constractor:

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);
    }

Upvotes: 0

Caleb Mauer
Caleb Mauer

Reputation: 672

Update for combined horizontal and vertical dragging

Starting from https://stackoverflow.com/a/42288914/3169805, I modified this to also do horizontal dragging, and fixed some quality issues like jumping around near the edges, and dead zones caused by going outside the scrollable area when you're window isn't maximixed. Also sets the cursor to the hand during the drag operation.

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);
    }
}

Huge thank you to the original answerer and everyone else on this thread for getting me started.

Upvotes: 1

PhuocLuong
PhuocLuong

Reputation: 739

My experience: follow user110777 Changed below code

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

Upvotes: 0

naseer mohammad
naseer mohammad

Reputation: 281

You can do this in C# WPF like this. note that there are two type of Point classes.From them you should use System.Windows.Point to get this work.These are mouse related events for your ScrollViewer.The code below will scroll your object to both horizontal and vertical inside the 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);
        }

Upvotes: 0

MadMaxIV
MadMaxIV

Reputation: 100

UWP variation:

    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);
        }
    }

I know that question was for WPF, but this was best result I found searching for UWP solution.

Upvotes: 3

user110777
user110777

Reputation: 421

I found a way of resolving this. It's following...

    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);
    }

thanks!

Upvotes: 23

John Laszlo
John Laszlo

Reputation: 41

This is how I did it, 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;
        }
    }

where Point oldMousePosition; is a member of the window.

Upvotes: 3

AL - Lil Hunk
AL - Lil Hunk

Reputation: 1015

I did this class to scroll while keeping the scrollviewer buttons. From the code from user110777, but this works with vertical instead of horizontally, and works well with the viewer (Since I'm now only capturing the content). Plus I'm using MouseLeftButtonDown instead of the preview in order to allow the user to click things like the combobox without causing a drag. (If you want label or Textblock to drag set their 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();
    } 
}

Upvotes: 13

Rajeev Ranjan
Rajeev Ranjan

Reputation: 1036

use this

for horizontal scrolling

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

Upvotes: 0

Related Questions