为什么组合框在对ItemsSource排序时会丢失其SelectedItem



考虑这个简单的例子:

主窗口.xaml

<Window x:Class="WPF_Sandbox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
x:Name="ThisControl">
<StackPanel>
<ComboBox ItemsSource="{Binding Collection, ElementName=ThisControl}" SelectedItem="a" />
<Button x:Name="SortButton">Sort</Button>
</StackPanel>
</Window>

主窗口.xaml.cs

using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace WPF_Sandbox
{
public partial class MainWindow
{
public ObservableCollection<string> Collection { get; } = new ObservableCollection<string>(new [] { "b", "a", "c" });
public MainWindow()
{
InitializeComponent();
SortButton.Click += (s, e) => Sort(Collection);
}
public static void Sort<T>(ObservableCollection<T> collection)
{
var sortableList = new List<T>(collection);
sortableList.Sort();
for (var i = 0; i < sortableList.Count; i++)
collection.Move(collection.IndexOf(sortableList[i]), i);
}
}
}

启动程序时,选择a。按下Sort时,选择不会改变,但列表会进行排序(仍如预期)
如果您a)再次按Sort或b)在排序前选择bc,则ComboBox将失去选择,SelectedItem将变为null

我把问题归结为ObservableCollection.Move方法。似乎每当你调用Move(i, i)(所以你实际上没有移动任何东西),i就是SelectedItem时,选择就会进入地狱。

我不是在寻找解决方案。显而易见的解决方法是根本不对ObservableCollection进行排序,并使用CollectionViewSource,或者调整Sort方法,使其仅在两个索引实际不同时调用Move

我的问题是,为什么会发生这种事?Move方法的文档中没有指示您不能两次传递相同的参数。此外,在CollectionChanged事件或CollectionChangedEventArgs类的文档中也没有提示为什么这不起作用。这是WPF中的一个错误吗?

我认为这是ItemControl's事件处理实现中的一个错误。看看这里:

case NotifyCollectionChangedAction.Move:
// items between New and Old have moved.  The direction and
// exact endpoints depends on whether New comes before Old.
int left, right, delta;
if (e.OldStartingIndex < e.NewStartingIndex)
{
left = e.OldStartingIndex + 1;
right = e.NewStartingIndex;
delta = -1;
}
else
{
left = e.NewStartingIndex;
right = e.OldStartingIndex - 1;
delta = 1;
}
foreach (ItemInfo info in list)
{
int index = info.Index;
if (index == e.OldStartingIndex)
{
info.Index = e.NewStartingIndex;
}
else if (left <= index && index <= right)
{
info.Index = index + delta;
}
}
break;

if语句似乎不期望e.OldStartingIndexe.NewStartingIndex具有相同的值,这导致delta1,从而在foreach循环内部引起一些意外的索引操作。我很惊讶它"只是"取消了选择,并没有完全破坏整个收藏。

最新更新