如何使用鼠标倾斜滚轮启用WPF以响应水平滚动?例如,我有一个Microsoft Explorer迷你鼠标,并尝试使用
水平滚动ScrollViewer中包含的内容HorizontalScrollBarVisibility="Visible"
但内容不会水平滚动。但是,垂直滚动可以像往常一样可靠地工作。
如果此时WPF不直接支持此类输入,是否有办法使用非托管代码互操作?
谢谢!
答案 0 :(得分:6)
我刚刚创建了一个类,它将 PreviewMouseHorizontalWheel 和 MouseHorizontalWheel 附加事件添加到所有UIElements。 这些事件包括作为参数的MouseHorizontalWheelEventArgs HorizontalDelta。
更新3
根据WPF标准,倾斜值被反转,其中向上为正,向下为负,因此左侧为正,右侧为负。
更新2
如果 AutoEnableMouseHorizontalWheelSupport 设置为true(默认情况下是这样),则无需使用这些事件。
只有将其设置为false ,您才需要调用MouseHorizontalWheelEnabler.EnableMouseHorizontalWheel(X)
其中X是顶级元素(Window,Popup或ContextMenu)或MouseHorizontalWheelEnabler.EnableMouseHorizontalWheelForParentOf(X)
,元素支持。您可以阅读提供的文档以获取有关这些方法的更多信息。
请注意,由于在Vista上添加了WM_MOUSE-H-WHEEL,所有这些在XP上都无效。
MouseHorizontalWheelEnabler.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
}
}
MouseHorizontalWheelEventArgs.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);
}
答案 1 :(得分:5)
在Window构造函数中调用AddHook()方法,以便窥探消息。查找WM_MOUSEHWHEEL,消息0x20e。使用wParam.ToInt32()&gt;&gt; 16获得移动量,是120的倍数。
答案 2 :(得分:1)
吨。 Webster向任何ScrollViewer和DependancyObject发布了WPF code snippet that adds horizontal mouse scroll support。它像其他人描述的那样利用AddHook和窗口消息。
我能够很快地将其调整为行为并将其附加到XAML中的ScrollViewer。
答案 3 :(得分:0)
使用附加属性的另一种解决方案。它适用于ScrollViewer
或包含ScrollViewer
的任何控件。这是一个非常简单的解决方案,最重要的是,它很容易重用。我对项目所做的工作是在Generic.xaml中为DataGrid
,ListBox
,ListView
和朋友设置此附加属性。这样,它总是可以正常工作。
这将在同一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)((ptr.ToInt32() >> 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;
}
用法
带有Window
的{{1}}的简单示例。这里DataGrid
只是我为测试用例弥补的一些虚假数据。
DataItems
或者,最终我将这种样式放入Generic.xaml或您的<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>
中,以应用于所有数据网格。您可以将此属性附加到其中带有Window.Resources
的任何控件上(当然,水平滚动也不被禁用)。
ScrollViewer
答案 4 :(得分:0)
此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();
}
}