2010-11-11 181 views
5

如何让WPF响应使用鼠标倾斜轮的水平滚动?例如,我有一个Microsoft Explorer迷你鼠标,并试图水平滚动ScrollViewer中包含的内容与如何使用鼠标滚轮在WPF中水平滚动?

HorizontalScrollBarVisibility="Visible" 

但内容不会水平滚动。然而,垂直滚动像往常一样可靠地工作。

如果此时此类输入不受WPF直接支持,是否有办法使用interop与非托管代码执行此操作?

谢谢!

+0

这不只是工作?非常失望。 – 2010-11-11 19:03:07

+0

不适用于Windows XP上的.NET 3.5,不适用于我的机器。 – 2010-11-11 22:04:43

回答

5

在Window构造函数中调用AddHook()方法,以便可以监视消息。查找WM_MOUSEHWHEEL,消息0x20e。使用wParam.ToInt32()>> 16获得移动量,为120的倍数。

+0

即使使用AddHook添加处理程序后,当我使用中间倾斜滚轮执行输入时,窗口仍然无法检测到。我也证实,例如Microsoft Word可以检测倾斜轮输入,同时Microsoft Spy ++不会在同一窗口中检测到输入。 – 2010-11-11 22:40:25

+0

鼠标是否带有某种实用程序,您安装了什么?对于鼠标制造商来说,包括这一点并不少见,为自己没有实现的程序添加横向滚动支持。很少。这样的实用程序只会识别流行的程序,如Word或您的浏览器。不是你的。您应该在TaskMgr.exe进程选项卡中看到它。 – 2010-11-11 22:47:54

+0

是的,它是Intellitype。你的解决方案应该是可行的,所以我打算将它标记为答案。 – 2010-11-11 23:24:43

1

T. Webster向任何ScrollViewer和DependancyObject发布了WPF code snippet that adds horizontal mouse scroll support。它像其他人所描述的那样利用AddHook和窗口消息。

我能够很快适应这种行为,并将其附加到XAML中的ScrollViewer。

+0

感谢您的信用。 – 2011-12-02 02:53:44

+0

Doh,我甚至没有注意到你是提交者:P – LongZheng 2012-01-02 04:34:32

+0

T. Webster,我发现你的代码段存在一个问题,那就是Logitech鼠标/驱动程序和Apple触控板驱动程序上的滚轮导致你的代码崩溃,算术错误“ – LongZheng 2012-02-01 08:25:08

4

我刚刚作出了一个类,添加PreviewMouseHorizo​​ntalWheelMouseHorizo​​ntalWheel连接事件的所有UI元素。 这些事件包含作为参数MouseHorizo​​ntalWheelEventArgs Horizo​​ntalDelta。

更新3

的倾斜值是根据WPF标准,最多为正,向下为负反转,如此作出积极的左,右负。

更新2

如果AutoEnableMouseHorizo​​ntalWheelSupport设置为true(因为它是默认情况下)有使用这些事件的特殊要求。

只有当它被设置为false,那么你将需要调用MouseHorizontalWheelEnabler.EnableMouseHorizontalWheel(X) 其中X是最高级别的元素(窗口,弹出窗口或文本菜单)或MouseHorizontalWheelEnabler.EnableMouseHorizontalWheelForParentOf(X)与元素,使支持。您可以阅读提供的文档以获取关于这些方法的更多信息。

请注意,所有这一切都没有在XP上,因为WM_MOUSE-H-WHEEL被添加到Vista上。

MouseHorizo​​ntalWheelEnabler.cs

using System; 
using System.Collections.Generic; 
using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Controls.Primitives; 
using System.Windows.Input; 
using System.Windows.Interop; 
using JetBrains.Annotations; 

namespace WpfExtensions 
{ 
    public static class MouseHorizontalWheelEnabler 
    { 
     /// <summary> 
     /// When true it will try to enable Horizontal Wheel support on parent windows/popups/context menus automatically 
     /// so the programmer does not need to call it. 
     /// Defaults to true. 
     /// </summary> 
     public static bool AutoEnableMouseHorizontalWheelSupport = true; 

     private static readonly HashSet<IntPtr> _HookedWindows = new HashSet<IntPtr>(); 

