Robert Westerlund

A developer's blog on code, technology and tools in the Web, .NET and other development areas.

NAVIGATION - SEARCH

Custom scroll buttons for the ScrollViewer control in Silverlight

Earlier today I had a discussion with a co-worker who needed to replace the default template of the ScrollViewer control, in order to replace the ScrollBar with custom buttons. He had found some example of doing this in WPF, but the same sample did not work in Silverlight (the samples relied on routed command which aren’t available in Silverlight) so I decided to write up a sample of my own.

Disclaimer: The sample is very rudimentary and may not be the best way to achieve this (also the placement of the commands might be questionable). Use the sample at your own risk.

To begin with, we need a ScrollViewer with some content.

<Grid x:Name="LayoutRoot" Background="White" Height="100" Width="120">
    <ScrollViewer>
        <StackPanel>
            <TextBlock Text="Text1"/>
            <TextBlock Text="Text2"/>
            <TextBlock Text="Text3"/>
            <TextBlock Text="Text4"/>
            <TextBlock Text="Text5"/>
            <TextBlock Text="Text6"/>
            <TextBlock Text="Text7"/>
            <TextBlock Text="Text8"/>
            <TextBlock Text="Text9"/>
        </StackPanel>
    </ScrollViewer>
</Grid>

Additionally, we will be using a stripped down version of the RelayCommand. I use a stripped down version since I really don’t need any more functionality for this sample.

public class RelayCommand<T>:ICommand
{
    private Action<T> _action;

    public RelayCommand(Action<T> action)
    {
        this._action = action;
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        if (!(parameter is T))
            throw new ArgumentException("The parameter must be of type T", "parameter");
        _action((T)parameter);
    }
}

The default look of the ScrollViewer control, using the above Xaml looks similar to this:

The default look of the ScrollViewer control, using the Content we supplied

And our goal is to make it look like this:

The modified look of the ScrollViewer control, using buttons instead of the ScrollBar, which we have as our aim

Quite an improvement, right?

I admit that it might not be prettiest design for my custom scroll buttons. To my defence I will say that beauty is not the aim of this post and… well, at least it works.

To get the ScrollViewer to look like what we want, we start by changing the declaration of the ScrollViewer to start using a custom template (if you do this in production code, you might want to define it as a style and set the Template property to be your ContentTemplate, especially since you will probably want to set other properties as well, but in order to keep the code a little shorter here, we’ll just set the template directly) which we will define later. The new declaration of the ScrollViewer should be:

<ScrollViewer Template="{StaticResource MyScrollViewer}">
...

The next step is to create the actual Template that we have just referenced. In our template, we’ll create two RepeatButtons (using RepeatButtons instead of Buttons allows the user to keep the button pressed to keep scrolling until released), to serve as up and down buttons, and between them we’ll place the ScrollContentPresenter (which will display the content of the ScrollViewer control, i.e. the TextBlocks).

<ControlTemplate x:Key="MyScrollViewer" TargetType="ScrollViewer">
    <StackPanel Width="100">
        <RepeatButton Content="Up" Command="{Binding ScrollUpCommand, ElementName=_this}"
		CommandParameter="{Binding ElementName=ScrollContentPresenter}"/>
        <ScrollContentPresenter Height="50" x:Name="ScrollContentPresenter"/>
        <RepeatButton Content="Down" Command="{Binding ScrollDownCommand, ElementName=_this}"
		CommandParameter="{Binding ElementName=ScrollContentPresenter}"/>
    </StackPanel>
</ControlTemplate>

So, what is the ScrollUpCommand used in the code above? The ScrollUpCommand, as well as the ScrollDownCommand, is defined in the code behind of the control, using the following code:

public ICommand ScrollUpCommand
{
	get
	{
		return new RelayCommand<ScrollContentPresenter>(scroll => scroll.LineUp());
	}
}

public ICommand ScrollDownCommand
{
	get
	{
		return new RelayCommand<ScrollContentPresenter>(scroll => scroll.LineDown());
	}
}

And there we have it, our nice little ScrollViewer without the default ScrollBar. The secret sauce lies in the CommandParameter, sending the ScrollContentPresenter which we want to scroll as a parameter to the command when it executes. Have fun with the code! Here is the full code for the sample:

MainPage.xaml:

<UserControl x:Class="SilverlightApplication11.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:DesignHeight="300" d:DesignWidth="400" x:Name="_this">
    <UserControl.Resources>
        <ControlTemplate x:Key="MyScrollViewer" TargetType="ScrollViewer">
            <StackPanel Width="100">
                <RepeatButton Content="Up" Command="{Binding ScrollUpCommand, ElementName=_this}" 
			CommandParameter="{Binding ElementName=ScrollContentPresenter}"/>
                <ScrollContentPresenter Height="50" x:Name="ScrollContentPresenter"/>
                <RepeatButton Content="Down" Command="{Binding ScrollDownCommand, ElementName=_this}" 
			CommandParameter="{Binding ElementName=ScrollContentPresenter}"/>
            </StackPanel>
        </ControlTemplate>
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot" Background="White" Height="100" Width="120">
        <ScrollViewer Template="{StaticResource MyScrollViewer}">
            <StackPanel>
                <TextBlock Text="Text1"/>
                <TextBlock Text="Text2"/>
                <TextBlock Text="Text3"/>
                <TextBlock Text="Text4"/>
                <TextBlock Text="Text5"/>
                <TextBlock Text="Text6"/>
                <TextBlock Text="Text7"/>
                <TextBlock Text="Text8"/>
                <TextBlock Text="Text9"/>
            </StackPanel>
        </ScrollViewer>
    </Grid>
</UserControl>

MainPage.xaml.cs:

using System.Windows.Controls;
using System.Windows.Input;

namespace SilverlightApplication11
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }

        public ICommand ScrollUpCommand
        {
            get
            {
                return new RelayCommand<ScrollContentPresenter>(scroll => scroll.LineUp());
            }
        }

        public ICommand ScrollDownCommand
        {
            get
            {
                return new RelayCommand<ScrollContentPresenter>(scroll => scroll.LineDown());
            }
        }
    }
}

RelayCommand.cs:

using System;
using System.Windows.Input;

namespace SilverlightApplication11
{
    public class RelayCommand<T>:ICommand
    {
        private Action<T> _action;

        public RelayCommand(Action<T> action)
        {
            this._action = action;
        }

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            if (!(parameter is T))
                throw new ArgumentException("The parameter must be of type T", "parameter");
            _action((T)parameter);
        }
    }
}

Add comment