fr en

How to use mouse wheel on Silverlight and Chrome when Windowless is enabled

2011-06-17 4 min read .NET Silverlight Aymeric

In Silverlight, the mouse wheel works very well with every main browser (Internet Explorer, Firefox, Chrome, etc.) but not when Windowless is enabled. When this feature is enabled, NAPI based browsers like Chrome or Firefox don’t allow Silverlight to manage the mouse wheel. This article explains how to get the mouse wheel event using DOM.

This article is based on code published on Compiled Experience but I’ve added some modifications to consider elements inherited from ItemsControl.

To use this behavior easily in your application, we’re going to create a Silverlight “Behavior” which will be added to your ListBox, ComboBox, etc.

Code is separated into 2 parts :

  • The “helper” code used to get information about wheel in DOM.
  • The behavior which calls the “helper” and which will be added to XAML.

“Helper” code

public class MouseWheelEventArgs : EventArgs
{
    public Point Location { get; private set; }
    public double Delta { get; private set; }
    public bool Handled { get; set; }
    public MouseWheelEventArgs(double delta)
        : this(delta, new Point())
    {
    }
    public MouseWheelEventArgs(double delta, Point location)
    {
        Delta = delta;
        Location = location;
    }
}

public class MouseWheelHelper
{
    private static Worker MouseWheelWorker;
    private bool isMouseOver;

    public MouseWheelHelper(UIElement element)
    {
        if (MouseWheelWorker == null)
            MouseWheelWorker = new Worker();
    
        MouseWheelWorker.Moved += HandleMouseWheel;
    
        element.MouseEnter += HandleMouseEnter;
        element.MouseLeave += HandleMouseLeave;
        element.MouseMove += HandleMouseMove;
    }
    
    public event EventHandler<MouseWheelEventArgs> Moved;
    
    private void HandleMouseWheel(object sender, MouseWheelEventArgs args)
    {
        if (isMouseOver)
            Moved(this, args);
    }
    
    private void HandleMouseEnter(object sender, EventArgs e)
    {
        isMouseOver = true;
    }
    
    private void HandleMouseLeave(object sender, EventArgs e)
    {
        isMouseOver = false;
    }
    
    private void HandleMouseMove(object sender, EventArgs e)
    {
        isMouseOver = true;
    }
    
    private class Worker
    {
        public Worker()
        {
            if (!HtmlPage.IsEnabled)
                return;
            HtmlPage.Window.AttachEvent("DOMMouseScroll", HandleMouseWheel);
            HtmlPage.Window.AttachEvent("onmousewheel", HandleMouseWheel);
            HtmlPage.Document.AttachEvent("onmousewheel", HandleMouseWheel);
        }
    
        public event EventHandler<MouseWheelEventArgs> Moved;
    
        private void HandleMouseWheel(object sender, HtmlEventArgs args)
        {
            double delta = 0;
            var eventObj = args.EventObject;
    
            if (eventObj.GetProperty("wheelDelta") != null)
            {
                delta = ((double)eventObj.GetProperty("wheelDelta")) / 120;
                if (HtmlPage.Window.GetProperty("opera") != null)
                    delta = -delta;
            }
            else if (eventObj.GetProperty("detail") != null)
            {
                delta = -((double)eventObj.GetProperty("detail")) / 3;
    
                if (HtmlPage.BrowserInformation.UserAgent.IndexOf("Macintosh") 
                        != -1)
                    delta = delta * 3;
            }
    
            if (delta == 0 || Moved == null)
                return;
    
            var wheelArgs = new MouseWheelEventArgs(delta);
            Moved(this, wheelArgs);
    
            if (wheelArgs.Handled)
                args.PreventDefault();
        }
    }
}
  • The MouseWheelEventArgs is used to get the mouse wheel information from the Worker class to the MouseWheelHelper class.
  • The MouseWheelHelper class is called by the behavior.
  • The Worker class finds information about mouse wheel in the DOM depending on browsers.

To get this information, we get the JavaScript DOMMouseScroll event for Firefox and the onmousewheel event for Chrome and others. When the wheel is used, information is transmitted from JavaScript to Silverlight.

The following code is the Silverlight behavior, which will be applied to controls in XAML. In the original post, we can only use this behavior with a ScrollViewer, so it was impossible to use it with a TreeView for example. With my modifications, it can be applied on any UIElement.

But to get properties like VerticalOffsetHorizontalOffset or methods like ScrollToVerticalOffset and ScrollToHorizontalOffset, a ScrollViewer object is required. Thankfully, for controls inherited from ItemsControl (TreeView, ListBox, etc.) the Silverlight Toolkit provides an extension method : GetScrollHost() allowing us to retrieve their ScrollViewer objects.

Don’t forget to add a reference to the System.Windows.Controls.Toolkit DLL in your project.

public class MouseWheelScrollBehavior : Behavior<UIElement>
{
    private MouseWheelHelper helper;

    protected override void OnAttached()
    {
        base.OnAttached();
    
        helper = new MouseWheelHelper(AssociatedObject);
        helper.Moved += OnMouseWheelMoved;
    }
    
    private void OnMouseWheelMoved(object sender, MouseWheelEventArgs e)
    {
        ScrollViewer sv;
        double verticalOffset;
        double horizontalOffset;
    
        if (AssociatedObject is ItemsControl)
        {
            ItemsControl tr = AssociatedObject as ItemsControl;
            sv = tr.GetScrollHost();
        }
        else
        {
            sv = AssociatedObject as ScrollViewer;
        }
    
        if (sv != null)
        {
            verticalOffset = sv.VerticalOffset;
            horizontalOffset = sv.VerticalOffset;
            sv.ScrollToVerticalOffset(sv.VerticalOffset + (e.Delta * -10));
            sv.ScrollToHorizontalOffset(sv.HorizontalOffset + (e.Delta * -10));
        }
    }
    
    protected override void OnDetaching()
    {
        base.OnDetaching();
    
        helper.Moved -= OnMouseWheelMoved;
    }
}

When the “Behavior” is created, apply it to every Listbox, TreeView, ComboBox, etc. in your application like this :

<ListBox>
  <i:Interaction.Behaviors>
    <behaviors:MouseWheelScrollBehavior/>
  </i:Interaction.Behaviors>
  <!-- Code -->
</ListBox>

Where i represents System.Windows.Interactivity (don’t forget the reference) and behavior represents the “Behavior” namespace created above.

To do this tedious task, the best solution is to create a default template for these controls :

<Style TargetType="ListBox">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="ListBox">
        <ScrollViewer>
          <i:Interaction.Behaviors>
            <behaviors:MouseWheelScrollBehavior/>
          </i:Interaction.Behaviors>
          <ItemsPresenter/>
        </ScrollViewer>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

So this behavior is now applied to all ListBox controls.