How to enable mouse wheel scrolling in Silverlight without extending controls

Introduction

Silverlight currently does not support mouse wheel events. However, you can attach an event to capture the mouse wheel movement through the HtmlPage object, find the control beneath the mouse position with the VisualTreeHelper class and scrolling the region (DataGrid, Combobox, ScrollViewer, TreeView, etc) through the Automation API.

Source Code and Live Demo

Mouse wheel event

There are several examples on the net how to capture the mouse wheel event. The following code snippet shows this very shortly. You can find a full example here (Thanks to Mike Snow): http://silverlight.net/blogs/msnow/archive/2008/07/29/tip-of-the-day-23-how-to-capture-the-mouse-wheel-event.aspx

public TestPage()
{
	InitializeComponent();
	HtmlPage.Window.AttachEvent("DOMMouseScroll", OnMouseWheel);
	HtmlPage.Window.AttachEvent("onmousewheel", OnMouseWheel);
	HtmlPage.Document.AttachEvent("onmousewheel", OnMouseWheel);
}

private void OnMouseWheel(object sender, HtmlEventArgs args)
{
	// code goes here…
}

Scrolling with the UI Automation API

As next step we have to implement programmatically scrolling for different types of controls, such as Combobox, Treeview, DataGrid, ItemsControl, ScrollViewer, etc. But how do we programmatically scroll those controls without implementing special derived classes from it. Furthermore the scrolling support for the DataGrid is unfortunately not implemented by using ScrollViewers to do scrolling. Instead the DataGrid implements a method called ScrollIntoView which is not really helpfully here.

As I played with the Silverlight UI Automation API (UIA), I found a very interesting interface called IScrollProvider. This interface is exposed through the UIA and enables us scrolling out of the box. The UIA Provider interfaces are meant for Screen readers and general automation (testing) but can equally be used at runtime.

// get automation peer
AutomationPeer automationPeer = FrameworkElementAutomationPeer.FromElement(element);
if (automationPeer == null)
{
    // create automation peer for element
    automationPeer = FrameworkElementAutomationPeer.CreatePeerForElement(element);
}
// try to get scroll provider
IScrollProvider scrollProvider = automationPeer.GetPattern(PatternInterface.Scroll) as IScrollProvider;
if (scrollProvider != null)
{
    scrollProvider.Scroll(horizontalScrollAmount, verticalScrollAmount);
}

That’s it!

Connect wheel event to the controls

How do we route the mouse turned event to the right control? You can implement the code above for every control where we need mouse scrolling and enable/disable it with the MouseEnter/MouseLeave event. But this brings us a lot of plumbing code and special controls.

The VisualTreeHelper class provides the method FindElementsInHostCoordinates. This method returns a list of UIElements beneath a specified point (the current mouse position).

Our mouse wheel class (see sample code here) always knows the current mouse position througth the subscribed MouseMove event on the root UIElement.

Here the code (reduced to the important lines):

// go through all element beneath the current mouse position
IEnumerable<UIElement> elements = VisualTreeHelper.FindElementsInHostCoordinates(currentPoint, s_rootElement);
foreach (UIElement element in elements)
{
    // get automation peer (if already created for this control)
    AutomationPeer automationPeer = FrameworkElementAutomationPeer.FromElement(element);
    if (automationPeer == null)
    {
        // create automation peer for element
        automationPeer = FrameworkElementAutomationPeer.CreatePeerForElement(element);
    }

    //expect null: some elements doesn't have an automation peer implemented
    if (automationPeer != null)
    {
        // try to get scroll provider
        IScrollProvider scrollProvider = automationPeer.GetPattern(PatternInterface.Scroll) as IScrollProvider;
        if (scrollProvider != null)
        {
            if (scrollProvider.VerticallyScrollable)
            {
                scrollProvider.Scroll(ScrollAmount.NoAmount, scrollAmount);

                // break the further search in the uielement collection
                break;
            }

            // don't break here, because of encapsulated scroll viewers such as in the treeview from the sl-toolkit
            //break;
        }
    }
}

 

Misc

With pressing the control key, horizontal scrolling is possible also:

