目录
介绍
第一种方法:记住选定的行,刷新DataGrid,再次选择行
最终方法:使用OneWay绑定,避免调用Refresh()
改进1:使ScrollIntoView()起作用
改进2:将选定的行显示为具有焦点
深入研究DataGrid格式
使用代码
出人意料的是,从代码中更改某些WPF DataGrid数据后遇到许多挑战,这些代码要求对行进行新的排序并滚动DataGrid以显示最初选择的行。本文重点介绍遇到的问题以及如何解决。最后是完整的示例代码。
介绍我正在使用WPF编写一个WPF应用程序,该WPF DataGrid应用程序显示具有rank属性的项目,这些项目按rank排序。用户界面应允许用户选择一些行(项目),并通过单击按钮将它们上下移动几行:
单击“向下移动”时,Rank 3-5的Item 3-5下移20行并获得新的Rank 23-25。Item 6-25上升了3 rank,为Item 3-5腾出了空间。网格自动按rank对Item进行排序,滚动并在网格中的新位置显示3个选定的行。
我认为这在“下移按钮”的处理程序中很容易实现:
- 检测选择了哪些行(Item)。
- 循环遍历它们,并将它们的Rank增加20。
- 在需要移开的行上循环并调整它们的行Rank。
- 刷新DataGrid。
不幸的是,刷新DataGrid使DataGrid忘记了选择了哪些行。如果用户需要多次按下“下移”按钮以将选定的行移至正确的位置,则会给用户带来严重的问题。
第一种方法:记住选定的行,刷新DataGrid,再次选择行听起来很简单,对吧?不幸的是,事实证明选择行并将它们显示在WPF DataGrid中非常复杂,原因是由于虚拟化,只有当前可见的Item才实际分配了DataRow和DataGridCell,但是Item被选中时的信息存储在这些类中。因此,如果某个Item从可见部分消失,则将其重新显示并再次标记为选中状态相当复杂。
幸运的是,我发现了这篇Technet文章WPF:以编程方式选择和聚焦DataGrid中的行或单元格
不幸的是,所需的代码既复杂又缓慢。就像这样(有关代码,请参见上一个链接):
- 循环浏览应选择的每个Item。
- 使用DataGrid.ItemContainerGenerator.ContainerFromIndex(itemIndex)以确定该行是否可见。
- 如果不是,请使用TracksDataGrid.ScrollIntoView(item),然后再次使用ContainerFromIndex(itemIndex)。
- 希望DataRow现在可以找到一个。给它Focus。
现在,如果你想给DataGridRow一个Focus,这DataGridRow是可见的,是很容易的,那你就错了。它涉及以下步骤(有关代码,请参见上一个链接):
- 在保存DataGridCells的DataGridRow中找到DataGridCellsPresenter。如果您认为这是微不足道的,那么您会再次犯错。您需要遍历可视化树以找到DataGridCellsPresenter。
- 如果找不到它,则它不在可视化树中,您必须自己应用DataRow模板,然后再次重复步骤1,这一次成功。
- 使用presenter.ItemContainerGenerator.ContainerFromIndex(0)找到的第一列。如果未找到任何内容,则它不在可视化树中,您必须将dataGrid.ScrollIntoView(rowContainer, dataGrid.Columns[0])列滚动到视图中。
- 现在,只有现在您才能调用DataGridCell.Focus()。
现在继续遍历每一行。
这不仅听起来很复杂,而且代码执行也很慢。在我的顶级工作站上,它花费了将近一秒钟。现在,假设用户单击几次按钮(10次是很容易的,如果他将10次增加1)。但是10秒的延迟根本不可接受。因此,我不得不寻找另一种解决方案。
最终方法:使用OneWay绑定,避免调用Refresh()由于用户不能在datagrid中直接改变任何数据,我将其设置为只读并使用默认绑定,默认绑定是OneTime,这意味着数据被分配给DataGrid的DataSource时,数据被分写入一次。我将绑定更改为OneWay,每次DataGrid数据更改时,该绑定都会复制新值。为此,我的item必须实现INotifyPropertyChanged:
public class Item: INotifyPropertyChanged {
public string Name { get; set; }
public int Rank {
get {
return rank;
}
set {
if (rank!=value) {
rank = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Rank)));
}
}
}
int rank;
public event PropertyChangedEventHandler? PropertyChanged;
}
每次Rank更改时,PropertyChanged都会调用该事件,DataGrid订阅该事件。
DataGrid现在显示了具有Rank值的行,但没有排序。经过一番谷歌搜索后,我发现实时排序需要像这样被激活:
var itemsViewSource = ((CollectionViewSource)this.FindResource("ItemsViewSource"));
itemsViewSource.Source = items;
itemsViewSource.IsLiveSortingRequested = true;
ItemsDataGrid.Columns[0].SortDirection = ListSortDirection.Ascending;
itemsViewSource.View.SortDescriptions.Add
(new SortDescription("Rank", ListSortDirection.Ascending));
进行此更改后,单击“下移”按钮的执行速度相当快,并且DataGrid排序正确,但是:选定的行不可见,无法再看到。通过DataGrid.ScrollIntoView(DataGrid.SelectedItem)添加,应该很容易解决该问题。哎,什么都没发生,DataGrid没有滚动。
经过更多的谷歌搜索后,我得出的结论是,当我在“下移”按钮单击事件中调用该ScrollIntoView()函数时,它根本没有执行任何操作,因为DataGrid当时尚未进行排序。因此,我不得不延迟调用ScrollIntoView(),但是怎么做呢?我首先考虑使用计时器,但是后来我找到了一个更好的解决方案:使用DataGrid.LayoutUpdated事件:
bool isMoveDownNeeded;
bool isMoveUpNeeded;
private void ItemsDataGrid_LayoutUpdated(object? sender, EventArgs e) {
if (isMoveUpNeeded) {
isMoveUpNeeded = false;
ItemsDataGrid.ScrollIntoView(ItemsDataGrid.SelectedItem);
}
if (isMoveDownNeeded) {
isMoveDownNeeded = false;
ItemsDataGrid.ScrollIntoView
(ItemsDataGrid.SelectedItems[ItemsDataGrid.SelectedItems.Count-1]);
}
}
而且,单击“下移”按钮执行得相当快,DataGrid排序正确,并且DataGrid滚动到选定的行。
当用户用鼠标选择一些行时,它们以深蓝色背景显示。但是,一旦单击该Move Down按钮,该按钮将BackGround变成灰色并且很难在我的显示器上看到。如第一种方法中所述,可以从后面的代码中为行赋予焦点,但这太复杂且太慢。幸运的是,有一个简单得多的解决方案:
这里的技巧只是使该行在刚被选中时(InactiveSelectionHighlightBrush)和被选中并具有焦点(HighlightBrush)时看起来相同。
如果您到这里都读过了,可以肯定地说您对DataGrid确实有兴趣。在这种情况下,我还建议您阅读有关DataGrid格式化的文章,黑魔法:使用绑定对WPF DataGrid进行格式化的指南。
使用代码该示例应用程序不需要太多代码,但是我花了很长时间使它工作,通过研究它,我希望您可以节省一些时间。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;
namespace TryDataGridScrollIntoView {
public partial class MainWindow : Window{
public MainWindow(){
InitializeComponent();
MoveDownButton.Click += MoveDownButton_Click;
MoveUpButton.Click += MoveUpButton_Click;
ItemsDataGrid.LayoutUpdated += ItemsDataGrid_LayoutUpdated;
var items = new List();
for (int i = 0; i < 100; i++) {
items.Add(new Item { Name = $"Item {i}", Rank = i });
}
var itemsViewSource = ((CollectionViewSource)this.FindResource("ItemsViewSource"));
itemsViewSource.Source = items;
itemsViewSource.IsLiveSortingRequested = true;
ItemsDataGrid.Columns[0].SortDirection = ListSortDirection.Ascending;
itemsViewSource.View.SortDescriptions.Add(new SortDescription
("Rank", ListSortDirection.Ascending));
}
const int rowsPerPage = 20;
private void MoveUpButton_Click(object sender, RoutedEventArgs e) {
var firstSelectedTrack = ItemsDataGrid.SelectedIndex;
if (firstSelectedTrack=
ItemsDataGrid.Items.Count) return;//cannot move down any further
int lastMoveTrack;
int moveTracksCount;
lastMoveTrack = Math.Min(ItemsDataGrid.Items.Count-1, lastSelectedTrack + rowsPerPage);
moveTracksCount = Math.Min(rowsPerPage, lastMoveTrack - lastSelectedTrack);
isMoveDownNeeded = true;
moveTracksUp(lastMoveTrack - moveTracksCount + 1, moveTracksCount, selectedTracksCount);
moveTracksDown(firstSelectedTrack, selectedTracksCount, moveTracksCount);
ItemsDataGrid.ScrollIntoView(ItemsDataGrid.SelectedItem); //doesn't work :-(
}
private void moveTracksDown(int firstTrack, int tracksCount, int offset) {
for (int itemIndex = firstTrack; itemIndex
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?


微信扫码登录