     /// <summary> 
     /// Enable Horizontal Wheel support for all the controls inside the window. 
     /// This method does not need to be called if AutoEnableMouseHorizontalWheelSupport is true. 
     /// This does not include popups or context menus. 
     /// If it was already enabled it will do nothing. 
     /// </summary> 
     /// <param name="window">Window to enable support for.</param> 
     public static void EnableMouseHorizontalWheelSupport([NotNull] Window window) { 
      if (window == null) { 
       throw new ArgumentNullException(nameof(window)); 
      } 

      if (window.IsLoaded) { 
       // handle should be available at this level 
       IntPtr handle = new WindowInteropHelper(window).Handle; 
       EnableMouseHorizontalWheelSupport(handle); 
      } 
      else { 
       window.Loaded += (sender, args) => { 
        IntPtr handle = new WindowInteropHelper(window).Handle; 
        EnableMouseHorizontalWheelSupport(handle); 
       }; 
      } 
     } 

     /// <summary> 
     /// Enable Horizontal Wheel support for all the controls inside the popup. 
     /// This method does not need to be called if AutoEnableMouseHorizontalWheelSupport is true. 
     /// This does not include sub-popups or context menus. 
     /// If it was already enabled it will do nothing. 
     /// </summary> 
     /// <param name="popup">Popup to enable support for.</param> 
     public static void EnableMouseHorizontalWheelSupport([NotNull] Popup popup) { 
      if (popup == null) { 
       throw new ArgumentNullException(nameof(popup)); 
      } 

      if (popup.IsOpen) { 
       // handle should be available at this level 
       // ReSharper disable once PossibleInvalidOperationException 
       EnableMouseHorizontalWheelSupport(GetObjectParentHandle(popup.Child).Value); 
      } 

      // also hook for IsOpened since a new window is created each time 
      popup.Opened += (sender, args) => { 
       // ReSharper disable once PossibleInvalidOperationException 
       EnableMouseHorizontalWheelSupport(GetObjectParentHandle(popup.Child).Value); 
      }; 
     } 

     /// <summary> 
     /// Enable Horizontal Wheel support for all the controls inside the context menu. 
     /// This method does not need to be called if AutoEnableMouseHorizontalWheelSupport is true. 
     /// This does not include popups or sub-context menus. 
     /// If it was already enabled it will do nothing. 
     /// </summary> 
     /// <param name="contextMenu">Context menu to enable support for.</param> 
     public static void EnableMouseHorizontalWheelSupport([NotNull] ContextMenu contextMenu) { 
      if (contextMenu == null) { 
       throw new ArgumentNullException(nameof(contextMenu)); 
      } 

      if (contextMenu.IsOpen) { 
       // handle should be available at this level 
       // ReSharper disable once PossibleInvalidOperationException 
       EnableMouseHorizontalWheelSupport(GetObjectParentHandle(contextMenu).Value); 
      } 

      // also hook for IsOpened since a new window is created each time 
      contextMenu.Opened += (sender, args) => { 
       // ReSharper disable once PossibleInvalidOperationException 
       EnableMouseHorizontalWheelSupport(GetObjectParentHandle(contextMenu).Value); 
      }; 
     } 

     private static IntPtr? GetObjectParentHandle([NotNull] DependencyObject depObj) { 
      if (depObj == null) { 
       throw new ArgumentNullException(nameof(depObj)); 
      } 

      var presentationSource = PresentationSource.FromDependencyObject(depObj) as HwndSource; 
      return presentationSource?.Handle; 
     } 

     /// <summary> 
     /// Enable Horizontal Wheel support for all the controls inside the HWND. 
     /// This method does not need to be called if AutoEnableMouseHorizontalWheelSupport is true. 
     /// This does not include popups or sub-context menus. 
     /// If it was already enabled it will do nothing. 
     /// </summary> 
     /// <param name="handle">HWND handle to enable support for.</param> 
     /// <returns>True if it was enabled or already enabled, false if it couldn't be enabled.</returns> 
     public static bool EnableMouseHorizontalWheelSupport(IntPtr handle) { 
      if (_HookedWindows.Contains(handle)) { 
       return true; 
      } 

      _HookedWindows.Add(handle); 
      HwndSource source = HwndSource.FromHwnd(handle); 
      if (source == null) { 
       return false; 
      } 

      source.AddHook(WndProcHook); 
      return true; 
     } 

     /// <summary> 
     /// Disable Horizontal Wheel support for all the controls inside the HWND. 
     /// This method does not need to be called in most cases. 
     /// This does not include popups or sub-context menus. 
     /// If it was already disabled it will do nothing. 
     /// </summary> 
     /// <param name="handle">HWND handle to disable support for.</param> 
     /// <returns>True if it was disabled or already disabled, false if it couldn't be disabled.</returns> 
     public static bool DisableMouseHorizontalWheelSupport(IntPtr handle) { 
      if (!_HookedWindows.Contains(handle)) { 
       return true; 
      } 

      HwndSource source = HwndSource.FromHwnd(handle); 
      if (source == null) { 
       return false; 
      } 

      source.RemoveHook(WndProcHook); 
      _HookedWindows.Remove(handle); 
      return true; 
     } 

