Create an automatic scrollable image slider in Silverlight

I guess someone will send me an comment and say something like: “Why not simply use XXXX, why write your own control” ;) I know there are a lot of controls out there that will do stuff for me, but I want to know how they are created, and try to create my own solution to learn more etc.

I’m working on a small and relative simple Silverlight app for my brother, and I wanted to make it possible for the users to see the images as thumbnails (he is a photographer on his spare time and want a site to show his works). The thumbnails should be placed after each other horizontal in a  scrollable list, the list should be scrollable automatically.

image


You can see the list of Thumbnails at the bottom of the image above. I wanted the list of Thumbnails to be scrollable, but not by moving a scrollbar, instead an automatic scrolling when the mouse is heading to the right or left side of the list. I was first thinking of using a Canvas and use the Canvas.Top and Left attached property to scroll the thumbnails, but I decided to take advantage of the ScrollViewer control. The problem with the ScrollViewer is that we don’t have a property to set the scrollers’s position, instead we have to use the ScrollToHorizontalOffset method. So I couldn’t define an animation for handling the scrolling, instead I decide to use the DispatchTimer class and handle the “animation” on my own.

private DispatcherTimer _thumbnailScrollTimer = new DispatcherTimer();

public AutomaticImageSlider()
{
      InitializeComponent();

       _thumbnailScrollTimer.Interval = new TimeSpan(0, 0, 0, 0, 1);
       _thumbnailScrollTimer.Tick += new EventHandler(_thumbnailScrollTimer_Tick);
}

The reason I used the DispathTimer is because every time it ticks based on an interval an event is trigged, the event handler for the Tick will be executed on the UI thread. I set the interval of the Timer control to 1 millisecond, this value can of course be changed, based on how fast and often I want to update the ScrollViewer. I put my ScrollViewer inside of a StackPanel, and hooked up to the StackPanel’s MouseMove event.

<StackPanel
        x:Name="thumbnailViewer"
        MouseMove="thumbnailViewer_MouseMove"
        HorizontalAlignment="Center"
        VerticalAlignment="Bottom"
        Background="Black">

            <ScrollViewer
                x:Name="thumbnailViewerScroller"
                Padding="0"
                BorderThickness="0"
                VerticalScrollBarVisibility="Hidden"
                HorizontalScrollBarVisibility="Hidden">
                
               <!-- Images here, I add the images dynamically -->
            
            </ScrollViewer>

</StackPanel>

I only wants to start the timer when I move the mouse over a specific part of the StackPanel, so when the mouse are in the middle part of the ScrollViewer the timer should be idle (stopped). The MouseMove event will check if the mouse is within a specific position and if it is, the ScollViewer should start to scroll until it reached the end of its content or the mouse is leaving the area.

private void thumbnailViewer_MouseMove(object sender, MouseEventArgs e)
 {
      var x = e.GetPosition(sender as UIElement).X;

       if (x >= 300 && x <= 700)
       {
            _acceleration = 0;
            _thumbnailScrollTimer.Stop();
            _thumbnailScrollTimerStarted = false;
       }
       else
       {
            if (x > 700)
                 _acceleration = (int)(Math.Pow((x - 512), 1.5) / 1200);
            else if (x < 300)
                 _acceleration = -(int)(Math.Pow((512 - x), 1.5) / 1200);

            if (!_thumbnailScrollTimerStarted)
                 _thumbnailScrollTimer.Start();

            _thumbnailScrollTimerStarted = true;
        }
  }

By using the MouseEventArgs’s GetPosition method, I can get the position of the mouse. If the mouse is between X 300 and 700, the ScrollView should stand still and the timer should be stopped. If the mouse X position is between 0 – 300, the ScrollViewer should start to scroll to the right direction, if the mouse X position is larger than 700, the ScrollViewer should start scrolling to the Left direction. I have a private field with the name _acceleration, it’s how fast the ScrollViewer should be moving. The acceleration will increased based on the mouse X position. This will create a smother and more user friendly scrolling. My acceleration calculation code is based on a fixed width of the ScrollViwer, the width is set to 1024 pixels. I use Math.Pow to make sure the acceleration should not be linear. As you can see, the MouseMove will start the timer. The Tick event of the timer will make sure to move the ScrollViewer’s scroller.

