Suicide KingThis post is about scrolling in WPF and the egocentric DataGrid control. I’ll give the project background but if all you’re interested in is the final solution, scroll on down a bit. I’ll leave a trail of headers so you should be able to find your way easily enough.

The Project

I’ve been working on a little application to help track spells for my wife’s character in our weekly Pathfinder games (my character might find a use for it as well but that’s just gravy). Since I had already spent a couple months dinking around with acquiring (and normalizing into XML) the reference version of the spells, I figured all I really needed was a UI to present and track them. I mean, I have a strong object graph and a library of spells, UI is really all that’s left, right?

For the UI, I decided to lean on WPF—mostly so I could come to understand the many data binding techniques I’d learned at TechEd this year. Plus, I had some ideas of how I wanted to leverage a composite UI for the presentation. This turns out to have been an awesome idea and allowed me to do exactly what I wanted, though it sometimes took a while to figure out how to do the cool stuff.

As part of the project, I also wanted to get some experience with actually using MVVM in a live project and set myself the task of using as little code-behind as possible.

The Last Fly in the Ointment

As I said above, things went well. I had to learn my way around a few problems, but things mostly worked the way I thought they should and while the end result would be cleaner if I’d started out knowing what I know now, I’m pretty proud of the results. I got the UI to alter dynamically depending on whether the character class had Domains. Or bloodlines. Or neither. I had some fun building in filtering capabilities. And you can even choose the sources you accept into your spell list.

The one thing that had me tearing my hair out, in the end, was that the list of spells didn’t scroll right. Since the spells need to be divided by level, I have a ListView with a custom data template. Each item in the ListView is a whole level’s worth of spells. The custom data template has a WrapPanel for the level header information (spells per day, spell DC—things that vary by level) and then a DataGrid for the spall data itself. Here’s a screenshot if you’re curious:

Spell Manager Labeled Screenshot

For mouse wheel scrolling, if the mouse was on the level header (the yellow bit), or if you scroll all the way to the right where the DataGrid ceases to rule, it’d scroll just fine. Actually, that’s a lie. It’d scroll just fine once you turned off ScrollViewer.CanScroll on the ListView (if you don’t, then the ListView scrolls it in chunks, a level/item at a time).

This got very annoying because as far as I’m concerned, if there’s a scroll bar, the mouse wheel should move it. And since the list tends to be long, I was forever trying to scroll with my wheel.

Why This is Happening—Mad King DataGrid

By poking at it repeatedly with a debugger and wiring up random events, I came to understand that the DataGrid was eating my MouseWheel event. And since that’s the bottom control on the event route (WPF starts the event routing at the bottom of the tree), the MouseWheel wasn’t being seen by itself (the DataGrid), its parent (the StackPanel), or its parent’s parent (the ListView). My guess is that DataGrids expect to be the king of scrolling and figure they own the MouseWheel events because you, the developer, will only screw it up if they let you.

Where to Even Start—ScrollViewer

So I went digging to see if there wasn’t something I could do to wrest MouseWheel events from the DataGrid. It was clear that I’d need to do something to manually handle mouse events and, also manually, dictate scrolling behavior during same. The only control I could find that exposed the right methods is the ScrollViewer control as explained in this Stack Overflow answer. That’s exactly what I need, but to use it, I’d need to insert a ScrollViewer into the control hierarchy. And the only place to do that, such that it’d actually work the way you’d expect, is to insert it into the ListView somehow. This turned out to be trickier than it looks.

Where to Finish

My initial impulse was to replace the ItemsPanel on the ListView. Unfortunately, the ItemsPanel has to be just that—a panel. ScrollViewer isn’t a panel and any panel you insert there is going to have the same problem as the ListView itself. It took me a while to work out how to do it (because the documentation for template-type properties is long on detail and short on information), but I finally figured out that overriding the ControlTemplate was the way to go. A little back and forth and I came out with this simple-after-the-fact solution.

	<ListView Margin="8,155,8,8" ItemsSource="{Binding Source={StaticResource characterViewModelClassesViewSource},  Path=SpellLevels, UpdateSourceTrigger=PropertyChanged}" ItemTemplate="{DynamicResource spellLevelDataTemplate}"  ScrollViewer.CanContentScroll="False" IsTextSearchEnabled="False" Name="spellsListView">
<ListView.Template>
<ControlTemplate>
<ScrollViewer PreviewMouseWheel="ScrollViewer_PreviewMouseWheel">
<StackPanel IsItemsHost="True"/>
</ScrollViewer>
</ControlTemplate>
</ListView.Template>
</ListView
>


With the ScrollViewer in place, the event itself was simple, too.

private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e) 
{
ScrollViewer scv = (ScrollViewer
)sender;
  scv.ScrollToVerticalOffset(scv.VerticalOffset - e.Delta);
e.Handled =
true;
}


If you actually followed the Stack Overflow link, you’ll recognize that I lifted the code pretty much wholesale.

So now, my ListView eats the MouseWheel event (via PreviewMouseWheel), but only after it actually moves the scroll bar correctly.