【问题标题】:How to horizontally scroll in WPF using mouse tilt wheel?如何使用鼠标倾斜轮在 WPF 中水平滚动?
【发布时间】:2011-05-08 16:05:23
【问题描述】:

如何让 WPF 响应使用鼠标倾斜滚轮的水平滚动?例如,我有一个 Microsoft Explorer 迷你鼠标,并尝试使用

水平滚动包含在 ScrollViewer 中的内容
HorizontalScrollBarVisibility="Visible"

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

如果此时 WPF 不直接支持此类输入,有没有办法使用与非托管代码的互操作来做到这一点?

谢谢!

【问题讨论】:

  • 这行不通?非常令人失望。
  • 不适用于 Windows XP 上的 .NET 3.5,不在我的机器上。

标签: c# wpf


【解决方案1】:

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

【讨论】:

  • 即使使用 AddHook 添加处理程序后,窗口仍然无法检测到我何时使用中间倾斜轮执行输入。我还确认,虽然 Microsoft Word 可以检测到倾斜轮输入,但同时 Microsoft Spy++ 不会检测到同一窗口上的输入。
  • 鼠标是否带有某种实用程序,您安装了什么?鼠标制造商将其包括在内并不少见,为自己不实现它的程序添加横向滚动支持。很少有人这样做。这样的实用程序只能识别流行的程序,例如 Word 或您的浏览器。不是你的。您应该会在 TaskMgr.exe 进程选项卡中看到它。
  • 是的,它是 Intellitype。您的解决方案应该可行,所以我将其标记为答案。
  • WM_MOUSEHWEEL 定义为 0x020A。
  • 不,那是 WM_MOUSEWHEEL,垂直的。
【解决方案2】:

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

我能够很快将其调整为一种行为,并将其附加到 XAML 中的 ScrollViewer。

【讨论】:

  • Doh,我什至没有注意到你是提交者:P
  • T. Webster,我发现您的 sn-p 存在问题,即 Logitech 鼠标/驱动程序和 Apple 触控板驱动程序上的滚轮会导致您的代码因“算术错误”而崩溃
  • 链接已失效。
【解决方案3】:

我刚刚创建了一个类,将 PreviewMouseHorizo​​ntalWheelMouseHorizo​​ntalWheel 附加事件添加到所有 UIElement。 这些事件包括作为参数的 MouseHorizo​​ntalWheelEventArgs Horizo​​ntalDelta。

更新 3

倾斜值根据WPF标准颠倒过来,上为正,下为负,所以设为左为正,右为负。

更新 2

如果 AutoEnableMouseHorizo​​ntalWheelSupport 设置为 true(默认情况下),则对使用这些事件没有特殊要求。

仅当它设置为 false 时,您才需要调用 MouseHorizontalWheelEnabler.EnableMouseHorizontalWheel(X) 其中 X 是顶级元素(Window、Popup 或 ContextMenu)或 MouseHorizontalWheelEnabler.EnableMouseHorizontalWheelForParentOf(X) 以及要启用支持的 Element。您可以阅读提供的文档以获取有关这些方法的更多信息。

请注意,所有这些在 XP 上都不起作用,因为在 Vista 上添加了 WM_MOUSE-H-WHEEL。

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);
}

