I have run over a small performance problem while I was trying to add some Items of an ObservableCollection to a ListView.
Here's the ObservableCollection:
private ObservableCollection<object> children = new ObservableCollection<object>();
public ObservableCollection<object> Children
{
get
{
return children;
}
}
I'm also using a ContentProperty:
[ContentProperty(Name="Children")]
Here's the CollectionChanged Event I need to start adding the List:
void children_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
InitGraphics();
}
In InitGraphics(), I will create my List and draw all elements needed for it. There's also a function called SynchronizeList(), which gets called right at the beginning of InitGraphics().
SynchronizeList() looks like this:
private void SynchronizeList()
{
foreach (var item in children)
{
//Casting ListViewItem as ValueBoxControl
ValueBoxControl control = (ValueBoxControl)item;
//Standards and needed attributes
control.ValueTextBox.IsHitTestVisible = false;
control.ClipColor = ClipColor;
control.Foreground = this.Foreground;
//Add the Item to the List
List.Items.Add(item);
}
}
Now, as you can see, my ListView is called List and Items are Usercontrols called ValueBoxControl. The standards and attributes have to be the same for every item and they should be unchangeable.
I need the SynchronizeList() function in a few other functions (for example: When the List gets toggled twice, I delete all Items and add them again with SynchronizeList(), etc.).
The problem occurs when I add about 50+ Items, because it obviously has to run through the foreach loop 50 times and add each item. While debugging, I have noticed that the children Collection doesn't have all 50 items from the start. It first has 1 item, then 2, then 3, etc. Which means that the SynchronizeList() gets called 50 times, while first adding 1 item, then 2, then 3, etc.
I have tried many things (like working with e.NewItems in the CollectionChangedEvent), but I currently see no possible solution.
Here's a complete example out of my fragments which shows my problem:
//ContentProperty to receive Items from XAML with the help of an ObservableCollection
[ContentProperty(Name="Children")]
public sealed partial class ListViewControl : ValueBoxControl
{
//Create a new ListView
ListView List = new ListView();
//Initialization
public ListViewControl()
{
//Events
children.CollectionChanged += children_CollectionChanged;
}
//ObservableCollection to receive Items from XAML
private ObservableCollection<object> children = new ObservableCollection<object>();
public ObservableCollection<object> Children
{
get
{
return children;
}
}
//Event which gets called whenever the List in XAML gets changed
void children_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
//Calls the function to draw the List
InitGraphics();
}
private void InitGraphics()
{
SynchronizeList();
if (List.Items.Counter >= 1)
{
//Draw the ListView with a custom design
}
}
private void SynchronizeList()
{
//Get each item in the ObservableCollection and add some attributes
foreach (var item in children)
{
//ListViewItem casted as ValueBoxControl
ValueBoxControl control = (ValueBoxControl)item;
//Standards & Limitations
control.ValueTextBox.IsHitTestVisible = false;
control.ClipColor = ClipColor;
control.Foreground = this.Foreground;
//Add Item to ListView
List.Items.Add(item);
}
}
}
The main problem I have here is that the CollectionChanged Event gets called 50 times for 50 items, each time receiving a new item, instead of receiving them all in one go.
Given the documentation for ItemCollection
, I suspect you want the ReplaceAll
method:
For implementations that track a "changed" event, the initial reset fires an event, but the items added don't fire discrete per-item events.
So something like:
private void SynchronizeList()
{
// Set properties in each item
foreach (var item in children)
{
//ListViewItem casted as ValueBoxControl
ValueBoxControl control = (ValueBoxControl)item;
//Standards & Limitations
control.ValueTextBox.IsHitTestVisible = false;
control.ClipColor = ClipColor;
control.Foreground = this.Foreground;
}
List.Items.ReplaceAll(children.ToArray());
}
Unfortunately it doesn't look like there's an AddRange
which is what you really want... it's at least worth giving that a try.
See more on this question at Stackoverflow