Implement MouseWheel support for Silverlight 3 controls (and for Silverlight 4 Slider and TreeView)

by Jim McCurdy 29. January 2010 10:43

In the upcoming Silverlight 4 release, mousewheel support for controls will be implemented out of the box for the ScrollViewer, DataGrid, and ListBox controls.  However Silverlight 3 still requires you to roll your own mouse wheel support.  And Silverlight 4 does not offer a scrolling solution for Slider and TreeView.

The following MouseWheelProps class provides generic mousewheel support for all Silverlight controls that support the IScrollProvider interface (ScrollViewer, ListBox, DataGrid) or the IRangeValueProvider interface (Slider).  This class implements a single attached property called Enable that can be added directly to a control, or as a style for a control.

Step1: Add the following MouseWheelProps class to your Silverlight project:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace ClassLibrary
{
	public static class MouseWheelProps
	{
		// The Enabled attached property
		public static readonly DependencyProperty EnabledProperty = 
			DependencyProperty.RegisterAttached("Enabled", typeof(bool), typeof(MouseWheelProps),
				new PropertyMetadata(false, OnEnabledPropertyChanged));

		public static bool GetEnabled(FrameworkElement element)
		{
			return (bool)element.GetValue(EnabledProperty);
		}

		public static void SetEnabled(FrameworkElement element, bool value)
		{
			element.SetValue(EnabledProperty, value);
		}

		private static void OnEnabledPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
		{
			UIElement element = sender as UIElement;
			if (element == null)
				return;
			bool enabled = (bool)e.NewValue;
			if (enabled)
				element.MouseWheel += OnMouseWheel;
			else
				element.MouseWheel -= OnMouseWheel;
		}

		private static void OnMouseWheel(object sender, MouseWheelEventArgs e)
		{
			if (e.Handled)
				return;

			UIElement element = sender as UIElement;
			if (element == null)
				return;

			AutomationPeer peer = FrameworkElementAutomationPeer.FromElement(element);
			if (peer == null)
				peer = FrameworkElementAutomationPeer.CreatePeerForElement(element);
			if (peer == null)
				return;

			// try to get the scroll provider or the range provider
			IScrollProvider m_ScrollProvider = peer.GetPattern(PatternInterface.Scroll) as IScrollProvider;
			IRangeValueProvider m_RangeValueProvider = null;
			if ((element is Control) && (element as Control).HasFocus())
				m_RangeValueProvider = peer.GetPattern(PatternInterface.RangeValue) as IRangeValueProvider;
			if (m_ScrollProvider == null && m_RangeValueProvider == null)
				return;

			// set scroll amount
			const double kMultiplier = 5.0; // x times the default
			const double kFactor = kMultiplier / (30 * 120);
			double delta = e.Delta;
			delta *= kFactor;
			int direction = Math.Sign(delta);

			bool shiftKey = (Keyboard.Modifiers & ModifierKeys.Shift) != 0;
			bool controlKey = (Keyboard.Modifiers & ModifierKeys.Control) != 0;

			if (m_ScrollProvider != null)
			{
				e.Handled = true;
#if true
				if (m_ScrollProvider.VerticallyScrollable && !controlKey)
				{
					double percent = m_ScrollProvider.VerticalScrollPercent - (delta * m_ScrollProvider.VerticalViewSize);
					if (percent < 0) percent = 0;
					if (percent > 100) percent = 100;
					m_ScrollProvider.SetScrollPercent(m_ScrollProvider.HorizontalScrollPercent, percent);
				}
				else
				if (m_ScrollProvider.HorizontallyScrollable && controlKey)
				{
					double percent = m_ScrollProvider.HorizontalScrollPercent - (delta * m_ScrollProvider.HorizontalViewSize);
					if (percent < 0) percent = 0;
					if (percent > 100) percent = 100;
					m_ScrollProvider.SetScrollPercent(percent, m_ScrollProvider.VerticalScrollPercent);
				}
#else
				ScrollAmount scrollAmount = (direction < 0) ? ScrollAmount.SmallIncrement : ScrollAmount.SmallDecrement;
				if (m_ScrollProvider.VerticallyScrollable && !controlKey)
					m_ScrollProvider.Scroll(ScrollAmount.NoAmount, scrollAmount);
				else
				if (m_ScrollProvider.HorizontallyScrollable && controlKey)
					m_ScrollProvider.Scroll(scrollAmount, ScrollAmount.NoAmount);
#endif
			}

			if (m_RangeValueProvider != null)
			{
				e.Handled = true;
				double newValue = m_RangeValueProvider.Value + (direction < 0 ? -m_RangeValueProvider.LargeChange : m_RangeValueProvider.LargeChange);
				if (newValue >= m_RangeValueProvider.Minimum && newValue <= m_RangeValueProvider.Maximum)
					m_RangeValueProvider.SetValue(newValue);
			}
		}
	}

	internal static class ExtensionMethods
	{
		// Extension for UIElement
		internal static bool HasFocus(this UIElement element)
		{
			if (element == null)
				return false;

			DependencyObject focusedElement = FocusManager.GetFocusedElement() as DependencyObject;
			while (focusedElement != null)
			{
				if (element == focusedElement)
					return true;
				focusedElement = VisualTreeHelper.GetParent(focusedElement);
			}

			return false;
		}
	}
}

