adamjcooper.com/blog

Enterprise Software Development in C#


Improved Mouse Wheel Support for Silverlight 2 Beta 2 ScrollViewer

This is an update to a previous post, but everything you need to easily add mouse wheel support to a Silverlight 2 Beta 2 ScrollViewer is listed here.

The Problem

Silverlight 2 Beta 2's ScrollViewer control doesn't recognize the mouse scroll wheel, and implementing a workaround is difficult for two reasons: 1) the only way to listen for mouse wheel events is via the browser which hosts your Silverlight app, and 2) the ScrollViewer control doesn't always fire the MouseLeave event when the mouse exits on the side where the scroll bar is, making it extremely difficult to know exactly which ScrollViewer the user is currently hovering over.

Problem #1 has been solved fairly well by Jeff Prosise and Peter Blois, but since they were interested in using the wheel for deep zoom and not the ScrollViewer, problem #2 was still left to be tackled.

The Solution

I've created a small static class that you can use to easily "attach" mouse wheel support to your scroll viewers as your application is initializing. You basically set-and-forget each ScrollViewer and instantly get mouse wheel support.

Demos

Here are a couple of demos to show it in action:

Default Behavior: Association by Hover

The default behavior is for the mouse wheel to affect which ever ScrollViewer is most immediately under the cursor at the time of the last mouse movement.

image 
Click here to run the default (hover) behavior demo...

Alternative Behavior: Association by Focus

Alternatively, you can modify one method call to change the default behavior from hover-to-associate to click-to-associate.

image 
Click here to run the focus behavior demo...

Usage

Give Your ScrollViewers a Solid Background

Even if you use my workaround API correctly, a ScrollViewer without a background won't always get registered with the mouse wheel. So make sure you give your ScrollViewers a non-transparent background, even if it's the same color as the background of the element behind it. If you don't, the scroll wheel will only work when the mouse is interacting with non-whitespace in the ScrollViewer, which is generally not what you want.

Initialize the Static Helper Class Exactly Once at the Root of the Application

Your root control needs to call ScrollViewerMouseWheelSupport.Initialize for everything to work correctly, like this:

public partial class MouseWheelDemo : UserControl{    public MouseWheelDemo()    {        InitializeComponent();        //Initialize the static scroll viewer mouse wheel support class by providing a reference to the root visual.        ScrollViewerMouseWheelSupport.Initialize(rootVisual);        .        .        .

Where rootVisual is the root element of your application (usually it's easiest to just give an instance name to the root element in your XAML and use it here).

If you prefer the associate-by-click model, just call the Initialize method like so:

ScrollViewerMouseWheelSupport.Initialize(rootVisual, MouseWheelAssociationMode.OnFocus);

The important thing to know is that this method should only be called once by the entire application. You don't need to call it for each control, just for the application itself.

Register Multiple ScrollViewers Correctly

Single ScrollViewer

If you only have one ScrollViewer on the page, all you need to do is use the AddMouseWheelSupport extension method, like so:

//Register a single scroll viewersingleScrollViewer.AddMouseWheelSupport();

Multiple ScrollViewers

If you have multiple ScrollViewers--including any ScrollViewers that are part of controls such as drop-down menus or combo boxes that you'd like to add mouse wheel support for--then you need to know a few things about the proper order of registration (if you get it wrong, you'll get strange results).

Internally my implementation keeps track of a tree of all of the ScrollViewers in the app which have been registered for mouse wheel support. I've done my best to hide all the details under the hood, but I do require that you follow a particular order of registration: ScrollViewers which contain child ScrollViewers must be registered first before any of their children can be registered.

I've overloaded the extension method to make registering multiple ScrollViewers a little bit easier, but ultimately everything can be done with a single method whose signature is:

public static ScrollViewer AddMouseWheelSupport(this ScrollViewer parentScrollViewer, ScrollViewer childScrollViewer, double scrollAmount)

 

Technically you could do everything with just this method. If you were adding a root ScrollViewer that didn't have a parent at all, you could call this as a simple static method (rather than an extension method) and pass in null for the first argument. But it's a lot easier to use an overload like this, which also provides a default for the scrollAmount:

myScrollViewer.AddMouseWheelSupport();

Let's say you have three ScrollViewers and scrollViewer1 contains scrollViewer2 which contains scrollViewer3.

image_thumb6_thumb2

Here's the long way to wire them up:

ScrollViewerMouseWheelSupport.AddMouseWheelSupport(null, scrollViewer1, 30.0);ScrollViewerMouseWheelSupport.AddMouseWheelSupport(null, scrollViewer2, 30.0);ScrollViewerMouseWheelSupport.AddMouseWheelSupport(null, scrollViewer3, 30.0);

Or, you could tighten things up with these fluid overloads:

ScrollViewerMouseWheelSupport    .AddMouseWheelSupport(scrollViewer1)    .AddMouseWheelSupport(scrollViewer2)    .AddMouseWheelSupport(scrollViewer3);

Both do exactly the same thing; the second version is just cleaner. It begins by using an overload which passes in null for the parent and then returns scrollViewer1. The next two calls use the returned ScrollViewer as the parent and set up a specified child (scrollViewer2 is added as a child to scrollViewer1, and scrollViewer3 is added as a child to scrollViewer2).

Or, let's say you have one parent with four children--A, B, C, D--like this:

image_thumb3_thumb2

Here's the long way to add mouse wheel support to all 5 ScrollViewers:

ScrollViewerMouseWheelSupport.AddMouseWheelSupport(null, parent, 30.0);ScrollViewerMouseWheelSupport.AddMouseWheelSupport(parent, childA, 30.0);ScrollViewerMouseWheelSupport.AddMouseWheelSupport(parent, childB, 30.0);ScrollViewerMouseWheelSupport.AddMouseWheelSupport(parent, childC, 30.0);ScrollViewerMouseWheelSupport.AddMouseWheelSupport(parent, childD, 30.0);

And here's the short way:

parent.AddMouseWheelSupport();parent.AddMouseWheelSupport(childA);parent.AddMouseWheelSupport(childB);parent.AddMouseWheelSupport(childC);parent.AddMouseWheelSupport(childD);

The API allows for variation, but that's the basic usage.

Limitations

This only works with ScrollViewer. It does not affect any other scrolling control (such as a ListBox).

Download

Here is a zip archive of a Visual Studio 2008 SP1 solution that contains the workaround source code in its own project/assembly (Cooper.Silverlight.Controls) as well as the source for the demos listed above:

Solution with Source Code and Demos (ScrollViewerMouseWheelDemo.zip)

Currently rated 5.0 by 3 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Comments

Add comment


(Will show your Gravatar icon)  

  Country flag

biuquote
  • Comment
  • Preview
Loading





 
Powered by BlogEngine.NET 1.4.5.0 | Design by adamjcooper