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?
