キャッシング付きの VirtualizingStackPanel を備えた ListBox を持つ WPF アプリケーションがあります。非常に多くの要素 (通常は 20 未満ですが、極端な場合には最大 100 以上) があるからではなく、要素の生成に時間がかかるためです。要素は実際には UIElement オブジェクトです。したがって、アプリケーションは UIElements を動的に生成する必要があります。
問題は、仮想化が機能しているように見えても、アプリケーションの応答が遅いことです。これは、「ノイズ」を最小限に抑えた概念実証ソリューションです。
そこで、主な問題は複雑な UIElement オブジェクトを動的に生成することであるため、それを並行して、つまりオフスレッドで行う必要があると考えました。しかし、コードを STA スレッドで実行する必要があるというエラーが表示されます。
多くの UI コンポーネントがこれを必要とするため、呼び出しスレッドは STA でなければなりません。
これは、WPF メイン UI スレッド以外のスレッドで UI (UIElement オブジェクト) を生成できないということですか?
以下は、概念実証ソリューションの関連コード フラグメントです。
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;
}
// ...
}
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>
ご覧のとおり、UIElement 型のプロパティ UI にデータバインドします。
<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>
最後に、私たちのアプリケーションが行うことは、構造化されたコンテンツ (一方ではパラメーターとローカル変数、もう一方ではステートメントと式) の混合を含むプロシージャーのリストであるコード ビューを作成することです。
つまり、UIElement オブジェクトは複雑すぎて、データバインディングだけで作成することはできません。
「ノンブロッキング UI」を作成できるように見えるため、XAML で「Async」設定を使用することも考えましたが、上記と同じエラーが発生するため、これを実装できませんでした。
多くの UI コンポーネントがこれを必要とするため、呼び出しスレッドは STA でなければなりません。
スタックトレース:
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:
編集:
1) XAML をさらに追加しました。2) スタックトレースを追加。