0

WPF 4.5 の ListBox にかなり長いリストを表示する際に速度の問題があります。私の ListBox には約 5000 個の項目が含まれており、項目の高さが異なる場合があります。各アイテムには、アイテムが表示されるとすぐに実行されるクエリによって取得される情報が表示されます。すべてのコンテンツに対して一度にこれを行うには時間がかかりすぎるため、この遅延クエリ実行が必要です。アイテムのプロパティが最初にアクセスされたときに作成されるNotifyTaskCompletionオブジェクトに ItemsControl をバインドすることで、遅延クエリを実行しました。

例えば:

public class ItemViewModel
{
    public NotifyTaskCompletion<ObservableCollection<Content>> QueryResult
    {
        get
        {                
            if(_queryTask == null)
            {
                _queryTask = new NotifyTaskCompletion<ObservableCollection<Content>> 
                                  (Task<ObservableCollection<Content>>.Run(queryFunction));
            }
            return _queryTask;
        }
    }
}

ItemTemplate:    
+--------------------------------------------------+
| TextBlock with header                            |
|                                                  |
| ItemsControl Source={Binding QueryResult.Result} |
+--------------------------------------------------+

したがって、アイテムが表示されるたびに、クエリを実行するスレッドが開始され、クエリが完了するとコンテンツが表示されます。クエリによって取得される情報は非常に多くなる可能性があります。つまり、ListBox 内の項目は ListBox 自体よりも大きくなる可能性があります。これが、ユーザーが項目全体を表示できるように、ListBox で VirtualizingPanel.ScrollUnit="Pixel" を設定する必要がある理由です。

+-------------------------------------------+
| ListBox, too small for big Item 1         |
|                                           |
| +------------------------------+          |
| | Header 1                     |          |
| |                              |          |
| | Lot's of information ....... |          |
| | Lot's of information ....... |          |
| | Lot's of information ....... |          |
| | Lot's of information ....... |          |
+-| Lot's of information ....... |----------+
  | Lot's of information ....... |
  | Lot's of information ....... |
  | Lot's of information ....... |
  +------------------------------+

  +------------------------------+
  | Header 2                     |
  |                              |
  | Not so much information..... |
  +------------------------------+

ただし、これによりスクロールが大幅に遅くなります。スクロールバーの親指をリストの中央にドラッグすると、数秒間フリーズした後、クエリされた情報なしでいくつかの項目が表示されます。次に、いくつかの項目が照会された情報を表示します。その後、ListBox は別の位置にジャンプしたように見え、これが再び別のフリーズなどにつながります。ListBox は現在のスクロール位置より上にあるすべてのアイテムをレンダリングしているように見えますが、すべてのクエリとクエリされたデータの複雑なレンダリングのために非常に時間がかかります。

VirtualizingPanel.ScrollUnit="Unit" を設定すると、速度の問題は発生しません。ListBox は、スクロール先の項目のみを表示するため、照会された情報のみが表示されます。残念ながら、「ピクセル」設定を使用する必要があるのは、アイテムが非常に大きい場合があるため、ユーザーがすべてを表示できるようにするためにピクセル スクロールが必要になる場合があるためです。

一度に数百のスレッドを開始することを避けるためにクエリ用のキューを用意したほうがよいという事実は認識していますが、ListBox がアイテムをレンダリングするという根本的な問題については何も変わらないと思います。表示されません。

この問題に取り組む方法がよくわかりません。誰でも私を助けることができますか?

4

1 に答える 1

0

ListBoxItem で DeferredScrolling とスクロールを行う

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <ListBox ItemsSource="{Binding Path=DeferedItems}" 
                ScrollViewer.IsDeferredScrollingEnabled="True" 
                ScrollViewer.VerticalScrollBarVisibility="Visible"
                VirtualizingStackPanel.VirtualizationMode="Standard">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <TextBlock Text="{Binding Path=ID}"/>
                    <ListBox ItemsSource="{Binding Path=DefStrings}" Margin="10,0,0,0" 
                             MaxHeight="300" 
                             ScrollViewer.VerticalScrollBarVisibility="Visible"/>
                    <TextBlock Text="{Binding Path=DT}" Margin="10,0,0,0"/>                      
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Grid>

public partial class MainWindow : Window
{
    private List<DeferedItem> deferedItems = new List<DeferedItem>();
    public MainWindow()
    {
        this.DataContext = this;
        for (Int32 i = 0; i < 100000; i++) deferedItems.Add(new DeferedItem(i));
        InitializeComponent();
    }
    public List<DeferedItem> DeferedItems { get { return deferedItems; } }

}
public class DeferedItem
{
    private Int32 id;
    private DateTime? dt = null;
    private List<String> defStrings = new List<string>();
    public DateTime DT
    {
        get
        {
            if (dt == null)
            {
                System.Threading.Thread.Sleep(1000);
                dt = DateTime.Now;
            }
            return (DateTime)dt;
        }
    }
    public List<String> DefStrings
    {
        get
        {
            if (defStrings.Count == 0)
            {
                for (Int32 i = id; i < id + 1000; i++) defStrings.Add(i.ToString() + " " + DateTime.Now.ToLongTimeString());
            }
            return defStrings;
        }
    }
    public Int32 ID { get { return id; } }
    public DeferedItem(Int32 ID) { id = ID; }
}
于 2015-01-04T18:00:02.940 に答える