在 DataGrid 中对列进行排序时拖放无法工作

问题描述 投票:0回答:1

行的重新排列最初按预期工作,但如果列已排序,拖放行将不起作用。

此外,如果我取消数据网格中的列排序,拖放功能可以正常工作。

using System.Collections.ObjectModel;

public class Product
{
    public int ProductId { get; set; }
    public string ProductName { get; set; }
    public string ProductPrice { get; set; }
}

public class ProductCollection : ObservableCollection<Product>
{
    public ProductCollection()
    {
        Add(new Product() { ProductId = 111, ProductName = "Books", ProductPrice = "500$" });
        Add(new Product() { ProductId = 222, ProductName = "Cameras", ProductPrice = "600$" });
        Add(new Product() { ProductId = 333, ProductName = "Cell Phones", ProductPrice = "700$" });
        Add(new Product() { ProductId = 444, ProductName = "Clothing", ProductPrice = "800$" });
        Add(new Product() { ProductId = 555, ProductName = "Shoes", ProductPrice = "900$" });
        Add(new Product() { ProductId = 666, ProductName = "Gift Cards", ProductPrice = "500$" });
        Add(new Product() { ProductId = 777, ProductName = "Crafts", ProductPrice = "400$" });
        Add(new Product() { ProductId = 888, ProductName = "Computers", ProductPrice = "430$" });
        Add(new Product() { ProductId = 999, ProductName = "Coins", ProductPrice = "460$" });
        Add(new Product() { ProductId = 332, ProductName = "Cars", ProductPrice = "4600$" });
        Add(new Product() { ProductId = 564, ProductName = "Boats", ProductPrice = "3260$" });
        Add(new Product() { ProductId = 346, ProductName = "Dolls", ProductPrice = "120$" });
        Add(new Product() { ProductId = 677, ProductName = "Gift Cards", ProductPrice = "960$" });

    }
}

MainPage.xaml

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfRowDragDropSample"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="WpfRowDragDropSample.MainWindow"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <local:ProductCollection x:Key="ProductList"/>
    </Window.Resources>
    <Grid DataContext="{Binding Source={StaticResource ProductList}}">
        <DataGrid d:LayoutOverrides="Width" Margin="0,28,0,0" Name="productsDataGrid"
                  AutoGenerateColumns="False" ItemsSource="{Binding}"
                  SelectionMode="Extended" ColumnWidth="*" AllowDrop="True" >
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding ProductId}" Header="ProductId"></DataGridTextColumn>
                <DataGridTextColumn Binding="{Binding ProductName}" Header="ProductName"></DataGridTextColumn>
                <DataGridTextColumn Binding="{Binding ProductPrice}" Header="ProductPrice"></DataGridTextColumn>
            </DataGrid.Columns>
        </DataGrid>
        <TextBlock TextWrapping="Wrap" Text="DataGrid Row Drag And Drop Sample" VerticalAlignment="Top" Margin="3,1,0,0" Height="24" HorizontalAlignment="Left" Width="268" FontSize="14.667" FontWeight="Bold" FontStyle="Italic"/>       
    </Grid>
</Window>

MainPage.xaml.cs

using System.Windows.Controls.Primitives;

public delegate Point GetPosition(IInputElement element);
int rowIndex = -1;
public MainWindow()
{
    InitializeComponent();
    productsDataGrid.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(productsDataGrid_PreviewMouseLeftButtonDown);
    productsDataGrid.Drop += new DragEventHandler(productsDataGrid_Drop);
}

void productsDataGrid_Drop(object sender, DragEventArgs e)
{
    if (rowIndex < 0)
        return;
    int index = this.GetCurrentRowIndex(e.GetPosition);           
    if (index < 0)
        return;
    if (index == rowIndex)
        return;           
    if (index == productsDataGrid.Items.Count - 1)
    {
        MessageBox.Show("This row-index cannot be drop");
        return;
    }
    ProductCollection productCollection = Resources["ProductList"] as ProductCollection;
    Product changedProduct = productCollection[rowIndex];
    productCollection.RemoveAt(rowIndex);
    productCollection.Insert(index, changedProduct);
}

void productsDataGrid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    rowIndex = GetCurrentRowIndex(e.GetPosition);
    if (rowIndex < 0)
        return;
    productsDataGrid.SelectedIndex = rowIndex;
    Product selectedEmp = productsDataGrid.Items[rowIndex] as Product;
    if (selectedEmp == null)
        return;
    DragDropEffects dragdropeffects = DragDropEffects.Move;
    if (DragDrop.DoDragDrop(productsDataGrid, selectedEmp, dragdropeffects)
                        != DragDropEffects.None)
    {               
        productsDataGrid.SelectedItem = selectedEmp;
    }
}

