滚动鼠标在带有WPF数据表格和其他UI元素的滚动查看器中不起作用

48

我正在尝试找出如何在一个带有ScrollViewer和DataGrid的WPF窗口上使鼠标滚动正常工作。以下是WPF和C#代码:

<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0">

            <Border Name="DataGridBorder" BorderThickness="2"  Margin="1" CornerRadius="4" BorderBrush="#FF080757">
                <dg:DataGrid AutoGenerateColumns="False" Name="ValuesDataGrid" 
                         BorderThickness="0" CanUserResizeColumns="True" FontWeight="Bold" HorizontalScrollBarVisibility="Auto" 
                         CanUserReorderColumns="False" IsReadOnly="True" IsTextSearchEnabled="True" AlternationCount="2"
                         SelectionMode="Extended" GridLinesVisibility="All"                
                         HeadersVisibility="Column" CanUserAddRows="False" CanUserDeleteRows="False" CanUserResizeRows="False" CanUserSortColumns="False"
                         RowDetailsVisibilityMode="Collapsed"  SelectedIndex="0"
                         RowStyle="{StaticResource CognitiDataGridRowStyle}"
                         >

                    <dg:DataGrid.Columns>
                        <dg:DataGridTemplateColumn Header="Title" >
                            <dg:DataGridTemplateColumn.CellTemplate>
                                <DataTemplate>
                                    <StackPanel Orientation="Horizontal" >
                                        <TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Text="{Binding Path=Name}" FontWeight="Normal"  />
                                    </StackPanel>
                                </DataTemplate>
                            </dg:DataGridTemplateColumn.CellTemplate>
                        </dg:DataGridTemplateColumn>
                    </dg:DataGrid.Columns>
                </dg:DataGrid>
            </Border>
        </Grid>
        <Button Grid.Row="1" Height="90" >hello world</Button>
    </Grid>
</ScrollViewer>

而 C# 代码如下

 public partial class Window1 : Window
  {
     public Window1()
     {
        InitializeComponent();
        initialize();
      }

    public void initialize()
    {
        ObservableCollection<MyObject> testList = new ObservableCollection<MyObject>();

        for (int i = 0; i < 20; i++)
        {
            MyObject my = new MyObject("jack " + i);
            testList.Add(my);
        }

        ValuesDataGrid.ItemsSource = testList;



    }
}

public class MyObject
{
    public string Name { get; set; }



    public MyObject(string name)
    {
        Name = name;
    }
   }

我遇到的问题是,使用鼠标滚轮时,在按钮上操作正常,但只要把鼠标指针移到表格上并尝试滚动,就没有反应了。虽然我可以直接移动ScrollViewer的滚动条,但我仍然是一个WPF新手,希望能得到关于如何使鼠标滚动在DataGrid中起作用的任何帮助。我猜这个问题应该有一个很简单的解决方案,但我还没能搞定。


我遇到了完全相同的问题,但是下面的解决方案在MVVM应用程序中不太好用。我们的架构师不希望在视图中有任何代码,只允许使用Xaml或Behaviors。 - Kolky
1
嗨Kolky,我知道这个答案对你来说可能太晚了,已经有将近6年了,但是也许对于其他处在类似情况的人会有用。通常,像这样的UI类型逻辑可以使用MVVM模式实现,例如使用代码后台类(code behind class)。这是一个关于它的MSDN文章链接:https://msdn.microsoft.com/en-us/library/gg405484(v=pandp.40).aspx - Don B
12个回答

78

我认为Dave的解决方案很好。 但是,我的一个建议是在滚动视图器上捕获PreviewMouseWheel事件,而不是在datagrid上捕获。 如果您不这样做,您可能会注意到基于您是在datagrid还是滚动条本身上滚动而有一些轻微的差异。 原因是当鼠标悬停在滚动条上时,滚动视图器将处理滚动,而当鼠标悬停在datagrid上时,datagrid事件将处理滚动。 例如,一次鼠标滚轮滚动datagrid可能会将您带到列表中更远的位置,而当您在滚动条上滚动时,则可能不会如此。 如果您在scrollviewer preview事件上捕获它,则在滚动时所有内容都将使用相同的测量值。 此外,如果您以这种方式捕获它,则无需为滚动视图器元素命名,因为您不需要引用该对象,而可以将传递到滚动视图器PreviewMouseWheel事件中的sender对象强制转换。 最后,我建议在事件结束时标记事件已处理,除非出于某种原因需要在层次结构较低的元素中捕获它。下面是示例:

private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
    {
        ScrollViewer scv = (ScrollViewer)sender;
        scv.ScrollToVerticalOffset(scv.VerticalOffset - e.Delta);
        e.Handled = true;
    }

23

我假设DataGrid不需要滚动 - 在DataGrid上设置VerticalScrollBar="None"。

