3

キャッシング付きの 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) スタックトレースを追加。

4

4 に答える 4

21

私は通常のC#環境で同じ問題に苦しんでいます。私もいろいろ試しました。コントロールのサイズを計算して、親のサイズを事前に調整しますか? 私は残念ながらこれをやっています。

子を動的にネストするコントロールを作成することもできます。これにより、一種の UIElement アダプターを作成できます。アダプターは開始時に作成され、UIElements を作成するためのすべての情報が含まれています。アダプターは、要求された子を必要に応じて STA スレッドにジャスト イン タイムで作成できます。上下にスクロールするときは、スクロールしている方向に事前に子を作成できます。このようにして、たとえば 5 ~ 10 個の UI 要素から始めて、さらに上にスクロールして計算できます。

フレームワーク内にこのようなものを提供するテクノロジーがあれば、これはあまり良くないことはわかっていますが、まだ見つけていません。

また、これら 2 つのことを確認することもできます。1つは、コントロールの応答性に非常に役立ちました。.NET Framework 4.5 が必要なため、もう一方はまだ開いています。

  1. SuspendLayoutResumeLayoutあまりうまく動作しません。これを試すことができます:

    /// <summary>
    /// An application sends the WM_SETREDRAW message to a window to allow changes in that 
    /// window to be redrawn or to prevent changes in that window from being redrawn.
    /// </summary>
    private const int WM_SETREDRAW = 11; 
    
    /// <summary>
    /// Suspends painting for the target control. Do NOT forget to call EndControlUpdate!!!
    /// </summary>
    /// <param name="control">visual control</param>
    public static void BeginControlUpdate(Control control)
    {
        Message msgSuspendUpdate = Message.Create(control.Handle, WM_SETREDRAW, IntPtr.Zero,
              IntPtr.Zero);
    
        NativeWindow window = NativeWindow.FromHandle(control.Handle);
        window.DefWndProc(ref msgSuspendUpdate);
    }
    
    /// <summary>
    /// Resumes painting for the target control. Intended to be called following a call to BeginControlUpdate()
    /// </summary>
    /// <param name="control">visual control</param>
    public static void EndControlUpdate(Control control)
    {
        // Create a C "true" boolean as an IntPtr
        IntPtr wparam = new IntPtr(1);
        Message msgResumeUpdate = Message.Create(control.Handle, WM_SETREDRAW, wparam,
              IntPtr.Zero);
    
        NativeWindow window = NativeWindow.FromHandle(control.Handle);
        window.DefWndProc(ref msgResumeUpdate);
        control.Invalidate();
        control.Refresh();
    }
    
  2. Dispatcher.Yield

于 2013-02-22T08:51:19.683 に答える
2

別のスレッドから UI スレッドの項目を変更することはできません。UIへの項目の追加を実際に処理するUIスレッドにデリゲートがある場合は機能するはずです。

編集:

ここから:

SynchronizationContextUI スレッドに を使用すると、より深刻な問題が発生するようです。

SynchronizationContextCOM+ サポートと結び付けられており、複数のスレッドにまたがるように設計されています。WPF では、複数のスレッドにまたがる Dispatcher を使用SynchronizationContextできないため、スレッドを実際にクロスすることはできません。

于 2012-12-04T21:06:30.583 に答える
1

1 行のテンプレートのみの場合は、ListView GridView を検討してください。

動的コンテンツに関しては、動的 UI 要素ではなく、書式設定されたコンテンツ (ラン、ハイパーリンク、テーブル) を表示する単一の UI 要素を使用します。

動的コンテンツの FlowDocument を検討してください。

FlowDocument クラス

FlowDocument はバックグラウンドで作成できます。
優先バインディングも参照してください。
PriorityBinding クラス

その後、FlowDocumentScrollViewer または他の 3 つのオプションで表示できます。

UI 要素を動的に追加すると、UI 要素を再利用できないため、仮想化が中断されると思われます。

于 2012-12-04T21:54:09.243 に答える
1

やってみました:

 ItemsSource="{Binding Persons, IsAsync=True}"

または、コードビハインドで非同期にしたい場合は、Dispatcher役立ちます

private void ParallelGenerateUI()
{
    Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)delegate()
    {
       _UI = GenerateUI();
        RaisePropertyChanged("UI");
    });
}

以下のコードをテストしたところ、エラーは発生しません。

public partial class MainWindow : Window 
{

    public MainWindow()
    {
        InitializeComponent();
        for (int i = 0; i < 10000; i++)
        {
            Persons.Add(new Person());
        }
    }

    private ObservableCollection<Person> myVar = new ObservableCollection<Person>();
    public ObservableCollection<Person> Persons
    {
        get { return myVar; }
        set { myVar= value; }
    }
}

  public class Person : INotifyPropertyChanged
{
    // ...

    UIElement _UI;
    public UIElement UI
    {
        get
        {
            if (_UI == null)
            {
                ParallelGenerateUI();
            }
            return _UI;
        }
    }

    private void ParallelGenerateUI()
    {
        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)delegate()
        {

            _UI = GenerateUI();
            NotifyPropertyChanged("UI");
        });

    }

    private UIElement GenerateUI()
    {
        Random rnd = new Random();

        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;
    }

    public event PropertyChangedEventHandler PropertyChanged;
    /// <summary>
    /// Notifies the property changed.
    /// </summary>
    /// <param name="info">The info.</param>
    public void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
}

ObservableBaseしかし、私は何をしているのかわかりません

于 2012-12-04T21:06:00.617 に答える