     /// <summary> 
     /// Disable Horizontal Wheel support for all the controls inside the window. 
     /// This method does not need to be called in most cases. 
     /// This does not include popups or sub-context menus. 
     /// If it was already disabled it will do nothing. 
     /// </summary> 
     /// <param name="window">Window to disable support for.</param> 
     /// <returns>True if it was disabled or already disabled, false if it couldn't be disabled.</returns> 
     public static bool DisableMouseHorizontalWheelSupport([NotNull] Window window) { 
      if (window == null) { 
       throw new ArgumentNullException(nameof(window)); 
      } 

      IntPtr handle = new WindowInteropHelper(window).Handle; 
      return DisableMouseHorizontalWheelSupport(handle); 
     } 

     /// <summary> 
     /// Disable Horizontal Wheel support for all the controls inside the popup. 
     /// This method does not need to be called in most cases. 
     /// This does not include popups or sub-context menus. 
     /// If it was already disabled it will do nothing. 
     /// </summary> 
     /// <param name="popup">Popup to disable support for.</param> 
     /// <returns>True if it was disabled or already disabled, false if it couldn't be disabled.</returns> 
     public static bool DisableMouseHorizontalWheelSupport([NotNull] Popup popup) { 
      if (popup == null) { 
       throw new ArgumentNullException(nameof(popup)); 
      } 

      IntPtr? handle = GetObjectParentHandle(popup.Child); 
      if (handle == null) { 
       return false; 
      } 

      return DisableMouseHorizontalWheelSupport(handle.Value); 
     } 

     /// <summary> 
     /// Disable Horizontal Wheel support for all the controls inside the context menu. 
     /// This method does not need to be called in most cases. 
     /// This does not include popups or sub-context menus. 
     /// If it was already disabled it will do nothing. 
     /// </summary> 
     /// <param name="contextMenu">Context menu to disable support for.</param> 
     /// <returns>True if it was disabled or already disabled, false if it couldn't be disabled.</returns> 
     public static bool DisableMouseHorizontalWheelSupport([NotNull] ContextMenu contextMenu) { 
      if (contextMenu == null) { 
       throw new ArgumentNullException(nameof(contextMenu)); 
      } 

      IntPtr? handle = GetObjectParentHandle(contextMenu); 
      if (handle == null) { 
       return false; 
      } 

      return DisableMouseHorizontalWheelSupport(handle.Value); 
     } 


     /// <summary> 
     /// Enable Horizontal Wheel support for all that control and all controls hosted by the same window/popup/context menu. 
     /// This method does not need to be called if AutoEnableMouseHorizontalWheelSupport is true. 
     /// If it was already enabled it will do nothing. 
     /// </summary> 
     /// <param name="uiElement">UI Element to enable support for.</param> 
     public static void EnableMouseHorizontalWheelSupportForParentOf(UIElement uiElement) { 
      // try to add it right now 
      if (uiElement is Window) { 
       EnableMouseHorizontalWheelSupport((Window)uiElement); 
      } 
      else if (uiElement is Popup) { 
       EnableMouseHorizontalWheelSupport((Popup)uiElement); 
      } 
      else if (uiElement is ContextMenu) { 
       EnableMouseHorizontalWheelSupport((ContextMenu)uiElement); 
      } 
      else { 
       IntPtr? parentHandle = GetObjectParentHandle(uiElement); 
       if (parentHandle != null) { 
        EnableMouseHorizontalWheelSupport(parentHandle.Value); 
       } 

       // and in the rare case the parent window ever changes... 
       PresentationSource.AddSourceChangedHandler(uiElement, PresenationSourceChangedHandler); 
      } 
     } 

     private static void PresenationSourceChangedHandler(object sender, SourceChangedEventArgs sourceChangedEventArgs) { 
      var src = sourceChangedEventArgs.NewSource as HwndSource; 
      if (src != null) { 
       EnableMouseHorizontalWheelSupport(src.Handle); 
      } 
     } 

     private static void HandleMouseHorizontalWheel(IntPtr wParam) { 
      int tilt = -Win32.HiWord(wParam); 
      if (tilt == 0) { 
       return; 
      } 

      IInputElement element = Mouse.DirectlyOver; 
      if (element == null) { 
       return; 
      } 

      if (!(element is UIElement)) { 
       element = VisualTreeHelpers.FindAncestor<UIElement>(element as DependencyObject); 
      } 
      if (element == null) { 
       return; 
      } 

      var ev = new MouseHorizontalWheelEventArgs(Mouse.PrimaryDevice, Environment.TickCount, tilt) { 
       RoutedEvent = PreviewMouseHorizontalWheelEvent 
       //Source = handledWindow 
      }; 

      // first raise preview 
      element.RaiseEvent(ev); 
      if (ev.Handled) { 
       return; 
      } 

      // then bubble it 
      ev.RoutedEvent = MouseHorizontalWheelEvent; 
      element.RaiseEvent(ev); 
     } 