DataGrid会吞噬鼠标滚轮事件。

我发现使用PreviewMouseWheel事件来滚动想要滚动的容器是可行的。您需要为此命名滚动查看器以更改垂直偏移量。

private void DataGrid_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
    {
       scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset-e.Delta);
    }

9
Don B的解决方案有改进之处,就是避免使用ScrollToVerticalOffset
scv.ScrollToVerticalOffset(scv.VerticalOffset - e.Delta);

VerticalOffset - Delta的结果会让人感到很突兀。ScrollViewer在使滚动更加流畅方面考虑得很多。我认为它还基于dpi和其他因素缩小了增量...

我发现最好捕获并处理PreviewMouseWheelEvent,并向预期的ScrollViewer发送MouseWheelEvent。我的Don B解决方案版本如下。

private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
    var eventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta);
    eventArg.RoutedEvent = UIElement.MouseWheelEvent;
    eventArg.Source = e.Source;

    ScrollViewer scv = (ScrollViewer)sender;
    scv.RaiseEvent(eventArg);
    e.Handled = true;
}

3
为启用触摸支持,您可能还想在您的DataGrid上将ScrollViewer.PanningMode设置为None,并在顶层ScrollViewer上将同一属性设置为VerticalFirst或其他值。 示例
<ScrollViewer VerticalScrollBarVisibility="Auto" Margin="5" PanningMode="VerticalFirst">
    <DataGrid ScrollViewer.PanningMode="None" ItemsSource="{Binding Items}" />
</ScrollViewer>

当然,也要使用 PreviewMouseWheel 事件,如 Don B 的答案所示,来修复原始的鼠标滚动问题。
private static void ScrollViewer_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
    var scrollViewer = (ScrollViewer)sender;
    scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - e.Delta);
    e.Handled = true;
}

或者,您可以将以下附加属性设置为您的ScrollViewer
public class TopMouseScrollPriorityBehavior
{
    public static bool GetTopMouseScrollPriority(ScrollViewer obj)
    {
        return (bool)obj.GetValue(TopMouseScrollPriorityProperty);
    }

    public static void SetTopMouseScrollPriority(ScrollViewer obj, bool value)
    {
        obj.SetValue(TopMouseScrollPriorityProperty, value);
    }

    public static readonly DependencyProperty TopMouseScrollPriorityProperty =
        DependencyProperty.RegisterAttached("TopMouseScrollPriority", typeof(bool), typeof(TopMouseScrollPriorityBehavior), new PropertyMetadata(false, OnPropertyChanged));

    private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var scrollViewer = d as ScrollViewer;
        if (scrollViewer == null)
            throw new InvalidOperationException($"{nameof(TopMouseScrollPriorityBehavior)}.{nameof(TopMouseScrollPriorityProperty)} can only be applied to controls of type {nameof(ScrollViewer)}");
        if (e.NewValue == e.OldValue)
            return;
        if ((bool)e.NewValue)
            scrollViewer.PreviewMouseWheel += ScrollViewer_PreviewMouseWheel;
        else
            scrollViewer.PreviewMouseWheel -= ScrollViewer_PreviewMouseWheel;
    }

    private static void ScrollViewer_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
    {
        var scrollViewer = (ScrollViewer)sender;
        scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - e.Delta);
        e.Handled = true;
    }
}

用法

<ScrollViewer b:TopMouseScrollPriorityBehavior.TopMouseScrollPriority="True" VerticalScrollBarVisibility="Auto" Margin="5" PanningMode="VerticalFirst">
    <DataGrid ScrollViewer.PanningMode="None" ItemsSource="{Binding Items}" />
</ScrollViewer>
其中b:是包含此行为的命名空间 这样,您就不需要编写任何代码并且您的应用程序完全符合MVVM模式

2
我尝试了Don B的解决方案,对于在数据表格上滚动时没有其他内部可滚动控件的情况,该解决方案效果很好。
但是,如果滚动查看器中有其他可滚动控件和数据表格,则需要在主滚动查看器的事件处理程序末尾不将事件标记为已处理,以便还可以在内部可滚动控件中捕获它,然而这会导致一个副作用:当只需在内部可滚动控件上进行滚动时,它也会在主滚动查看器上发生。
因此,我更新了Dave的解决方案,其中包括如何找到滚动查看器的差异,以便无需知道滚动查看器的名称。
private void DataGrid_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
    ScrollViewer scrollViewer = (((DependencyObject)sender).GetVisualParent<ScrollViewer>());
    scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - e.Delta);
}

如果不依赖于第三方库,那就更好了。 - linac
1
第三方库是什么?那些是本地的WPF控件。 - Vadim Tofan
2
GetVisualParent 不是。 - linac
1
在“PresentationCore”中使用“System.Windows.Media.VisualTreeHelper.GetParent(DependencyObject)”怎么样? - fjch1997

