如何在不调用所有对象的c'tor的情况下对列表框ItemSource进行排序?WPF

biswetbf  于 2023-04-22  发布在  其他
关注(0)|答案(1)|浏览(99)

我有以下列表框

<ListBox SelectionMode="Extended" ItemsSource="{Binding Containers}" AllowDrop="True" Margin="0,0,5,0">
<ListBox.ItemContainerStyle>
    <Style TargetType="ListBoxItem">
        <Setter Property="IsSelected" Value="{Binding Content.IsSelected, Mode=TwoWay, RelativeSource={RelativeSource Self}}"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ListBoxItem">
                    <ContentPresenter/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
    <DataTemplate DataType="{x:Type vm:ContainerViewModel}">
        <local:ContainerUserControl DataContext="{Binding}" />
    </DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
    <ItemsPanelTemplate>
        <VirtualizingStackPanel IsVirtualizing="True" VirtualizationMode="Recycling" />
    </ItemsPanelTemplate>
</ListBox.ItemsPanel>

ContainerUserControl是具有扩展器(具有标头和内容)的用户控件。
绑定的项源为:

private ObservableCollection<ContainerViewModel> _containers;
    public ObservableCollection<ContainerViewModel> Containers
    {
        get => _containers;
        set
        {
            _containers = value;
            OnPropertyChanged();
        }
    }

问题是,当我为Containers分配新集合时,每个元素的构造函数都被调用:

public partial class ContainerUserControl : UserControl
{
    public ContainerUserControl()
    {
        InitializeComponent();
        Debug.Print("in ContainerUserControl");
    }
}

如果我有成千上万的项目,它可能需要很长的时间。现在让我们假设我有10 k个项目,我想使用以下代码对这个集合进行排序:

Containers = new ObservableCollection<ContainerViewModel>(Containers.OrderByDescending(i => i.Name));

我将看到usercontrol构造函数被调用了10 k次。在阅读了一些帖子后,我决定使用“Move”方法实现就地排序,但不幸的是,即使我这样做了:

_containers.Move(0, 1);

我看到我遍历了userControl c 'tor。如果我有成千上万的移动操作,就像使用orderby方法并分配排序列表一样。此外,我试图创建一个新的排序集合,并在itemsSource之间切换,但没有帮助,仍然输入了c' tor 10 k次。

public ObservableCollection<ContainerViewModel> SortedContainers { get; set; } // already sorted
public ListBox ContainerListBox { get; set; } // the listbox from xaml

ContainerListBox.ItemsSource = null;
ContainerListBox.ItemsSource = SortedContainers;

无论我怎么尝试,我都无法避免c 'tor被调用数千次,并产生可怕的性能问题。我如何避免c' tor调用?为什么这个c 'tor无论如何都被调用?
编辑:我对500个对象进行了一些时间测量:我看到每个c 'tor通常需要1-2毫秒,但有时超过20毫秒。此外,构建500个对象集合的总时间大于所有c' tor时间的总和。下面是我测量的开始和结束:\n开始:x1c 0d1x结束:

你可以看到所有c 'tor时间的总和是1169 ms,但总时间是18:52:25.835 - 18:52:29.153,也就是3.318秒。(通常500个对象需要4秒,所以大约10 k甚至更多)为什么会这样?
任何帮助将不胜感激:)

pzfprimi

pzfprimi1#

ListBox使用UI虚拟化。它不会加载所有项目。只加载虚拟化视口中的项目。因为默认情况下启用了虚拟化,所以ListBox.ItemPanel的覆盖是多余的。
这同样适用于DataTemplate内部的DataCOntext绑定:DataTemplate(或者一般来说父元素)的DataContext是隐式继承的。不需要显式设置它。
在您的例子中,所有项目都被加载,因为您的ListBox没有高度限制。它会自动拉伸以使所有项目适合。要启用UI虚拟化,请为ListBox分配Height,以便ScrollViewer可以工作。ScrollViewer对于UI虚拟化至关重要。
高度可以显式设置,例如通过设置ListBox.Height属性,或者隐式设置,例如通过将ListBox添加到Grid,其中行高设置为除Auto之外的任何值。
此外,您不应该替换源集合。这样做效率很低。这会严重影响性能。排序和分组是通过修改绑定到ItemsControlCollectionView来完成的。在WPF中,绑定引擎将自动使用默认的ColectionView来显示项目。这使您可以修改显示的项,而无需修改原始基础集合。例如,对CollectionView进行排序不会对基础集合进行排序。
使用静态CollectionViewSource.GetDefaultView方法获得默认的CollectionView

<ListBox ItemsSource="{Binding Containers}"
         VirtualizingPanel.VirtualizationMode="Recycling"
         Height="300">
  <ListBox.ItemTemplate>
    <DataTemplate DataType="ContainerViewModel">
      <ContainerUserControl />
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>
private ObservableCollection<ContainerViewModel> _containers;
public ObservableCollection<ContainerViewModel> Containers
{
  get => _containers;
  set
  {
    _containers = value;
    OnPropertyChanged();
  }
}

private void SortContainersByName()
{
  var sortDescription = new SortDescription(nameof(ContainerViewModel.Name), ListSortDirection.Ascending);
  ICollectionView containersView = CollectionViewSource.GetDefaultView(this.Containers);

  // Apply sorting criteria
  containersView.SortDescriptions.Add(sortDescription);
}

private void ClearSortContainersByName()
{
  SortDescription sortDescriptionToRemove = this.DataGridItemsView.SortDescriptions
    .FirstOrDefault(sortDescription => sortDescription.PropertyName.Equals(nameof(ContainerViewModel.Name), StringComparison.Ordinal));
  ICollectionView containersView = CollectionViewSource.GetDefaultView(this.Containers);

  // Clear sorting criteria
  containersView.SortDescriptions.Remove(sortDescriptionToRemove);
}

相关问题