Silverlight Playground
about Silverlight and other Amenities

Silverlight 3.0 RTW: An universal MouseWheelScrolling behavior

2009-07-10T17:45:00+01:00 by Andrea Boschin

Silverlight 3.0 RTW introduces a new event many people has asked for in the last months, the MouseWheel event. Handling this event is pretty simple while it simply raise and event that take a Delta to the developer with a positive or negative value according to the direction and the amount of the scrolling.

Showing an sample about MouseWheel take a few lines of code and is unuseful while I think many people can simply experiment and understant how it works in a few minutes. So I decided to merge this sample with some words about the new Behaviors introduced with Expression Blend.

My focus in this post is to show how to create a Behavior,  and using the MouseWheel event, add to all the scrollable controls the capability to scroll using the Mouse wheel.

What is a Behavior?

Some of you may have already used the Behaviors in the ASP.NET AJAX framework. The simple answer to the question of this paragraph is: "They are a Silverlight implementation of the ASP.NET AJAX semantics that allow to create reusable and attachable behaviors to HTML controls". You may find the origin of Silverlight Behaviors in an example posted by Nikhil Kothari during the May 2008.

While the simple answer is very clear and let you understand the concept under Behaviors we have to explore them more deeply to understant how to create a behavior and let it available to our projects.

The beta of Blend 3 introduces the concept of behavior into the Blend interface. There are some built-in behaviors we can simply drag on the design surface to give a new life to our graphical elements. Into the Asset folder, where usually we find controls, effects, resources and other thing, there is a new "behaviors" sheet like in the image I attached here on the right. 

With Expression Blend 3.0 many types of behaviors have been introduced. Behavior<T> is the most simple flavour that apply to a DependencyObject (the generic T) and handle events and other aspects from the source controls. A behavior can simply modify the aspect of a control, adding elements, changing the properties, or can handle one or more events to add some new features. The MouseDragElementBehavior is an example of it. Simply it attach Mouse events and let the element to be dragged into the page.

Writing a behavior

Writing a behavior is a pretty simple task. A behavior is a class extending Behavior<T> so the first thing to do is referencing the assemblies Microsoft.Expression.Interactions.dll and System.Windows.Interactivity.dll. from the following folder:

C:\Program Files\Microsoft SDKs\Expression\Blend 3\Interactivity\Libraries\Silverlight

If you add an existing behavior from Blend 3.0 a reference to these assemblies is automatically added to your project.

Once you have added the reference it is time to create the class:

   1: public class MouseWheelScrollBehavior : Behavior<Control>
   2: {
   3:     // Add implementation here
   4: }

While we are extending the "scrollable" components in Silverlight we need to create a type that can be attached to Control classes. In silverlight there is not any common class to scrollable components like ScrollViewer, DataGrid and ListBox. This imply we have to find a way to scroll eterogeneous components, but this is matter for the next paragraph. For now simply continue to analyze how to create the Behavior.

The next thing to do is to attach the MouseWheel event on the target object. Once we have extended the Behavior class we have two methods to override useful to handle attach and detach of the behavior to the target: OnAttached is called when the behavior attach an object and OnDetaching when it is detaching from the object. OnAttached and OnDetaching is the perfect location to attach and detach public events that we will handle to give the behavior to the target object. Obviously the target object is exposed by the Behavior<T> in the AssociatedObject property. Here is my code sample:

   1: /// <summary>
   2: /// Called after the behavior is attached to an AssociatedObject.
   3: /// </summary>
   4: /// <remarks>Override this to hook up functionality to the AssociatedObject.</remarks>
   5: protected override void OnAttached()
   6: {
   7:     this.AssociatedObject.MouseWheel += new MouseWheelEventHandler(AssociatedObject_MouseWheel);
   8:     base.OnAttached();
   9: }
  10:  
  11: /// <summary>
  12: /// Called when the behavior is being detached from its AssociatedObject, but before it has actually occurred.
  13: /// </summary>
  14: /// <remarks>Override this to unhook functionality from the AssociatedObject.</remarks>
  15: protected override void OnDetaching()
  16: {
  17:     this.AssociatedObject.MouseWheel -= new MouseWheelEventHandler(AssociatedObject_MouseWheel);
  18:     base.OnDetaching();
  19: }

Now the behavior is ready to be attached to the object but it still does nothing. We need to implement the scrolling for scrollable components, so it is time to enter the next paragraph.

Scrolling the Scrollable... not so simple!

While I'm writing the sample I've attached to this post, there were a moment when I thinked this behavior is impossible to be implemented and I was ready to switch to a less generic ScrollViewer scroller behavior. The reason for this is that there is not any common scrolling interface to scrollable components. So while creating a Behavior for a ScrollViewer is simple, it is not so simple to use it for DataGrid or ListBox.

Just a moment before discarting my sample I've gived a last chance to "bing" to find someone handling the same problem. I finally found this that explain how to use Automation API in Silverlight to enable the scrolling of components without extending them.

