如何在 WPF 数据网格上自动滚动

Posted

技术标签:

【中文标题】如何在 WPF 数据网格上自动滚动【英文标题】:How to autoscroll on WPF datagrid 【发布时间】:2010-11-04 20:26:08 【问题描述】:

我觉得我很傻。我现在搜索了 15 分钟,发现了几种不同的在数据网格上滚动的解决方案,但似乎没有一个适合我。

我将 WPF 与 .NET 3.5 和 WPF Toolkit DataGrid 一起使用。当我的可观察集合发生变化时,我的网格会更新,效果很好。现在,我的 DataGrid 位于普通 Grid 内,如果 DataGrid 变得太大,则会出现滚动条。也不错……

现在是 1.000.000 美元的问题:

如何让数据网格滚动到最后一行? 有:

没有自动滚动属性 没有 CurrentRowSelected 索引 一个 CurrentCell,但没有可以用于 CurrentCell = AllCells.Last 的集合

有什么想法吗?我觉得自己真的很蠢,而且这个问题这么难,似乎很奇怪。我错过了什么?

【问题讨论】:

WPF 就像一辆没有***的汽车,甚至只有引擎和底盘。 【参考方案1】:

当您使用带有滚动条的 datagridview 时,必须使用此技术。因为我正在尝试其他技术。但这些都不能正常工作....

var border = VisualTreeHelper.GetChild(mainDataGrid, 0) as Decorator;
if(border != null)
   var scroll = border.Child as ScrollViewer;
    if (scroll != null) scroll.ScrollToEnd(); 

【讨论】:

【参考方案2】:

如果您正在寻找一种 MVVM 方式进行自动滚动,那么您可以使用自动滚动行为。行为滚动到选定的项目,只需添加对 System.Windows.Interactivity.dll 的引用:

public class ScrollIntoViewBehavior : Behavior<DataGrid>

    protected override void OnAttached()
    
        base.OnAttached();
        AssociatedObject.SelectionChanged += new SelectionChangedEventHandler(AssociatedObject_SelectionChanged);
    

    void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs e)
    
        if (sender is DataGrid)
        
            DataGrid grid = (sender as DataGrid);
            if (grid?.SelectedItem != null)
            
                grid.Dispatcher.InvokeAsync(() =>
                
                    grid.UpdateLayout();
                    grid.ScrollIntoView(grid.SelectedItem, null);
                );
            
        
    

    protected override void OnDetaching()
    
        base.OnDetaching();
        AssociatedObject.SelectionChanged -=
            new SelectionChangedEventHandler(AssociatedObject_SelectionChanged);
    

XAML

<DataGrid>
    <i:Interaction.Behaviors>
        <local:ScrollIntoViewBehavior/>
    </i:Interaction.Behaviors>
</DataGrid>

【讨论】:

【参考方案3】:

以下代码对我有用;

Private Sub DataGrid1_LoadingRow(sender As Object, e As DataGridRowEventArgs) Handles DataGrid1.LoadingRow
    DataGrid1.ScrollIntoView(DataGrid1.Items.GetItemAt(DataGrid1.Items.Count - 1))
End Sub

【讨论】:

【参考方案4】:

;)

if (mainDataGrid.Items.Count > 0)

    var border = VisualTreeHelper.GetChild(mainDataGrid, 0) as Decorator;
    if (border != null)
    
        var scroll = border.Child as ScrollViewer;
        if (scroll != null) scroll.ScrollToEnd();
    

【讨论】:

非常感谢,如果生活总是那么简单就好了:-) 一段很棒的代码,将它包装在 ArgumentOutOfRange 异常中,这将是完美的,因为当列表框可能为空时。 我也想知道这段代码去哪儿了(什么事件被钩住了)?我已经尝试过 SizeChanged 和 LayoutUpdated 等。我能得到的最好的结果是我的 DataGrid 向下滚动了一半。我试过这个装饰器/ScrollToEnd 版本和 ScrollIntoView 版本。 thanx 这对我有用...从你的回答看来你是所有技术的大师 :) gr8 ((INotifyCollectionChanged)MyDataGrid.Items).CollectionChanged += Your_Event_Handler;挂钩【参考方案5】:

如果你对 datagrid.datacontext 使用了 dataview,你可以使用这个:

private void dgvRecords_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)

    var dv = dgvRecords.DataContext as DataView;
    if (dv.Count > 0)
    
        var drv = dv[dv.Count - 1] as DataRowView;
        dgvRecords.ScrollIntoView(drv);
    

【讨论】:

【参考方案6】:

