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

问题描述 投票:0回答:1

我有以下列表框

<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 是具有扩展器(带有标题和内容)的用户控件。 视图模型是 ContainerViewModel

绑定的物品来源为:

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

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

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

如果我有几千件物品,可能需要很长时间。 现在假设我有 10k 个项目,我想使用以下代码对这个集合进行排序:

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

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

_containers.Move(0, 1);

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

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 无论如何都被称为?

任何帮助将不胜感激:)

c# wpf mvvm data-binding observablecollection
1个回答
0
投票

ListBox
使用UI虚拟化。它不会加载所有项目。只有那些在虚拟化视口内的。因为默认情况下启用虚拟化,所以覆盖
ListBox.ItemPanel
是多余的。
同样适用于
DataTemplate
内的 DataCContext 绑定:
DataContext
(或一般父元素)的
DataTemplate
是隐式继承的。无需明确设置。

在您的情况下,所有项目都已加载,因为您的

ListBox
没有高度限制。它会自动拉伸以使所有项目适合。要启用 UI 虚拟化,请为
ListBox
分配一个
Height
,以便
ScrollViewer
可以工作。
ScrollViewer
对于 UI 虚拟化是必不可少的。
高度可以显式设置,例如通过设置
ListBox.Height
属性或隐式设置,例如通过将
ListBox
添加到
Grid
,其中行高设置为除
Auto
以外的任何值。

此外,您不应替换源集合。过滤、排序和分组是通过修改“CollectionView

that is bound to the
ItemsControl
. In WPF the binding engine will automatically use the default 
ColectionView
to display the items. This allows you to modify the displayed items without modifying the original underlying collection. For example, sorting a
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);
}
© www.soinside.com 2019 - 2024. All rights reserved.