void _thumbnailScrollTimer_Tick(object sender, EventArgs e)
{
     thumbnailViewerScroller.ScrollToHorizontalOffset(thumbnailViewerScroller.HorizontalOffset + _acceleration);

     if (thumbnailViewerScroller.HorizontalOffset + _acceleration <= 0)
        _thumbnailScrollTimer.Stop();
}

I use one if statement to make sure the Timer will stop when the ScrollViewer’s scroller is at the beginning of the ScrollViewer. I should also have one if it reaches the end of the ScrollViewer, but I don’t really know when it reaches the end, the HorizontalOffset value it’s based on the ScrollerViewer’s content, and that content can vary.

Here is the code of a full working example:

MainPage.xaml

<UserControl x:Class="SilverlightApplication7.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
  <Grid x:Name="LayoutRoot">


        <StackPanel
        x:Name="thumbnailViewer"
        MouseMove="thumbnailViewer_MouseMove"
        Background="Black" Height="208" Width="1024">

            <ScrollViewer
                x:Name="thumbnailViewerScroller"
                Padding="0"
                BorderThickness="0"
                VerticalScrollBarVisibility="Hidden"
                HorizontalScrollBarVisibility="Hidden">

                <StackPanel Orientation="Horizontal">
                    <Rectangle Width="200" Height="200" Fill="Red" Margin="4"></Rectangle>
                    <Rectangle Width="200" Height="200" Fill="Red" Margin="4"></Rectangle>
                    <Rectangle Width="200" Height="200" Fill="Red" Margin="4"></Rectangle>
                    <Rectangle Width="200" Height="200" Fill="Red" Margin="4"></Rectangle>
                    <Rectangle Width="200" Height="200" Fill="Red" Margin="4"></Rectangle>
                    <Rectangle Width="200" Height="200" Fill="Red" Margin="4"></Rectangle>
                    <Rectangle Width="200" Height="200" Fill="Red" Margin="4"></Rectangle>
                    <Rectangle Width="200" Height="200" Fill="Red" Margin="4"></Rectangle>
                    <Rectangle Width="200" Height="200" Fill="Red" Margin="4"></Rectangle>
                </StackPanel>
            
            </ScrollViewer>

        </StackPanel>
  </Grid>
</UserControl>


MainPage.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace SilverlightApplication7
{
    public partial class MainPage : UserControl
    {
        private int _acceleration = 0;
        private bool _thumbnailScrolltTimerStarted;
        private DispatcherTimer _thumbnailScrollTimer = new DispatcherTimer();

        public MainPage()
        {
            InitializeComponent();

            _thumbnailScrollTimer.Interval = new TimeSpan(0, 0, 0, 0, 1);
            _thumbnailScrollTimer.Tick += new EventHandler(_thumbnailScrollTimer_Tick);

        }

        void _thumbnailScrollTimer_Tick(object sender, EventArgs e)
        {
            thumbnailViewerScroller.ScrollToHorizontalOffset(thumbnailViewerScroller.HorizontalOffset + _acceleration);

            if (thumbnailViewerScroller.HorizontalOffset + _acceleration <= 0)
                _thumbnailScrollTimer.Stop();
        }


        private void thumbnailViewer_MouseMove(object sender, MouseEventArgs e)
        {
            var x = e.GetPosition(sender as UIElement).X;

            if (x >= 300 && x <= 700)
            {
                _acceleration = 0;
                _thumbnailScrollTimer.Stop();
                _thumbnailScrolltTimerStarted = false;
            }
            else
            {
                if (x > 700)
                    _acceleration = (int)(Math.Pow((x - 512), 1.5) / 1200);
                else if (x < 300)
                    _acceleration = -(int)(Math.Pow((512 - x), 1.5) / 1200);

                if (!_thumbnailScrolltTimerStarted)
                    _thumbnailScrollTimer.Start();

                _thumbnailScrolltTimerStarted = true;
            }

        }
    }
}

Feel free to send me any comments and feedback, you can also find me on twitter: http://www.twitter.com/fredrikn

5 Comments

Comments have been disabled for this content.