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