Preserving OrderBy for New Items?

Jun 4, 2008 at 8:47 AM
I have a query which unions three collections and orders each of them individually, like this:

_items = _folders.AsBindable().Cast<

IFolderItem>().OrderBy(i => i.Name)
    .Union(_trades.AsBindable().Cast<
IFolderItem>().OrderBy(i => i.Name))
    .Union(_titles.AsBindable().Cast<
IFolderItem>().OrderBy(i => i.Name));

I've got this bound to a TreeView in WPF, using a HierarchicalDataTemplate, and it works really well. I see all the folders first, then trades, and then titles.

I then insert a new title like this:

var
title = new Title();
title.Name =
"Lmnop";
folder.Titles.Add(title);

(Where "folder" is the currently-selected folder, and "folder.Titles" is just a public property surfacing the _titles collection above.)

This new title always seems to appear at the bottom of the tree, rather than nestled in between titles starting with "L" and those starting with "M" like I'd expect.

I've tried the same thing on a simpler version of the query that does away with the two unions (just queries _titles) and it works just fine, so perhaps it has something to do with the unions.

Cheers,
Matt

 

Coordinator
Jun 4, 2008 at 3:22 PM
Edited Jun 4, 2008 at 3:25 PM
Hi Matt,

Because of the nature of inserts in a union (the inserted item indexes get all muddled up, especially if the collections are async), the OrderBy is working, but the Union will ignore the indexes. I think the API should make this clearer (i.e., Union accepts regular queries but not OrderBy queries?).

You could rewrite your query as:

_items = _folders.AsBindable().Cast<IFolderItem>()
    .Union(_trades.AsBindable().Cast<IFolderItem>())
    .Union(_titles.AsBindable().Cast<IFolderItem>())
    .OrderBy(i => i, new FolderItemComparer())
    .ThenBy(i => i.Title);
   
Where a FolderItemComparer implements IComparer<IFolderItem> and checks the type of each folder item. You could probably make that comparer generic and wrap it in something like:

      .OrderByTypes<Folder, Trade, Title>()
      .ThenBy(i => i.Title);

I apologise for the Union not working the way you expect it to. I'll have more of a think about what I can do there. Maybe I can translate the indexes and it will work for 80% of the cases.

Paul
Coordinator
Jun 4, 2008 at 3:50 PM
I just tried this out of interest and it appears to work. Here's a basic version of the comparer with no error checking:

internal class TypeComparer<TItem> : IComparer<TItem>
{
    private List<Type> _types;

    public TypeComparer(params Type[] types)
    {
        _types = new List<Type>(types);
    }

    public int Compare(TItem x, TItem y)
    {
        return _types.IndexOf(x.GetType()).CompareTo(_types.IndexOf(y.GetType()));
    }
}

And the query:

_items = _folders.AsBindable().Cast<IFolderItem>()
    .Union(_trades.AsBindable().Cast<IFolderItem>())
    .Union(_titles.AsBindable().Cast<IFolderItem>())
    .OrderBy(i =>i, new TypeComparer<IFolderItem>(typeof(Folder), typeof(Trade), typeof(Title)))
    .ThenBy(i => i.Title);

However I feel this is more of a workaround than a good solution.
Coordinator
Jun 4, 2008 at 4:01 PM
Edited Jun 4, 2008 at 4:05 PM
Funnily enough, and it reads nicer too, you could do:

_items = _folders.AsBindable().Cast<IFolderItem>()
    .Union(_trades.AsBindable().Cast<IFolderItem>())
    .Union(_titles.AsBindable().Cast<IFolderItem>())
    .OrderByDescending(i =>i is Folder)
    .ThenByDescending(i =>i is Trade)
    .ThenByDescending(i =>i is Title)
    .ThenBy(i => i.Title);
Jun 4, 2008 at 8:53 PM
Funnily enough, I already had a "SortOrder" property that I could use to achieve this without a custom comparer! I just figured I wouldn't have to use it so I didn't try. I'll put it back that way tonight and move the OrderBy to the end of the query and see how I go.

Thanks Paul!
Jun 6, 2008 at 12:08 AM
For those interested, I ended up using Paul's suggestion of ordering by a series of "is type" calls and then by name. It's a lot more readable than a "SortOrder" property, and it works a treat.