How To Create An Animated ScrollViewer (or ListBox) in WPF

UPDATED 05/22/09

In the comments, someone mentioned that the project wasn’t working properly for keyed scrolling. I’ve updated the project with:

  • Key scrolling (left, right, up, down, page up, page down)
  • CanKeyboardScroll property on the AnimatedScrollViewer so that keyboard scrolling can be turned off
  • ScrollToSelectedItem property on the AnimatedListBox so that the user can have it automatically scroll to a ListBoxItem

That last one is a little hacky… I use the ListBox ItemContainerGenerator to get the heights of all the items up to the one you want and then scroll it that. I’m almost positive there is a better way and if anyone knows what it is, I’d love to hear it.

First things first, here are the project files.

Animated ScrollViewer and ListBox Project Files (Updated 5/22/09) – Contains the AnimatedScrollViewer control library with AnimatedScrollViewer and AnimatedListBox

Animated ScrollViewer and ListBox DLL (Updated 5/22/09) – For those of you who don’t care how it works and just want it to work

OK… this is going to be something of a whirlwind since I’ve never written a post this in-depth before… it will strain the limits of my ADD.

Problem:

The Listbox/ScrollViewer not only doesn’t animate, but it seems impossible to tweak it so that it animates.

The Reason:

The reason has everything to do with the ScrollViewer. Basically, the ScrollContainer and the ScrollBars are very tightly intertwined. There is a lot of code that does all the scrolling calculations and that code needs to apply to the scrolled content as well as the UI for the ScrollBars. If you dig deep enough, you’ll see the reasons. Reasons which I assume for the moment you don’t care about… you’re probably in more of a “make the @#&($ thing work!” mood. I know I was.

The Solution:

My solution was basically to completely bypass the built-in ScrollBars and put in new ScrollBars with new logic. They look and act just like normal ScrollBars, so you should be able to style them just you would any normal ScrollViewer.

OK… how I did it. (I’m going to use both Blend 2 and Visual Studio 2008)

First, create a new custom control for WPF. This can be done by going into Visual Studio and creating a new Project. Select “WPF Custom Control Library”

clip_image001[7]

In Blend:

 clip_image001[9]

Add a WPF application to the project too so you have something to test. In the WPF application, get Blend to generate the default template for a normal ScrollViewer, accessible (in Blend) by putting a ScrollViewer into the project and right-clicking on it and selecting “Edit Control Parts (Template) –> Edit a Copy…”

clip_image001

Once have the default ScrollViewer template, select the “PART_VerticalScrollBar” and the “PART_HorizontalScrollBar” and copy and paste them. Rename your new ScrollBars something you like… I used “PART_AniVerticalScrollBar” and “PART"_AniHorizontalScrollBar”. Now, set the Visibility of the original ScrollBars to “Collapsed”. (We can’t get rid of them, because the ScrollViewer will be looking for them and will throw a conniption if it can’t find them.)

Also, change the Value of your new ScrollBars to 0. You’ll probably have to click on the orange box next to Value and select “Reset”.

clip_image001[11]

In Visual Studio, right-click on your WPF Custom Control project and go to “Add –> New Item…” . Then select “Custom Control (WPF)” and name it something you like (mine is named AnimatedScrollViewer). This should add a class to your project as well as a basic template to your Generic.xaml file.

Copy the ScrollViewer template that we just made and paste it into the Generic.xaml. The only change we need to make is to change:

TargetType="{x:Type ScrollViewer}"

to

TargetType="{x:Type local:AnimatedScrollViewer}"

in the Style and the ControlTemplate.

OK… that’s pretty much it with the XAML. Now we get to move into the code.

Right now, our class inherits from Control, but we want it to inherit from ScrollViewer like so:

public class AnimatedScrollViewer : ScrollViewer

Next get some containers for our new spiffy ScrollBars so that we can access them from the custom control code. Type the following before the class:

[TemplatePart(Name = "PART_AniVerticalScrollBar", Type = typeof(ScrollBar))]
[TemplatePart(Name = "PART_AniHorizontalScrollBar", Type = typeof(ScrollBar))]

and the following just inside the class:

ScrollBar _aniVerticalScrollBar;
ScrollBar _aniHorizontalScrollBar;

Now, we’ll override the OnApplyTemplate and make the connection between the template scrollBars and our class ScrollBars:

public override voidOnApplyTemplate()
{
    base.OnApplyTemplate();

    ScrollBar aniVScroll = base.GetTemplateChild("PART_AniVerticalScrollBar") asScrollBar;
    if(aniVScroll != null)
    {
        _aniVerticalScrollBar = aniVScroll;
    }
    _aniVerticalScrollBar.ValueChanged += newRoutedPropertyChangedEventHandler<double>(_aniVerticalScrollBar_ValueChanged);

    ScrollBar aniHScroll = base.GetTemplateChild("PART_AniHorizontalScrollBar") asScrollBar;
    if(aniHScroll != null)
    {
        _aniHorizontalScrollBar = aniHScroll;
    }
    _aniHorizontalScrollBar.ValueChanged += newRoutedPropertyChangedEventHandler<double>(_aniHorizontalScrollBar_ValueChanged);

    this.PreviewMouseWheel += newMouseWheelEventHandler(AnimatedScrollViewer_PreviewMouseWheel);
}

Before we address the three event handlers we added, we need to create the Dependency Properties with which they will be futzing.

(We’re going start going a little bit faster. Please download the code for the excruciating detail.) We need to add the following Dependency Properties. I’m using a “PropertyName (type)”.

Dependency Properties

ScrollingTime (TimeSpan) – This will be an easy way to change the speed of the scrolling. I created mine to default at half a second, but if you changed it to 0 seconds, it would act just like any normal ScrollViewer.

ScrollingSpline (KeySpline) – This property along with the ScrollingTime property is meant to give designers and developers the easiest control possible over the animation. This property describes the spline along which the scrolling will animate. If you don’t know what this means, just leave it alone, you’ll be fine.

TargetVerticalOffset (double) and TargetHorizontalOffset (double) – These are properties that tell the ScrollViewer where it will be animating to. In the PropertyChangedCallback, they kick off a method that starts the animation.

VerticalScrollOffset (double) and HorizontalScrollOffset (double) – For some reason the normal VerticalOffset and HorizontalOffset properties in a ScrollViewer are not capable of animation. So I wrote these properties that can be animated using standard storyboard procedures. If you use them to animate, make sure you also change the TargetVerticalOffset and TargetHorizontalOffset stuff as well… otherwise there will be a disconnect between the two.

Event Handlers

CustomPreviewMouseWheel event handler – This grabs any mouse wheel spinning and uses it to change the TargetVerticalOffset so that the ScrollViewer will still animate the scrolling when the mouse wheel spins.

VScrollBar_ValueChanged and HScrollBar_ValueChanged event handlers – These are called whenever the the ScrollBars are interacted with. There was a really weird problem with some of the interaction (the arrow keys and fast-scrolling buttons weren’t working properly), so these handlers hold logic to try to translate the weirdness into something viable. They then set the Target_Offset properties appropriately.

Methods

animateScroller – This method builds the animation programmatically based off of the appropriate properties and runs it.

And that’s really about it. Once you have the AnimatedScrollViewer working, you can just add use it inside your ListBox templates and it should work. (For those who are averse to doing such a thing, I’ve added extremely simple AnimatedListBox.)

13 thoughts on “How To Create An Animated ScrollViewer (or ListBox) in WPF

  1. Nice blog! Is your theme custom made or did you download it from somewhere? A theme like yours with a few simple tweeks would really make my blog stand out. Please let me know where you got your design. Bless you gdadddbfcdcdbece

  2. Hi,

    Just wanted to add that I have managed to sort out the horizontal scrolling : )
    Just in case anyone else would like to do the same here is what I did:

    1. Add a ‘PreviewMouseWheel’ handler to the AnimatedScrollViewer
    2. Put the following code in the handler:

    AnimatedScrollViewer.AnimatedScrollViewer scrollviewer = sender as AnimatedScrollViewer.AnimatedScrollViewer;

    double newHorizontalPos = scrollviewer.TargetHorizontalOffset;

    if (e.Delta > 0)
    {
    newHorizontalPos = NormalizeScrollPos(scrollviewer, (newHorizontalPos – e.Delta), Orientation.Horizontal);
    }
    else
    {
    newHorizontalPos = NormalizeScrollPos(scrollviewer, (newHorizontalPos – e.Delta), Orientation.Horizontal);
    }

    e.Handled = true;

    if (newHorizontalPos != scrollviewer.TargetHorizontalOffset)
    {
    scrollviewer.TargetHorizontalOffset = newHorizontalPos;
    }

    Hope this helps others and many thanks for the original guide, solved my requirements nicely.

  3. Hi,

    I was wondering how I could make it so that when I scroll the mouse wheel is scrolls using the horizontal rather than the vertical.

    I am trying to make a smooth sliding horizontal layout.

    Many thanks

    Mark

  4. I work for a online community institution and find your content material extremely useful for work we are running.great work and look forward to a lot more website posts

  5. Hi I’d like to see this control in silverlight. is there any change to port to Silverlight platform?

  6. Hi,

    I just tested the samle app aand wanted to let you know that the scrollbar position isn’t updated when using the keyboard to scroll.

Comments are closed.