禁用WPF中ItemsControl上的鼠标滚轮

31

我有一个用户控件,它包含一个滚动视图器和一堆子控件,例如文本框、单选按钮和列表框等。我可以使用鼠标滚轮来滚动父滚动视图器,直到鼠标停在列表框内,然后鼠标滚轮事件开始传递给列表框。

有没有办法让列表框将这些事件发送回父控件?像这个问题建议的那样(当鼠标位于 ScrollViewer 的子控件上时鼠标滚轮无效),将列表框从父控件中删除并不是一个解决方案。

我已经尝试过:

private void ListBox_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
    e.Handled = true;
}

但那也没有起作用。

谢谢

8个回答

41
这可以通过附加的行为来实现。 这里提供了相关解决方案:

“因此,我想出了以下的 IgnoreMouseWheelBehavior。从技术上讲,它并不是忽略鼠标滚轮事件,而是将该事件"转发"回到 ListBox 上并向外传递。请查看它。”

/// <summary>
/// Captures and eats MouseWheel events so that a nested ListBox does not
/// prevent an outer scrollable control from scrolling.
/// </summary>
public sealed class IgnoreMouseWheelBehavior : Behavior<UIElement>
{

  protected override void OnAttached( )
  {
     base.OnAttached( );
      AssociatedObject.PreviewMouseWheel += AssociatedObject_PreviewMouseWheel ;
  }

  protected override void OnDetaching( )
  {
      AssociatedObject.PreviewMouseWheel -= AssociatedObject_PreviewMouseWheel;
      base.OnDetaching( );
  }

  void AssociatedObject_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
  {

      e.Handled = true;

      var e2 = new MouseWheelEventArgs(e.MouseDevice,e.Timestamp,e.Delta);
      e2.RoutedEvent = UIElement.MouseWheelEvent;

      AssociatedObject.RaiseEvent(e2);

  }

}

以下是在 XAML 中使用它的方法。

<ScrollViewer Name="IScroll">
    <ListBox Name="IDont">
        <i:Interaction.Behaviors>
            <local:IgnoreMouseWheelBehavior />
        </i:Interaction.Behaviors>
    </ListBox>
</ScrollViewer>

i 命名空间是什么:

 xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

为我工作吧!仅仅隐藏垂直和水平滚动条是不够的,因为鼠标滚轮仍然可以滚动项目。 - Yohanes Nurcahyo
因为缺少交互性 XML 命名空间,所以在 Visual Studio 2017 中无法正常工作。 - Sasino
1
据我所记,@Sasinosoft 交互性 DLL 总是与 VS 分开的。最初必须安装 Blend。现在看起来你可以通过 NuGet 包 (Microsoft.Xaml.Behaviors.Wpf) 获取这些内容。不过,我个人没有设置验证。 - Amanduh

40
您引用的答案正是导致问题的原因。在您的ScrollViewer中,ListBox(其中包括一个ScrollViewer)捕捉并处理了鼠标滚轮事件,阻止其冒泡,因此ScrollViewer根本不知道该事件已发生。
使用以下极为简单的ControlTemplate来演示您的ListBox(请注意,它没有ScrollViewer,因此不会捕捉MouseWheel事件)。 ScrollViewer仍将随着鼠标在ListBox上滚动而滚动。
<UserControl.Resources>
     <ControlTemplate x:Key="NoScroll">
         <ItemsPresenter></ItemsPresenter>
     </ControlTemplate>
</UserControl.Resources>

<ScrollViewer>
    <SomeContainerControl>
        <.... what ever other controls are inside your ScrollViewer>
        <ListBox Template="{StaticResource NoScroll}"></ListBox>
    <SomeContainerControl>
</ScrollViewer>

当鼠标进入ScrollViewer时,您确实可以选择捕获鼠标,以便它继续接收所有鼠标事件,直到释放鼠标。但是,如果您希望得到响应,这个选项将要求您将任何后续的鼠标事件委托给包含在ScrollViewer中的控件......以下MouseEnter MouseLeave事件处理程序就足够了。

private void ScrollViewerMouseEnter(object sender, MouseEventArgs e)
{
    ((ScrollViewer)sender).CaptureMouse();
}

private void ScrollViewerMouseLeave(object sender, MouseEventArgs e)
{
    ((ScrollViewer)sender).ReleaseMouseCapture();
}

我提供的解决方法都不是最优选,我建议重新考虑你实际想要做什么。如果你在问题中解释你想要实现什么,我相信你会得到更多的建议...


我按照Simon的建议进行了操作,但使用了一种实现变体:我使用Blend获取了当前ListBox模板的副本,然后删除了ListBox的ScrollViewer。 - Jean Libera
太好了!这解决了我三个小的UI问题。谢谢。 - Vegar
<ItemsPresenter> 移除了列标题。 - Louis Rhys
@shawn1874 为什么你想要“整个列表框随鼠标滚动而滚动”?这会使列表框失去意义,因为它将无法滚动(相反,整个内容都会滚动)。 - Simon Fox
这个答案给了我一个很好的方向来解决我的问题,非常感谢。 - Lak Fu
显示剩余6条评论

15

我跟随Amanduh的方法解决了我在WPF中使用多个数据网格和滚动查看器时遇到的同样问题:

public sealed class IgnoreMouseWheelBehavior 
{
    public static bool GetIgnoreMouseWheel(DataGrid gridItem)
    {
        return (bool)gridItem.GetValue(IgnoreMouseWheelProperty);
    }

    public static void SetIgnoreMouseWheel(DataGrid gridItem, bool value)
    {
        gridItem.SetValue(IgnoreMouseWheelProperty, value);
    }

