We have a WPF application that has a ListBox with a VirtualizingStackPanel with caching. Not because it has massively many elements (typically less than 20 but perhaps up to 100 or more in extreme cases) but because elements take time to generate. The elements are in fact UIElement objects. So the application dynamically needs to generate UIElements.
The problem is that even though the virtualization appears to work, the application is still slow to become responsive, and this is in a proof of concept solution with minimal "noise".
So we figured that since the main problem is that we generate complex UIElement objects dynamically, we need to do that in parallel, i.e. off-thread. But we get an error that the code needs to be run on a STA thread:
The calling thread must be STA, because many UI components require this.
Does this mean that we cannot generate UI (UIElement objects) on thread other than the WPF main UI thread?
Here's a relevant code fragment from our proof of concept 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 a relevant piece of XAML:
<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 />
<!--<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 databind to a property UI 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 closing context, what our application does is create a code view where the list is of procedures which again contain a mix of structured content (for parameters and local variables on one hand and statements and expressions on the other.)
In other words our UIElement objects are too complex to create via databinding alone.
Another thought we had was to use "Async" settings in the XAML as it appears possible to create "non-blocking UI" but we have not been able to implement this because we get the same error as above:
The calling thread must be STA, because many UI components require this.
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:UsersChristianDesktopWPF4.5_VirtualizingStackPanelNewFeaturesWPF4.5_VirtualizingStackPanelNewFeaturesPerson.cs:line 84
at WPF4._5_VirtualizingStackPanelNewFeatures.Person.<ParallelGenerateUI>b__2() in c:UsersChristianDesktopWPF4.5_VirtualizingStackPanelNewFeaturesWPF4.5_VirtualizingStackPanelNewFeaturesPerson.cs:line 68
at System.Threading.Tasks.Task`1.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
InnerException:
Edits:
1) Added more XAML.
2) Added stacktrace.
See Question&Answers more detail:
os