导读:本期聚焦于小伙伴创作的《C#中如何使用ListView列表视图实现增删改查与自定义渲染?》,敬请观看详情。在WinForms桌面程序里,ListView常常因为视图模式切换不当导致数据错乱。其实控件的View属性决定项如何排列,Details模式配合Columns才能显示多列。本文从基础绑定讲起,对比LargeIcon与SmallImageList的加载差异,说明用BeginUpdate避免界面闪烁的做法。针对右键编辑场景,给出基于ListViewItem.Tag挂载实体对象的方案,比靠索引取数据更稳。最后用OwnerDraw自绘进度条单元格,解决原生控件无法直观展示任务状态的短板。

在C#的WinForms开发中,ListView是一个功能非常丰富的列表视图控件,它不仅能以图标、列表、平铺等方式展示数据,还能在Details模式下模拟出类似表格的多列结构。掌握它的各种用法,可以让我们在开发桌面工具、管理后台时更加高效。

C#中如何使用ListView列表视图实现增删改查与自定义渲染?

一、ListView基础结构与视图模式

ListView最核心的属性是View,它决定了项(ListViewItem)以什么形式呈现。常用的视图包括LargeIconSmallIconListDetailsTile。其中Details模式最常用,因为它允许我们定义多列(ColumnHeader),把每一项的数据拆成多个字段显示。

在Details模式下,必须先往Columns集合里添加列头,否则即使添加了子项(SubItems)也不会显示出来。下面代码演示了如何初始化一个两列的列表视图:

using System.Windows.Forms;

ListView lv = new ListView();
lv.View = View.Details;
lv.FullRowSelect = true;
lv.GridLines = true;

lv.Columns.Add("编号", 80);
lv.Columns.Add("名称", 150);

ListViewItem item = new ListViewItem("1");
item.SubItems.Add("测试项");
lv.Items.Add(item);

上面的例子里,第一列对应ListViewItem本身的Text,后续列通过SubItems.Add补充。如果需要在运行时动态改列宽,可以设置lv.Columns[0].Width = -2,让列宽自动适配内容或标题。

需要注意的是,LargeIcon和SmallIcon模式依赖ImageList。如果设置了lv.LargeImageList却没有给Item指定ImageIndex,运行时不会报错但图标位置会空白,这种隐性问题在调试时容易被忽略。

二、数据绑定与批量操作的性能处理

当我们需要向ListView中添加几百甚至上千条数据时,如果直接在循环里调用Items.Add,界面会频繁重绘,造成明显卡顿。正确的做法是使用BeginUpdateEndUpdate把刷新过程包裹起来。

下面示例展示从实体列表批量加载数据,并利用Tag属性保存原始对象,方便后续取用:

public class UserModel
{
    public int Id { get; set; }
    public string Name { get; set; }
}

void LoadData(ListView lv, List<UserModel> users)
{
    lv.BeginUpdate();
    lv.Items.Clear();
    foreach (var u in users)
    {
        ListViewItem it = new ListViewItem(u.Id.ToString());
        it.SubItems.Add(u.Name);
        it.Tag = u;
        lv.Items.Add(it);
    }
    lv.EndUpdate();
}

通过把UserModel赋值给Tag,我们在双击或右键菜单里就不需要根据选中的行号去反查数组,而是直接var model = item.Tag as UserModel即可,这样逻辑更健壮,也避免了数据源顺序变化带来的bug。

另外,如果数据来自数据库查询,建议把查询和UI加载放在异步方法里,加载完再切回主线程操作ListView,否则在大量数据下主界面一样会失去响应。

三、增删改查的常见实现方式

列表视图的增删改查通常指:新增一行、删除选中行、编辑某行内容、根据条件筛选。新增和删除都比较直观,编辑则要注意Details模式下子项的更新。

以下代码演示了删除选中项以及修改选中项第二列文本:

// 删除选中项
if (lv.SelectedItems.Count > 0)
{
    lv.Items.Remove(lv.SelectedItems[0]);
}

// 修改选中项名称
if (lv.SelectedItems.Count > 0)
{
    ListViewItem sel = lv.SelectedItems[0];
    sel.SubItems[1].Text = "新名称";
    var m = sel.Tag as UserModel;
    if (m != null) m.Name = "新名称";
}

查询或筛选时,如果不想重新查库,也可以在内存里遍历Items,把不符合条件的项临时隐藏(设置item.Visible = false),不过ListView本身没有内置过滤,大量数据下还是建议重新绑定。

实际项目中,很多同学会用TextBox输入关键字,在TextChanged事件里做模糊匹配。这里要注意防抖,可以用一个Timer延迟300毫秒再执行筛选,避免每次按键都刷新整个列表。

四、使用OwnerDraw自绘实现进度条单元格

原生的ListView在Details模式下只能显示文字和图片,如果我们需要在某一列展示任务进度,就得开启自绘。把OwnerDraw设为true,然后处理DrawSubItem事件。

下面代码在第三列绘制一个简单的进度条:

lv.OwnerDraw = true;
lv.DrawSubItem += (s, e) =>
{
    if (e.ColumnIndex == 2)
    {
        Rectangle rect = e.Bounds;
        float percent = 0.6f; // 假设进度60%
        e.Graphics.FillRectangle(Brushes.White, rect);
        e.Graphics.FillRectangle(Brushes.Green, rect.X, rect.Y, rect.Width * percent, rect.Height);
        e.Graphics.DrawRectangle(Pens.Gray, rect);
    }
    else
    {
        e.DrawDefault = true;
    }
};
lv.DrawColumnHeader += (s, e) => e.DrawDefault = true;

自绘虽然灵活,但也会带来额外维护成本,比如字体缩放、高DPI适配都要自己处理。如果项目只是简单展示,用DataGridView可能更省事;但若是需要图标混排加自定义视觉,ListView配合OwnerDraw依然是很好的选择。

总结来看,ListView的用法从基础视图切换,到Tag挂载实体、批量更新防闪烁,再到自绘扩展,覆盖了大多数桌面列表场景。理解这些机制后,就能在面对不同业务需求时快速选出合适的实现方案。

C#ListViewWinForms修改时间:2026-08-05 21:31:24

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。