Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expand Wpf Treeview to support Sorting

Hi I created this small example, and I would like to expand it to support Sorting.

public class Country
{
    public string Name { get; set; }
    public int SortOrder { get; set; }
}

My xaml:

<TreeView Name="CountryTreeView" ItemsSource="{Binding}">
  <TreeView.ItemTemplate>
    <DataTemplate>
      <TextBlock Text="{Binding Path=Name}"/>
    </DataTemplate>
  </TreeView.ItemTemplate>
</TreeView>

And the code-behind:

readonly ObservableCollection<Country> Countries;

public MainWindow()
{
   InitializeComponent();

   Countries = new ObservableCollection<Country>
   {
       new Country{Name = "Denmark", SortOrder = 0},
       new Country{Name = "Norway", SortOrder = 1},
       new Country{Name = "Sweden", SortOrder = 2},
       new Country{Name = "Iceland", SortOrder = 3},
       new Country{Name = "Greenland", SortOrder = 4},
   };

   CountryTreeView.DataContext = Countries;
}

I would like to make it possible for the Treeview to Sort the Countries depending on the SortOrder value.

It needs to be able to do this on the fly. So if I for example change SortOrder = 10 for Name = "Denmark" the TreeView would automatically reflect this.

like image 794
gulbaek Avatar asked Apr 11 '26 02:04

gulbaek


2 Answers

I don't think there is a default sort for TreeViews. You can either sort the items prior to entering them into the collection, or overwrite the ObservableCollection to include a Sort method.

I overwrite it in one of my projects:

public class SortableObservableCollection<T> : ObservableCollection<T>
{
    // Constructors
    public SortableObservableCollection() : base(){}
    public SortableObservableCollection(List<T> l) : base(l){}
    public SortableObservableCollection(IEnumerable<T> l) :base (l) {}

    #region Sorting

    /// <summary>
    /// Sorts the items of the collection in ascending order according to a key.
    /// </summary>
    /// <typeparam name="TKey">The type of the key returned by <paramref name="keySelector"/>.</typeparam>
    /// <param name="keySelector">A function to extract a key from an item.</param>
    public void Sort<TKey>(Func<T, TKey> keySelector)
    {
        InternalSort(Items.OrderBy(keySelector));
    }

    /// <summary>
    /// Sorts the items of the collection in descending order according to a key.
    /// </summary>
    /// <typeparam name="TKey">The type of the key returned by <paramref name="keySelector"/>.</typeparam>
    /// <param name="keySelector">A function to extract a key from an item.</param>
    public void SortDescending<TKey>(Func<T, TKey> keySelector)
    {
        InternalSort(Items.OrderByDescending(keySelector));
    }

    /// <summary>
    /// Sorts the items of the collection in ascending order according to a key.
    /// </summary>
    /// <typeparam name="TKey">The type of the key returned by <paramref name="keySelector"/>.</typeparam>
    /// <param name="keySelector">A function to extract a key from an item.</param>
    /// <param name="comparer">An <see cref="IComparer{T}"/> to compare keys.</param>
    public void Sort<TKey>(Func<T, TKey> keySelector, IComparer<TKey> comparer)
    {
        InternalSort(Items.OrderBy(keySelector, comparer));
    }

    /// <summary>
    /// Moves the items of the collection so that their orders are the same as those of the items provided.
    /// </summary>
    /// <param name="sortedItems">An <see cref="IEnumerable{T}"/> to provide item orders.</param>
    private void InternalSort(IEnumerable<T> sortedItems)
    {
        var sortedItemsList = sortedItems.ToList();

        foreach (var item in sortedItemsList)
        {
            Move(IndexOf(item), sortedItemsList.IndexOf(item));
        }
    }

    #endregion // Sorting
}

You would then sort it by calling something like

Countries.Sort(country => country.SortOrder);

I like overwriting it because it let me add additional functionality to it as well such as IndexOf or AddRange/RemoveRange

like image 92
Rachel Avatar answered Apr 13 '26 15:04

Rachel


You can also use the CollectionViewSource.GetDefaultView with ListCollectionView to do the sorting for you, tho you do have to set it for all the child collections as well. I use a linq query to flatten my list to find all the children and set the sorting.

foreach (var structure in Model.Structures.Flatten(t => t.SubItems))
{
    var view = CollectionViewSource
        .GetDefaultView(structure.SubItems) as ListCollectionView;

    if (view != null)
    {
        view.CustomSort = (x,y) => string.Compare( x, y );
    }
}

elsewhere

public static IEnumerable<T> Flatten<T>(
    this IEnumerable<T> list, Func<T, IEnumerable<T>> subitems)
{
    foreach (T child in list)
    {
        yield return child;

        foreach (T other in Flatten(subitems(child), subitems))
        {
            yield return other;
        }
    }
}
like image 30
Joel Lucsy Avatar answered Apr 13 '26 14:04

Joel Lucsy