0

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:

enter image description here

seems that the AsyncObservableCollection cannot locate the IComparable implementation, maybe this should be implemented in CheckedListItem<T> instead of Country?

pivutali
  • 161
  • 1
  • 12
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/161895/discussion-between-pivutali-and-mjwills). – pivutali Dec 24 '17 at 11:58
  • `CheckedListItem` may need to implement `IComparable` also.. – mjwills Dec 24 '17 at 19:42
  • 1
    For the record, I don't view this as a duplicate of the post voted for by others, nor did I cast my vote as duplicate. I *did* cast my vote as the question is missing a good [mcve] that reliably reproduces the problem. There is both way too much detail in the above, and not enough, because even though you've included a lot of useless information (I assure you, you can reproduce the problem without your special `AsyncObservableCollection`, and likely without much of the rest), you have not included something that can be copy/pasted into a blank project, compiled, and run. – Peter Duniho Dec 24 '17 at 20:23
  • Please read, in addition to [mcve], the page at [ask], including all of the articles linked at the bottom of that page. Those resources will give you critical information to help you present your question in a clear, answerable way. Take it to heart, be willing to be self-critical and gain insight into how this post and previous ones fail to meet the bar required on Stack Overflow, and you will get the help you need. – Peter Duniho Dec 24 '17 at 20:24

0 Answers0