TDS文件搜索_Winform版本与avalonia开发差异比较: (二) 列表虚拟化的实现-百万数据轻松展示
一、列表虚拟化与海量数据展示
在tds中,当用户在关键词后加了/a参数,会列出所有的文件。此时可能会有上百万个。为了流畅操作和显示这些数据,只能借助列表虚拟化技术来实现。

列表虚拟化是一种优化技术,用于处理大量数据时提高性能和用户体验。它通过实时计算来模拟海量数据的展示,此时的性能流畅度与数据大小无关,仅与实时计算需要的执行时间有关。其核心思想是按需加载和按需渲染。
在用户滚动列表时,虚拟化技术会自动将大量数据分成多个小块(或页面),每次只加载和渲染当前视图范围内的数据块。这个过程是事件驱动,当用户滚动列表时,这些事件通知应用程序加载和渲染新的数据块。在后台,虚拟化管理模块还将即将进入视图范围的数据项缓存起来,以便快速访问。这减少了对数据源的频繁访问,提高了性能。
主要原理很简单:
- 视口渲染:列表虚拟化技术的核心是只渲染用户当前可视区域内的元素,而不是一次性渲染整个列表。当用户滚动时,动态地加载和卸载元素。
- 占位元素:为了保持列表的滚动条高度和布局正确,虚拟化列表会使用占位元素来表示未渲染部分的高度。
- 元素复用:虚拟化列表通常会重用相同的组件实例(数据或UI元素)来渲染不同的数据项,通过缓存从而减少开销。
Winform和Avalonia各自的列表虚拟化技术实现不太一样,Winform需要考虑对实时事件手动处理,Avalonia则可以依赖自带的响应式模式绑定自动完成。我一起来看看具体都实现吧。
二、Winform 与 Avalonia 实现的对比
2.1 Winform虚拟列表,以ListView为例
Winform的虚拟列表的开启需要将控件ListView的VirtualMode属性设置为true。在虚拟化过程中,用户滚动列表时,ListView会触发两个关键事件,需要我们进行实现:
ListView.CacheVirtualItems事件:当用户滚动列表时,此事件会被触发。它通知我们哪些项即将进入视图范围,我们可以在这个事件中缓存这些项。例如,可以使用一个数组来存储这些即将显示的项,作为我们的cache。这样可以减少对数据源的频繁访问,提高性能。ListView.RetrieveVirtualItem事件:当ListView需要将某个项渲染到UI上时,会触发此事件。我们可以通过从缓存中读取对应的项并返回给UI来实现。如果缓存中没有找到对应的项,我们也可以选择动态生成它。
CacheVirtualItems有时候也可以不实现,RetrieveVirtualItem也可以实时处理动态生成ListViewItem。
以下是简化后的代码实现,需要详细实现朋友们可以参考项目源码。
private ListViewItem[] CurrentCacheItemsSource; // 用于存放缓存的数组
private int firstitem; // 缓存的起始索引
private bool refcache = false; // 标记是否需要重新缓存
// 动态获取对象并提供给UI
private void ListView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
// 如果缓存中有对应的项,直接从缓存中获取
if (CurrentCacheItemsSource != null && e.ItemIndex >= firstitem && e.ItemIndex < firstitem + CurrentCacheItemsSource.Length)
{
e.Item = CurrentCacheItemsSource[e.ItemIndex - firstitem];
}
else
{
// 如果缓存中没有对应的项,动态生成一个
e.Item = GenerateListViewItem(e.ItemIndex);
}
// 如果动态生成失败,返回一个空对象以避免异常
if (e.Item == null)
{
e.Item = new ListViewItem(new string[] { "加载失败", "", "" });
}
}
// 生成缓存
private void ListView1_CacheVirtualItems(object sender, CacheVirtualItemsEventArgs e)
{
// 如果要缓存的范围已经在缓存中,直接返回
if (e.StartIndex >= firstitem && e.EndIndex <= firstitem + CurrentCacheItemsSource.Length)
{
return;
}
// 更新缓存的起始索引和长度
firstitem = e.StartIndex;
int length = e.EndIndex - e.StartIndex + 1;
// 重新生成缓存
CurrentCacheItemsSource = new ListViewItem[length];
for (int i = 0; i < length; i++)
{
// 从数据源中获取对应的项并存入缓存
CurrentCacheItemsSource[i] = GenerateListViewItem(firstitem + i);
}
// 标记缓存已更新
refcache = false;
// 自动调整列宽以适应内容
ListView1.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
}
// 根据索引生成ListViewItem
private ListViewItem GenerateListViewItem(int index)
{
// 这里可以根据实际的数据源来生成ListViewItem
// 示例:从一个列表中获取数据
if (index < vresultNum)
{
FrnFileOrigin f = vlist[index];
return new ListViewItem(new string[] { f.Name, f.Path, f.Size.ToString() });
}
return null;
}
实际使用中,ListViewItem虚拟化的缓存是我们手动将数据关联到一个数组或List中的。当数据发生变化时,除了自动刷新各个索引处对象的值外,还需要控制长度。
如果说缓存有100个元素,我们可以通过设定ListView的VirtualListSize属性来改变要显示的元素个数,比如只显示前10个,这样就可以在数据变小的时避免重新创建数组/列表对象。
5.2 Avalonia的虚拟面板 以为例
与Winform不同,Avalonia的ListBox等控件中没有VirtualMode属性,需要通过VirtualizingStackPanel等方式开启,xml代码如下。
<ListBox x:Name="fileListBox" ItemsSource="{Binding Items.DisplayedData, Mode=OneWay}" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel /> <!-- 虚拟化面板 -->
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<!-- 自定义实现 -->
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
绑定的核心思想是,我们将真实数据的引用存起来,Listbox则是绑定到一个IEnumerable<T>对象接口中。当真实数据需要更新时,我们控制IEnumerable<T>的更新。
由于IEnumerable<T>是延迟实现的(yield return)因此不会产生额外数组开销。可以通过Take(DisplayCount)的方法去动态控制所展示数据的长度,类似于Winform的VirtualListSize, 这样能够有效提升性能,尤其是在处理大量数据以及处理数据长度频繁变化的场景。
在下面的代码中给出了一个ViewModel的示例实现,需要用到Avalonia.ReactiveUI库(这个库需要单独在Nuget上下载)。
public class DataViewModel : ReactiveObject
{
private IList<FrnFileOrigin> _allData = [];
private IEnumerable<FrnFileOrigin> _displayedData = [];
private int _displayCount = 100;
public IEnumerable<FrnFileOrigin> DisplayedData
{
get => _displayedData;
private set => this.RaiseAndSetIfChanged(ref _displayedData, value);
}
bool isShowOpenWith = true;
public bool IsShowOpenWith
{
get => isShowOpenWith;
set
{
this.RaiseAndSetIfChanged(ref isShowOpenWith, value);
}
}
public int DisplayCount
{
get => _displayCount;
private set
{
_displayCount = value;
}
}
public DataViewModel()
{
}
public void Bind(IList<FrnFileOrigin> _allData)
{
if (this._allData != _allData)
{
// 生成测试数据(实际中可能从文件或数据库加载)
this._allData = _allData;
//UpdateDisplayedData();
}
}
public void UpdateDisplayedData()
{
// 使用 LINQ 的 Take(),这是惰性求值的,性能很好
DisplayedData = _allData.Take(DisplayCount);
}
// 快速切换到不同数量级
public void SetDisplayCount(int count)
{
DisplayCount = count;
}
}
在上述代码中,DisplayedData 属性通过 RaiseAndSetIfChanged 方法来实现属性值的更新和通知,确保绑定到该属性的界面元素能够及时响应数据的变化。而 DisplayCount 属性在更新时会调用 UpdateDisplayedData() 方法,从而保证 DisplayedData 的内容始终与 DisplayCount 保持一致。
还有一个问题就是,Avalonia在VirtualizingStackPanel中,虚拟化后可能数据不是瓶颈,UI显示同样会造成卡顿,尤其是在应用虚拟化时实时渲染发复杂的布局。因此我们可以设置VirtualizingStackPanel.CacheLength属性。
这个属性是一个double值,决定了在视口上方和下方(或左方和右方)要保持多少额外的空间。值为 0.5 表示系统在每一侧(上下或左右)将缓冲视口大小的一半,此时将实例化更多UI元素。尽管会占更多内存,但大大减少Measure-Arrange循环的次数(measure:确定控件所需的最小宽度和高度; arrange:将控件放置在父控件中,并确定其最终的位置和大小;Arrange:) 否则,在UI复杂程度较高时,GC压力巨大。
三、最后
感谢您的耐心阅读,希望各位从零开始的新朋友和老朋友有所收获!如果你对这篇文章的内容有任何建议或想法,欢迎随时交流!本文中TDS文件搜索工具的Winform版本已在仓库完全开源了!点个 Star ️支持一下!代码仓库地址 不清楚的请关注微信公众号"萤火初芒",发送消息 “TDS”即可查看!

