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

目录

介绍

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

最终方法:使用OneWay绑定,避免调用Refresh()

改进1:使ScrollIntoView()起作用

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

深入研究DataGrid格式

使用代码


出人意料的是,从代码中更改某些WPF DataGrid数据后遇到许多挑战,这些代码要求对行进行新的排序并滚动DataGrid以显示最初选择的行。本文重点介绍遇到的问题以及如何解决。最后是完整的示例代码。

介绍

我正在使用WPF编写一个WPF应用程序,该WPF DataGrid应用程序显示具有rank属性的项目,这些项目按rank排序。用户界面应允许用户选择一些行(项目),并通过单击按钮将它们上下移动几行:

单击向下移动Rank 3-5Item 3-5下移20行并获得新的Rank 23-25Item 6-25上升了3 rank,为Item 3-5腾出了空间。网格自动按rankItem进行排序,滚动并在网格中的新位置显示3个选定的行。

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

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

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

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

听起来很简单,对吧?不幸的是,事实证明选择行并将它们显示在WPF DataGrid中非常复杂,原因是由于虚拟化,只有当前可见的Item才实际分配了DataRowDataGridCell,但是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. 在保存DataGridCellsDataGridRow中找到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,这意味着数据被分配给DataGridDataSource时,数据被分写入一次。我将绑定更改为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 further

      var 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 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<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

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WPF DataGridWPF(Windows Presentation Foundation)框架中的一个控件,它允许您显示和编辑表格数据。下面是一些关于 WPF DataGrid 的常见问题和答案: 1. 如何创建 WPF DataGrid? 您可以在 XAML 中使用以下代码创建 WPF DataGrid: ``` <DataGrid ItemsSource="{Binding YourData}"> <DataGrid.Columns> <DataGridTextColumn Header="Column1" Binding="{Binding Property1}" /> <DataGridTextColumn Header="Column2" Binding="{Binding Property2}" /> <DataGridTextColumn Header="Column3" Binding="{Binding Property3}" /> </DataGrid.Columns> </DataGrid> ``` 2. 如何将数据绑定到 WPF DataGrid? 您可以在 XAML 中使用以下代码将数据绑定到 WPF DataGrid: ``` <DataGrid ItemsSource="{Binding YourData}"> ... </DataGrid> ``` 其中,“YourData”是您要绑定的数据集合。 3. 如何在 WPF DataGrid 中添加列? 您可以在 XAML 中使用“DataGrid.Columns”元素添加列。例如,以下代码将添加一个名为“Column1”的文本列: ``` <DataGrid.Columns> <DataGridTextColumn Header="Column1" Binding="{Binding Property1}" /> </DataGrid.Columns> ``` 4. 如何在 WPF DataGrid 中编辑数据? WPF DataGrid 允许您直接在表格中编辑数据。只需将“IsReadOnly”属性设置为“False”,就可以启用编辑模式。例如,以下代码将启用编辑模式: ``` <DataGrid IsReadOnly="False"> ... </DataGrid> ``` 5. 如何在 WPF DataGrid 中添加行? 您可以在代码中使用“Items.Add()”方法添加行。例如,以下代码将添加一个新行: ``` YourData.Add(new YourDataItem { Property1 = "Value1", Property2 = "Value2", Property3 = "Value3" }); ``` 其中,“YourData”是您要绑定的数据集合,“YourDataItem”是数据项的类型。 希望这些信息对您有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值