目录

介绍

第一种方法:记住选定的行,刷新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个选定的行。

我认为这在“下移按钮”的处理程序中很容易实现:

  1. 检测选择了哪些行(Item)。
  2. 循环遍历它们,并将它们的Rank增加20。
  3. 在需要移开的行上循环并调整它们的行Rank。
  4. 刷新DataGrid。

不幸的是,刷新DataGrid使DataGrid忘记了选择了哪些行。如果用户需要多次按下“下移按钮以将选定的行移至正确的位置,则会给用户带来严重的问题。

第一种方法:记住选定的行,刷新DataGrid,再次选择行

听起来很简单,对吧?不幸的是,事实证明选择行并将它们显示在WPF DataGrid中非常复杂,原因是由于虚拟化,只有当前可见的Item才实际分配了DataRow和DataGridCell,但是Item被选中时的信息存储在这些类中。因此,如果某个Item从可见部分消失,则将其重新显示并再次标记为选中状态相当复杂。

幸运的是,我发现了这篇Technet文章WPF:以编程方式选择和聚焦DataGrid中的行或单元格

不幸的是,所需的代码既复杂又缓慢。就像这样(有关代码,请参见上一个链接):

  1. 循环浏览应选择的每个Item。
  2. 使用DataGrid.ItemContainerGenerator.ContainerFromIndex(itemIndex)以确定该行是否可见。
  3. 如果不是,请使用TracksDataGrid.ScrollIntoView(item),然后再次使用ContainerFromIndex(itemIndex)。
  4. 希望DataRow现在可以找到一个。给它Focus。

现在,如果你想给DataGridRow一个Focus,这DataGridRow是可见的,是很容易的,那你就错了。它涉及以下步骤(有关代码,请参见上一个链接):

  1. 在保存DataGridCells的DataGridRow中找到DataGridCellsPresenter。如果您认为这是微不足道的,那么您会再次犯错。您需要遍历可视化树以找到DataGridCellsPresenter。
  2. 如果找不到它,则它不在可视化树中,您必须自己应用DataRow模板,然后再次重复步骤1,这一次成功。
  3. 使用presenter.ItemContainerGenerator.ContainerFromIndex(0)找到的第一列。如果未找到任何内容,则它不在可视化树中,您必须将dataGrid.ScrollIntoView(rowContainer, dataGrid.Columns[0])列滚动到视图中。
  4. 现在,只有现在您才能调用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没有滚动。

改进1:使ScrollIntoView()起作用

经过更多的谷歌搜索后,我得出的结论是,当我在“下移按钮单击事件中调用该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滚动到选定的行。

改进2:将选定的行显示为具有焦点

当用户用鼠标选择一些行时,它们以深蓝色背景显示。但是,一旦单击该Move Down按钮,该按钮将BackGround变成灰色并且很难在我的显示器上看到。如第一种方法中所述,可以从后面的代码中为行赋予焦点,但这太复杂且太慢。幸运的是,有一个简单得多的解决方案:

<DataGrid.Resources><SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Blue"/><SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="Blue"/><SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="White"/><SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}" Color="White"/>
</DataGrid.Resources>

这里的技巧只是使该行在刚被选中时(InactiveSelectionHighlightBrush)和被选中并具有焦点(HighlightBrush)时看起来相同。

深入研究DataGrid格式

如果您到这里都读过了,可以肯定地说您对DataGrid确实有兴趣。在这种情况下,我还建议您阅读有关DataGrid格式化的文章,黑魔法:使用绑定对WPF DataGrid进行格式化的指南。

使用代码

该示例应用程序不需要太多代码,但是我花了很长时间使它工作,通过研究它,我希望您可以节省一些时间。

<Window x:Class="TryDataGridScrollIntoView.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:TryDataGridScrollIntoView"mc:Ignorable="d"Title="Move" Height="450" Width="400"><Window.Resources><CollectionViewSource x:Key="ItemsViewSource" CollectionViewType="ListCollectionView"/></Window.Resources><Grid><Grid.RowDefinitions><RowDefinition Height="*"/><RowDefinition Height="auto"/></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition/><ColumnDefinition/></Grid.ColumnDefinitions><DataGrid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" x:Name="ItemsDataGrid"DataContext="{StaticResource ItemsViewSource}" ItemsSource="{Binding}" AutoGenerateColumns="False" EnableRowVirtualization="True" RowDetailsVisibilityMode="Collapsed" EnableColumnVirtualization="False"AllowDrop="False" CanUserAddRows="False" CanUserDeleteRows="False" CanUserResizeRows="False"><DataGrid.Resources><SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Blue"/><SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="Blue"/><SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="White"/><SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}" Color="White"/><!--<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="{DynamicResource {x:Static SystemColors.HighlightColor}}"/>--></DataGrid.Resources><DataGrid.Columns><DataGridTextColumn Binding="{Binding Path=Rank, StringFormat=N0, Mode=OneWay}" Header="Rank"  IsReadOnly="True" Width="45"/><DataGridTextColumn Binding="{Binding Path=Name}" Header="Name" IsReadOnly="True"/></DataGrid.Columns></DataGrid><Button Grid.Row="1" Grid.Column="0" x:Name="MoveDownButton" Content="Move _Down"/><Button Grid.Row="1" Grid.Column="1" x:Name="MoveUpButton" Content="Move Up"/></Grid>
</Window>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<Item>();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<=0) return;//cannot move up any furthervar selectedTracksCount = ItemsDataGrid.SelectedItems.Count;int firstMoveTrack;int moveTracksCount;firstMoveTrack = Math.Max(0, firstSelectedTrack - rowsPerPage);moveTracksCount = Math.Min(rowsPerPage, firstSelectedTrack - firstMoveTrack);isMoveUpNeeded = true;moveTracksDown(firstMoveTrack, moveTracksCount, selectedTracksCount);moveTracksUp(firstSelectedTrack, selectedTracksCount, moveTracksCount);}private void MoveDownButton_Click(object sender, RoutedEventArgs e) {var firstSelectedTrack = ItemsDataGrid.SelectedIndex;var selectedTracksCount = ItemsDataGrid.SelectedItems.Count;var lastSelectedTrack = firstSelectedTrack + selectedTracksCount - 1;if (lastSelectedTrack + 1 >= ItemsDataGrid.Items.Count) return;//cannot move down any furtherint 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<firstTrack+tracksCount; itemIndex++) {Item item = (Item)ItemsDataGrid.Items[itemIndex]!;item.Rank += offset;}}private void moveTracksUp(int firstTrack, int tracksCount, int offset) {for (int itemIndex = firstTrack; itemIndex<firstTrack+tracksCount; itemIndex++) {Item item = (Item)ItemsDataGrid.Items[itemIndex]!;item.Rank -= offset;}}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]);}}}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;}
}

https://www.codeproject.com/Articles/5294035/WPF-DataGrid-Solving-Sorting-ScrollIntoView-Refres

WPF DataGrid:解决排序、ScrollIntoView、刷新和焦点问题相关推荐

  1. WPF DataGrid 如何将被选中行带到视野中

    WPF DataGrid 如何将被选中行带到视野中 目录 前言 准备工作 方法一 方法二 总结 独立观察员 2021 年 12 月 11 日 前言 在 WPF 开发中,显示表格一般使用 DataGri ...

  2. WPF DataGrid 通过自定义表头模拟首行固定

    WPF DataGrid 通过自定义表头模拟首行固定 独立观察员 2021 年 9 月 25 日 最近工作中要在 WPF 中做个表格,自然首选就是 DataGrid 控件了.问题是,UI 设计的表格是 ...

  3. 使用绑定进行WPF DataGrid格式化的指南

    目录 介绍 WPF DataGrid结构 WPF绑定基础 使用的业务数据 将DataGrid与业务数据连接 DataGrid格式 格式化列 格式化完整行 根据显示的值格式化单元格 根据业务逻辑数据格式 ...

  4. DataGrid自动排序

    DataGrid是ASP.NET中非常重要的一个控件.它能方便的让我们实现编辑.排序功能:但是排序功能默认的是升序(ASC),能不能让DataGrid同时实现升降序排列呢?这篇文章将给你一个比较好的解 ...

  5. 让Asp.NET的DataGrid可排序、可选择、可分页

    '*************************************************************** 'Author: 李洪根 'MAIL: lihonggen0@gci- ...

  6. LayUi前端框架删除数据缓存问题(解决删除后刷新页面内容又会显示问题)

    LayUi前端框架删除数据缓存问题(解决删除后刷新页面内容又会显示问题) 参考文章: (1)LayUi前端框架删除数据缓存问题(解决删除后刷新页面内容又会显示问题) (2)https://www.cn ...

  7. 为 VUE 项目添加 PWA 解决发布后刷新报错问题

    为 VUE 项目添加 PWA 解决发布后刷新报错问题 参考文章: (1)为 VUE 项目添加 PWA 解决发布后刷新报错问题 (2)https://www.cnblogs.com/morang/p/9 ...

  8. WPF Viewport3D 解决透视模式时窗体模糊

    原文:WPF Viewport3D 解决透视模式时窗体模糊 最近折腾Viewport3D玩,遇到了一些诡异的问题,研究一下略有心得,特此和大家分享~ 三维图形概述: https://msdn.micr ...

  9. DataGrid双向排序以及给HeaderText加图标

    DataGrid双向排序以及给HeaderText加图标 DataGrid默认情况下只支持单向排序,而且默认是升序(ASC).能不能使之支持双向排序并且给HeaderText加图标呢?        ...

最新文章

  1. 你哪来这么多事(二):学生信息查找
  2. 《Pro ASP.NET MVC 3 Framework》学习笔记之十七【示例项目SportsStore】
  3. 反向区域DNS解析服务
  4. 3.8 高级检索方式(二)
  5. 批量改名_手把手教你用Python批量给图片添加水印 | 知了干货分享
  6. Metrics, tracing 和 logging 的关系
  7. 第二季2:/package/mpp/sample的总体分析
  8. 《真还传》剧终:6亿欠款还完了?罗永浩被执行信息清零,最新回应来了
  9. python绘制拟合回归散点图_Python之简单线性回归
  10. 找回Win8.1(windows server 2012 R2)的双拼
  11. 敏捷开发用户故事系列之五:用户故事的分类
  12. fastjson 添加key value_采坑系列—fastjson
  13. 使用ffmpeg推流rtmp
  14. 图像去雾算法学习笔记1——何凯明博士基于暗通道先验的单幅图像去雾算法公式推导
  15. <笠翁对韵>全文及译文(上卷)
  16. 浅谈一下量化交易与程序化交易
  17. Shell运行原理及Linux权限
  18. 2018年专业技术人员权益保护-测试题答案
  19. 湖北2022农民丰收节 国稻种芯:麻城启动王忠林宣布活动
  20. 《期权、期货及其他衍生产品》读书笔记(第九章:价值调节量)

热门文章

  1. 2017年12月计算机一级c,2017年12月计算机二级《C语言》强化模拟题(1)
  2. linux下载python的地址_Linux下Python获取IP地址的代码
  3. 传统东方韵味尽显|国潮国风包装样机
  4. 电商促销PSD分层海报设计流程,设计师收好
  5. Windows下设置开机自启动的方式(手动/C++代码的形式)
  6. Linux虚拟内存管理 | 虚拟地址与物理地址映射、段错误SIGSEGV
  7. Docker基础知识:Containers,Namespace,CGroups
  8. python使用 GPUs
  9. SeismicUnix:wtlib.c[小波变换库]
  10. leetcode题库:3.无重复字符的最长子串