下期预告:
“TDS文件搜索_Winform版本与avalonia开发差异比较:(三)系统文件图标ico动态获取与虚拟化时的绑定”
TDS文件搜索_Winform版本与avalonia开发差异比较: (二) 列表虚拟化的实现-百万数据轻松展示的更多相关文章
- Html5+Mui前端框架,开发记录(二):提交不了数据?
1.Mui 请求Webapi接口,返回所需要的数据(get,post) mui.ajax({ url: getAddress() + '/api/Qiliu/get?jsoncallback=?', ...
- 小程序开发笔记【二】,抽奖结果json数据拼装bug解决
抽奖结果数据json格式数据拼接bug,如下图,只发布了两个奖项,每个奖项设置2个奖品,但最后拼接数据的时候出现3个奖项 json数据格式如下 "luckyResult":[ { ...
- ionic2如何升级到最新版本、配置开发环境
好久没写东西了,去年用了angular2的RC版本和ionic2写了一个项目,因为开发周期和有些版本不稳定,所以一直没有升级,ng2新版本引用Aot打包,听说优化还不错,现在尝试升级ioni ...
- 文件搜索工具everything
Everything是voidtools开发的一款文件搜索工具,官网描述为“基于名称实时定位文件和目录(Locate files and folders by name instantly) (“Ev ...
- Everything(文件搜索神器)
前言 Everything官网: http://www.voidtools.com/ 软件版本: V1.3.4.686 (x64) 操作系统: windows 7/10 搜索FTP(内网)资源 比如内 ...
- gcc编译时头文件和库文件搜索路径
特殊情况:用户自定义的头文件使用#include"mylib"时,gcc编译器会从当前目录查找头文件 一.头文件 gcc 在编译时寻找所需要的头文件 : ※搜寻会从-I开始( ...
- Android开发学习总结(一)——搭建最新版本的Android开发环境
Android开发学习总结(一)——搭建最新版本的Android开发环境(转) 最近由于工作中要负责开发一款Android的App,之前都是做JavaWeb的开发,Android开发虽然有所了解,但是 ...
- 文件搜索神器 Everything
Everything 是一款 NTFS 磁盘格式下的文件搜索工具,1月5日发布测试版本 1.3.0.631b Beta,增加文件列表.收藏夹.自定义快捷键.高级搜索等功能,取消了比较实用的 etp/f ...
- 用pyenv和virtualenv搭建单机多版本python虚拟开发环境
作为主流开发语言, 用python 开发的程序越来越多. 方便的是大多linux系统里面都默认集成了python, 开发可以随时随地开始. 但有时候这也成为了一个短板, 比如说有时候我们需要开发和调试 ...
- 搭建最新版本的Android开发环境
只为成功找方法,不为失败找借口! Android开发学习总结(一)——搭建最新版本的Android开发环境 最近由于工作中要负责开发一款Android的App,之前都是做JavaWeb的开发,Andr ...
随机推荐
- Luogu P9671 [ICPC2022 Jinan R] Identical Parity 题解
P9671 [ICPC2022 Jinan R] Identical Parity 构造题.感觉中上位黄. 对于 \(k\) 为偶数的情况,构造奇偶交替的序列.每次区间向后挪动时,最左边出去的元素和最 ...
- SpringBoot--如何整体读取多个配置属性及其相关操作
上篇文章讲到使用@Value注解每次只能读取一个配置属性,若要整体读取多个属性,或者读取具有某种结构关系的一组属性可使用@ConfigurationProperties注解来处理. @Configur ...
- 前端开发系列062-网络篇之前端开发Ajax简单介绍
一.Ajax技术简单介绍 Ajax是一门异步的用于发送网络请求的技术. 全称为:Async javascript and XML. 简单说明:Ajax 这个概念是由 Jesse James Garre ...
- WSL初探
1 简介 WSL( Windows Subsystem for Linux )是微软开发的兼容层,允许在 Windows 10 及更高版本上运行原生Linux二进制文件(如 Ubuntu . Debi ...
- ETL中Python组件的运用
Python是一种高级.通用.解释型编程语言,以简洁.易读.易学的语法而闻名,被广泛应用于Web开发.数据科学.人工智能.自动化脚本等领域. python的特点包含 易读易学:Python的语法设计简 ...
- Python使用diffusers加载文生图模型教程
还在为搭建AI绘画环境焦头烂额? Hugging Face统计显示,diffusers库下载量已突破1亿次,成为最快增长的AI工具库! 本文将手把手教你: - 3行代码加载Stable Diffusi ...
- SciTech-Mathmatics-Analysis-Infinite Series+Limit: 无穷级数+极限: $\large e = \lim{(1+\frac{1}{n})^n}$
SciTech-Mathmatics-Analysis Infinite Series: 无穷级数 Limit: 极限: \(\large e = \underset{n \rightarrow \i ...
- SciTech-Mathmatics-RealAnalysis: Cantor-Schröder-Bernstein Theorem
Cantor Set Theory 与 Cantor-Schröder-Bernstein Theory 是 Lebesgue积分及Real Analysis的Kernel. 证明过程有re-mapp ...
- 官宣!Dora-rs 官方中文教程正式发布!
Dora-rs:是一个为现代AI机器人应用设计的.以数据流为核心的机器人开发框架 . 在机器人开发的世界里,我们常常面临这样的困境: 过于复杂: 传统软件栈学习曲线陡峭,配置繁琐,让快速原型验证成为奢 ...
- Linux 系统目录结构-九五小庞
在 Linux 系统中,有几个目录是比较重要的,平时需要注意不要误删除或者随意更改内部文件. /etc: 上边也提到了,这个是系统中的配置文件,如果你更改了该目录下的某个文件可能会导致系统不能启动. ...