用于添加 AutoScroll To Last 元素:

YourDataGrid.ScrollIntoView(YourDataGrid.Items.GetItemAt(YourDataGrid.Items.Count-1));

愿此帮助:)

【讨论】:

YourDataGrid.Items.Last() 可以使代码更简洁。 @Anas Items 没有 Last() 方法【参考方案7】:

这是另一个出色的解决方案。

public sealed class CustomDataGrid : DataGrid

    protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
    
        base.OnItemsSourceChanged(oldValue, newValue);
    
    protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
    
        base.OnItemsChanged(e);
        if (this.Items.Count > 0) this.ScrollIntoView(this.Items[this.Items.Count - 1]);
    

【讨论】:

【参考方案8】:

我发现最简单的方法是从 ScrollViewer.ScrollChanged 附加事件中调用 ScrollIntoView 方法。这可以在 XAML 中设置如下:

<DataGrid
...
ScrollViewer.ScrollChanged="control_ScrollChanged">

ScrollChangedEventArgs 对象具有各种有助于计算布局和滚动位置(Extent、Offset、Viewport)的属性。请注意,在使用默认 DataGrid 虚拟化设置时,这些通常以行数/列数来衡量。

这是一个示例实现,它在将新项目添加到 DataGrid 时保持底部项目处于视图中,除非用户移动滚动条以查看网格中更高的项目。

    private void control_ScrollChanged(object sender, ScrollChangedEventArgs e)
    
        // If the entire contents fit on the screen, ignore this event
        if (e.ExtentHeight < e.ViewportHeight)
            return;

        // If no items are available to display, ignore this event
        if (this.Items.Count <= 0)
            return;

        // If the ExtentHeight and ViewportHeight haven't changed, ignore this event
        if (e.ExtentHeightChange == 0.0 && e.ViewportHeightChange == 0.0)
            return;

        // If we were close to the bottom when a new item appeared,
        // scroll the new item into view.  We pick a threshold of 5
        // items since issues were seen when resizing the window with
        // smaller threshold values.
        var oldExtentHeight = e.ExtentHeight - e.ExtentHeightChange;
        var oldVerticalOffset = e.VerticalOffset - e.VerticalChange;
        var oldViewportHeight = e.ViewportHeight - e.ViewportHeightChange;
        if (oldVerticalOffset + oldViewportHeight + 5 >= oldExtentHeight)
            this.ScrollIntoView(this.Items[this.Items.Count - 1]);
    

【讨论】:

【参考方案9】:

WPF DataGrid 自动滚动

只要鼠标按钮在按钮控件上,就会自动滚动。

XAML

<Button x:Name="XBTNPageDown" Height="50" MouseLeftButtonDown="XBTNPageDown_MouseLeftButtonDown"  MouseUp="XBTNPageDown_MouseUp">Page Down</Button>

代码

    private bool pagedown = false;
    private DispatcherTimer pageDownTimer = new DispatcherTimer();

    private void XBTNPageDown_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    
        pagedown = true;
        pageDownTimer.Interval = new TimeSpan(0, 0, 0, 0, 30);
        pageDownTimer.Start();
        pageDownTimer.Tick += (o, ea) =>
        
            if (pagedown)
            
                var sv = XDG.FindVisualChild<ScrollViewer>();
                sv.PageDown();
                pageDownTimer.Start();
            
            else
            
                pageDownTimer.Stop();
            
        ;
    

    private void XBTNPageDown_MouseUp(object sender, MouseButtonEventArgs e)
    
        pagedown = false;
    

这是扩展方法

将它放在您选择的静态类中并添加对上面代码的引用。

   public static T FindVisualChild<T>(this DependencyObject depObj) where T : DependencyObject
    
        if (depObj != null)
        
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
            
                DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
                if (child != null && child is T)
                
                    return (T)child;
                

                T childItem = FindVisualChild<T>(child);
                if (childItem != null) return childItem;
            
        
        return null;
    

注意:属性 sv 可以移动以避免重复工作。

任何人都有 RX 方法来做到这一点?

【讨论】:

【参考方案10】:

其实……

我在学习关于在 WPF 中执行 DataContext 的集合视图时也遇到了同样的问题。

我也面临着将 WPF 程序拼凑在一起的任务,我需要以编程方式使用按钮在 DataGrid 上上下移动,因为我需要将它放在仅为生产建设者\t我公司的电阻式触摸屏上,并且没有鼠标或键盘供他们使用。