     private static IntPtr WndProcHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { 
      // transform horizontal mouse wheel messages 
      switch (msg) { 
       case Win32.WM_MOUSEHWHEEL: 
        HandleMouseHorizontalWheel(wParam); 
        break; 
      } 
      return IntPtr.Zero; 
     } 

     private static class Win32 
     { 
      // ReSharper disable InconsistentNaming 
      public const int WM_MOUSEHWHEEL = 0x020E; 
      // ReSharper restore InconsistentNaming 

      public static int GetIntUnchecked(IntPtr value) { 
       return IntPtr.Size == 8 ? unchecked((int)value.ToInt64()) : value.ToInt32(); 
      } 

      public static int HiWord(IntPtr ptr) { 
       return unchecked((short)((uint)GetIntUnchecked(ptr) >> 16)); 
      } 
     } 

     #region MouseWheelHorizontal Event 

     public static readonly RoutedEvent MouseHorizontalWheelEvent = 
      EventManager.RegisterRoutedEvent("MouseHorizontalWheel", RoutingStrategy.Bubble, typeof(RoutedEventHandler), 
      typeof(MouseHorizontalWheelEnabler)); 

     public static void AddMouseHorizontalWheelHandler(DependencyObject d, RoutedEventHandler handler) { 
      var uie = d as UIElement; 
      if (uie != null) { 
       uie.AddHandler(MouseHorizontalWheelEvent, handler); 

       if (AutoEnableMouseHorizontalWheelSupport) { 
        EnableMouseHorizontalWheelSupportForParentOf(uie); 
       } 
      } 
     } 

     public static void RemoveMouseHorizontalWheelHandler(DependencyObject d, RoutedEventHandler handler) { 
      var uie = d as UIElement; 
      uie?.RemoveHandler(MouseHorizontalWheelEvent, handler); 
     } 

     #endregion 

     #region PreviewMouseWheelHorizontal Event 

     public static readonly RoutedEvent PreviewMouseHorizontalWheelEvent = 
      EventManager.RegisterRoutedEvent("PreviewMouseHorizontalWheel", RoutingStrategy.Tunnel, typeof(RoutedEventHandler), 
      typeof(MouseHorizontalWheelEnabler)); 

     public static void AddPreviewMouseHorizontalWheelHandler(DependencyObject d, RoutedEventHandler handler) { 
      var uie = d as UIElement; 
      if (uie != null) { 
       uie.AddHandler(PreviewMouseHorizontalWheelEvent, handler); 

       if (AutoEnableMouseHorizontalWheelSupport) { 
        EnableMouseHorizontalWheelSupportForParentOf(uie); 
       } 
      } 
     } 

     public static void RemovePreviewMouseHorizontalWheelHandler(DependencyObject d, RoutedEventHandler handler) { 
      var uie = d as UIElement; 
      uie?.RemoveHandler(PreviewMouseHorizontalWheelEvent, handler); 
     } 

     #endregion 
    } 
} 

MouseHorizo​​ntalWheelEventArgs.cs

using System.Windows.Input; 

namespace WpfExtensions 
{ 
    public class MouseHorizontalWheelEventArgs : MouseEventArgs 
    { 
     public int HorizontalDelta { get; } 

     public MouseHorizontalWheelEventArgs(MouseDevice mouse, int timestamp, int horizontalDelta) 
      : base(mouse, timestamp) { 
      HorizontalDelta = horizontalDelta; 
     } 
    } 
} 

至于VisualTreeHelpers.FindAncestor,它的定义如下:

/// <summary> 
/// Returns the first ancestor of specified type 
/// </summary> 
public static T FindAncestor<T>(DependencyObject current) where T : DependencyObject { 
    current = GetVisualOrLogicalParent(current); 

    while (current != null) { 
    if (current is T) { 
     return (T)current; 
    } 
    current = GetVisualOrLogicalParent(current); 
    } 

    return null; 
} 

private static DependencyObject GetVisualOrLogicalParent(DependencyObject obj) { 
    if (obj is Visual || obj is Visual3D) { 
    return VisualTreeHelper.GetParent(obj); 
    } 
    return LogicalTreeHelper.GetParent(obj); 
}