I leave the full explanation to the good article I've linked. Here the only thing we need to Know is that Automation API expose a IScrollProvider interface doing exactly what I'm searching for. So we need to change the OnAttached method to create the Automation Peer for the attaching object:

   1: /// <summary>
   2: /// Gets or sets the peer.
   3: /// </summary>
   4: /// <value>The peer.</value>
   5: private AutomationPeer Peer { get; set; }
   6:  
   7: /// <summary>
   8: /// Called after the behavior is attached to an AssociatedObject.
   9: /// </summary>
  10: /// <remarks>Override this to hook up functionality to the AssociatedObject.</remarks>
  11: protected override void OnAttached()
  12: {
  13:     this.Peer = FrameworkElementAutomationPeer.FromElement(this.AssociatedObject);
  14:  
  15:     if (this.Peer == null)
  16:         this.Peer = FrameworkElementAutomationPeer.CreatePeerForElement(this.AssociatedObject);
  17:  
  18:     this.AssociatedObject.MouseWheel += new MouseWheelEventHandler(AssociatedObject_MouseWheel);
  19:     base.OnAttached();
  20: }

First we search the Automation interfaces if the control already created it. If the interface has not been found we need to create it. The AutomationPeer is saved in a member property to be used when the MouseWheel event is raised. Here is the code to scroll the target:

   1: /// <summary>
   2: /// Handles the MouseWheel event of the AssociatedObject control.
   3: /// </summary>
   4: /// <param name="sender">The source of the event.</param>
   5: /// <param name="e">The <see cref="System.Windows.Input.MouseWheelEventArgs"/> instance containing the event data.</param>
   6: void AssociatedObject_MouseWheel(object sender, MouseWheelEventArgs e)
   7: {
   8:     this.AssociatedObject.Focus();
   9:  
  10:     int direction = Math.Sign(e.Delta);
  11:  
  12:     ScrollAmount scrollAmount = 
  13:         (direction < 0) ? ScrollAmount.SmallIncrement : ScrollAmount.SmallDecrement;
  14:  
  15:     if (this.Peer != null)
  16:     {
  17:         IScrollProvider scrollProvider =
  18:             this.Peer.GetPattern(PatternInterface.Scroll) as IScrollProvider;
  19:  
  20:         bool shiftKey = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
  21:  
  22:         if (scrollProvider != null && scrollProvider.VerticallyScrollable && !shiftKey)
  23:             scrollProvider.Scroll(ScrollAmount.NoAmount, scrollAmount);
  24:         else if (scrollProvider != null && scrollProvider.VerticallyScrollable && shiftKey)
  25:             scrollProvider.Scroll(scrollAmount, ScrollAmount.NoAmount);
  26:     }
  27: }

When we get the Delta we need to extract the direction of the scrolling. This is because we are unable to speficy the amount of pixels we have to scroll the target but simply the ScrollAmount that may be a SmallIncrement or SmallDecrement (or LargeIncrement, LargeDecrement). So using the direction we decide from Increment and Decrement.

While the controls can scroll both vertically or horizontally I decided to check if the shift key is pressed to allow horizontal scroll. Finally using the Scroll method in the IScrollProvider I apply the scrolling to the target control. This is very simple because the IScrollProvider does all the work for us. There is no need to check boundaries, we have simply to say how to scroll and all the work is done.

Using the Behavior

Using Blend is the simpler way to apply behaviors. The blend assets library scan the classes of the project and show them in the library so dragging the behavior on the scrollable component is sufficient to apply the behavior. We need to learn ho to apply the behavior by code. Here is a sample:

   1: <UserControl
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   4:     xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
   5:     xmlns:local="clr-namespace:Elite.Silverlight3.MouseWheelSample.Silverlight.Classes"
   6:     xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
   7:     x:Class="Elite.Silverlight3.MouseWheelSample.Silverlight.MainPage" 
   8:     Width="Auto" Height="Auto">
   9:  
  10: ... omissis ...
  11:  
  12: <data:DataGrid Grid.Column="1" Grid.Row="0" ItemsSource="{Binding DataItems}" Margin="20">
  13:     <i:Interaction.Behaviors>
  14:         <local:MouseWheelScrollBehavior />
  15:     </i:Interaction.Behaviors>
  16: </data:DataGrid>

In the UserControl we declare the namespaces we have to use. In this sample "i" stand for interactivity, the assembly where the Behavior<T> classe is located. The "local" refer to the local classes where we put the new Behavior. In the second part we attach the behavior to a DataGrid. To attach the behavior to a ScrollViewer or to a ListBox the code is pretty similar. Running the project we will have the mouse wheel working on the DataGrid.

Conclusion