但是这个例子对我有用,使用本文前面提到的ScrollIntoView 方法:

    private void OnMoveUp(object sender, RoutedEventArgs e)
    
        ICollectionView myCollectView = CollectionViewSource.GetDefaultView(Orders);
        if (myCollectView.CurrentPosition > 0)
            myCollectView.MoveCurrentToPrevious();

        if (myCollectView.CurrentItem != null)
            theDataGrid.ScrollIntoView(myCollectView.CurrentItem);
    

    private void OnMoveDown(object sender, RoutedEventArgs e)
    
        ICollectionView  myCollectView = CollectionViewSource.GetDefaultView(Orders);
        if (myCollectView.CurrentPosition < Orders.Count)
            myCollectView.MoveCurrentToNext();

        if (myCollectView.CurrentItem !=null)
            theDataGrid.ScrollIntoView(myCollectView.CurrentItem);
    

Orders 是 List&lt;T&gt; 集合

在 XAML 中:

    <StackPanel Grid.Row="1"
        Orientation="Horizontal">
            <Button Click="OnMoveUp">
                <Image Source="Up.jpg" />
            </Button>
            <Button Click="OnMoveDown">
                <Image Source="Down.jpg" />
              </Button>
    </StackPanel>

    <DataGrid Grid.Row="2"
              x:Name="theDataGrid"
              ItemSource="Binding Orders"
              ScrollViewer.CanContentScroll="True"
              ScrollViewer.VerticalScrollBarVisibility="Auto" Margin="0,0,0,5">

    << code >>


    </DataGrid>

请遵循前面的建议,将 DataGrid 单独保留,而不是在堆栈面板中。对于 DataGrid 的行定义(本例中为第三行),我将高度设置为 150,滚动条正常工作。

【讨论】:

【参考方案11】:

我知道这是一个迟到的答案,但对于四处搜索的人来说,我发现滚动到 DataGrid 底部的最简单方法。在DataContextChanged 事件中输入:

myDataGrid.ScrollIntoView(CollectionView.NewItemPlaceholder);

简单吧?

这就是它起作用的原因:在每个数据网格上,DataGrid 底部都有一个位置,您可以在其中将新项目添加到它所绑定的列表中。那是CollectionView.NewItemPlaceholder,您的DataGrid 中只会有一个。所以你可以滚动到那个。

【讨论】:

-1:数据上下文仅在您将新视图模型添加到视图时才会更改,因此仅在第一次时才会滚动到底部,而不是每次将项目添加到 itemsource 时 好吧,它不会,但 datacontext 只是一个例子,它在我的应用程序中就像一个魅力...... 另外,这不是问题。 OP 询问如何将自动滚动功能添加到 DataGrid 控件,而不是如何向下一次。 就像我说的,这只是一个例子,你可以在任何地方调用它。请删除-1。谢谢。【参考方案12】:

我为网格自动滚动写了一个附加属性:

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls;

public static class DataGridBehavior

    public static readonly DependencyProperty AutoscrollProperty = DependencyProperty.RegisterAttached(
        "Autoscroll", typeof(bool), typeof(DataGridBehavior), new PropertyMetadata(default(bool), AutoscrollChangedCallback));

    private static readonly Dictionary<DataGrid, NotifyCollectionChangedEventHandler> handlersDict = new Dictionary<DataGrid, NotifyCollectionChangedEventHandler>();

    private static void AutoscrollChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
    
        var dataGrid = dependencyObject as DataGrid;
        if (dataGrid == null)
        
            throw new InvalidOperationException("Dependency object is not DataGrid.");
        

        if ((bool)args.NewValue)
        
            Subscribe(dataGrid);
            dataGrid.Unloaded += DataGridOnUnloaded;
            dataGrid.Loaded += DataGridOnLoaded;
        
        else
        
            Unsubscribe(dataGrid);
            dataGrid.Unloaded -= DataGridOnUnloaded;
            dataGrid.Loaded -= DataGridOnLoaded;
        
    

    private static void Subscribe(DataGrid dataGrid)
    
        var handler = new NotifyCollectionChangedEventHandler((sender, eventArgs) => ScrollToEnd(dataGrid));
        handlersDict.Add(dataGrid, handler);
        ((INotifyCollectionChanged)dataGrid.Items).CollectionChanged += handler;
        ScrollToEnd(dataGrid);
    

    private static void Unsubscribe(DataGrid dataGrid)
    
        NotifyCollectionChangedEventHandler handler;
        handlersDict.TryGetValue(dataGrid, out handler);
        if (handler == null)
        
            return;
        
        ((INotifyCollectionChanged)dataGrid.Items).CollectionChanged -= handler;
        handlersDict.Remove(dataGrid);
    

    private static void DataGridOnLoaded(object sender, RoutedEventArgs routedEventArgs)
    
        var dataGrid = (DataGrid)sender;
        if (GetAutoscroll(dataGrid))
        
            Subscribe(dataGrid);
        
    

    private static void DataGridOnUnloaded(object sender, RoutedEventArgs routedEventArgs)
    
        var dataGrid = (DataGrid)sender;
        if (GetAutoscroll(dataGrid))
        
            Unsubscribe(dataGrid);
        
    

    private static void ScrollToEnd(DataGrid datagrid)
    
        if (datagrid.Items.Count == 0)
        
            return;
        
        datagrid.ScrollIntoView(datagrid.Items[datagrid.Items.Count - 1]);
    

    public static void SetAutoscroll(DependencyObject element, bool value)
    
        element.SetValue(AutoscrollProperty, value);
    

    public static bool GetAutoscroll(DependencyObject element)
    
        return (bool)element.GetValue(AutoscrollProperty);
    

