行的重新排列最初按预期工作,但如果列已排序,拖放行将不起作用。
此外,如果我取消数据网格中的列排序,拖放功能可以正常工作。
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;
}
我希望拖放比列排序具有更高的优先级,以便两者可以同时工作。
您的排序不起作用,因为您试图在
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 ***/
}
}