Search code examples
wpfsortingcollectionviewsource

CollectionViewSource sorting with default and backup sources


I am working on a scheduling program in which items have a scheduled date, but the user can elect to override this for a date they choose. To implement this, my Item object uses two properties: ScheduledDate (DateTime) and ActualDate (DateTime?). Therefore, if the ActualDate property is null, the user has not overridden the schedule for this item.

In one of my views, I need to display these items in a ListBox, sorted by the actual date. The trouble I am having is how to implement a CollectionViewSource with these two properties.

I know it's not correct, but I need something like this:

<CollectionViewSource x:Key="TransactionsViewSource"
                      Source="{Binding ElementName=ThisControl, 
                                       Path=Items}">
    <CollectionViewSource.SortDescriptions>
        <cm:SortDescription PropertyName="ActualDate ?? ScheduledDate"/>
    </CollectionViewSource.SortDescriptions>
</CollectionViewSource>

(ThisControl is the name of the UserControl that hosts the ListBox.)

If I add a second SortDescriptor (like below), I get a list sorted by ActualDate, then by Scheduled Date, which groups all of the overridden items together. This is not the desired behavior.

<CollectionViewSource x:Key="TransactionsViewSource"
                      Source="{Binding ElementName=ThisControl, 
                                       Path=Items}">
    <CollectionViewSource.SortDescriptions>
        <cm:SortDescription PropertyName="ActualDate"/>
        <cm:SortDescription PropertyName="ScheduledDate"/>
    </CollectionViewSource.SortDescriptions>
</CollectionViewSource>

Thanks.


Solution

  • I ended up creating a new method in my UserControl class that used LINQ to keep the underlying ObservableCollection sorted. I then called this method whenever an item was edited (actual date overridden) or if a new item was added. Finally, I removed the CollectionViewSource from the XAML and bound the ListBox to the Items property (which I already had as a dependency property). The result looks like this:

    XAML:

    <ListBox ItemsSource="{Binding ElementName=ThisControl,
                                   Path=Items}"/>
    

    C#:

    public static readonly DependencyProperty ItemsProperty =
        DependencyProperty.Register("Items",
                                    typeof(ObservableCollection<MyItem>),
                                    typeof(MyControl),
                                    new UIPropertyMetadata(null));
    
    public ObservableCollection<MyItem> Items
    {
        get { return (ObservableCollection<MyItem>) GetValue(ItemsProperty); }
        set { SetValue(ItemsProperty, value); }
    }
    
    private void SortItems()
    {
        Items = new ObservableCollection<MyItem>(Items.OrderBy(i => i.ActualDate ??
                                                                    i.ScheduledDate));
    }
    

    Then I just use SortItems() anywhere that the items in the collection or the collection itself changes.

    It works perfectly, and I didn't have to create and manage a new property. I can live with the little bit of overhead that LINQ creates.