Click here to Skip to main content
15,886,518 members
Articles / Desktop Programming / WPF

Improving WPF Mouse Wheel Processing

Rate me:
Please Sign up or sign in to vote.
4.91/5 (29 votes)
11 Jun 2016MIT16 min read 94K   3.8K   52  
How to quickly improve your WPF application to give your users a pleasant mouse wheel experience
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Windows;
using Lada.ComponentModel;
using Microsoft.Win32;
using System.Windows.Controls;

namespace Lada.Windows.Input
{
  #region ScrollIncrementScaleType
  public enum ScrollIncrementScaleType
  {
    Star,
    Unity,
  }
  #endregion

  #region MouseWheelScrollIncrement
  [TypeConverter(typeof(MouseWheelScrollIncrementConverter))]
  public sealed class MouseWheelScrollIncrement : ObservableObject, IWeakEventListener, IEquatable<MouseWheelScrollIncrement>, ICloneable
  {
    // MouseWheelUp native behavior and increments
    //   DocumentGrid           : CanVerticalScroll ? 16* : PageUp
    //   FlowDocumentView       : ScrollData.MouseWheelUp
    //   ScrollContentPresenter : 48
    //   ScrollData             : 48
    //   TextBoxView            : ScrollData.MouseWheelUp
    //   StackPanel             : Vertical            ? 1* : 16*
    //   VirtualizingStackPanel : Vertical && Logical ? 1* : 16*

    #region Constants

    #region Logical Increments
    public static readonly MouseWheelScrollIncrement  StarLogicalDefault  = new MouseWheelScrollIncrement( 1, ScrollIncrementScaleType.Star);
    public static readonly MouseWheelScrollIncrement  StarLogicalDefaultV = new MouseWheelScrollIncrement( 1, ScrollIncrementScaleType.Star,  Orientation.Vertical);
    public static readonly MouseWheelScrollIncrement  StarLogicalDefaultH = new MouseWheelScrollIncrement( 1, ScrollIncrementScaleType.Star,  Orientation.Horizontal);

    public static readonly MouseWheelScrollIncrement      LogicalDefault  = new MouseWheelScrollIncrement( 3, ScrollIncrementScaleType.Unity);
    public static readonly MouseWheelScrollIncrement      LogicalDefaultV = new MouseWheelScrollIncrement( 3, ScrollIncrementScaleType.Unity, Orientation.Vertical);
    public static readonly MouseWheelScrollIncrement      LogicalDefaultH = new MouseWheelScrollIncrement( 1, ScrollIncrementScaleType.Unity, Orientation.Horizontal);
    #endregion

    #region Physical Increments
    public static readonly MouseWheelScrollIncrement StarPhysicalDefault  = new MouseWheelScrollIncrement(16, ScrollIncrementScaleType.Star);
    public static readonly MouseWheelScrollIncrement StarPhysicalDefaultV = new MouseWheelScrollIncrement(16, ScrollIncrementScaleType.Star,  Orientation.Vertical);
    public static readonly MouseWheelScrollIncrement StarPhysicalDefaultH = new MouseWheelScrollIncrement(16, ScrollIncrementScaleType.Star,  Orientation.Horizontal);

    public static readonly MouseWheelScrollIncrement     PhysicalDefault  = new MouseWheelScrollIncrement(48, ScrollIncrementScaleType.Unity);
    public static readonly MouseWheelScrollIncrement     PhysicalDefaultV = new MouseWheelScrollIncrement(48, ScrollIncrementScaleType.Unity, Orientation.Vertical);
    public static readonly MouseWheelScrollIncrement     PhysicalDefaultH = new MouseWheelScrollIncrement(48, ScrollIncrementScaleType.Unity, Orientation.Horizontal);
    #endregion

    #endregion

    #region Static
    public static MouseWheelScrollIncrement Parse(string s)
    {
      s = s.Trim();
      ScrollIncrementScaleType factorType = ScrollIncrementScaleType.Unity;
      double increment = 1.0;
      if (s.EndsWith("*"))
      {
        factorType = ScrollIncrementScaleType.Star;
        s = s.Substring(0, s.Length - 1).TrimEnd();
      }
      if (s.Length > 0 && !double.TryParse(s, out increment))
        throw new FormatException("ScrollIncrement format examples: 48, 16*, *, 0.5*");
      return new MouseWheelScrollIncrement(increment, factorType);
    }
    #endregion

    #region Instance

    #region Fields
    private readonly ScrollIncrementScaleType _scaleType;
    private int _scale = int.MinValue;
    private double _increment;
    private Func<int> _fetchScale;
    #endregion

