Adolfo Perez
Adolfo Perez

Reputation: 2874

Custom Sort of ObservableCollection<T> with base type

I have the following simplified classes:

public class BaseContainer
{
    public BaseContainer()
    {
        Children = new ObservableCollection<BaseContainer>();
    }
    public ObservableCollection<BaseContainer> Children { get; set; }
}

public class ItemA : BaseContainer
{
    public ItemA()
    {
        base.Children.Add(new ItemB() { ItemBName = "bb" });
        base.Children.Add(new ItemA() { ItemAName = "ab" });
        base.Children.Add(new ItemB() { ItemBName = "ba" });
        base.Children.Add(new ItemA() { ItemAName = "aa" });
    }
    public string ItemAName { get; set; }
}

public class ItemB : BaseContainer
{
    public string ItemBName { get; set; }
}

I'm trying to sort my ItemA.Children collection based on two conditions:

  1. All Item A's must come first in the collection
  2. Item A's should be sorted by ItemAName
  3. Item B's should be sorted by ItemBName

So after sorting I'd expect something like this:

Any ideas on how to accomplish this?

I was able to sort by class type name:

        List<BaseContainer> temp = base.Children.ToList();
        temp.Sort((x, y) => string.Compare(x.GetType().Name, y.GetType().Name));
        base.Children.Clear();
        base.Children.AddRange(temp);

But Names are not sorted...

Thanks!

Upvotes: 1

Views: 900

Answers (3)

Olivier Jacot-Descombes
Olivier Jacot-Descombes

Reputation: 112612

You initialize the items by specifying ItemName, but neither BaseContainer nor ItemA nor ItemB define such a property.

Declare ItemName in the base classBaseContainer`. The item classes automatically inherit this property and there is no need to redefine such a field there.

public class BaseContainer
{
    public string ItemName { get; set; }

    ...
}

public class ItemA : BaseContainer
{
    // Inherits ItemName 
}

public class ItemB : BaseContainer
{
    // Inherits ItemName 
}

Now sort the list like this:

var result = base.Children
    .OrderBy(b => b.GetType().Name)
    .ThenBy(b => b.ItemName);

And there is no need to store it to a temporary list before sorting. The Result is an IEnumerable<BaseContainer>. You can append a .ToList() if you want to store the result as list.

var result = base.Children
    .OrderBy(b => b.GetType().Name)
    .ThenBy(b => b.ItemName)
    .ToList();

UPDATE

According to your comment you don't have a common ItemName property. Why? You really should have one. Refactor you classes. If for some reason you don't want an ItemName in BaseContainer, either create an abstract base class Item or let the items implement a common interface.

public abstract class Item : BaseContainer
{
    public string ItemName { get; set; }
}

public class ItemA : Item
{
}

public class ItemB : Item
{
}

Or

public interface IItem
{
    string ItemName { get; set; }
}

public class ItemA : BaseContainer, IItem
{
    public string ItemName { get; set; }
}

public class ItemB : BaseContainer, IItem
{
    public string ItemName { get; set; }
}

Now you can sort like this

var result = base.Children
    .OfType<Item> // or .OfType<IItem>
    .OrderBy(i => i.GetType().Name)
    .ThenBy(i => i.ItemName);

and the result will be an IEnumerable<Item> or IEnumerable<IItem>.


Yeat another option is to override the ToString method that every object inherits from Syste.Object.

public class ItemA : BaseContainer
{
    public string ItemAName { get; set; }

    public override string ToString()
    {
        return "ItemA: " + ItemAName;
    }
}

This simplifies sorting as both conditions (sorting by type and sorting by name) can be done in one step. This string will also be displayed in listboxes, comboboxes etc.

var result = base.Children
    .OrderBy(b => b.ToString());

If this is not possible, create and implement an interface for this purpose:

public interface ISortable
{
    string SortString { get; }
}

public class ItemA : BaseContainer, ISortable
{
    public string ItemAName { get; set; }

    public string SortString { get { return "ItemA: " + ItemAName; } }
}

And sort with

var result = base.Children
    .OfType<ISortable> 
    .OrderBy(b => b.SortString);

Upvotes: 1

L.B
L.B

Reputation: 116168

var result = temp.OrderBy(x => x.GetType().Name).ThenBy(x => GetPropValue(x));

string GetPropValue(object o)
{
    Type t = o.GetType();
    var p = t.GetProperty("ItemAName");
    if (p != null)
    {
        return (string)p.GetValue(o, null);
    }
    p = t.GetProperty("ItemBName");
    return (string)p.GetValue(o, null);
}

Upvotes: 2

user27414
user27414

Reputation:

This will get all the A's, order them by AName, then concat with all the B's ordered by B name.

var result = Children.OfType<ItemA>().OrderBy(a => a.ItemAName).Cast<Base>()
    .Concat(Children.OfType<ItemB>().OrderBy(b => b.ItemBName));

I'm not sure this is the design I would go with... you might consider adding something to the base class to facilitate this kind of sorting.

Upvotes: 1

Related Questions