We have a WPF application that has a ListBox with VirtualizingStackPanel with caching. Not because it has many elements (usually less than 20, but maybe up to 100 or more in extreme cases), but because the elements take time to generate. Elements are actually UIElement objects. Thus, the application must dynamically generate UIElements.
The problem is that while virtualization seems to be working, the application is still responding slowly, and this is proof of a conceptual solution with minimal “noise”.
So, we realized that since the main problem is that we generate complex UIElement objects dynamically, we need to do this in parallel, that is, disable the stream. But we get an error that the code must be running in the STA stream:
The calling thread must be an STA because it requires many user interface components.
Does this mean that we cannot generate UIs (UIElement objects) in a thread other than the main thread of the WPF user interface?
Here's the corresponding code snippet from our proof of conceptual solution:
public class Person : ObservableBase { // ... UIElement _UI; public UIElement UI { get { if (_UI == null) { ParallelGenerateUI(); } return _UI; } } private void ParallelGenerateUI() { var scheduler = TaskScheduler.FromCurrentSynchronizationContext(); Task.Factory.StartNew(() => GenerateUI()) .ContinueWith(t => { _UI = t.Result; RaisePropertyChanged("UI"); }, scheduler); } private UIElement GenerateUI() { var tb = new TextBlock(); tb.Width = 800.0; tb.TextWrapping = TextWrapping.Wrap; var n = rnd.Next(10, 5000); for (int i = 0; i < n; i++) { tb.Inlines.Add(new Run("A line of text. ")); } return tb; } // ... }
and here is the corresponding XAML fragment:
<DataTemplate x:Key="PersonDataTemplate" DataType="{x:Type local:Person}"> <Grid> <Border Margin="4" BorderBrush="Black" BorderThickness="1" MinHeight="40" CornerRadius="3" Padding="3"> <Grid> <Grid.RowDefinitions> <RowDefinition /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition /> </Grid.ColumnDefinitions> <TextBlock Text="Name : " Grid.Row="0" FontWeight="Bold" HorizontalAlignment="Right" /> <TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding Name}" /> <TextBlock Text=" - Age : " Grid.Column="2" Grid.Row="0" FontWeight="Bold" HorizontalAlignment="Right" /> <TextBlock Grid.Column="3" Grid.Row="0" Text="{Binding Age}" /> <ContentControl Grid.Column="4" Grid.Row="0" Content="{Binding Path=UI}" /> </Grid> </Border> </Grid> </DataTemplate>
As you can see, we bind data to the user interface of properties of type UIElement.
<ListBox x:Name="listbox" ItemsSource="{Binding Persons}" Background="LightBlue" ItemTemplate="{StaticResource PersonDataTemplate}" ItemContainerStyle="{StaticResource ListBoxItemStyle}" VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.IsVirtualizingWhenGrouping="True" VirtualizingStackPanel.ScrollUnit="Pixel" VirtualizingStackPanel.CacheLength="10,10" VirtualizingStackPanel.CacheLengthUnit="Item" > <ListBox.GroupStyle> <GroupStyle HeaderTemplate="{StaticResource GroupHeaderTemplate}" /> </ListBox.GroupStyle> </ListBox>
In the final context, what our application does, a code presentation is created in which the list contains procedures that again contain a combination of structured content (for parameters and local variables, on the one hand, and operators and expressions on the other.)
In other words, our UIElement objects are too complex to create using only data binding.
Another thought we had was to use the “Async” settings in XAML, since it seems possible to create a “non-blocking interface”, but we were not able to implement this because we are getting the same error as above:
The calling thread must be an STA because it requires many user interface components.
Stacktrace:
System.InvalidOperationException was unhandled by user code HResult=-2146233079 Message=The calling thread must be STA, because many UI components require this. Source=PresentationCore StackTrace: at System.Windows.Input.InputManager..ctor() at System.Windows.Input.InputManager.GetCurrentInputManagerImpl() at System.Windows.Input.KeyboardNavigation..ctor() at System.Windows.FrameworkElement.FrameworkServices..ctor() at System.Windows.FrameworkElement.EnsureFrameworkServices() at System.Windows.FrameworkElement..ctor() at System.Windows.Controls.TextBlock..ctor() at WPF4._5_VirtualizingStackPanelNewFeatures.Person.GenerateUI() in c:\Users\Christian\Desktop\WPF4.5_VirtualizingStackPanelNewFeatures\WPF4.5_VirtualizingStackPanelNewFeatures\Person.cs:line 84 at WPF4._5_VirtualizingStackPanelNewFeatures.Person.<ParallelGenerateUI>b__2() in c:\Users\Christian\Desktop\WPF4.5_VirtualizingStackPanelNewFeatures\WPF4.5_VirtualizingStackPanelNewFeatures\Person.cs:line 68 at System.Threading.Tasks.Task`1.InnerInvoke() at System.Threading.Tasks.Task.Execute() InnerException:
Changes:
1) Added more XAML. 2) Added stacktrace.