嵌套控件场景下如何启用UWP ListView虚拟化?
//add an expander control for every subject
foreach (var currentSubject in subjects.Subjects)
{
var subjectLessons = lessons.Lessons.Where(lesson => lesson.subjectId == currentSubject.id).ToList();
await CoreApplication.MainView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
var expander = new Expander { Header = currentSubject.description, };
var listviewlessons = new LessonsListView() { ItemsSource = subjectLessons, };
expander.Content = listviewlessons;
LezioniPopupStackPanel.Children.Add(expander);
});
}
## 核心问题 加载数据时UI卡顿近10秒,测试发现给ListView设置固定高度(如100)时,虚拟化生效,加载耗时不足1秒,但需求是ListView高度自适应,请问如何在嵌套Expander的场景下启用虚拟化? --- # 解决方案 ## 1. 替换外层StackPanel为VirtualizingStackPanel StackPanel会一次性渲染所有子元素,完全不支持虚拟化,这是卡顿的核心原因。直接替换为`VirtualizingStackPanel`并开启回收式虚拟化: ```xml <ScrollViewer Grid.Row="1" x:Name="testScroll"> <VirtualizingStackPanel x:Name="LezioniPopupStackPanel" VirtualizingPanel.VirtualizationMode="Recycling" VirtualizingPanel.IsVirtualizing="True"/> </ScrollViewer>
2. 配置ListView的虚拟化与自适应高度
在UserControl的ListView中显式开启虚拟化,同时禁用内部滚动——因为外层已有ScrollViewer,ListView不需要独立滚动,这样才能让高度自适应内容的同时保持虚拟化:
<ListView x:Name="listView" ItemTemplate="{StaticResource LessonListViewDataTemplate}" VirtualizingStackPanel.VirtualizationMode="Recycling" ScrollViewer.VerticalScrollMode="Disabled" ScrollViewer.VerticalScrollBarVisibility="Disabled"/>
3. 延迟加载Expander内的ListView数据
默认展开所有Expander会一次性渲染所有ListView内容,改为默认折叠Expander,仅在用户点击展开时加载对应数据:
foreach (var currentSubject in subjects.Subjects) { var subjectLessons = lessons.Lessons.Where(lesson => lesson.subjectId == currentSubject.id).ToList(); await CoreApplication.MainView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { var expander = new Expander { Header = currentSubject.description, IsExpanded = false }; var listviewlessons = new LessonsListView(); // 绑定展开事件,延迟加载数据 expander.Expanded += (s, e) => { listviewlessons.ItemsSource = subjectLessons; }; expander.Content = listviewlessons; LezioniPopupStackPanel.Children.Add(expander); }); }
4. 优化Dispatcher回调逻辑
原代码中Dispatcher回调用了不必要的async,去掉后减少线程切换开销:
// 移除回调内的async,因为没有异步操作 await CoreApplication.MainView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { // 控件创建与添加逻辑 });
内容的提问来源于stack exchange,提问作者Gabboxl

