Reputation: 4430
Consider this simple example:
MainWindow.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>
MainWindow.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);
}
}
}
When starting the program, a
is selected. On pressing Sort
the selection doesn't change but the list gets sorted (still as expected).
If you a) press Sort
again or b) select b
or c
before sorting, the ComboBox
loses its selection and SelectedItem
becomes null
.
I pinpointed the issue down to the ObservableCollection.Move
method. It appears that whenever you call Move(i, i)
(so you do not actually move anything) with i
being the SelectedItem
, the selection goes to hell.
I'm not looking for a solution. Obvious workaround would be to not sort the ObservableCollection
at all and use a CollectionViewSource
or adjusting the Sort
method to only call Move
when the two indices actually differ.
The question I have is, why is this happening in the first place? There is no indication in documentation for the Move
method that you must not pass the same parameter twice. Also there is no hint why this would not work in the documentation for the CollectionChanged
event or the CollectionChangedEventArgs
class. Is this a bug in WPF?
Upvotes: 2
Views: 621
Reputation: 4430
I believe this to be a bug in the implementation of the ItemControl's
event handling. Take a look here:
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;
The if
statement does not seem to expect e.OldStartingIndex
and e.NewStartingIndex
to be of the same value which results in delta
being 1
which then causes some unintended index manipulation inside of the foreach
loop. I'm surprised it "only" deselects the item and not completely ruins the whole collection.
Upvotes: 2