Scrolling GridView by placing mouse on edge

Scrolling GridView by placing mouse on edge

The start screen on Windows 8 has an interesting feature - it scrolls if you move your mouse to left or right edge, without the need for scrolling using the mouse wheel. I pretty much never use it because the scrolling is rather slow, and I usually know where I want to go so I can scroll faster, but for reading and exploring purposes, slower scrolling can come in handy. How to accomplish it using the GridView control to make a pointer scrolling GridView?

Disclaimer: this is something I played with and decided to share with everyone on my personal blog. The code below should not be copy pasted to your app in production without testing and probably adjusting details, for some of the reasons that Kris mentioned in his comment on this post, but for one more very important reason, too - you should not go copy pasting things from any blog or website in the first place. Trying to understand the code and adjusting the details to your needs is the way to go, in my opinion, because scenarios are always different. However, this post could be a starting point in case you need to accomplish something similar, and maybe the parts of the code will be useful to you. Thanks for reading! :)

I will call it the scrolling (in italics) GridView, because the GridView is by default scrollable, but this adds another scrolling mode. Let's start by creating a new project in Visual Studio 2012 of type "Grid App".

scrolling with mouse new app

When you run the app, you get an initial GridView on first page that doesn't really scroll if you place a mouse on left or right border of it. Give it a try!

Adding support for scrolling GridView

First of all, we want to react when pointer is moved to one of the edges by scrolling the inner ScrollViewer of the GridView. This means moving it to a certain horizontal offset. So, the first step is to find that ScrollViewer in the visual tree when the page gets loaded. Add the Loaded event handler to the GroupedItemsPage:

ScrollViewer _gridViewInnerScrollView;  
public GroupedItemsPage()  
{
    this.InitializeComponent();
    this.Loaded += GroupedItemsPage_Loaded;
}

void GroupedItemsPage_Loaded(object sender, RoutedEventArgs e)  
{
    _gridViewInnerScrollView = FindFirstChild<ScrollViewer>(itemGridView) as ScrollViewer;
    if (_gridViewInnerScrollView != null)
    {
        itemGridView.PointerMoved += itemGridView_PointerMoved;
    }
}

You may notice that we used the method called FindFirstChild of type ScrollViewer on the itemGridView (main GridView on the page). This basically means that we're traversing the visual tree starting from itemGridView until we find a first ScrollViewer. How do we do it? Just search recursively for each of the children objects of the current object.

DependencyObject FindFirstChild<T>(DependencyObject initial)  
{
    DependencyObject current = initial;

    if (current == null)
        return null;

    if (current.GetType() == typeof(T))
        return current;
    else
    {
        var count = VisualTreeHelper.GetChildrenCount(current);
        DependencyObject result = null;
        for (int i = 0; i < count; i++)
        {
            result = FindFirstChild<T>(VisualTreeHelper.GetChild(current, i));
            if (result != null) break;
        }
        return result;
    }
}

This either returns null, or the ScrollViewer. If the ScrollViewer has been found, we attach the PointerMoved event handler to itemGridView. This event handler is responsible for changing the horizontal offset.

private void itemGridView_PointerMoved(object sender, PointerRoutedEventArgs e)  
{
    if (e.Pointer.PointerDeviceType == PointerDeviceType.Mouse)
    {
        var pointerpoint = PointerPoint.GetCurrentPoint(e.Pointer.PointerId);
        var bounds = Window.Current.Bounds;
        if (Convert.ToInt32(pointerpoint.Position.X) == Convert.ToInt32(bounds.Right - 1))
        {
            _gridViewInnerScrollView.ScrollToHorizontalOffset(_gridViewInnerScrollView.HorizontalOffset + 0.3);
        }
        else if (Convert.ToInt32(pointerpoint.Position.X) == Convert.ToInt32(bounds.Left))
        {
            _gridViewInnerScrollView.ScrollToHorizontalOffset(_gridViewInnerScrollView.HorizontalOffset - 0.3);
        }
    }
}

The first thing we do is check if the pointer is of type Mouse. If it is, we get the current point of the mouse and current window bounds. If the current mouse X (horizontal) position is equal to right bound (converted to integer to avoid issues with comparing doubles), we scroll to horizontal offset increasing the current horizontal offset for some value, in this case 0.3. Else, if the mouse is on the left of the window bounds, move the horizontal offset to the left by subtracting 0.3. You can experiment with the values, depending on the panel you use (VirtualizingStackPanel - use smaller values, around 0.3, or StackPanel - use larger values around 15 etc.) Beware, using Bounds to test whether the mouse is on the edge is a tricky thing - it may not work in snapped view and multimonitor environments (thanks Kris for the comment).

That's it - a scrolling GridView!

Create a reusable scrollable GridView control

You might find it annoying to do all this every time you have a GridView in your app. You know, Don't Repeat Yourself! Why not create a control called ScrollableGridView that inherits from GridView with the mouse scrolling already implemented? Just create a new class called ScrollableGridView, inherit from GridView and override OnApplyTemplate to attach the necessary events. Everything else is the same!

public class ScrollableGridView : GridView  
{
    ScrollViewer _gridViewInnerScrollView;
    protected override void OnApplyTemplate()
    {
        _gridViewInnerScrollView = FindFirstChild<ScrollViewer>(this) as ScrollViewer;
        if (_gridViewInnerScrollView != null)
        {
            this.PointerMoved += itemGridView_PointerMoved;
        }
        base.OnApplyTemplate();
    }

    DependencyObject FindFirstChild<T>(DependencyObject initial)
    {
        DependencyObject current = initial;

        if (current == null)
            return null;

        if (current.GetType() == typeof(T))
            return current;
        else
        {
            var count = VisualTreeHelper.GetChildrenCount(current);
            DependencyObject result = null;
            for (int i = 0; i < count; i++)
            {
                result = FindFirstChild<T>(VisualTreeHelper.GetChild(current, i));
                if (result != null) break;
            }
            return result;
        }
    }

    private void itemGridView_PointerMoved(object sender, PointerRoutedEventArgs e)
    {
        if (e.Pointer.PointerDeviceType == PointerDeviceType.Mouse)
        {
            var pointerpoint = PointerPoint.GetCurrentPoint(e.Pointer.PointerId);
            var bounds = Window.Current.Bounds;
            if (Convert.ToInt32(pointerpoint.Position.X) == Convert.ToInt32(bounds.Right - 1))
            {
                _gridViewInnerScrollView.ScrollToHorizontalOffset(_gridViewInnerScrollView.HorizontalOffset + 0.3);
            }
            else if (Convert.ToInt32(pointerpoint.Position.X) == Convert.ToInt32(bounds.Left))
            {
                _gridViewInnerScrollView.ScrollToHorizontalOffset(_gridViewInnerScrollView.HorizontalOffset - 0.3);
            }
        }
    }
}

That's it! Make sure you check out my last post if you wish to find out how to get Syncfusion WinRT studio controls for free!

Enter the Syncfusion giveaway here!

 

Igor Ralic

igor ralic

View Comments
Microsoft Certified Solutions Developer: Windows Store Apps in C#