The tecnique I've illustrated has the big advantage or working for all the scrollable controls but not for ComboBox because it does not directly implements IScrollProvider interface. The drawback is that it works only on windows. This is a big problem but there is not any solution to it while as I already said it is the only way to scroll all the controls programmatically. Moreover we have to take note that the MouseWheel event only works in Windows with Internet Explorer and also with Firefox when you are not using the windowless mode. This is a limitation of the architecture of Safari and Firefox and the only way to work around it is using the DOM events. So due to this limitations the example is a good solution to be used where it works. In the others cases it simply does nothing.

Download: Elite.Silverlight3.MouseWheelSample.zip (1,2 MB)

Video: MouseWheelScrollBehavior.wmv (1.4 MB)

Comments

July 22. 2009 04:38

Trackback from di .NET e di altre Amenit

Silverlight 3.0 RTW: Nuovi articoli pubblicati

di .NET e di altre Amenit

July 24. 2009 23:23

I have wrapped the datagrid and many other SL controls. Can you show me an example to attache a behavior in C# code?

Martin Nyborg

July 26. 2009 17:50

LOL, good clean behavior patterns FTW or not, tell me one control with a scrollbar that wouldn't cry for a defaultly implemented mouse wheel funtionality the users are yearning for.

Thank you very much, just what I was looking for. Hope it will be default in the next Silverlight versions.

mepfuso

July 26. 2009 18:09

For the record, I guess your

else if (scrollProvider != null && scrollProvider.VerticallyScrollable && shiftKey)

should better be  scrollProvider.HorizontallyScrollable && shiftKey  instead,

but as it doesn't throw any harsh exceptions, most copy-'n'-paste-developers won't care a lot :-P

mepfuso

July 27. 2009 19:19

Good article, thanks!

If there is no DataGrid it's possible to use the follow solution without Automation UI:

if (sender is ListBox)
{
  ListBox lbox = sender as ListBox;
  if (lbox == null)
    return;

  FrameworkElement f = (FrameworkElement)VisualTreeHelper.GetChild(lbox, 0);
  ScrollViewer sv = (ScrollViewer)f.FindName("ScrollViewer");
  if (sv == null)
    return;

  sv.ScrollToVerticalOffset(sv.VerticalOffset - e.Delta / 120);
}
else if (sender is ScrollViewer)
{
  ScrollViewer sv = sender as ScrollViewer;
  if (sv == null)
    return;

  sv.ScrollToVerticalOffset(sv.VerticalOffset - e.Delta / 12);
}

The only thing I don't understand is troubles with Firefox. I have no troubles with it. Will you explain what are you talking about?

P.S.
My English isn't well too and I use MS Word to correct my simplest misprints. That's a hint Smile

pwr

August 13. 2009 01:22

Well, how would you do that if you don't have Blend 3 or even no Blend at all? VS2008 doesn't know those assesmblies that need to be referenced.

Thanks in advance!

DJanjicek

August 27. 2009 08:17

Trackback from Dan Wahlin's WebLog

Building Line of Business Applications with Silverlight 3 – Pros and Cons

Dan Wahlin's WebLog

August 29. 2009 16:02

Thanks Unni for your response. I've completely missed the question...

Andrea Boschin

August 29. 2009 18:58

DJanjicek, the Blend SDK is a standalone installer - you don't need Blend installed to build these behaviors or use them.

http://www.microsoft.com/downloads/details.aspx?FamilyID=F1AE9A30-4928-411D-970B-E682AB179E17&displaylang=en

Thanks,
Unni
Program Manager, Expression Blend

Unni Ravindranathan

September 21. 2009 01:49

Thanks, that was helpful! Smile

Your code is clear and very well constructed. It was a pleasure to read it Smile

Thanks,
Jacek Ciereszko

Jacek Ciereszko

October 7. 2009 21:26

Silverlight 30 RTW An universal MouseWheelScrolling behavior  , fascinated me. I have recently begun to develop utilising Silverlight but I am realising it is a big learning curve.  My recent experience is with php, mysql, most linux based tools and flash. The problems of using Silverlight to produce a satisfactory page structure which runs speedily in all the leading browsers, Internet Explorer, Safari, Firefox and Google Chrome seems a large head ache which I find is taking several hours to overcome.  Absorbing to read your thoughts and the comments in your web site on Silverlight.  I feel the tutorial web sites and Microsofts Silverlight site are strict and address the identical items, discussion in web logs often references genuine ways to resolve problems which takes me through the learning curve more rapidly.  Thanks for the post, it has assisted in a small way to take me through the migration.

e-g-o

October 12. 2009 12:10

Thanks for your code. I'm wondering why Microsoft didn't add this MouseWheel behavior by default?

Thanks again,
Thomas

Thomas Halwax

November 26. 2009 00:14

Excellent work done!  Thanks!  But, I saw in silverlight forum that this effect is directly available if we include .net framework 4 I guess. Anyway, thanks again

Raja

November 26. 2009 00:20

You are right Raja. Silverlight 4.0 has this feature directly available but it is in beta since yesterday.

Andrea Boschin

Add comment




  Country flag

biuquote
  • Comment
  • Preview
Loading