    public static readonly DependencyProperty IgnoreMouseWheelProperty =
        DependencyProperty.RegisterAttached("IgnoreMouseWheel", typeof(bool),
        typeof(IgnoreMouseWheelBehavior), new UIPropertyMetadata(false, OnIgnoreMouseWheelChanged));

    static void OnIgnoreMouseWheelChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    {
        var item = depObj as DataGrid;
        if (item == null)
            return;

        if (e.NewValue is bool == false)
            return;

        if ((bool)e.NewValue)
            item.PreviewMouseWheel += OnPreviewMouseWheel;
        else
            item.PreviewMouseWheel -= OnPreviewMouseWheel;
    }

    static void OnPreviewMouseWheel(object sender, MouseWheelEventArgs e)
    {
        e.Handled = true;

        var e2 = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
                     {RoutedEvent = UIElement.MouseWheelEvent};

        var gv = sender as DataGrid;
        if (gv != null) gv.RaiseEvent(e2);
    }
}

3
非常好用。为了更好的移植性,您可以将DataGrid类型替换为UIElement。我还建议将其命名为“PassthroughMouseWheel”,因为这就是它所做的。特别是它不会吞噬(最终忽略)滚轮事件。另外,这是一个附加属性,而不是行为。在这里,类名是具有误导性的。 - ygoe
谢谢,很棒的解决方案。但是,有人可以解释一下为什么它有效吗? - Alexander
谢谢你的回答。我将它用于具有多个展开器的ListView上。如果鼠标在ListView上,滚动视图器将无法滚动。你的解决方案完美地解决了这个问题 :) - bbedson

9

正如Simon所说,标准ListBox模板中的ScrollViewer捕获了该事件。要绕过它,您可以提供自己的模板。

<ControlTemplate x:Key="NoWheelScrollListBoxTemplate" TargetType="ListBox">
    <Border BorderThickness="{TemplateBinding Border.BorderThickness}" Padding="1,1,1,1" BorderBrush="{TemplateBinding Border.BorderBrush}" Background="{TemplateBinding Panel.Background}" Name="Bd" SnapsToDevicePixels="True">
        <!-- This is the new control -->
        <l:NoWheelScrollViewer Padding="{TemplateBinding Control.Padding}" Focusable="False">
            <ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
        </l:NoWheelScrollViewer>
    </Border>
    <ControlTemplate.Triggers>
        <Trigger Property="UIElement.IsEnabled" Value="False">
            <Setter TargetName="Bd" Property="Panel.Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" />
        </Trigger>
        <Trigger Property="ItemsControl.IsGrouping" Value="True">
            <Setter Property="ScrollViewer.CanContentScroll" Value="False" />
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

而 NoWheelScrollViewer 的实现非常简单。

public class NoWheelScrollViewer : ScrollViewer
{
    protected override void OnMouseWheel(MouseWheelEventArgs e)
    {
        // Do nothing
    }
}

然后,每当您希望列表框不处理鼠标滚轮时。

<ListBox Template="{StaticResource NoWheelScrollListBoxTemplate}">

更详细的解释会更有帮助。例如,为什么在Listbox上设置CanContentScroll = false就不够好呢?为什么我们必须要做这个相对复杂的模板才能让listbox忽略事件,然后再编写事件处理程序呢? - shawn1874
1
@shawn1874 所有问题的答案都是因为这就是 WPF 的工作方式。要覆盖 WPF,您必须提供一个使用自定义控件的自定义模板。 - Cameron MacFarland

3
一个对我有效的简单解决方案是覆盖内部控件模板以删除滚动查看器(如有必要),方法如下:
例如,我有这样的结构:
ListView (a) - ListView (b) -- ListView (c)
我想将(b)的鼠标滚轮滚动传递到(a),但仍希望保留(c)的鼠标滚轮滚动。我只需像这样覆盖(b)的模板即可。这使我能够将(b)的内容除(c)外都传递到(a)。同时,我仍然可以滚动(c)的内容。如果我想连(c)也一起移除,那么我需要重复相同的步骤。
<ListView.Template>
  <ControlTemplate>
     <ItemsPresenter />
  </ControlTemplate>
</ListView.Template>

2

我试图将Simon Fox的答案应用于DataGrid。我发现模板隐藏了我的标题,并且我从C#中没有得到mouseLeave事件。最终,这是对我有效的:

    private void DataGrid_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
    {
        ((DataGrid)sender).CaptureMouse();
    }

    private void DataGrid_MouseWheel(object sender, MouseWheelEventArgs e)
    {
        ((DataGrid)sender).ReleaseMouseCapture();
    }

1
如果原始解决方案无法正常工作,可以使用修改后的Simon Fox解决方案:
public sealed class IgnoreMouseWheelBehavior : Behavior<UIElement>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.PreviewMouseWheel += AssociatedObject_PreviewMouseWheel;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.PreviewMouseWheel -= AssociatedObject_PreviewMouseWheel;
        base.OnDetaching();
    }

    static void AssociatedObject_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
    {
        if (!(sender is DependencyObject))
        {
            return;
        }

        DependencyObject parent = VisualTreeHelper.GetParent((DependencyObject) sender);
        if (!(parent is UIElement))
        {
            return;
        }

        ((UIElement) parent).RaiseEvent(
            new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta) { RoutedEvent = UIElement.MouseWheelEvent });
        e.Handled = true;
    }
}

1

你必须从ScrollViewer监听PreviewMouseWheel(它有效),而不是从listbox。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接