Introduction

Windows Presentation Foundation (aka WPF) is an outstanding graphical framework for creating modern and attractive applications in .NET. Presently it lacks decent support for mouse wheels such as horizontal, smooth and nested scrolls as well as the high-resolution mode.

The WPF Mouse Wheel Library (MWLib) I will present here aims to go beyond those limitations with a minimal effort on your behalf. It is written in C# and compiled with .NET 3.5 Service Pack 1 Client Profile (packaged with Windows 7). You’ll be able to easily integrate this library into any existing WPF application, giving your users a tremendous feeling of fluidity when using their mouse wheel. Besides improving scroll experience with horizontal, smooth and nested scrolls, the library supports any resolution mouse wheels, implements motion debouncing as well as smooth and nested zoom. It also provides support to enable mouse wheels on any range-like controls.

Roadmap

For those eager to get their hands dirty before eventually resuming reading, we’ll begin with a quick-start guide. Then after some background information and an overview of MWLib features, we’ll go through a small tutorial: you will create a WPF application, spotting out-of-box mouse wheel support limitations and using MWLib to fix them. Finally, we’ll examine in depth mouse wheel limitations and improvements by playing with the two demo applications. This will allow us to compare the behavior of the wheel in various situations before and after the integration of the library.

Quick Start

For people who want to immediately benefit from MWLib please:

Download the library provided at the top of this article, include it in your solution and reference it in your project.

Background

Before going further, I suggest skimming through Handling Enhanced Mouse Wheels in your application by Tanvi Shah K & H Steve Davis. It describes a short history of the wheel, its description, the high-resolution mode, and the interfacing with native Windows.

Wheel Model in Windows

Microsoft has modeled a mouse wheel made of discrete and evenly spaced notches. When you rotate the wheel, a “wheel delta” is reported each time a notch is encountered. Originally, one notch was intended to be the base unit of wheel rotation measurement.

Its value was set to 120 by Microsoft so that vendors could build “enhanced” wheels, measuring their amount of rotation with a finer accuracy simply by reporting it with a lower ‘wheel delta’. Such wheels can be switched from standard to high resolution mode (DPI switching).

Instant native resolution can be defined as:

nativeResolution = 120 / abs(wheelDelta)

Fields Of Application

Originally the “scroll wheel” was used mainly for scrolling, hence its name. In WPF however holding a keyboard modifier down changes the field of application: if one holds the Control key while rotating the wheel text size will increase or decrease in some web browsers; on the other hand an image in an image-editing program will zoom in or out. Other applications scroll horizontally if you hold the Shift key down.

WPF Issues

In WPF implementation mouse wheel event handlers (UIElement.OnMouseWheel) are overridden in the following graphical elements:

No Out-Of-Box Support Of High Resolution Mode

All the above mouse wheel event handlers only use wheel delta sign for resolving scroll or zoom direction: resolution is not computed. As a consequence when an enhanced wheel enters high resolution mode, wheel motion cannot be adapted. The outcome is a hyper-sensitive movement (see below) instead of a finer control of the motion making it a disadvantage rather than an advantage.

Inconsistent Nested Scroll

In WPF nested scroll works only when inner elements are subtypes of TextBoxBase.

Yet it does not work for ScrollViewer or FlowDocumentScrollViewer instances. The reason is that their OnMouseWheel handler always sets RoutedEventArgs.Handled to True. But this property should be set to False when this element reaches one of its scrolling limit so that scrolling carries on.

On the other hand FlowDocumentPageViewer always sets RoutedEventArgs.Handled to False, leading to simultaneous scrolling of both the inner (moving a page forward or backward) and outer elements.

In the context of nested scroll you expect coherence in mouse wheel field of application.

For example suppose that cursor is over a ScrollViewer inner element and outer element is a FlowDocumentScrollViewer. If you hold down the Control key while rotating the wheel you expect that a zoom action will be performed (on the outer element), but instead a scroll action takes place (on the inner element). This is the consequence of bad handling of keyboard modifiers in inner element OnMouseWheel handler: ScrollViewer does not handle zoom but scrolls instead no matter what modifiers are being used.

MWLib Features

MWLib aims to overcome the WPF issues mentioned above.

Out-Of-Box Support of High Resolution Mode

Users no longer face a hyper-sensitive movement.
Instead users obtain a finer control leading to more responsiveness.

Consistent Nested Scroll

Nested scroll is now fully functional and coherent for all fields of application. Moreover transition between inner and outer scrollable elements are smooth even when inner element is item-based (e.g. a ListBox) and outer is pixel-based (e.g. an Image within a ScrollViewer).

Field Of Application Discrimination

You can now assign keyboard modifiers to desired fields of application. Mismatches between intended operation and observed outcome are fixed.

What’s more, MWLib offers:

Additional Fields Of Application

Horizontal Scrolling (default modifier is the Shift key).

Adaptation : mouse wheel can now act on any range-like control (e.g. move a Slider cursor).

Tilting Scroll Wheel Support

You can now scroll horizontally with the tilting scroll wheel.

Motion Smoothing

Smooth displacement can be enabled on any UI element within any field of application.

Motion Debouncing

With an enhanced wheel a debouncing algorithm may be needed to filter out jitter produced by unintentional movements of your finger. They are likely to occur when scrolling slowly, lifting finger off the wheel...

Vendors usually implement such an algorithm to emulate standard resolution wheels when high resolution mode is off. MWLib comes with built-in debouncing for high-resolution too.

Versatile Scroll Increments

One can now adjust scroll increments absolutely or proportionally to system settings (see mouse wheel control panel).

Features Propagation

You can implicitly benefit from MWLib features anywhere in UI elements tree (thanks to WPF attached property inheritance) or override them locally. Control templates as well inherit from those features with no additional code.

Works on Several Controls

Your First Smooth Scrolling Application

In this section we’ll create from scratch a simple WPF application in order to:

Spot out-of-box scrolling limitations.

Apply MWLib to fix them.

Create a new solution in Visual Studio. Add a new WPF application project: let’s call it “SmoothScroll” for instance. In the project properties, under “Application” tab, select “NET Framework 3.5 Client Profile” as the target framework. Build and run.

We are now ready to add new functionality to the application. To begin, we’ll embed in the main window a ListBox with enough items to test vertical scrolling and with data items that are wide enough to test horizontal scrolling. Here is the corresponding XAML code:

In bold we highlight the code that has been updated. Some repeating text has been skipped with ellipses. Let’s execute it.

Out-Of-Box Experience (without MWLib)

Vertical Scrolling

Place the mouse cursor in the list box then roll the wheel one notch toward yourself: the list box should scroll 3 items. If it’s not the case, launch the Mouse Control Panel Applet, activate the Wheel tab, modify the number of lines per notch to 3, and press 'OK'.

Horizontal Scrolling

Unfortunately, mouse wheel horizontal scrolling is still not implemented in the current version of WPF. That’s one of the problems addressed by MWLib.

High Resolution Mode

Let's test an enhanced wheel in high-resolution mode. If you do not own such a wheel or if this mode is not enabled, launch the enhanced wheel emulator application, change the Event Interval from 8 to 32 ms then press 'Apply'. Please notice that if you exit from this application, standard resolution will be restored.

Repeat the vertical scroll test : granularity has now increased from 3 to 8 x 3 = 24 items per notch... Instead of getting finer control over scrolling we end-up with a too sensitive wheel! Indeed WPF does not take into account the wheel delta value at all and raises 8 events per notch.

Improving Experience (with MWLib)

We are going to fix the above issues. Please add a reference to MWLib and then update main window XAML like this:

In standard resolution mode you will not see any difference concerning vertical scrolling. Granularity is still 3 items per notch, however horizontal scrolling is now available. To test it just tilt the wheel or press the Shift key and roll the wheel. As a bonus horizontal scroll is smooth.

In high resolution mode notice that granularity of the vertical scroll is not 24 items per notch anymore but 3. Great! There is nevertheless a small difference with standard mode: the list scrolls a bit more smoothly, one element at a time. Eight events per notch are raised, which are converted into three events by the library.

Logical vs. Physical Scroll

By default the list box vertical scrolling is not smooth. To find out why, we have to dig a little bit deeper in MSDN.
As explained in ‘ScrollViewer Overview’ WPF implements two scrolling modes:

Physical scrolling mode:

Smooth scrolling is only available in physical mode where displacement can be controlled at pixel level.

Logical scrolling mode:

It is also called item-based scrolling. In this mode UI virtualization is on.

“UI virtualization refers to a technique by which a subset of UI elements are generated from a larger number of data items based on which items are visible on-screen. Generating many UI elements when only a few elements might be on the screen can adversely affect the performance of your application” (VirtualizingStackPanel Class, Remarks section).

A few controls like ListBox, ListView and DataGrid are in logical mode by default. In order to preserve application performance, MWLib keeps them in this mode by default. Nevertheless we can easily switch those controls to physical mode, which will turn on smooth scrolling.

Switching to Physical Mode

To switch the list box from logical to physical mode you have two choices:

Either modify the value of the ScrollMode attached property:

i:MouseWheel.ScrollMode="Physical"

This property applies to the decorated element and all its descendants. If we want the scrolling mode to be applied to a specific orientation, we can use VScrollMode and HScrollMode properties instead.

It is organized as a tree of tab items in which each leaf is a small ‘lab’ where you can test wheel behavior on various WPF controls. I suggest you navigate the tree and play with the wheel, observing both scroll and zoom.

Zooming can be tested in the FlowDocumentxxx tabs by pressing the Control modifier key while rotating the wheel.

Horizontal scrolling is not yet implemented in native WPF.

We can notice that the user experience is particularly poor with a wheel in high resolution mode, or in nested scroll or zoom tabs.

Here you can notice the improvement brought by MWLib.
In addition to the various tabs already available in the ‘before’ application, the ‘after’ application offers a new tab: Adaptation, where the mouse wheel controls the cursor of a slider.
Furthermore an Options panel lets you customize various aspects of wheel motion.

It shows a list of available wheels with their name and resolution and a visual representation of attached properties of MouseWheel.

Before going further it’s worth noting that some of them participate in value inheritance.

