0

在 WPF 4.5 的 ListBox 中显示一个相当长的列表时,我遇到了一些速度问题:我的 ListBox 包含大约 5000 个项目,这些项目可以有不同的高度。每个项目显示通过查询检索到的信息,该查询在项目显示后立即执行。这种延迟的查询执行是必要的,因为一次对所有内容执行此操作会花费太多时间。我通过将 ItemsControl 绑定到NotifyTaskCompletion对象来完成延迟查询,该对象是在首次访问项目中的 Property 时创建的。

例如:

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 回答