一ListView
listView的五个视图模式
| 模式名称 | 形象比喻 | 适用场景 | 图标要求 |
|---|---|---|---|
| Details (详细) | Excel 表格 | 最常用!显示多列数据(如姓名、年龄、日期)。 | 使用 SmallImageList |
| LargeIcon (大图标) | 桌面图标 | 像 Windows 桌面那样,大图配下面一行字。 | 使用 LargeImageList |
| SmallIcon (小图标) | 控制面板 | 左右排列的小图标,文字在图标右边。 | 使用 SmallImageList |
| List (列表) | 垂直名单 | 只有一列数据,自上而下排列。 | 使用 SmallImageList |
| Tile (平铺) | 卡片 | 较大的图标,右侧显示多行简短信息。 | 通常用 Small/Medium |
二常用属性
三常用事件
一、高频事件(★★★★★ / ★★★★☆)
开发必用,90% 的业务场景都会用到,必须优先掌握。
1. SelectedIndexChanged
- 触发时机 :ListView 的选中项(高亮) 发生改变时(单选 / 多选、取消选中都会触发)
- 核心作用:获取当前选中的项,执行选中后的业务逻辑
- 常用场景:选中列表项后显示详情、启用 / 禁用功能按钮、加载对应子数据
- 频次:★★★★★(最高频,无之一)
- 小贴士 :多选时会多次触发,建议加
if(listView1.SelectedItems.Count > 0)避免空值报错。
2. ItemActivate
- 触发时机 :用户双击列表项 或 按Enter 键激活选中项时触发
- 核心作用:处理项的「激活操作」(打开、编辑、查看详情)
- 常用场景:双击列表项打开详情窗口、编辑数据
- 频次:★★★★★(第二高频)
- 优势 :比鼠标双击事件更适配 ListView,仅点击项生效,无需判断点击位置。
3. ColumnClick
- 触发时机 :点击列表列头时触发
- 核心作用:获取点击的列索引,实现列表排序
- 常用场景:点击列头对数据升序 / 降序排序(详情视图必备)
- 频次:★★★★☆
4. MouseClick
- 触发时机 :鼠标左键 / 右键 / 中键点击 ListView任意位置时触发
- 核心作用:获取鼠标坐标、按钮类型,实现精准点击逻辑
- 常用场景:弹出右键菜单、点击空白处取消选中
- 频次:★★★★☆
- 对比 :比基础
Click事件更强大(带鼠标参数),WinForm 开发优先用它。
二、中频事件(★★★☆☆)
特定场景常用,开启复选框、编辑标签、键盘操作时必备。
1. ItemChecked
- 触发时机 :ListView复选框 选中状态改变后触发
- 核心作用:获取所有勾选的项,执行批量操作
- 常用场景:批量删除、批量选中、统计勾选数量
- 频次:★★★★☆(复选框场景必用)
- 前提 :必须先设置
listView1.CheckBoxes = true。
2. ItemCheck
- 触发时机 :复选框选中状态改变前触发
- 核心作用 :验证 / 取消勾选操作(限制勾选数量、禁止勾选禁用项)
- 常用场景:最多勾选 3 项、禁止勾选特定项
- 频次:★★★☆☆
- 关键 :可通过
e.NewValue = CheckState.Unchecked强制取消勾选。
3. MouseDoubleClick
- 触发时机 :鼠标双击 ListView任意位置(空白处也会触发)
- 核心作用:自定义双击逻辑
- 常用场景 :替代
ItemActivate,精准控制双击生效区域 - 频次:★★★☆☆
- 推荐 :列表项激活优先用
ItemActivate,空白双击用此事件。
4. BeforeLabelEdit / AfterLabelEdit
- 触发时机 :列表项标签开始编辑前 / 编辑完成后
- 核心作用:限制编辑内容、保存修改后的文本
- 常用场景:允许用户直接修改列表项名称
- 频次:★★★☆☆
- 前提 :必须先设置
listView1.LabelEdit = true。
5. KeyDown
- 触发时机:键盘按键按下时触发
- 核心作用:处理键盘快捷键
- 常用场景 :按
Delete删除选中项、Ctrl+A全选 - 频次:★★★☆☆
三、低频事件(★★☆☆☆ 及以下)
极少使用,仅自定义 UI、特殊布局需求时用到。
1. 自定义绘制类
DrawItem、DrawSubItem、DrawColumnHeader
- 触发时机:绘制项 / 子项 / 列头时触发
- 核心作用:自定义 ListView 外观(改颜色、图标、样式)
- 场景:美化界面、定制列表样式
- 频次:★★☆☆☆
- 前提:必须设置
listView1.OwnerDraw = true。
2. 布局 / 状态类
Layout、SizeChanged、Scroll、ItemHover
- 触发时机:布局改变、大小改变、滚动条滚动、鼠标悬停
- 场景:自适应布局、悬停提示、滚动监听
- 频次:★☆☆☆☆
3. 验证类
Validating、Validated
- 触发时机:控件验证时
- 场景:极少用于 ListView
- 频次:★☆☆☆☆
4.易混淆事件对比
| 事件组合 | 核心区别 |
|---|---|
SelectedIndexChanged vs ItemChecked |
前者 =高亮选中 改变;后者 =复选框勾选改变(完全独立) |
ItemActivate vs MouseDoubleClick |
前者 = 仅双击项 生效;后者 = 双击任意位置生效(推荐用前者) |
ItemCheck vs ItemChecked |
前者 =勾选前 (可取消);后者 =勾选后(仅处理结果) |
- 多选场景下,
SelectedIndexChanged会多次触发,务必加非空判断;- 右键菜单必须用
MouseClick事件,判断e.Button == MouseButtons.Right;- 用复选框→开
CheckBoxes = true,用标签编辑→开LabelEdit = true;- 自定义绘制→必须开
OwnerDraw = true,否则绘制事件不生效。
四常用代码操作
在 Details(详细信息)模式下,ListView 的操作主要围绕着"行(Item)"和"子项(SubItem)"展开。
1. 添加数据(最基础操作)
在 ListView 中,每一行被称为一个 ListViewItem。第一列是"本体",后面的列都是它的"子项"。
方法1
cs
// 1. 创建一行,并设置第一列的内容(比如序号或姓名)
ListViewItem item = new ListViewItem("1");
// 2. 给这一行添加后续列的数据
item.SubItems.Add("张三"); // 第二列
item.SubItems.Add("25"); // 第三列
// 3. 把这一行正式塞进控件里
listView1.Items.Add(item);
方法2 字符串数组
直接用字符串数组一次性创建所有列。
cs
listView1.View = View.Details;
listView1.Columns.Add("ID", 80);
listView1.Columns.Add("姓名", 120);
listView1.Columns.Add("年龄", 80);
listView1.Items.Add(new ListViewItem(new[] {"1","张飞","20"}));
方法3:循环添加多行(批量添加)
从数组、集合里循环加数据(最常用)
cs
listView1.View = View.Details;
listView1.Columns.Add("ID", 80);
listView1.Columns.Add("姓名", 120);
listView1.Columns.Add("年龄", 80);
// 模拟多条数据
string[,] data = {
{ "1", "张三", "20" },
{ "2", "李四", "21" },
{ "3", "王五", "22" }
};
for (int i = 0; i < data.GetLength(0); i++)
{
listView1.Items.Add(new ListViewItem(new[]
{
data[i,0],
data[i,1],
data[i,2]
} ));
}
方法4 :先创建 item,再批量加入(性能高)
先把所有行创建好,最后一次性加入。
cs
List<ListViewItem> listItems = new List<ListViewItem>();
listItems.Add(new ListViewItem(new[] { "2", "张三", "20" }));
listItems.Add(new ListViewItem(new[] { "3", "张四", "22" }));
//一次性加入
listView1.Items.AddRange(listItems.ToArray());//只接受数组
✅ 数据量大时界面不卡顿
方法5:绑定对象集合(高级用法,项目最常用)
把自己的类(如 User)自动转成 ListView 行。
cs
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
// 模拟数据源
var users = new List<User>
{
new User{Id=1, Name="张三", Age=20},
new User{Id=2, Name="李四", Age=21}
};
// 绑定到 ListView
foreach (var user in users)
{
// ✅ 用 ListViewItem 包装数组,Add 方法就能正确识别了
listView1.Items.Add(new ListViewItem(new[]
{
user.Id.ToString(),
user.Name,
user.Age.ToString()
}));
}
✅ 真正项目开发必用
2.带图标添加
如果你绑定了 SmallImageList,代码里只需要多传一个索引号:
cs
// 参数1:文字;参数2:ImageList 里的图片编号 (从 0 开始)
ListViewItem item = new ListViewItem("1", 0);
item.SubItems.Add("李四");
item.SubItems.Add("20");
listView1.Items.Add(item);
3. 获取用户选中的是哪一行
当用户点击了某一行,你可能想知道他选的是谁。注意:ListView 默认支持多选,所以选中的结果是一个集合。
cs
// 判断用户是否真的选中了东西
if (listView1.SelectedItems.Count > 0)
{
// 获取选中的第一行
ListViewItem selectedRow = listView1.SelectedItems[0];
// 获取这一行的文字(第一列)
string id = selectedRow.Text;
// 获取第二列(姓名)
string name = selectedRow.SubItems[1].Text;
MessageBox.Show("你选中了:" + name);
}
4. 修改和删除数据
修改:直接定位到某一行进行赋值。
cs
// 把选中的那一行的姓名改掉
listView1.SelectedItems[0].SubItems[1].Text = "王五";
删除选中行:
cs
foreach (ListViewItem item in listView1.SelectedItems)
{
listView1.Items.Remove(item);
}
清空全表:
cs
listView1.Items.Clear();
ListViewItem
1.是什么
ListViewItem 是 WinForms 里专门给 ListView 控件用的 "行对象" ,相当于 ListView 里的一行数据。
- 它本身不是一个 "列表容器",而是用来表示列表里的单个条目。
- 它的核心作用:
- 存这一行的主文本(你代码里的
"文本") - 存这一行的图标索引、子项(SubItems)、分组信息、状态(选中 / 高亮)等
- 是
ListView和它的数据行之间的桥梁
- 存这一行的主文本(你代码里的
2.本质
ListViewItem是.NET系统提取写好的一个类,它提取定义好了一堆属性,专门用来存和ListView行相关的信息:
Text:显示的文字ImageIndex:图标索引SubItems:多列数据Tag:附加数据Font、ForeColor:样式Group:归属哪个分组
-
它不负责存储整个列表 ,只负责存储一行的所有信息。
-
它不存储列表,列表存在别的地方
- 装所有行 的容器是:
listView.Items - 装一行多列 的容器是:
item.SubItems
- 装所有行 的容器是:
UI框架交互与数据模型设计
核心架构思想
这是职业程序员的"基本功"。它讲究的是"分工明确":
-
M (Model - 模型) :就是你的
User类。它只负责存数据,不管界面长啥样。 -
V (View - 视图) :就是你的
ListView。它只负责显示,不存数据。 -
C/VM (控制器/视图模型):就是你写的那些逻辑,负责把模型里的数据"翻译"给视图看。
为什么要分层? 假设哪天老板说:"咱们不用
ListView了,换成别的控件。"如果你用了高级做法,你只需要改"翻译"的那部分代码,你的User类和业务逻辑完全不用动。
| 特性 | 普通做法 (手动改界面) | 高级做法 (数据驱动) |
|---|---|---|
| 工作量 | 每次改数据都要手动改界面代码 | 只管改数据,界面自动更新 |
| 准确性 | 容易漏掉界面更新,导致数据不一致 | 仓库和货架永远同步 |
| 扩展性 | 想加个"保存到 JSON"功能会很乱 | 只需要在仓库变动时加一行保存代码 |
第一步:定义你的"商品"是什么(User类)
先告诉电脑,"用户"长什么样。
cs
public class User
{
public int Id { get; set; } // 序号
public string Name { get; set; } // 姓名
public int Age { get; set; } // 年龄
}
第二步:建立"智能仓库" (BindingList)
BindingList<User> 和普通 List 的区别在于:它自带"报警器"。
cs
// 在窗体类里声明
BindingList<User> users = new BindingList<User>();
作用:
List<User>(普通列表) 你可以把它想象成一个普通仓库:
- 你可以往里面放东西(
Add)、拿东西(Remove)- 但仓库自己不会主动通知别人"我变了!"
- 比如你在代码里加了个用户,界面上的表格 / 列表控件根本不知道,不会自动刷新
BindingList<User>(绑定列表) 它就是个带自动报警器的智能仓库:
- 除了放东西、拿东西,它自带一个
ListChanged事件(就是 "报警器")- 只要你往里面加 / 删 / 改数据,它就会自动触发这个事件,通知绑定它的界面控件:"喂,我变了,你快更新显示!"
第三步:连接"仓库"与"货架" (数据变更事件)
这是最关键的逻辑,我们要告诉程序:一旦仓库有变动,就去刷新货架。
监听变动:
cs
// 在窗体初始化时绑定
users.ListChanged += Users_ListChanged;
编写"刷货架"的方法 (RenderRow):
cs
private void Users_ListChanged(object sender, ListChangedEventArgs e)
{
// 简单粗暴的方法:先清空货架,再根据仓库里的东西重新摆
listView1.Items.Clear();
foreach (var user in users)
{
ListViewItem item = new ListViewItem(user.Id.ToString());
item.SubItems.Add(user.Name);
item.SubItems.Add(user.Age.ToString());
listView1.Items.Add(item);
}
}
第四步:操作"仓库"即可
有了上面的自动化连接,你以后再也不用写 listView1.Items.Add 这种代码了。
添加数据:
cs
// 你只管往仓库里加人,界面会自动跳出新行!
users.Add(new User { Id = 1, Name = "张三", Age = 25 });
删除数据:
cs
// 你只管从仓库里移除,界面会自动消失那一行!
users.RemoveAt(0);


