How To Create An Animated ScrollViewer (or ListBox) in WPF
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.
The Listbox/ScrollViewer not only doesn’t animate, but it seems impossible to tweak it so that it animates.
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.
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”
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…”
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”.
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:
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:
Now, we’ll override the OnApplyTemplate and make the connection between the template scrollBars and our class ScrollBars:
public override voidOnApplyTemplate()
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);
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)”.
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.
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.
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.)