Data binding between visual options and attached properties of MouseWheel follows the MVVM pattern: the properties of the view - MouseWheelOptionsView - are bound to the properties of its view-model - MouseWheelOptions which is set as a data context of the application main window. Then the scroll and zoom properties of MouseWheelOptions are bound to the corresponding attached properties of MouseWheel:

In the above code the Enhanced binding as well as the Scroll and Zoom bindings are applied to the left workspace panel of the application - WorkspaceView. All of them are inheritable thus becoming available to all elements of the visual tree of WorkspaceView.

Adaptation options are not inheritable, and directly applied to the Slider control as shown below:

The Enhanced check-box is bound to the MouseWheel.Enhanced attached property. It must be turned on so that MWLib features become active.

Note that you can declare this property as an attribute of the visual tree root element or from the level you wish the improved features to be enabled from.

One may wonder what happens if MouseWheel.Enhanced is set to False on an element. We can experiment it by un-checking Enhanced in the Options panel. The overall behavior of the wheel will mimic the native behavior we’ve seen previously in the ‘before’ application.

Auto: in this mode, the library uses some heuristics (CreateEnhancedAutoBehavior) to decide which one of the logical or physical modes should be applied. Actually logical mode is used for elements that support item-based scrolling, except when such elements contain nested scrollable elements. In this case and in all other cases physical mode is used.

Physical: this mode can be applied to any scrollable element type. It’s the one that allows smooth motion control (CreateEnhancedPhysicalBehavior).

It is the value by which the scroll position is increased or decreased when you roll the wheel by one notch towards or away from yourself.

Scroll increment is implemented as a custom type (ScrollIncrement) rather than Double. In this manner, we can easily define a value scaled by the number of vertical scrolling lines or horizontal scrolling characters selected by the user in the mouse wheel control panel (star notation).
For example, modify your first smooth scrolling application with the following code:

Set its MouseWheel.ScrollMode property to Physical. Alternatively, you can use the VScrollMode and the HScrollMode properties to have more specific control over vertical or horizontal direction.

Set its MouseWheel.ScrollSmoothing property to Smooth : the default for elements in physical scroll mode. As before the VScrollSmoothing and the HScrollSmoothing properties are available for controlling a desired direction.

I don’t advise you to enable smooth zoom : it consumes a lot of CPU time and does not significantly enhance user experience more than linear zoom (the default value). If you still want to enable it use the MouseWheel.ZoomSmoothing attached property.

Hello, I just made a little class that makes WPF support MouseHorizontalWheel / PreviewMouseHorizontalWheel events here[^]

1) Any chance to integrate it into your library instead of relying on shift+vertical scroll wheel to do horizontal scrolling?
2) If not, any chance to put it up on GitHub so I can create a pull request / fork it?
3) If all else fails, any objections if I put it on GitHub and patch it accordingly?

On certain machines/mice we where experiencing a crash whenever the user scrolled the mouse wheel. I haven't bothered investigating why or how...I only came up with a simple work around since it seemed to effect very few people. If you experience a crash whenever using the mouse wheel make this change:

This is all nice, but way too complicated. I don't need smoth, bouncing stuff. I just want WPF to respect my mouse driver's acceleration hints. So my window gets scroll messages with larger delta values when I turn the wheel faster, but WPF ignores them. When I simply drop in MWLib, my application does anything but work as expected. The ListBox with a custom item container scrolls 3 pixels per turn, and the ScrollViewer with a custom template scrolls slow as hell.

What do I need to do to make MWLib not change anything but simply respect that acceleration feature?

I had problems with scrolling in my WPF-App, while searching I found this lib and yes, scrolling was nice again, but Visual Studio 2012 Designer was not working OK anymore, exactly it was a problem with my Odyssey OutlookBar that was working without a problem in the designer:

"Object reference not set to an instance of an object."

Compiling and running was no problem.

Found out that it was the added WPF Mouse Wheel lib.
Removed it and the Designer was OK again.

So I don't know what ecactly messes up my object ... as it doesn't tell anything more than that.

I was hoping this article/library would have a solution to the ComboBox inside a ScrollViewer problem, since code I wrote to implement nested scrolling has a similar problem. Unfortunately, I cannot even find the code in this library that handles nested scrolling... it's way too abstract and underdocumented to be able to wrap my mind around at the moment.

I ran the SmoothScroll app after upgrading the project to .NET 4.0 (for those who try this, I encountered a minor compile error that I fixed by adding a reference to System.Xaml in WpfMouseWheelLib).

Vertical scrolling works as expected. For horizontal scrolling, I can only get it to work when using Shift + vertical mouse wheel. This is consistent with the documentation, but I cannot perform horizontal scrolling without Shift.

If I use a mouse like the G9 which has horizontal scrolling by pushing the wheel left or right, it doesn't work with MouseWheelLib (have to use vertical wheel and press Shift). Same thing for the Wireless Trackpad, horizontal scrolling doesn't work unless I scroll vertically with 2 fingers and press the Shift key.

Could horizontal scrolling be added to the library as there are now more devices that support horizontal scrolling (MS touch mouse, Logitech Trackpad, etc.)?