You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WinUI3 ListView数据虚拟化时滚动条总长度设置问题

WinUI 3 ListView 随机访问虚拟化滚动条长度问题解决

问题核心原因

你遇到的滚动条长度随显示内容变化的问题,本质是继承ObservableCollection<T>导致的Count属性干扰:ObservableCollection的Count返回当前已加载的项数,ListView会优先用这个值计算滚动范围,忽略了IItemsRangeInfo接口提供的TotalCount。同时,ListView的虚拟化配置也需要确保启用基于项的滚动。

解决方案步骤

1. 自定义集合:直接实现接口,避免ObservableCollection干扰

不要继承ObservableCollection<T>,直接实现INotifyCollectionChanged、IItemsRangeInfo和IDisposable,精准控制集合的Count和TotalCount暴露逻辑:

public class VirtualizedItemsCollection<T> : INotifyCollectionChanged, IItemsRangeInfo, IDisposable, INotifyPropertyChanged
{
    private readonly List<T> _loadedItems = new List<T>();
    private int _totalCount = 10000;
    private ItemIndexRange _currentVisibleRange;

    public int TotalCount
    {
        get => _totalCount;
        set
        {
            if (_totalCount != value)
            {
                _totalCount = value;
                OnPropertyChanged(nameof(TotalCount));
            }
        }
    }

    // 仅返回当前已加载的项数,控件会用TotalCount作为总项数
    public int Count => _loadedItems.Count;

    public T this[int index] => _loadedItems[index];

    public event NotifyCollectionChangedEventHandler CollectionChanged;
    public event PropertyChangedEventHandler PropertyChanged;

    public void LoadItems(int startIndex, int count)
    {
        _loadedItems.Clear();
        
        // 模拟从数据源加载数据
        for (int i = startIndex; i < startIndex + count && i < _totalCount; i++)
        {
            _loadedItems.Add((T)(object)new SampleData(i));
        }

        // 通知控件集合内容重置
        CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    public void RangesChanged(ItemIndexRange visibleRange, IReadOnlyList<ItemIndexRange> trackedItems)
    {
        if (_currentVisibleRange == null || !_currentVisibleRange.Equals(visibleRange))
        {
            _currentVisibleRange = visibleRange;
            LoadItems(visibleRange.FirstIndex, visibleRange.Length);
        }
    }

    private void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public void Dispose()
    {
        // 按需清理资源,比如取消数据加载任务
    }
}

2. 调整ListView的虚拟化配置

确保ListView启用基于项的滚动和虚拟化,这是控件正确识别TotalCount的前提:

<ListView x:Name="MyListView"
          IsItemClickEnabled="False"
          VirtualizingStackPanel.VirtualizationMode="Recycling"
          VirtualizingStackPanel.IsVirtualizing="True"
          ScrollViewer.CanContentScroll="True">

    <ListView.ItemTemplate>
        <DataTemplate x:DataType="local:SampleData">
            <Grid>
                <TextBlock x:Phase="0" Text="{x:Bind Id}"/>
            </Grid>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

3. 简化UserControl中的数据源绑定

ListView会自动触发RangesChanged请求初始可见范围,无需手动调用LoadItems:

public MyUserControl()
{
    this.InitializeComponent();
    MyListView.ItemsSource = new VirtualizedItemsCollection<SampleData>();
}

关键说明

  • ScrollViewer.CanContentScroll="True":开启基于项的滚动模式,让ListView根据项索引而非像素计算滚动位置,这样才能结合TotalCount正确渲染滚动条长度。
  • 避免ObservableCollection:它的Count属性会让ListView误以为总项数就是已加载的数量,导致滚动条长度错误。
  • 正确的范围变更判断:在RangesChanged中直接比较整个ItemIndexRange,确保任何可见范围变化都能触发数据加载。

内容的提问来源于stack exchange,提问作者kinton

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 14:05:02