如何在 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<T>
集合
在 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 数据网格和其他 UI 元素的滚动查看器中不起作用