1

大家好,我看到这里发布的大部分解决方案,基本上都是正确的,但我认为有一种更简单、更优雅的语法可以应用。

假设我们不需要使用数据网格来滚动,而是想要使用主窗口来滚动

按照 "Dave" 和 "Vladim Tofan" 的答案操作即可

Private Void Scrlll(Object sebder, MouseWheelEventArgs e)
{
   var windows = (Window.GetWindow(this) as MainWindow).MainScroll;
   windows.ScrollToVerticalOffset(windows.VerticalOffset - e.Delta);
}

抱歉,英语不好。

1

@fjch1997,我使用了您的解决方案,它非常有效。

我只发现了一个问题,与@Vadim Tofan的评论有关:

对于滚动查看器具有其他可滚动控件和数据网格的情况,这要求在主滚动查看器的事件处理程序结束时不应将事件标记为已处理,以便也可以在内部可滚动控件中捕获,但是这会导致当滚动仅应在内部可滚动控件上发生时,它也会发生在主滚动查看器上。

我也尝试删除e.Handled = true语句,但效果不好-两个滚动条同时移动。因此,最终我将事件处理程序方法改进为以下方法:

private static void ScrollViewer_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
    ScrollViewer scrollViewer = (ScrollViewer)sender;
    FrameworkElement origicalControlSender = e.OriginalSource as FrameworkElement;

    ScrollViewer closestScrollViewer = origicalControlSender.GetParent<ScrollViewer>();

    if (closestScrollViewer != null && !ReferenceEquals(closestScrollViewer, scrollViewer))
    {
        return;
    }

    scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - e.Delta);
    e.Handled = true;
}

public static T GetParent<T>(this FrameworkElement control)
    where T : DependencyObject
{
    FrameworkElement parentElement = control?.Parent as FrameworkElement;

    if (parentElement == null)
    {
        return null;
    }

    T parent = parentElement as T;

    if (parent != null)
    {
        return parent;
    }

    return GetParent<T>(parentElement);
}

现在,如果存在内部的 ScrollViewer,它会阻止外部的滚动条滚动。


FrameworkElement 不包含 GetParent() 方法的定义。 - Sirop4ik
@AlekseyTimoshchenko GetParent方法在上面的答案中,应该放在一个静态类中,因为它是一个扩展方法(这是由this关键字建议的)。如果这个链接有帮助,请告诉我。 - silviubogan
是的,它很有帮助,谢谢! - Sirop4ik

0

我知道已经有一段时间了,但我遇到了同样的问题,并通过DockPanel解决了它。

<DockPanel Grid.ColumnSpan="3" LastChildFill="True">
    <Button DockPanel.Dock="Top" Content="Add" Grid.Column="3" HorizontalAlignment="Right" Width="110" Height="30" Margin="5"/>
    <DataGrid x:Name="xxDG" SelectionUnit="Cell" ItemsSource="{Binding}" Margin="0, 0, 0, 0" >
        ...   
    </DataGrid>
</DockPanel>

由于某种原因,这个处理鼠标滚轮事件的功能非常出色。


0
我发现了这个链接:http://wpfthoughts.blogspot.com/2014/05/datagrid-vertical-scrolling-issues.html,觉得应该在这里加上。 "如果你的目标是 Framework 4.5,那么 DataGrid 内部 VirtualizingPanel 上有一个新的依赖对象叫做 ScrollUnit,可以设置为 Item(默认值)或 Pixel。如果我们稍微修改一下 XAML,就可以看到它的工作原理。"
<DataGrid Name="DG" ItemsSource="{Binding B0}" AutoGenerateColumns="False" IsReadOnly="true" RowDetailsVisibilityMode="Visible" Width="200" 
      Height="100" VirtualizingPanel.ScrollUnit="Pixel">

0

Grid控件内置了ScrollPanel,因此将其包装在ScrollPanel中对我来说根本行不通(将Grid设置为固定高度是不可能的,因为我希望它能够与应用程序的其他部分一起自动调整大小)。

我的做法是结合了这里几个评分较高的解决方案,但基本上的想法是摆脱ScrollPanel,只需将DataGrid的PreviewMouseEvent传递回实际处理滚动的父控件(在我的情况下是Grid)。

    private void DataGrid_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
    {

        MouseWheelEventArgs eventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
        {
            RoutedEvent = MouseWheelEvent, Source = e.Source
        };
        DependencyObject parent = VisualTreeHelper.GetParent((DependencyObject) sender);

        while (parent != null && !(parent is Grid))
        {
            parent = VisualTreeHelper.GetParent(parent);
        }

        if (parent != null)
        {
            Grid grid = (Grid) parent;
            grid.RaiseEvent(eventArg);
        }

        e.Handled = true;
    }

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