01/22/09: Scroller updated in this post.
Last week I had the opportunity to really dive into some fun with a Silverlight 2 ListBox. It all started with a requirement for a simple Scroller control. You know, the simple left-to-right scrollers that you see in so many applications? I’ve seen them in several applications, yet when I searched around for a Silverlight version that met my criteria I came up empty. Along the way, I found some really great Image Carousel controls around that sort of had the scrolling behavior I was looking for, but overall the majority of them were not quite “calm enough” for a typical Line of Business application and I wasn’t really intending on using images only. There were some really great pieces of information that I found as I attempted to assemble this control and now that I have a full implementation that is functional, I thought I’d share it with the community.
The first challenge here was that I needed to have two horizontal rows of items in my ScrollerControl. Thanks to a post over at The Problem Solver and the WrapPanel from the Silverlight Toolkit, I was able to pull this off pretty easily. So, I went to work on a custom template for a ScrollBar. My initial thought here was that I’d stretch the ScrollBar to the Height of the ListBox, delete the Thumb, make the Background Transparent, and delete the Large Increase/Decrease controls. This worked great, but the scrolling was what you would typically get when clicking the Small Increase/Decrease portions of a ScrollBar (i.e. the little arrows)….not very cool. What I really needed was for each click to move to the next/previous column of items.
At this point, I decided to lose the custom template for the ScrollBar and just go with a pair of RepeatButtons for the Previous/Next buttons so that I could have a little more control over things. As I didn’t want to see the default ScrollBar of my ListBox, I hid it by setting the HorizontalScrollBarVisibility and the VerticalScrollBarVisibility to Hidden in the ControlTemplate of the ListBox. Though I don’t want to see the ScrollViewer (that is part of our ListBox), I do need to manipulate it to give this per-item scroll experience to the user.
There are a couple of important properties of the ScrollViewer that we care about. First, the ScrollableWidth which represents the width of the area that can be scrolled. You can envision this as placing all of the columns end-to-end (including those that are scrolled out of sight) and computing the total width. Secondly, the HorizontalOffset which is simply the indicator of the current scroll position (relative to the entire ScrollableWidth). Using these two properties and the ItemWidth, we can compute how far to move the ScrollViewer for each click of our Previous/Next buttons.
Since I needed direct access to the ScrollViewer in my ListBox and its not readily available from the default ListBox, I decided to create a custom ListBox control that exposed the underlying ScrollViewer as a ScrollHost property. I saw this little trick used in the ItemContainerGenerator class of the Silverlight Toolkit (which has some really great utility code along with the awesome controls….well worth your time to investigate that). Also, since I was using a WrapPanel as my ItemsPanelTemplate and I needed the ItemWidth for my calculations and as the WrapPanel just happens to have an ItemWidth property, I decided to expose that as a property as well. I overrode the PrepareContainerForItemOverride() method in my custom ScrollerListBox in order to set these properties. This worked pretty well and with a little math and a call to ScrollViewer.ScrollToHorizontalOffset(), I was able to create the scrolling behavior that I needed.
But, as it got later I thought……wouldn’t it be cool if the scrolling was animated? …I mean, it is Silverlight after all and every demo at every conference or presentation with Silverlight has things flying around. This proved to be a little more difficult as you can’t directly animate HorizontalOffset. While searching for a way to solve that, I came across what I thought was a really cool solution on Rob’s Usability Development blog for creating an AnimationHelperControl to give you something to animate. His post also made use of his excellent AnimateTo extension methods that he describes in another post. I’ll definitely be borrowing those.
As I didn’t want to hard code the amount of time it took to animate the scroll, I added a ScrollTime Dependency Property to my custom ScrollerControl and was able to set that declaratively in my Page.Xaml.
I think the final result gives a nice subtle use of animation that doesn’t distract the user from their data. (As an aside…for a REALLY clean and effective usage of animation in a Silverlight application, check out the application that Billy Hollis shows in this video on dnrTV).
My demo uses employees that I’ve generated in a TestData class and then binds the various fields to the ItemTemplate of my ListBox using standard Silverlight DataBinding. I was a bit too lazy to create a separate image for each of my employees, so I used the same image for each (hope you like Abe). These images could just as easily be streamed from the database, but that’s a different post.
Here is the live demo.
Here is the project source.
I have a few more ideas for the ScrollerControl such as the ability to drag between the items (sort of like the iPhone). Anyway, I’ll post those as I implement them.