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

.NET Framework WPF ListView懒加载首次点击异常排查

WPF ListView懒加载首次加载全部数据的问题及解决

问题描述

开发基于.NET Framework的WPF应用时,为ListView实现懒加载:点击按钮从数据库分段获取数据并添加至ListView,直到垂直滚动条出现。但首次点击按钮时会加载数据库全部数据,后续点击则正常(仅加载至滚动条出现)。

现有代码

ListView的ItemsSource绑定至PeopleList(BindingList<Person>类型):

private void DisplayAllPeople()
{
    _peopleTablePageNumber = 0;
    PeopleList = new BindingList<Person>();

    bool isScrollBarVisible = false;
    bool isEndOfTable = false;
    while (!isScrollBarVisible && !isEndOfTable)
    {
        IEnumerable<Person> peoplePage = _dataCollector.GetPersons()
            .Skip(_peopleTablePageNumber * _peoplePerPageCount)
            .Take(_peoplePerPageCount);

        PeopleList.AddRange(peoplePage);

        _peopleTablePageNumber++;

        ScrollViewer sv = FindVisualChild<ScrollViewer>(listViewPeople);
        Visibility VerticalScrollbarVisibility = sv.ComputedVerticalScrollBarVisibility;
        isScrollBarVisible = (VerticalScrollbarVisibility == Visibility.Visible);

        isEndOfTable = (peoplePage.Count() < _peoplePerPageCount);
    }
}

private childItem FindVisualChild<childItem>(DependencyObject obj) 
    where childItem : DependencyObject
{
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
    {
        DependencyObject child = VisualTreeHelper.GetChild(obj, i);
        if (child != null && child is childItem)
            return (childItem)child;
        else
        {
            childItem childOfChild = FindVisualChild<childItem>(child);
            if (childOfChild != null)
                return childOfChild;
        }
    }
    return null;
}

DataCollector类的GetPersons方法:

public List<Person> GetPersons()
{
    return _dBContext.Persons.ToList();
}

原因分析

  1. UI布局未及时更新
    首次执行循环时,向PeopleList添加数据后,WPF的UI渲染是异步的——ListView还没完成布局更新,此时获取的ComputedVerticalScrollBarVisibility始终为Collapsed,循环会持续执行直到加载完所有数据,直到数据量足够触发滚动条或数据库数据耗尽。

  2. 未实现真正的数据库分页
    GetPersons()方法直接调用ToList()会一次性将所有数据库数据加载到内存,后续的Skip/Take只是在内存集合中做过滤,既浪费内存,也没实现真正的分段查询,违背了懒加载的初衷。

解决方案

1. 强制UI实时更新布局

在每次添加数据后调用UpdateLayout(),让ListView立即完成布局更新,确保滚动条状态判断准确:

private void DisplayAllPeople()
{
    _peopleTablePageNumber = 0;
    PeopleList = new BindingList<Person>();

    bool isScrollBarVisible = false;
    bool isEndOfTable = false;
    while (!isScrollBarVisible && !isEndOfTable)
    {
        // 改用分页查询方法(见下文)
        IEnumerable<Person> peoplePage = _dataCollector.GetPersonsPage(_peopleTablePageNumber, _peoplePerPageCount);

        PeopleList.AddRange(peoplePage);

        // 强制ListView更新布局,确保滚动条状态可被正确检测
        listViewPeople.UpdateLayout();

        _peopleTablePageNumber++;

        ScrollViewer sv = FindVisualChild<ScrollViewer>(listViewPeople);
        isScrollBarVisible = sv.ComputedVerticalScrollBarVisibility == Visibility.Visible;

        isEndOfTable = peoplePage.Count() < _peoplePerPageCount;
    }
}

2. 实现数据库层面的分页查询

修改DataCollector,直接在数据库执行Skip/Take,避免一次性加载全部数据:

// 替换原GetPersons方法,新增分页查询方法
public IEnumerable<Person> GetPersonsPage(int pageNumber, int pageSize)
{
    return _dBContext.Persons
        .Skip(pageNumber * pageSize)
        .Take(pageSize)
        .ToList();
}

额外优化:异步加载避免UI阻塞

将数据查询改为异步执行,防止长时间阻塞UI线程:

private async void DisplayAllPeople()
{
    _peopleTablePageNumber = 0;
    PeopleList = new BindingList<Person>();

    bool isScrollBarVisible = false;
    bool isEndOfTable = false;
    while (!isScrollBarVisible && !isEndOfTable)
    {
        // 异步从数据库获取分页数据
        var peoplePage = await Task.Run(() => 
            _dataCollector.GetPersonsPage(_peopleTablePageNumber, _peoplePerPageCount));

        // 回到UI线程更新数据和布局
        Application.Current.Dispatcher.Invoke(() => 
        {
            PeopleList.AddRange(peoplePage);
            listViewPeople.UpdateLayout();
        });

        _peopleTablePageNumber++;

        // 在UI线程检测滚动条状态
        bool scrollBarVisible = false;
        Application.Current.Dispatcher.Invoke(() => 
        {
            var sv = FindVisualChild<ScrollViewer>(listViewPeople);
            scrollBarVisible = sv.ComputedVerticalScrollBarVisibility == Visibility.Visible;
        });
        isScrollBarVisible = scrollBarVisible;

        isEndOfTable = peoplePage.Count() < _peoplePerPageCount;
    }
}

内容的提问来源于stack exchange,提问作者MR.Iraji

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 20:22:09