// horizontal scrolling?
bool ctrlKey = (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control;

if (scrollProvider.HorizontallyScrollable && ctrlKey)
{
    scrollProvider.Scroll(scrollAmount, ScrollAmount.NoAmount);
}

Conclusion

The UIA (UI Autamation API) provide an eas way to do scrolling without extending the existing controls and the VisualTreeHelper gets the possibility to dispatch the mouse wheel-turned event to the right control.

Most of the controls with scrolling regions implements the IScrollProvider API, unfortunately Textbox not. I’m trying to get another way to enable scrolling for textbox.

About these ads
Tagged

34 thoughts on “How to enable mouse wheel scrolling in Silverlight without extending controls

  1. Brian says:

    This is just what I was looking for. It looks like the source code zip file is missing the MouseWheelScrollingWeb project – any chance you can update the zip file?

    Thanks!

  2. beatkiener says:

    Sorry, I misunderstood your question.
    Of course, the web project is missing, but you should be able to start the Silverlight project directly. Visual studio will create a TestPage.html on the fly.

  3. Andrus says:

    How to change this so it works in SL3 OOB application also ? Shoudl this changed to use SL3 OnMouseWheel events ?

  4. RogueIV says:

    beatkiener,
    Thank you very much! This is by far the most elegant and useful solution I have seen to this problem. Bravo.

    rogueiv

  5. Andrus says:

    Using Ctrl+Wheel for horizontal scrolling is nice idea.

    How to add zoom it / zoom out feature for a scrolling with ctrl+shift+wheel presses just like Internet Explorer Ctrl ++ / – or Ctrl+wheel command ?

    • beatkiener says:

      I think one way is to use a ScaleTransform for zooming in/out the a specify element of the application.

      I’ve just added the following code to the MouseWheelService class and it works pretty good.

      // zoom?
      bool zooming = ctrlKey && (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
      
      if (zooming)
      {
             // go through all element beneath the current mouse position
          IEnumerable elements = VisualTreeHelper.FindElementsInHostCoordinates(currentPoint, s_rootElement);
          foreach (UIElement element in elements)
          {
              IMouseWheelZoom mouseZooming = element as IMouseWheelZoom;
      
              if (mouseZooming != null)
              {
                  mouseZooming.OnMouseWheelZooming( new MouseWheelZoomingEventArgs(currentPoint, e.Delta));
                  break;
              }
          }
      
      }
      else
      {
         // normal....
      }
      

      The code searches for a UIElement beneath the current mouse position which implements the interface IMouseWheelZoom. In this way you can simply delegate the functionality to any UIElement which implement IMouseWheelZoom. You can find a quick implementation of IMouseWheelZoom in the Page.cs file.

      Here is the code: http://files.thekieners.com/blogcontent/MouseWheelScrolling/MouseWheelWithZooming.zip

      Cheers,
      Beat Kiener

      • Andrus says:

        Nice but not sure is this useful: If DataGrid is zoomed, its scrollbars move out of screen. How to force only Text inside DataGrid to zoom so that DataGrid scrollbars remain visible ?

  6. Joshua Smyth says:

    Thank you very much for this code. It works very nice and I don’t think I could have figured it out myself.
    Would be nice for MouseWheel scrolling to be natively supported on the microsoft silverlight controls, but this works nicely.

  7. [...] How to enable mouse wheel scrolling in Silverlight without extending controls [...]

  8. Varsha says:

    How can I scroll all the datagrid whenever I am scrolling any one of the datagrid in my xaml page?
    I have 3 Datagrid on my xaml page and whenever user scrolls first datagrid then other 2 datagrid also scroll.
    Thanks

    • beatkiener says:

      Hi,
      Sorry for delay, I was very busy last days. Did you find a solution to parallel scroll all three data grids?

      I think there are two cases:
      First case is when the user scrolls a data grid with mouse wheel. In that case you can grab the IScrollProvider from each data grid and delegate your scroll command to all there grids.

      In the second case the user manually drags the scrollbar. In that case you can grab a reference to the vertical scrollbar and delegate the scroll event to the two other data grids and scroll them via IScrollProvider.
      I didn’t try this out, but I would start with this class:

      public class MyDataGrid : DataGrid
      {
      
          private ScrollBar verticalScrollbar;
      
          public MyDataGrid()
          {
          }
      
          public override void OnApplyTemplate()
          {
              base.OnApplyTemplate();
      
              verticalScrollbar = base.GetTemplateChild("VerticalScrollbar") as ScrollBar;
              verticalScrollbar.Scroll += new ScrollEventHandler(verticalScrollbar_Scroll);
          }
      
          void verticalScrollbar_Scroll(object sender, ScrollEventArgs e)
          {
              // Here access the two other data grids an scroll them via IScrollProvider
              IScrollProvider datagrid2 = null; // todo
      
              // scroll based on the scroll origin
              switch (e.ScrollEventType)
              {
                  case ScrollEventType.First:
                      datagrid2.SetScrollPercent(System.Windows.Automation.ScrollPatternIdentifiers.NoScroll, 0); // 0%
                      break;
      
                  case ScrollEventType.LargeDecrement:
                      datagrid2.Scroll(System.Windows.Automation.ScrollAmount.NoAmount, System.Windows.Automation.ScrollAmount.LargeDecrement);
                      break;
      
                  case ScrollEventType.LargeIncrement:
                      datagrid2.Scroll(System.Windows.Automation.ScrollAmount.NoAmount, System.Windows.Automation.ScrollAmount.LargeIncrement);
                      break;
      
                  case ScrollEventType.Last:
                      datagrid2.SetScrollPercent(System.Windows.Automation.ScrollPatternIdentifiers.NoScroll, 100); // 100%
                      break;
      
                  case ScrollEventType.SmallDecrement:
                      datagrid2.Scroll(System.Windows.Automation.ScrollAmount.NoAmount, System.Windows.Automation.ScrollAmount.SmallDecrement);
                      break;
      
                  case ScrollEventType.SmallIncrement:
                      datagrid2.Scroll(System.Windows.Automation.ScrollAmount.NoAmount, System.Windows.Automation.ScrollAmount.SmallIncrement);
                      break;
      
                  case ScrollEventType.ThumbPosition:
                  case ScrollEventType.ThumbTrack:
                  case ScrollEventType.EndScroll:
      
                      double percent = e.NewValue / this.verticalScrollbar.Maximum;
                      datagrid2.SetScrollPercent(System.Windows.Automation.ScrollPatternIdentifiers.NoScroll, percent);
      
                      break;
                  default:
                      break;
              }
             
          }
      
      }
      
  9. John Doe says:

    This code doesn’t work when the application is in full screen mode. Any ideas?

  10. Varsha says:

    Hi, thanks for the reply but I want the scrolling for both Horizontal as well as Vertical. Can we do this?
    Thanks again.

  11. Nits says:

    Hi,
    How do you make this work in full screen mode?

  12. Mike says:

    Very sweet, Kiener. Thanks for taking the time to share this.

  13. Britney Spears says:

    Hi Beat,

    Quite a nifty solution. Thank you for sharing the code.

  14. cbenac says:

    Kiener,

    Thanks so much for sharing this code. This is indeed the best solution for mouse scrolling that I’ve found in the net.

  15. [...] a side note, I did actually find this blog that talks about using UI Automation to handle the scrolling instead. Even though this seems like a [...]

  16. Andrea says:

    Thank you so much, very brilliant solution :D

  17. John says:

    Thank You! This works right out of the box. No fuss, no muss just plain works! Again, thanks…

  18. Aditya says:

    Thanks for the solution. However, I am facing an issue when I try scrolling with the groups enabled in my datagrid. It works fine when the groups are expanded. When they are collapsed and try to scroll up, they throw index out of bound exception on the scrollProvider.scroll() method call. Can anyone please help me with this?

  19. Elissa says:

    Your way of describing everything in this post
    is really fastidious, all be capable of simply know it, Thanks a lot.

  20. I always spent my half an hour to read this blog’s articles or reviews everyday along with a mug of coffee.

  21. Ageless male says:

    Hello, I log on to your blogs regularly. Your humoristic style is witty, keep it up!

  22. Diana says:

    Wonderful blog! Do you have any tips and hints for aspiring writers?

    I’m hoping to start my own blog soon but I’m a little lost
    on everything. Would you suggest starting with a free platform like WordPress or go for a paid option?
    There are so many choices out there that I’m completely confused .. Any ideas? Thank you!

  23. install whatsapp on mac says:

    When someone writes an article he/she keeps the idea of a user in his/her mind that how a user can know
    it. So that’s why this piece of writing is amazing. Thanks!

  24. Hello there, just became alert to your blog through
    Google, and found that it’s really informative. I’m going to watch out for brussels.
    I’ll appreciate if you continue this in future. Many people will be benefited from your writing. Cheers!

  25. Hey there! I know this is somewhat off topic but I was wondering
    which blog platform are you using for this website?

    I’m getting sick and tired of WordPress because I’ve had problems with hackers and I’m looking at alternatives for another platform. I would be great if you could point me in the direction of a good platform.

  26. Wonderful blog! I found it while browsing on Yahoo News.
    Do you have any suggestions on how to get listed in Yahoo News?
    I’ve been trying for a while but I never seem to get there! Thanks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: