First of all, sorry if the question is a bit long, but I need to explain all the details to provide a verifiable example.
Summary of the Problem
In my application I use an extension class of ObservableCollection
called AsyncObservableCollection
, this class allow me to add items to a collection across multiple threads.
All working well, but there is a problem that happen when I use the sorting on the CollectionViewSource
, infact seems that I need to implement on the object of the collection the IComparable
interface for avoid this exception:
Cannot compare two element in the array
This error happen only the second time that I add items to the collection, for the first valorization all working well.
Code Structure
First of all, I need to show you the AsyncObservableCollection
, this class have the following implementation:
public class AsyncObservableCollection<T> : ObservableCollection<T>
{
private readonly SynchronizationContext _synchronizationContext = SynchronizationContext.Current;
public AsyncObservableCollection()
{
}
public AsyncObservableCollection(IEnumerable<T> list)
: base(list)
{
}
private void ExecuteOnSyncContext(Action action)
{
if (SynchronizationContext.Current == _synchronizationContext)
{
action();
}
else
{
_synchronizationContext.Send(_ => action(), null);
}
}
protected override void InsertItem(int index, T item)
{
ExecuteOnSyncContext(() => base.InsertItem(index, item));
}
protected override void RemoveItem(int index)
{
ExecuteOnSyncContext(() => base.RemoveItem(index));
}
protected override void SetItem(int index, T item)
{
ExecuteOnSyncContext(() => base.SetItem(index, item));
}
protected override void MoveItem(int oldIndex, int newIndex)
{
ExecuteOnSyncContext(() => base.MoveItem(oldIndex, newIndex));
}
protected override void ClearItems()
{
ExecuteOnSyncContext(() => base.ClearItems());
}
public void RemoveAll(Predicate<T> predicate)
{
//Controllo se l'insieme viene modificato
CheckReentrancy();
List<T> itemsToRemove = Items.Where(x => predicate(x)).ToList();
itemsToRemove.ForEach(item => Items.Remove(item));
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
in this AsyncObservableCollection
I need to pass also a custom type called CheckedListItem
, this class allow me to add in my ComboBox
, for each Item
, a CheckBox
with the Item Name
. Like this for example.
The implementation of CheckedListItem
is this:
public class CheckedListItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isChecked;
private T item;
public CheckedListItem() { }
public CheckedListItem(T item, bool isChecked = false)
{
this.item = item;
this.isChecked = isChecked;
}
public T Item
{
get { return item; }
set
{
item = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item"));
}
}
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsChecked"));
}
}
}
in the ComboBox
I've as AsyncObservableCollection<CheckedListItem<T>>
a list of Country
, essentially the Country
class is like this:
public class Country
{
public string Name { get; set; }
public string ISO { get; set; }
}
each Country
have an ISO
, for more details about the ISO
(it's not important for this question, you could see here)
XAML and Collection valorization
In my code the user can load a specific Country
to the AsyncObservableCollection<CheckedListItem<Country>>
, this will appear inside a ComboBox
that is defined in this way:
<ComboBox ItemsSource="{Binding Source={StaticResource GroupedData}}"
ItemTemplate="{StaticResource CombinedTemplate}" />
the GroupedData
bound the AsyncObservableCollection<CheckedListItem<Country>>
sorting for ISO:
<CollectionViewSource Source="{Binding Countries}" x:Key="GroupedData">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Item.ISO" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<DataTemplate x:Key="NormalItemTemplate">
<CheckBox IsChecked="{Binding IsChecked}"
Content="{Binding Item.Name}"
Checked="Countries_Checked" Unchecked="Countries_Unchecked"/>
</DataTemplate>
<DataTemplate x:Key="CombinedTemplate">
<ContentPresenter x:Name="Presenter"
Content="{Binding}"
ContentTemplate="{StaticResource NormalItemTemplate}" />
</DataTemplate>
essentially when I add the data to the collection I do something similar to:
var ckli = new CheckedListItem<Country>();
ckli.Item = new Country
{
Name = "England",
ISO = "EN"
}
Countries.Add(ckli);
where Countries
is defined in that way:
private AsyncObservableCollection<CheckedListItem<Country>> _countries = new AsyncObservableCollection<CheckedListItem<Country>>();
public AsyncObservableCollection<CheckedListItem<Country>> Countries
{
get { return _countries; }
set
{
_countries = value;
OnPropertyChanged(); //<- INotifyPropertyChanged of ViewModel
}
}
The Problem
as I said, when I add a Country
for the first time to Countries
, all working well, but when I execute the same method for adding the Country
specified by the user, I get the exception above:
Cannot compare two element in the array
I tried to solve this by implementing IComparable
interface on Country
class. So I guess I should implement on the ISO
property, and I did something similar to:
public class Country : IComparer<Country>
{
public string Name { get; set; }
public string ISO { get; set; }
public int Compare(Country x, Country y)
{
if(x.ISO == y.ISO)
return 0;
if(x.ISO != y.ISO)
return -1;
return 1;
}
}
I don't know if this is a good implementation, I really don't use this interface and still I don't know if I need to implement IComparer
to the sorted property or for all properties. Anyway, the issue is not solved with my solution, so I hope that someone read this heavy and boring question, to give me an help.
Thanks.
UPDATE #1
Following the example suggested, I tried to implement the IComparable
in this way:
public class Country : IComparable<Country>
{
public string Name { get; set; }
public string ISO { get; set; }
public int CompareTo(object obj)
{
return CompareTo(obj as Country);
}
public int CompareTo(League other)
{
if(other == null)
{
return 1;
}
return this.ISO.CompareTo(other.ISO);
}
}
I get the same exception on this line:
seems that the AsyncObservableCollection
cannot locate the IComparable
implementation, maybe this should be implemented in CheckedListItem<T>
instead of Country
?