    #region Initialization
    public MouseWheelScrollIncrement(double value, ScrollIncrementScaleType scaleType)
    {
      _scaleType = scaleType;
      _increment = value;
    }
    private MouseWheelScrollIncrement(double value, ScrollIncrementScaleType scaleType, Orientation orientation)
    {
      _scaleType = scaleType;
      _increment = value;
      Orientation = orientation;
    }
    #endregion

    #region Object
    public override string ToString()
    {
      if (ScaleType == ScrollIncrementScaleType.Star)
        return string.Format("{0}*", Increment);
      else
        return Increment.ToString();
    }
    public override int    GetHashCode() { return ScaleType.GetHashCode() ^ Increment.GetHashCode() ^ Orientation.GetHashCode(); }
    public override sealed bool Equals(object obj) { return Equals(obj as MouseWheelScrollIncrement); }
    #endregion

    #region IEquatable<MouseWheelScrollIncrement>
    public bool Equals(MouseWheelScrollIncrement other)
    {
      if (ReferenceEquals(this, other)) return true;
      if (other == null) return false;
      return ScaleType == other.ScaleType && DoubleEx.AreClose(Increment, other.Increment) && Orientation == other.Orientation;
    }
    #endregion

    #region ICloneable
    object ICloneable.Clone() { return Clone(); }
    #endregion

    #region IWeakEventListener
    public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
    {
      if (managerType == typeof(UserPreferenceChangedEventManager))
        OnUserPreferenceChanged(sender, (UserPreferenceChangedEventArgs)e);
      else
        return false;
      return true;
    }
    #endregion

    #region Queries
    public ScrollIncrementScaleType ScaleType { get { return _scaleType; } }
    public double                       Value { get { return Increment * Scale; } }
    #endregion

    #region Properties
    public int Scale
    {
      get
      {
        if (_scale < 0)
        {
          if (ScaleType == ScrollIncrementScaleType.Star)
          {
            if (_fetchScale == null)
              throw new InvalidOperationException("Orientation should be called first");
            _scale = _fetchScale();
            UserPreferenceChangedEventManager.AddListener(this);
          }
          else
            _scale = 1;
        }
        return _scale;
      }
      private set
      {
        Debug.Assert(value > 0);
        if (_scale == value) return;
        _scale = value;
        OnPropertyChanged("Scale", "Value");
      }
    }
    public double Increment
    {
      get { return _increment; }
      set
      {
        if (DoubleEx.AreClose(_increment, value)) return;
        _increment = value;
        OnPropertyChanged("Increment", "Value");
      }
    }
    internal Orientation Orientation
    {
      get
      {
        if (_fetchScale == FetchVerticalFactor)
          return Orientation.Vertical;
        else if (_fetchScale == FetchHorizontalFactor)
          return Orientation.Horizontal;
        else
          throw new InvalidOperationException("Orientation still not initialized");
      }
      set
      {
        if (_fetchScale != null)
        {
          if (Orientation == value) return;
          throw new InvalidOperationException("Orientation can only be set once");
        }
        if (value == Orientation.Vertical)
          _fetchScale = FetchVerticalFactor;
        else
          _fetchScale = FetchHorizontalFactor;
      }
    }
    #endregion

    #region Operators
    public static implicit operator double(MouseWheelScrollIncrement source) { return source.Value; }
    #endregion

    #region Methods
    public MouseWheelScrollIncrement          Clone() { return Clone(Orientation); }
    public MouseWheelScrollIncrement          Clone(Orientation orientation) { return new MouseWheelScrollIncrement(_increment, _scaleType, orientation); }
    public MouseWheelScrollIncrement SetOrientation(Orientation orientation) { Orientation = orientation; return this; }
    #endregion

    #region Helpers
    private int GetScale()
    {
      if (ScaleType == ScrollIncrementScaleType.Unity)
        return 1;
      if (_fetchScale == null)
        throw new InvalidOperationException("Orientation should be set first");
      return _fetchScale();
    }
    private int   FetchVerticalFactor() { return SystemParameters.WheelScrollLines; }
    private int FetchHorizontalFactor() { return SystemParametersEx.WheelScrollChars; }

    private void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
    {
      if (e.Category == UserPreferenceCategory.Mouse)
        Scale = GetScale();
    }
    #endregion

    #endregion
  }
  #endregion

  #region MouseWheelScrollIncrementConverter
  public class MouseWheelScrollIncrementConverter : StringableConverter<MouseWheelScrollIncrement>
  {
    #region StringableConverter<MouseWheelScrollIncrement>
    protected override MouseWheelScrollIncrement Parse(string value) { return MouseWheelScrollIncrement.Parse(value); }
    #endregion
  }
  #endregion
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer (Senior)
Switzerland Switzerland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions