您当前的位置: 首页 > 

寒冰屋

暂无认证

  • 0浏览

    0关注

    2286博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

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

寒冰屋 发布时间:2021-04-09 23:06:48 ,浏览量:0

目录

介绍

第一种方法:记住选定的行,刷新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变成灰色并且很难在我的显示器上看到。如第一种方法中所述,可以从后面的代码中为行赋予焦点,但这太复杂且太慢。幸运的是,有一个简单得多的解决方案:


  
  
  
  

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

深入研究DataGrid格式

如果您到这里都读过了,可以肯定地说您对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            
关注
打赏
1665926880
查看更多评论
0.1330s