用法:

    <DataGrid c:DataGridBehavior.Autoscroll="Binding AutoScroll"/>

【讨论】:

@user161953 我认为访问者需要对此代码进行更多解释..! 非常非常好。谢谢你。 我必须对此进行一些细微的修改,以便它必须监视对整数属性的更改而不是布尔值。我很高兴地报告它即使在修改后也能正常工作。 不起作用:The attachable property 'Autoscroll' was not found in type 'DataGridBehavior'. 一直在尝试让 DataGrid 在遵守 MVVM 的同时自动滚动。在我的视图模型中创建 AutoScroll 属性然后将复选框 IsChecked 绑定到 AutoScroll 属性是一种享受。【参考方案13】:
listbox.Add(foo);
listbox.SelectedIndex = count - 1;
listbox.ScrollIntoView(listbox.SelectedItem);
listbox.SelectedIndex = -1;

【讨论】:

这非常适合将选择滚动到屏幕的“中间”(进行一些逻辑检查和更改)。【参考方案14】:

如果您使用 MVVM 模式,您可以将本文与其他文章结合使用:http://www.codeproject.com/KB/WPF/AccessControlsInViewModel.aspx。

这个想法是使用附加属性来访问 ViewModel 类中的控件。一旦你这样做了,你需要检查数据网格不为空,并且它有任何项目。

if ((mainDataGrid != null) && (mainDataGrid.Items.Count > 0))
//Same snippet

【讨论】:

【参考方案15】:

如果大数据datagrid.ScrollIntoView(itemInRow, column);不能正常工作,那么我们只需要使用以下一个:

if (mainDataGrid.Items.Count > 0) 
         
            var border = VisualTreeHelper.GetChild(mainDataGrid, 0) as Decorator; 
            if (border != null) 
             
                var scroll = border.Child as ScrollViewer; 
                if (scroll != null) scroll.ScrollToEnd(); 
             
         

【讨论】:

【参考方案16】:

你应该使用datagrid方法

datagrid.ScrollIntoView(itemInRow);

datagrid.ScrollIntoView(itemInRow, column);

这种方式不会让查找滚动查看器等变得混乱。

【讨论】:

@user1034912 如果您对此答案有疑问,您应该详细说明为什么它不起作用,或者您的情况可能不同,您需要提出另一个问题。自从 6 年前回答了这个问题以来,底层框架可能已经发生了一些变化。有时,当网格被虚拟化时,我在滚动到视图时遇到问题。从赞成票的数量来看,很明显这对很多人都有效,所以问题可能出在您的代码而不是这个答案上。【参考方案17】:

您需要获取对 DataGrid 的 ScrollViewer 对象的引用。然后,您可以操纵 VerticalOffset 属性滚动到底部。

要为您的应用程序添加更多闪光...您可以向滚动条添加样条动画,以便所有内容看起来与应用程序的其余部分相同。

【讨论】:

嗯,好吧,如果找到一些附加属性,比如 ScrollViewer.CanContentScroll = "True",但没有任何效果。我知道,RTFM,但是,嘿,我仍然对这里的一行代码抱有希望。我知道,被宠坏的小子:-)

以上是关于如何在 WPF 数据网格上自动滚动的主要内容,如果未能解决你的问题,请参考以下文章

如何在 WPF Datagrid 上启用滚动条?

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

WPF Toolkit:如何滚动数据网格以显示后面代码中的选定项目?

在 WPF ListView 中,如何防止自动滚动?

wpf 动态添加滚动条

如何在剑道网格中自动启用或禁用滚动条?