2010-11-11 221 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); 
}