Step 2: Add the attached property c:MouseWheelProps.Enabled="True" either directly to a control, or as a Style that can be referenced by a control.

Added directly to a control:

<UserControl x:Class="SilverlightApplication.MainPage"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:app="clr-namespace:SilverlightApplication"
	xmlns:DataGrid="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
>
	<Grid>
		<DataGrid:DataGrid c:MouseWheelProps.Enabled="True" />
	</Grid>
</UserContro>

Added as a Style:

<UserControl x:Class="SilverlightApplication.MainPage"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:app="clr-namespace:SilverlightApplication"
	xmlns:DataGrid="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
>
	<Grid>
		<Grid.Resources>
			<Style x:Key="CommonDataGrid" TargetType="DataGrid:DataGrid">
				<Setter Property="c:MouseWheelProps.Enabled" Value="True" />
			</Style>
		</Grid.Resources>
		<DataGrid:DataGrid Style="{StaticResource CommonDataGrid}" />
	</Grid>
</UserContro>

That's all there is to it!

Bookmark and Share

Comments

1/29/2010 12:34:20 PM #

trackback

Implement MouseWheel support for Silverlight 3 controls

You've been kicked (a good thing) - Trackback from DotNetKicks.com

DotNetKicks.com | Reply

2/1/2010 2:58:16 PM #

trackback

Implement MouseWheel support for Silverlight 3 controls

Thank you for submitting this cool story - Trackback from DotNetShoutout

DotNetShoutout | Reply

2/4/2010 4:30:58 AM #

trackback

Social comments and analytics for this post

This post was mentioned on Twitter by Erwin8: #silverlight - Implement MouseWheel support for Silverlight 3 controls: Tags: silverlight mousewheel tweet Posted ... http://bit.ly/bX9yV7

uberVU - social comments | Reply

4/7/2010 6:46:10 PM #

David Nelson

I am not able to make this solution work for a Silverlight 3 DataGrid. First, as noted by a commenter in the Silverlight forums where you posted this solution (forums.silverlight.net/.../289377.aspx), System.Windows.Controls.Control does not have a HasFocus method. I can only surmise that you are using an extension method, so I implemented one. However, the solution still does not work, as the automation peer for the DataGrid returns null for both the Scroll pattern and the RangeValue pattern.

David Nelson | Reply

4/7/2010 7:00:07 PM #

Jim McCurdy

David,

Sorry about that.  My HasFocus() extension method actually checked for focus among the element's children, so it was a pretty important piece of the solution.  I updated tha code above to included that method and it's dependant methods.

Jim

Jim McCurdy | Reply

4/8/2010 12:45:28 PM #

David Nelson

I found another implementation of a HasFocus extension method (below) which works fine. Determining focus is not the main problem; the problem is that the automation peer is returning null for the scroll pattern. Any idea why that would be?

public static bool HasFocus(this Control aControl, bool aCheckChildren) {
  var oFocused = FocusManager.GetFocusedElement() as DependencyObject;
  if (!aCheckChildren)
    return oFocused == aControl;
  while (oFocused != null) {
    if (oFocused == aControl)
      return true;
    oFocused = VisualTreeHelper.GetParent(oFocused);
  }
  return false;
}

David Nelson | Reply

4/8/2010 3:14:28 PM #

Jim McCurdy

David,

I checked the following code with a DataGrid:


AutomationPeer peer = FrameworkElementAutomationPeer.FromElement(element);
if (peer == null) // peer is null here for DataGrid
     peer = FrameworkElementAutomationPeer.CreatePeerForElement(element);
if (peer == null) // peer is a valid object for DataGrid
     return;


So I am not sure what is different about your implementation....
Jim

By the way, I like your HasFocus() method better - a lot faster and simpler

Jim McCurdy | Reply

5/1/2010 1:25:23 PM #

Jared

Thank you for this excellent post.  I was trying to do the same thing but running into alot of problems.  You saved me HOURS!

Jared | Reply

5/3/2010 5:49:49 PM #

jim mccurdy

Your welcome!

jim mccurdy | Reply

6/17/2010 12:13:03 PM #

Baumann Benjamin

It works "out of the box" with my datagrids.
Thank you, you saved me a lot of time.

Baumann Benjamin | Reply

8/30/2010 6:22:40 AM #

Social Media Packages

Yeap gotta agree, works "out of the box"

Well done Jim. Top marks.

Social Media Packages | Reply

Add comment


(Will show your Gravatar icon)

Click to change captcha
biuquote
  • Comment
  • Preview
Loading




My Photo Jim is always looking for new clients in need of software development expertise.  He is particularly well suited for Silverlight, .NET, ASP.NET, and WPF projects.

Professional Biography

Jim McCurdy operates Face to Face Software, where he works designing, developing, and managing software projects for a variety of clients.

Jim currently specializes in development projects for Silverlight, .NET, ASP.NET, and WPF platforms. One such project is a complete web site using Silverlight, .NET, and C#; a unique financial and lifestyle planning web application at YinYangMoney.com.

Jim has worked in the software industry for as long as he can remember, and has broad expertise in Web, Windows, and systems technologies. Jim has been a team player on many agile development projects throughout his career, and is a founding member at software startups Astral Development and Powerhouse Entertainment.

You can Email Jim at Face to Face Software.


Welcome Readers