I took an example for a TreeView where you filter items with a Boolean "IsRemovedByFilter". The example had the items re-created with a copy constructor.
I did the same on a tree list view, and replaced the copy constructor with ListCollectionView.EditItem and CommitEdit.
The more times you filter the items, the slower it gets.
Here is the code:
MainWindow:
<Window x:Class="TreeListView_Filtering_Issue.MainWindow" xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation" Title="MainWindow" Height="350" Width="525"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition /> </Grid.RowDefinitions> <TextBox Text="{Binding FilterText, UpdateSourceTrigger=PropertyChanged}" /> <telerik:RadTreeListView ItemsSource="{Binding Items}" AutoGenerateColumns="False" Grid.Row="1"> <telerik:RadTreeListView.ChildTableDefinitions> <telerik:TreeListViewTableDefinition ItemsSource="{Binding FilteredChildren}" /> </telerik:RadTreeListView.ChildTableDefinitions> <telerik:RadTreeListView.Columns> <telerik:GridViewDataColumn DataMemberBinding="{Binding Name}" IsReadOnly="True"/> </telerik:RadTreeListView.Columns> </telerik:RadTreeListView> </Grid></Window>
The data context is being set by (just a quick and dirty way for this example):
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
The ViewModels are:
public class MainViewModel : ViewModelBase{ private ObservableCollection<Person> _persons = new ObservableCollection<Person>(); private ListCollectionView _items; private string _filterText = string.Empty; public MainViewModel() { for (int i = 0; i < 100; i++) { Person person = new Person(null) { Name = "Test" + i, }; _persons.Add(person); for (int j = 0; j < 20; j++) { Person child = new Person(person) { Name = "Child." + i + "." + j, }; person.Children.Add(child); } } _items = new ListCollectionView(_persons); _items.Filter = nonFilteredChidrenPredicate; } internal static bool nonFilteredChidrenPredicate(object obj) { if (obj is Person) { return !(obj as Person).IsRemovedByFilter; } return false; } public ListCollectionView Items { get { return _items; } } public string FilterText { get { return _filterText; } set { if (value == null) value = string.Empty; _filterText = value; FilterItems(); } } internal void FilterItems() { Queue<Person> queue = new Queue<Person>(); foreach (var item in _persons) { queue.Enqueue(item); } while (queue.Count > 0) { Person current = queue.Dequeue(); current.IsRemovedByFilter = false; if (current.Name.ToUpper().Contains(FilterText.ToUpper())) { current.IsRemovedByFilter = false; //if (!string.IsNullOrEmpty(FilterText)) //{ // current.IsExpanded = true; // if (selectedItem == null) // selectedItem = current; //} // show and expand parent if one child fits with filter setParentFilterStatus(current); } else { current.IsRemovedByFilter = true; } // filter tree leaves if (current.Children.Count > 0) { foreach (var item in current.Children) { queue.Enqueue(item); } } } foreach (var root in _persons) { Items.EditItem(root); Items.CommitEdit(); refreshFilterStatus(root, root.Children); } } private void refreshFilterStatus(Person parent, IEnumerable<Person> items) { foreach (var item in items) { parent.FilteredChildren.EditItem(item); parent.FilteredChildren.CommitEdit(); } foreach (var item in items) { refreshFilterStatus(item, item.Children); } } private void setParentFilterStatus(Person person) { Person currentPerson = person; while (currentPerson.Parent != null) { currentPerson.Parent.IsRemovedByFilter = false; //// if a filter is applied, expand parent's nodes //if (!string.IsNullOrEmpty(FilterText)) // currentPerson.Parent.IsExpanded = true; currentPerson = currentPerson.Parent; } }}public class Person : ViewModelBase{ public string Name { get; set; } public bool IsRemovedByFilter { get; set; } public Person Parent { get; private set; } public ObservableCollection<Person> Children { get; private set; } public ListCollectionView FilteredChildren { get; private set; } public Person(Person parent) { Children = new ObservableCollection<Person>(); FilteredChildren = new ListCollectionView(Children); }}
Run the example, and type the letter z. The filtering is fast. Delete the z, it now took a little more time.
Repeat this procedure few more times. You should notice that it becomes slower and slower each time (my guess would be event handlers leaking, but I didn't confirm it).
With dotTrace I can see that when the first z was typed, OnSourceViewColelctionChanged was called 5150 times.
The the z was deleted, OnSourceViewColelctionChanged was called 15150 times.
After repeating it 5 more times, OnSourceViewColelctionChanged was called 105150 times when the z was typed, and 115150 times when the z was deleted.
Currently, it is much faster (also due to virtualization) to just refresh the entire ListCollectionView