【讨论】:

    【解决方案4】:

    使用附加属性的另一种解决方案。它适用于任何为ScrollViewer 或包含ScrollViewer 的控件。这是一个相当简单的解决方案,最重要的是它很容易重复使用。我对我的项目所做的是在 Generic.xaml 中为DataGridListBoxListView 和朋友设置这个附加属性。这样,它总是能正常工作。

    这将适用于同一 UI 中的多个滚动查看器,并且将应用于当前将鼠标悬停在其上的任何一个

    这是附加属性的代码,以及一个辅助类

    注意:C#6 语法

    需要的代码

    using System;
    using System.Collections.Generic;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Interop;
    
    namespace MyTestProject
    {
        public class TiltWheelHorizontalScroller
        {
            public static bool GetEnableTiltWheelScroll(DependencyObject obj) => (bool)obj.GetValue(EnableTiltWheelScrollProperty);
            public static void SetEnableTiltWheelScroll(DependencyObject obj, bool value) => obj.SetValue(EnableTiltWheelScrollProperty, value);
    
            public static readonly DependencyProperty EnableTiltWheelScrollProperty =
                    DependencyProperty.RegisterAttached("EnableTiltWheelScroll", typeof(bool), typeof(TiltWheelHorizontalScroller), new UIPropertyMetadata(false, OnHorizontalMouseWheelScrollingEnabledChanged));
    
            static HashSet<int> controls = new HashSet<int>();
            static void OnHorizontalMouseWheelScrollingEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
            {
                Control control = d as Control;
                if (control != null && GetEnableTiltWheelScroll(d) && controls.Add(control.GetHashCode()))
                {
                    control.MouseEnter += (sender, e) =>
                    {
                        var scrollViewer = d.FindChildOfType<ScrollViewer>();
                        if (scrollViewer != null)
                        {
                            new TiltWheelMouseScrollHelper(scrollViewer, d);
                        }
                    };
                }
            }
        }
    
        class TiltWheelMouseScrollHelper
        {
            /// <summary>
            /// multiplier of how far to scroll horizontally. Change as desired.
            /// </summary>
            private const int scrollFactor = 3;
            private const int WM_MOUSEHWEEL = 0x20e;
            ScrollViewer scrollViewer;
            HwndSource hwndSource;
            HwndSourceHook hook;
            static HashSet<int> scrollViewers = new HashSet<int>();
    
            public TiltWheelMouseScrollHelper(ScrollViewer scrollViewer, DependencyObject d)
            {
                this.scrollViewer = scrollViewer;
                hwndSource = PresentationSource.FromDependencyObject(d) as HwndSource;
                hook = WindowProc;
                hwndSource?.AddHook(hook);
                if (scrollViewers.Add(scrollViewer.GetHashCode()))
                {
                    scrollViewer.MouseLeave += (sender, e) =>
                    {
                        hwndSource.RemoveHook(hook);
                    };
                }
            }
    
            IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
            {
                switch (msg)
                {
                    case WM_MOUSEHWEEL:
                        Scroll(wParam);
                        handled = true;
                        break;
                }
                return IntPtr.Zero;
            }
    
            private void Scroll(IntPtr wParam)
            {
                int delta = (HIWORD(wParam) > 0 ? 1 : -1) * scrollFactor;
                scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset + delta);
            }
    
            private static int HIWORD(IntPtr ptr) => (short)((((int)ptr.ToInt64()) >> 16) & 0xFFFF);
        }
    }
    

    如果你还没有这个扩展方法,你将需要它。

    /// <summary>
    /// Finds first child of provided type. If child not found, null is returned
    /// </summary>
    /// <typeparam name="T">Type of chiled to be found</typeparam>
    /// <param name="source"></param>
    /// <returns></returns>
    public static T FindChildOfType<T>(this DependencyObject originalSource) where T : DependencyObject
    {
        T ret = originalSource as T;
        DependencyObject child = null;
        if (originalSource != null && ret == null)
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(originalSource); i++)
            {
                child = VisualTreeHelper.GetChild(originalSource, i);
                if (child != null)
                {
                    if (child is T)
                    {
                        ret = child as T;
                        break;
                    }
                    else
                    {
                        ret = child.FindChildOfType<T>();
                        if (ret != null)
                        {
                            break;
                        }
                    }
                }
            }
        }
        return ret;
    }
    

    用法

    WindowDataGrid 的简单示例。这里DataItems只是我为测试用例编造的一些假数据。

    <Window x:Class="MyTestProject.MainWindow"
                    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:ap="clr-namespace:MyTestProject"
                    Title="MainWindow" Height="350" Width="525"
                    DataContext="{Binding RelativeSource={RelativeSource Self}}">
        <Grid>
            <DataGrid x:Name="dataGrid"
                      ItemsSource="{Binding DataItems}"
                      ap:TiltWheelHorizontalScroller.EnableTiltWheelScroll="True"/>
        </Grid>
    </Window>
    

    或者,我最终做了什么,将此样式放入 Generic.xaml 或您的 Window.Resources 以应用于所有数据网格。您可以将此属性附加到其中包含 ScrollViewer 的任何控件(当然,水平滚动未被禁用)。

    <Style TargetType="{x:Type DataGrid}" BasedOn="{StaticResource {x:Type DataGrid}}">
        <Setter Property="ap:TiltWheelHorizontalScroller.EnableTiltWheelScroll" Value="True"/>
    </Style>
    

    【讨论】:

    • 对我来说最好的解决方案。但我不得不修复这个方法(发生OverflowException算术运算): private static int HIWORD(IntPtr ptr) { var value = Convert.ToInt64(ptr.ToString()); if (value > int.MaxValue) { return -1; } 返回 1; }
    【解决方案5】:

    此 Microsoft 链接提供了您的确切要求: horizontal scrolling

    然后重写这个方法 -

    private void OnMouseTilt(int tilt)
        {
            // Write your horizontal handling codes here.
            if(!mainScrollViewer.IsVisible) return;
    
            if (tilt > 0)
            {
                mainScrollViewer.LineLeft();
            }
            else
            {
                mainScrollViewer.LineRight();
            }
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-17
      • 1970-01-01
      • 2021-09-13
      • 1970-01-01
      • 2014-07-20
      • 2011-12-11
      相关资源
      最近更新 更多