.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(); }
原因分析
UI布局未及时更新
首次执行循环时,向PeopleList添加数据后,WPF的UI渲染是异步的——ListView还没完成布局更新,此时获取的ComputedVerticalScrollBarVisibility始终为Collapsed,循环会持续执行直到加载完所有数据,直到数据量足够触发滚动条或数据库数据耗尽。未实现真正的数据库分页
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
相关产品推荐
相关产品推荐

