【问题标题】:disable mouse wheel on itemscontrol in wpf在 wpf 中的 itemscontrol 上禁用鼠标滚轮
【发布时间】:2011-01-12 10:36:18
【问题描述】:

我有一个用户控件,它有一个滚动查看器,然后是一堆子控件,如文本框、单选按钮和列表框等。我可以使用鼠标滚轮滚动父滚动查看器,直到我的鼠标落在列表框内,然后鼠标滚轮事件开始进入列表框。

有没有办法让列表框将这些事件发送回父控件?像这个问题建议 (Mouse wheel not working when over ScrollViewer's child controls) 从父控件内部删除列表框不是解决方案。

我试过了

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

但这也没用。

谢谢

【问题讨论】:

    标签: c# .net wpf listbox mousewheel


    【解决方案1】:

    您引用的答案正是导致您的问题的原因,您的 ScrollViewer 内的 ListBox(由 ScrollViewer 等组成)捕获并处理 MouseWheel 事件,防止它冒泡,因此 ScrollViewer 没有想想曾经发生过的事件。

    为您的 ListBox 使用以下极其简单的 ControlTemplate 进行演示(注意它没有 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 中的控件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。
    • 太棒了!这为我解决了三个小的 UI 怪癖。谢谢。
    • 删除了列标题
    • 我不明白为什么对 OP 想要做什么感到困惑。拥有一个所有元素都完全可见的列表框,并且嵌入在带有其他项目的堆栈面板中,这对我来说似乎是一种相当常见的设计。这个想法是,您希望整个内容通过鼠标悬停在列表框或堆栈面板中的任何其他控件上进行滚动。堆栈面板将位于滚动查看器中,而垂直堆栈面板将具有列表框、文本、文本框等。我不能真正投票给这个,因为作者说这两种解决方法都不是首选,这令人困惑。
    • @shawn1874 为什么你会“希望用鼠标在列表框上滚动整个内容”?这将使列表框变得毫无意义,因为它不能滚动(相反,整个东西都会滚动)
    【解决方案2】:

    这可以通过附加行为来完成。

    http://josheinstein.com/blog/index.php/2010/08/wpf-nested-scrollviewer-listbox-scrolling/

    编辑: 这是链接的解决方案:

    “因此,我提出了以下 IgnoreMouseWheelBehavior。从技术上讲,它并没有忽略 MouseWheel,而是将事件“转发”出 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"
    

    【讨论】:

    • 为我工作!仅仅隐藏垂直和水平滚动是不够的,因为 MouseWheel 仍在滚动项目。
    • 它在 Visual Studio 2017 中不起作用,因为缺少交互性 XML 命名空间。
    • @Sasinosoft 交互性 DLL 始终与 VS 分开,据我所知。最初您必须安装 Blend。现在看起来你可以通过 NuGet 包 (Microsoft.Xaml.Behaviors.Wpf) 获得这些。不过,我没有准备好亲自验证这一点。
    【解决方案3】:

    我按照 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);
        }
    }
    

    【讨论】:

    • 效果很好。为了获得更大的可移植性,您可以将DataGrid 类型替换为UIElement。我还建议将其命名为PassthroughMouseWheel,因为它就是这样做的。特别是它不会吃掉(最终忽略)轮子事件。 – 哦,这是附加属性,而不是行为。类名在这里具有误导性。
    • 谢谢,很好的解决方案。但是,谁能解释它为什么起作用?
    • 感谢您的回答。我将它用于具有多个扩展器的 ListView。如果鼠标在列表视图上,滚动查看器将不会滚动。你的解决方案很完美:)
    【解决方案4】:

    正如 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 不够好?为什么我们必须做这个相对复杂的模板只是为了让列表框忽略事件,然后还要编写事件处理程序?
    • @shawn1874 您所有问题的答案都是,因为这就是 WPF 的工作方式。要覆盖 WPF,您必须提供一个使用自定义控件的自定义模板。
    【解决方案5】:

    我试图将 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();
        }
    

    【讨论】:

      【解决方案6】:

      一个对我有用的简单解决方案是覆盖内部控件模板以像这样删除滚动查看器(无论需要哪一个)

      例如 我有这样的结构

      • 列表视图(一)

        • 列表视图(b)

          • 列表视图 (c)

      我想将 (b) 的鼠标滚轮滚动冒泡到 (a),但希望保持 (c) 的鼠标滚轮滚动可用。 我只是像这样覆盖了(b)的模板。这使我能够将 (b) 的内容冒泡,除了 (c) 到 (a)。另外,我仍然可以滚动(c)的内容。如果我想删除(c),那么我必须重复相同的步骤。

      <ListView.Template>
        <ControlTemplate>
           <ItemsPresenter />
        </ControlTemplate>
      </ListView.Template>
      

      【讨论】:

        【解决方案7】:

        如果原始版本不起作用,则修改后的 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;
            }
        }
        

        【讨论】:

          【解决方案8】:

          您必须从 ScrollViewer 收听 PreviewMouseWheel(它有效),但不能从列表框收听。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多