上手C# WinForms:手把手教你写一个Windows任务管理器
兔子个人的学习笔记,希望对你有帮助。
一、准备工作:环境与项目创建
1. 开发环境
- Visual Studio 2019/2022(社区版免费即可)
- 安装「.NET桌面开发」工作负载
- 目标框架:.NET Framework 4.7.2 及以上(新手最稳的版本)
2. 创建项目
- 打开VS → 新建项目 → 选择「Windows 窗体应用(.NET Framework)」
- 项目名称命名为任意你喜欢的,这里兔子喜欢用日期+名称作为文件夹名
选择保存路径,D盘啦,E盘啦看你哪个方便,或者直接放在VS的安装文件夹下。
- 点击创建,自动生成一个空白窗体
Form1
二、界面搭建:拖拽控件即可完成的布局
我们不用写一行布局代码,全靠拖拽+属性设置就能完成专业的界面。先看最终用到的控件清单:
| 控件类型 | 控件名称(Name) | 作用 | 关键属性设置 |
|---|---|---|---|
| ListView | listViewProcess | 显示进程列表 | View=Details;FullRowSelect=True;HideSelection=False |
| Button | btnRefresh | 手动刷新 | Text=刷新进程 |
| Button | btnKill | 结束进程 | Text=结束进程 |
| TextBox | txtSearch | 搜索输入 | 清空默认Text |
| Button | btnSearch | 查询按钮 | Text=查询进程 |
| ComboBox | cboAppPath | 启动程序选择 | DropDownStyle=DropDown;Items预设notepad.exe、calc.exe |
| Button | btnStartApp | 启动按钮 | Text=启动应用程序 |
| Label | lblCount | 显示进程数量 | Text=进程总数:0 |
| Timer | timerRefresh | 自动刷新 | Interval=2000;Enabled=True |
布局步骤(自己觉得怎么好看怎么来)
接下来我们打开工具箱,一般在左侧,如果没有可以在视图里找,或者Ctrl+w,在点x
- 上方横向排列:刷新按钮(Button)、结束按钮(Button)、搜索输入框(TextBox)、查询按钮(Button)、程序下拉框(ComboBox)、启动按钮(Button),接下来在工具箱拖动或双击这些控件。[有四个按钮,可以拖一个,剩下直接选择复制]
选中任意控件,右击或按F4进入属性,找到Text可改变显示文字
2.中间大区域放listViewProcess,占满窗体下方
- 选中ListView → 右键「编辑列」,添加三列:PID(80px)、进程名称(160px)、内存占用(120px)
- 选中ListView → 属性里找到
Anchor,设置为Top, Left, Right, Bottom,窗体会自动缩放
有人会,兔子兔子,我添加了列,为什么界面还是空白
答:ListView默认是大图标显示模式,只有Details模式才会显示列标题和列分布
最后一个Label同理,选中这个标签,按F4打开属性窗口,修改属性:
Name:改成lblCount(代码里用这个名字更新文字)Text:改成进程总数:0(程序启动前的初始文字)AutoSize:设为True(文字长度变化时自动调整宽度,不会被截断
补充:Timer是组件,拖入窗体后不会显示在界面上,会出现在设计界面底部的灰色组件里
💡 新手提示:所有事件方法都通过「双击控件」自动生成,不要手写方法名,能避免90%的绑定错误。
三、核心功能:从0到1实现进程管理
3.1 先引入必要的命名空间
打开Form1.cs,在顶部using区域添加一行,所有进程操作都在这个命名空间里:
using System.Diagnostics;3.2 加载系统进程列表
这是最核心的基础方法,后续所有刷新、查询都会复用它。原理是调用系统APIProcess.GetProcesses()获取所有进程,再逐条添加到ListView。
private void LoadAllProcess() { listViewProcess.BeginUpdate(); // 暂停绘制,防闪烁 //记得把listView构件name改成listViewProcess,不然会报错 // 记录刷新前的选中项和滚动位置 int? selectedPid = null; int? topPid = null; //int?:可空整数类型 if (listViewProcess.SelectedItems.Count > 0) selectedPid = int.Parse(listViewProcess.SelectedItems[0].Text); if (listViewProcess.TopItem != null) topPid = int.Parse(listViewProcess.TopItem.Text); listViewProcess.Items.Clear(); Process[] allProcess = Process.GetProcesses(); lblCount.Text = $"进程总数:{allProcess.Length}"; foreach (Process p in allProcess) { try { ListViewItem item = new ListViewItem(p.Id.ToString()); item.SubItems.Add(p.ProcessName); item.SubItems.Add((p.WorkingSet64 / 1024.0 / 1024.0).ToString("0.00")); listViewProcess.Items.Add(item); } catch { /* 跳过权限不足的系统进程 */ } } // 恢复选中状态和滚动位置 if (selectedPid.HasValue) { foreach (ListViewItem item in listViewProcess.Items) { if (item.Text == selectedPid.Value.ToString()) { item.Selected = true; break; } } } if (topPid.HasValue) { foreach (ListViewItem item in listViewProcess.Items) { if (item.Text == topPid.Value.ToString()) { listViewProcess.TopItem = item; break; } } } listViewProcess.EndUpdate(); // 一次性重绘 }窗体启动时自动调用:双击窗体空白处生成Form1_Load事件,写入:
private void Form1_Load(object sender, EventArgs e) { LoadAllProcess(); }3.3 按名称查询进程
支持模糊查询、不区分大小写,输入为空时显示全部进程。
// 类成员变量:记录查询状态 private bool isSearchMode = false; private string currentKeyword = ""; private void btnSearch_Click(object sender, EventArgs e) { string keyword = txtSearch.Text.Trim(); currentKeyword = keyword; isSearchMode = keyword.Length > 0; listViewProcess.BeginUpdate(); // 保存刷新前的状态 int? selectedPid = null; int? topPid = null; if (listViewProcess.SelectedItems.Count > 0) selectedPid = int.Parse(listViewProcess.SelectedItems[0].Text); if (listViewProcess.TopItem != null) topPid = int.Parse(listViewProcess.TopItem.Text); listViewProcess.Items.Clear(); Process[] allProcess = Process.GetProcesses(); int matchCount = 0; foreach (Process p in allProcess) { try { // 关键词为空 或 进程名包含关键词(不区分大小写) if (keyword.Length == 0 || p.ProcessName.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0) { ListViewItem item = new ListViewItem(p.Id.ToString()); item.SubItems.Add(p.ProcessName); item.SubItems.Add((p.WorkingSet64 / 1024.0 / 1024.0).ToString("0.00")); listViewProcess.Items.Add(item); matchCount++; } } catch { } } lblCount.Text = $"匹配:{matchCount} / 总数:{allProcess.Length}"; // 恢复状态 if (selectedPid.HasValue) { foreach (ListViewItem item in listViewProcess.Items) { if (item.Text == selectedPid.Value.ToString()) { item.Selected = true; break; } } } if (topPid.HasValue) { foreach (ListViewItem item in listViewProcess.Items) { if (item.Text == topPid.Value.ToString()) { listViewProcess.TopItem = item; break; } } } listViewProcess.EndUpdate(); }3.4 结束指定进程
通过PID精准定位进程,调用Kill()方法强制结束,加异常捕获避免权限不足崩溃。
private void btnKill_Click(object sender, EventArgs e) { if (listViewProcess.SelectedItems.Count == 0) { MessageBox.Show("请先选中一个进程!"); return; } // 直接取第一列文本就是PID string pidStr = listViewProcess.SelectedItems[0].Text; int pid = int.Parse(pidStr); try { Process target = Process.GetProcessById(pid); target.Kill(); MessageBox.Show("进程已结束!"); LoadAllProcess(); } catch (Exception ex) { MessageBox.Show("无法结束进程:" + ex.Message); } }⚠️ 测试建议:用记事本(notepad)、计算器(calc)测试,不要随意结束系统核心进程。
3.5 启动新应用程序
同一个Process类不仅能结束进程,还能启动程序,支持程序名、完整路径、甚至网址。
private void btnStartApp_Click(object sender, EventArgs e) { string appName = cboAppPath.Text.Trim(); if (string.IsNullOrEmpty(appName) || appName == "请选择你需要打开的应用程序") { MessageBox.Show("请选择或输入要启动的程序!"); return; } try { Process.Start(appName); } catch (Exception ex) { MessageBox.Show("启动失败:" + ex.Message); } }四、进阶体验:让任务管理器更好用
4.1 自动定时刷新
双击设计界面底部的timerRefresh组件,写入定时刷新逻辑,自动判断当前模式:
private void timerRefresh_Tick(object sender, EventArgs e) { if (isSearchMode) { // 查询模式下保持筛选 btnSearch_Click(sender, e); } else { // 普通模式刷新全部 LoadAllProcess(); } }4.2 双缓冲彻底解决闪烁
ListView默认双缓冲是关闭的,用反射强制开启,刷新会平滑很多。在窗体构造函数里添加:
public Form1() { InitializeComponent(); // 开启ListView双缓冲 typeof(ListView).InvokeMember("DoubleBuffered", System.Reflection.BindingFlags.SetProperty | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null, listViewProcess, new object[] { true }); }五、新手常见坑与解决方案
1. 报错 CS0111:已定义同名方法
原因:手动写了事件方法,又双击控件生成了一遍,导致重复定义。解决:删掉重复的方法,所有事件都通过「双击设计界面控件」自动生成。
2. 点击按钮没反应
原因:按钮的Click事件没绑定到对应的方法。解决:选中按钮 → 属性窗口点⚡闪电图标 → 找到Click行 → 下拉选择对应方法。
3. 刷新后选中行不见了
原因:清空+重建所有项,选中状态自然丢失。解决:刷新前记录PID,刷新完重新定位选中,本文代码已内置该逻辑。
4. 列表刷新一闪一闪
原因:每添加一行就重绘一次,几十次连续重绘造成闪烁。解决:用BeginUpdate+EndUpdate批量更新,配合双缓冲。
5. CS0103报错
原因:界面没有加上对应控件,或对应控件name属性没有修改
六、总结与扩展
你已经学到的
- WinForms常用控件的布局与属性设置
System.Diagnostics.Process类的核心用法:获取、启动、结束进程- 事件驱动编程的基本思想
- 界面性能优化的通用思路:批量更新、双缓冲、状态保持
可以继续扩展的功能
- 右键菜单:添加「打开文件位置」「结束进程树」
- 实时显示CPU、内存使用率
- 进程排序:点击列标题按PID/名称/内存排序
- 最小化到系统托盘
- 自定义皮肤与背景图
整个项目写下来不到300行代码,但涵盖了WinForms开发最核心的流程:界面设计→事件绑定→逻辑编写→性能优化。非常适合作为C#桌面开发的入门练手项目,做完之后再去做其他WinForms程序都会得心应手。