I am new to WPF, even though I have tried to read as much as I can, I am newbie in need of the wisdom the WPF sages here.
I have a viewlist with a custom view that display a tileview of linecharts. The user selects an item in a separate listbox, this sets the datasource to be used in the listview. My problem is that the UI freezes while the listivew is generating the view. here is how the layout looks:
((sorry, I am new at the site so I can not post images, yet, here is a link: ScreenShot))
The datasource consist of a class that contains some descriptive properties and a list of a list of datapoints (x,y). The tileview takes the list of a list of datapoints and generates one line chart for each listitem using the list of datapoints to draw the line.
Currently I have a datasource with a list holding 200 items and each item has about 200 datapoints (x,y).
When I select an item in the listbox of the left, I use the SelectionChanged event to set the ItemsSource property my listview with the new dataset. The problem is that the UI freezes until all the charts are generated.
I have been trying different approaches without success. I tried to set the datasource as an asynchronous one:
DataSeries="{Binding IsAsync=True, Path=Data}"
This only helps a tinny tinny bit by releasing the UI before the view is rendered. That means that after changing the datasource it will freeze the UI for a moment, then release it showing the view with all 200 empty charts and after a bit refreshing the view with the populated charts.
I also tried to put the binding inside a thread like this:
private void List_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//Find the selected itemsource
string runName = (string)ExperimentList.SelectedItem;
ExperimentalRun selectedRun = _experimentRuns.Find(item => item.Name == runName);
//bind datasource thru a new thread
Thread newThread = new Thread(lstProductsNewSource);
newThread.Start(selectedRun);
}
private void lstProductsNewSource(Object selectedRun)
{
this.Dispatcher.BeginInvoke(DispatcherPriority.Background, (ThreadStart)delegate()
{
lstProducts.ItemsSource = ((ExperimentalRun)selectedRun).RunGraphData;
});
}
lstProducts is the name of the listview that has the tileview of the charts. The RunGraphData porperty points at a list of a list of datapoints.
I was hoping that this will release the UI but that did not work. In this case there is no time consuming process by itself before the databind, the data is readily available in memory, what is time consuming is the creation of the viewitems and the databinding of them, not the rendering.
As additional details I will like to specify that the charts are generated by a usercontrol and here is how the xml of the tile view looks:
<local:TileView x:Key="ImageView">
<local:TileView.ItemTemplate>
<DataTemplate>
<StackPanel Width="150" VerticalAlignment="Top">
<local:graph DataSeries="{Binding IsAsync=True, Path=Data}"/>
<TextBlock TextWrapping="Wrap" HorizontalAlignment="Center" Text="{Binding >IsAsync=True, Path=ContainerName}" ></TextBlock>
</StackPanel>
</DataTemplate>
</local:TileView.ItemTemplate>
</local:TileView>
The graph usercontrol is a typical chart control that receives the dataset and binds it.
I seek advice on how to manage to keep my UI responsive while the listview is binding. Can you help me?
Edit:
I forgot to mention that I tried to use virtualization on the listview without any noticeable difference:
<ListView Name="lstProducts" View="{StaticResource GridView}" >VirtualizingStackPanel.IsVirtualizing="True" >
</ListView>
I was expecting that it will load the current window worth of items and that when I scroll down it will start loading new items, but even when I use the IsVirtualizing property set to true it seems to load all at once, same delay and even if I scroll down there is no extra delay giving me the impression that all the items are already present.
Edit 2:
I found another question in this site with an answer that provided a type of solution: link
Following the advice provided in the after mentioned thread, I modified my previous threading attempt from trying to encapsulate the entire databinding event into a single background thread to pumping the data one graph at a time, by taking advantage of the ObservableCollection auto-updating properties. Basically I create an empty datasource and assign it, then I add elements to the datasource one by one by creating a thread with background priority every time I add a point. This looks like this:
private void ExperimentList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//Find the selected itemsource
string runName = (string)ExperimentList.SelectedItem;
ExperimentalRun selectedRun = _experimentRuns.Find(item => item.Name == runName);
//create and empty datasource to be filled one item at a time
ExperimentalRun pumpRun = new ExperimentalRun(((ExperimentalRun)selectedRun).Name);
lstProducts.ItemsSource = pumpRun.RunGraphData;
//fill the emtpy source using a new thread per item added
foreach (var item in selectedRun.RunGraphData)
{
PumpExperimentalRunData pump = new PumpExperimentalRunData(pumpRun, item);
Thread newThread = new Thread(PumpAdd);
newThread.Start(pump);
}
}
private void PumpAdd(object PumpExperimentalRunData)
{
this.Dispatcher.BeginInvoke(DispatcherPriority.Background, (ThreadStart)delegate()
{
((PumpExperimentalRunData)PumpExperimentalRunData).PumpRun.RunGraphData.Add(((PumpExperimentalRunData)PumpExperimentalRunData).Item);
});
}
This approach seems to work, but I am worried that it creates too many threads (200) every time I change datasource … What I have read so far recommends to use just a few threads, and this doesn’t qualify as a few I think. I will like to do it differently, but I am out of ideas.
Can anyone see the error of my ways or point out a better approach?
You have to use VirtualizingStackPanel.