I have some speed issues with displaying a rather long list in a ListBox in WPF 4.5: My ListBox contains about 5000 items and the items can have different heights. Each item shows information that is retrieved through a query that is executed as soon as the item is displayed. This delayed query execution is necessary as it would take too much time to do this for all content at once. I've done the delaye queries by binding an ItemsControl to a NotifyTaskCompletion object that is created when the Property in the item is first accessed.
For example:
public class ItemViewModel
{
public NotifyTaskCompletion<ObservableCollection<Content>> QueryResult
{
get
{
if(_queryTask == null)
{
_queryTask = new NotifyTaskCompletion<ObservableCollection<Content>>
(Task<ObservableCollection<Content>>.Run(queryFunction));
}
return _queryTask;
}
}
}
ItemTemplate:
+--------------------------------------------------+
| TextBlock with header |
| |
| ItemsControl Source={Binding QueryResult.Result} |
+--------------------------------------------------+
So each time an item is displayed, it starts a thread that performs the query and the content is displayed once the query is done. The information that is retrieved through the query can be quite a lot, which means that the item in the ListBox can be bigger than the ListBox itself. This is why I need to set VirtualizingPanel.ScrollUnit="Pixel" in the ListBox to make sure that the user can see the whole item.
+-------------------------------------------+
| ListBox, too small for big Item 1 |
| |
| +------------------------------+ |
| | Header 1 | |
| | | |
| | Lot's of information ....... | |
| | Lot's of information ....... | |
| | Lot's of information ....... | |
| | Lot's of information ....... | |
+-| Lot's of information ....... |----------+
| Lot's of information ....... |
| Lot's of information ....... |
| Lot's of information ....... |
+------------------------------+
+------------------------------+
| Header 2 |
| |
| Not so much information..... |
+------------------------------+
However, this slows down scrolling considerably. If I drag the thumb in the scrollbar to the middle of the list there is a freeze of a few seconds after which some items get displayed without the queried information. Then, some of items display the queried information. The ListBox then seems to jump to another position, which again leads to another freeze and so on. It looks like the ListBox renders all of the items that are above the current scrolling position, which takes a very long time because of all the queries and the complex rendering of the queried data.
If I set VirtualizingPanel.ScrollUnit="Unit", I don't have any speed issues. The ListBox only displays the items that are scrolled to and thus only the information that is queried is shown. Unfortunately, I need to use the "pixel" setting because my items are sometimes so big that I need pixel scrolling to make sure that the user can see all of it.
I'm aware of the fact that it'd be better to have a queue for the queries to avoid starting hundreds of threads at once, but I don't think this would change anything about the fundamental problem that the ListBox renders items that I don't wont to be displayed.
I'm not really sure how to tackle this problem. Can anyone help me out?
Have DeferredScrolling and scroll in the ListBoxItem
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding Path=DeferedItems}"
ScrollViewer.IsDeferredScrollingEnabled="True"
ScrollViewer.VerticalScrollBarVisibility="Visible"
VirtualizingStackPanel.VirtualizationMode="Standard">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=ID}"/>
<ListBox ItemsSource="{Binding Path=DefStrings}" Margin="10,0,0,0"
MaxHeight="300"
ScrollViewer.VerticalScrollBarVisibility="Visible"/>
<TextBlock Text="{Binding Path=DT}" Margin="10,0,0,0"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
public partial class MainWindow : Window
{
private List<DeferedItem> deferedItems = new List<DeferedItem>();
public MainWindow()
{
this.DataContext = this;
for (Int32 i = 0; i < 100000; i++) deferedItems.Add(new DeferedItem(i));
InitializeComponent();
}
public List<DeferedItem> DeferedItems { get { return deferedItems; } }
}
public class DeferedItem
{
private Int32 id;
private DateTime? dt = null;
private List<String> defStrings = new List<string>();
public DateTime DT
{
get
{
if (dt == null)
{
System.Threading.Thread.Sleep(1000);
dt = DateTime.Now;
}
return (DateTime)dt;
}
}
public List<String> DefStrings
{
get
{
if (defStrings.Count == 0)
{
for (Int32 i = id; i < id + 1000; i++) defStrings.Add(i.ToString() + " " + DateTime.Now.ToLongTimeString());
}
return defStrings;
}
}
public Int32 ID { get { return id; } }
public DeferedItem(Int32 ID) { id = ID; }
}