private bool GetMouseTargetRow(Visual theTarget, GetPosition position)
{
    Rect rect = VisualTreeHelper.GetDescendantBounds(theTarget);
    Point point = position((IInputElement)theTarget);
    return rect.Contains(point);           
}

private DataGridRow GetRowItem(int index)
{
    if (productsDataGrid.ItemContainerGenerator.Status
            != GeneratorStatus.ContainersGenerated)
        return null;
    return productsDataGrid.ItemContainerGenerator.ContainerFromIndex(index)
                                                    as DataGridRow;
}

private int GetCurrentRowIndex(GetPosition pos)
{
    int curIndex = -1;
    for (int i = 0; i < productsDataGrid.Items.Count; i++)
    {
        DataGridRow itm = GetRowItem(i);
        if (GetMouseTargetRow(itm, pos))
        {
            curIndex = i;
            break;
        }
    }
    return curIndex;
}    

图 1: Now select a row to drop on place of other row like I select computers product row to put on the top of the grid.

图 2: Drag and drop selected row to other place.

我希望拖放比列排序具有更高的优先级,以便两者可以同时工作。

c# wpf datagrid
1个回答
0
投票

您的排序不起作用,因为您试图在

DataGrid
主动对
CollectionView
进行排序时修改基础集合(Microsoft 文档:Collection views)。

你基本上已经引入了第二个排序标准。第一个是列的排序标准(例如,数字排序)。在这个主要排序标准之后,您想要按索引排序(本质上您正在更改拖动行的索引)。
这意味着,您必须将您的标准投射到排序描述中,而不是四处移动项目。换句话说,如果您不合并这两种排序,列排序将始终覆盖您对源集合的修改。您必须通过排序来“移动”项目。

首先,您必须只处理集合视图。当

DataGrid
排序时,它对基础集合的
ICollectionView
进行排序。这意味着,如果您要查找已排序(或过滤)的索引
DataGrid
(或一般的
ItemsControl
),您必须从集合视图中检索该索引,例如:

ItemsControl.Items.IndexOf(item);

如果您正在寻找已排序/过滤的项目

ItemsControl

ItemsControl.Items.GetItemAt(itemIndex);

否则所见即所得。已排序

CollectionView
和底层未排序集合(如
ProductCollection
)的索引并不相同。
在调试您的原始代码时,您应该看到您正在移动底层
ProductCollection
中的项目。但是您仍然会在
DataGrid
.
中看到项目没有相应地排列 这是因为
DataGrid
不关心底层
ProductCollection
是如何排序的,只要对
CollectionView
应用了排序或过滤器(当单击
DataGrid
列时,
DataGrid
隐式分配一个
SortDescription 
到它绑定的
CollectionView
)。

在您的特殊情况下,您想要应用自定义排序条件after应用了列的主要排序条件,您可以简化排序:为了提高性能,您不必每次都执行列排序用户拖动一行。
如果您在初始列排序后跟踪集合视图中项目的索引就足够了。换句话说,在初始列排序(例如按属性名称)之后,您保存当前索引的快照。因为我们现在只对拖动行的索引相对于 DataGrid 中的

displayed
视图感兴趣。我们更改已排序
CollectionView
的索引,而不是基础
ProductCollection
集合的索引。

为此,一旦发生掉落,我们就会应用我们的自定义排序算法。我们可以通过将

IComparer
分配给
ListCollectionView.CustomSort
属性来分配自定义排序算法。

我们所做的就是改变掉落物品的索引和其他物品的索引。 但是因为我们正在处理

ICollectionView
我们不能真正移动项目。此外,我们必须维护作为初始列排序结果的原始索引。
为了解决这个问题,我们必须在先前的列排序之后抽象出项目索引,以便我们可以将它们用作我们的排序标准。我们可以向行数据模型添加一个新属性。一个更简洁的解决方案是创建一个表来跟踪和操作那些抽象的行索引。像
Dictionary<K,V>
这样的表结构非常适合快速查找,这里需要什么。

为了实现这一点,下面的例子引入了一个

SortedIndex
类型。它是一个链接索引对象,可以插入其他
SortIndex
节点并自动调整后面节点的索引。
它基本上表现得像一个双向链表。
SortIndex
将简化收集索引的维护。因为我们不必在集合中找到插入项来虚拟移动后面的所有项,所以我们也提高了性能。

MainPage.xaml

<Window>  
  <DataGrid ItemsSource="{Binding ProductList}" 
            Sorting="OnProductsDataGridSorting"
            AutoGenerateColumns="False"
            AllowDrop="True">
  <DataGrid.Columns>
    <DataGridTextColumn Binding="{Binding ProductId}" Header="ProductId" />
    <DataGridTextColumn Binding="{Binding ProductName}" Header="ProductName" />
    <DataGridTextColumn Binding="{Binding ProductPrice}" Header="ProductPrice" />
   </DataGrid.Columns>
 </DataGrid>
</Window>

MainPage.xaml.cs

partial class MainPage : Window
{
  public ProductCollection ProductList { get; }
  private bool IsSortIndexingPending { get; set; }
  private Dictionary<Product, SortIndex> ProductSortPriorityTable { get; }
  public delegate Point GetPosition(IInputElement element);
  int rowIndex = -1;

  public MainPage()
  {
    InitializeComponent();

    this.ProductList = new ProductCollection();
    this. ProductSortPriorityTable = new Dictionary<Product, SortIndex>();

    productsDataGrid.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(productsDataGrid_PreviewMouseLeftButtonDown);
    productsDataGrid.Drop += new DragEventHandler(productsDataGrid_Drop);
  }

  private void OnProductsDataGridSorting(object sender, DataGridSortingEventArgs e)
  {
    this.CurrentColumnSortComparer = ((CollectionView)CollectionViewSource.GetDefaultView(this.ProductList)).Comparer;
    this.ProductSortPriorityTable.Clear();
    this.IsSortIndexingPending = true;
  }

  void productsDataGrid_Drop(object sender, DragEventArgs e)
  {
    /*** Your existing code (unchanged) ***/
    
    if (this.rowIndex < 0)
      return;
    int index = GetCurrentRowIndex(e.GetPosition);
    if (index < 0)
      return;
    if (index == this.rowIndex)
      return;
    if (index == this.productsDataGrid.Items.Count - 1)
    {
      _ = MessageBox.Show("This row-index cannot be drop");
      return;
    }

    /*** New code ***/

    var collectionView = (ListCollectionView)CollectionViewSource.GetDefaultView(this.ProductList);
    IndexCurrentSortedProductListView(CollectionView collectionView);

    var droppedProductItem = (Product)collectionView.GetItemAt(this.rowIndex);
    SortIndex droppedProductItemSortIndex = this.ProductSortPriorityTable[droppedProductItem];
    var dropTargetProductItem = (Product)collectionView.GetItemAt(index);
    SortIndex dropTargetProductItemSortIndex = this.ProductSortPriorityTable[dropTargetProductItem];

    // Virtually move the dropped row item. 
    // Because the CollectionView does not support moving items, 
    // we have to rearrange the collection view by applying a new sort.
    // This index based sort uses the pre-sorted indices 
    // and not the original indices from the underlying ProductList collection.
    dropTargetProductItemSortIndex.InsertNext(droppedProductItemSortIndex);

    // Finally sort by new index based on the pre-sorted collection view
    collectionView.CustomSort = Comparer<Product>.Create((product1, product2) =>
    {
      int rowItemPriority1 = this.ProductSortPriorityTable[product1].Index;
      int rowItemPriority2 = this.ProductSortPriorityTable[product2].Index;
      return rowItemPriority1.CompareTo(rowItemPriority2);
    });
  }

  private void IndexCurrentSortedProductListView(CollectionView collectionView)
  {
    if (this.IsSortIndexingPending)
    {
      for (int rowIndex = 0; rowIndex < this.ProductList.Count; rowIndex++)
      {
        SortIndex sortIndex;
        if (rowIndex > 0)
        {
          int previousRowIndex = rowIndex - 1;
          var previousRowItem = (Product)collectionView.GetItemAt(previousRowIndex);
          SortIndex previousSortIndex = this.ProductSortPriorityTable[previousRowItem];
          sortIndex = new SortIndex(rowIndex, previousSortIndex, null);
          previousSortIndex.SetNext(sortIndex);
        }
        else
        {
          sortIndex = new SortIndex(rowIndex);
        }

        var rowItem = (Product)collectionView.GetItemAt(rowIndex);
        this.ProductSortPriorityTable.Add(rowItem, sortIndex);
      }

      this.IsSortIndexingPending = false;
    }
  }

  void productsDataGrid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
  {
    /*** Nothing has changed here ***/
  }

  private bool GetMouseTargetRow(Visual theTarget, GetPosition position)
  {   
    /*** Nothing has changed here ***/
  }

  private DataGridRow GetRowItem(int index)
  {
    /*** Nothing has changed here ***/
  }

  private int GetCurrentRowIndex(GetPosition pos)
  {    
    /*** Nothing has changed here ***/
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.