透過 System.Diagnostics.Process 可建立類似 Windows 工作管理員的進程監控工具。本文實作進程列表即時刷新、資源使用率顯示、右鍵選單操作 (結束進程、開啟檔案位置、屬性)。
public class ProcessInfo
{
public int Id { get; set; }
public string Name { get; set; }
public string ProcessName { get; set; }
public long WorkingSet64 { get; set; } // 實體記憶體
public long VirtualMemorySize64 { get; set; } // 虛擬記憶體
public TimeSpan TotalProcessorTime { get; set; }
public int BasePriority { get; set; }
public bool Responding { get; set; }
public string FilePath { get; set; }
public string CompanyName { get; set; }
public string Description { get; set; }
public DateTime StartTime { get; set; }
public ProcessThreadCollection Threads { get; set; }
public ProcessModuleCollection Modules { get; set; }
// 格式化顯示用
public string WorkingSetDisplay => FormatBytes(WorkingSet64);
public string VirtualMemoryDisplay => FormatBytes(VirtualMemorySize64);
public string CpuTimeDisplay => TotalProcessorTime.ToString(@"hh\:mm\:ss");
private static string FormatBytes(long bytes)
{
string[] units = { "B", "KB", "MB", "GB", "TB" };
int i = 0;
double size = bytes;
while (size >= 1024 && i < units.Length - 1) { size /= 1024; i++; }
return $"{size:0.##} {units[i]}";
}
}
using System.Diagnostics;
using System.Management; // 需參考 System.Management.dll
public static class ProcessMonitor
{
private static readonly string[] _systemProcessNames =
{ "System", "Idle", "Registry", "smss", "csrss", "wininit", "services", "lsass" };
public static List GetAllProcesses()
{
var list = new List();
Process[] processes = Process.GetProcesses();
foreach (Process proc in processes)
{
try
{
// 系統核心進程無法存取詳細資訊,跳過或標記
if (_systemProcessNames.Contains(proc.ProcessName))
{
list.Add(new ProcessInfo
{
Id = proc.Id,
ProcessName = proc.ProcessName,
Name = "[System Process]"
});
continue;
}
proc.Refresh(); // 更新快照
var info = new ProcessInfo
{
Id = proc.Id,
ProcessName = proc.ProcessName,
Name = GetProcessDisplayName(proc),
WorkingSet64 = proc.WorkingSet64,
VirtualMemorySize64 = proc.VirtualMemorySize64,
TotalProcessorTime = proc.TotalProcessorTime,
BasePriority = proc.BasePriority,
Responding = proc.Responding,
FilePath = GetProcessPath(proc),
StartTime = proc.StartTime,
Threads = proc.Threads,
Modules = proc.Modules
};
// 取得檔案版本資訊
if (!string.IsNullOrEmpty(info.FilePath))
{
try
{
var fvi = FileVersionInfo.GetVersionInfo(info.FilePath);
info.CompanyName = fvi.CompanyName;
info.Description = fvi.FileDescription;
}
catch { }
}
list.Add(info);
}
catch (System.ComponentModel.Win32Exception)
{
// Access denied (權限不足)
list.Add(new ProcessInfo
{
Id = proc.Id,
ProcessName = proc.ProcessName,
Name = "[Access Denied]"
});
}
catch (Exception)
{
// 其他錯誤
}
}
return list.OrderBy(p => p.ProcessName).ToList();
}
private static string GetProcessDisplayName(Process proc)
{
try
{
string desc = FileVersionInfo.GetVersionInfo(proc.MainModule.FileName).FileDescription;
return string.IsNullOrEmpty(desc) ? proc.ProcessName : desc;
}
catch { return proc.ProcessName; }
}
private static string GetProcessPath(Process proc)
{
try { return proc.MainModule.FileName; }
catch { return string.Empty; }
}
}
public partial class TaskManagerForm : Form
{
private ListView _listView;
private Timer _refreshTimer;
private int _selectedPid = -1;
private readonly object _updateLock = new();
public TaskManagerForm()
{
InitializeListView();
_refreshTimer = new Timer { Interval = 2000 }; // 2 秒刷新
_refreshTimer.Tick += RefreshTimer_Tick;
_refreshTimer.Start();
LoadProcesses();
}
private void InitializeListView()
{
_listView = new ListView
{
Dock = DockStyle.Fill,
View = View.Details,
FullRowSelect = true,
GridLines = true,
HideSelection = false,
DoubleBuffered = true // 關鍵:防止閃爍 (需反射設定或繼承)
};
_listView.Columns.Add("PID", 60);
_listView.Columns.Add("名稱", 180);
_listView.Columns.Add("進程名", 120);
_listView.Columns.Add("記憶體", 100);
_listView.Columns.Add("虛擬記憶體", 100);
_listView.Columns.Add("CPU 時間", 80);
_listView.Columns.Add("優先序", 60);
_listView.Columns.Add("狀態", 80);
_listView.Columns.Add("路徑", 300);
_listView.MouseClick += ListView_MouseClick;
Controls.Add(_listView);
}
private async void RefreshTimer_Tick(object sender, EventArgs e)
{
await LoadProcessesAsync();
}
private async Task LoadProcessesAsync()
{
// 背景執行緒取得資料
var processes = await Task.Run(() => ProcessMonitor.GetAllProcesses());
// UI 執行緒更新
if (InvokeRequired)
{
Invoke(new Action(() => UpdateListView(processes)));
}
else
{
UpdateListView(processes);
}
}
private void UpdateListView(List processes)
{
lock (_updateLock)
{
_listView.BeginUpdate();
try
{
// 建立現有項目查找表 (Key: PID)
var existingItems = _listView.Items.Cast()
.ToDictionary(item => (int)item.Tag, item => item);
foreach (var proc in processes)
{
if (existingItems.TryGetValue(proc.Id, out var item))
{
// 更新現有項目
UpdateListViewItem(item, proc);
existingItems.Remove(proc.Id);
}
else
{
// 新增項目
var newItem = CreateListViewItem(proc);
_listView.Items.Add(newItem);
}
}
// 移除已結束的進程
foreach (var orphan in existingItems.Values)
orphan.Remove();
// 恢復選取
if (_selectedPid > 0)
{
var selItem = _listView.Items.Cast()
.FirstOrDefault(item => (int)item.Tag == _selectedPid);
if (selItem != null)
{
selItem.Selected = true;
selItem.EnsureVisible();
}
}
}
finally
{
_listView.EndUpdate();
}
}
}
private ListViewItem CreateListViewItem(ProcessInfo proc)
{
var item = new ListViewItem(proc.Id.ToString()) { Tag = proc.Id };
item.SubItems.Add(proc.Name);
item.SubItems.Add(proc.ProcessName);
item.SubItems.Add(proc.WorkingSetDisplay);
item.SubItems.Add(proc.VirtualMemoryDisplay);
item.SubItems.Add(proc.CpuTimeDisplay);
item.SubItems.Add(proc.BasePriority.ToString());
item.SubItems.Add(proc.Responding ? "正在執行" : "未回應");
item.SubItems.Add(proc.FilePath ?? "N/A");
return item;
}
private void UpdateListViewItem(ListViewItem item, ProcessInfo proc)
{
item.SubItems[1].Text = proc.Name;
item.SubItems[2].Text = proc.ProcessName;
item.SubItems[3].Text = proc.WorkingSetDisplay;
item.SubItems[4].Text = proc.VirtualMemoryDisplay;
item.SubItems[5].Text = proc.CpuTimeDisplay;
item.SubItems[6].Text = proc.BasePriority.ToString();
item.SubItems[7].Text = proc.Responding ? "正在執行" : "未回應";
item.SubItems[8].Text = proc.FilePath ?? "N/A";
}
private void ListView_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right && _listView.SelectedItems.Count > 0)
{
_selectedPid = (int)_listView.SelectedItems[0].Tag;
ShowContextMenu(_listView.SelectedItems[0]);
}
}
private void ShowContextMenu(ListViewItem item)
{
var menu = new ContextMenuStrip();
int pid = (int)item.Tag;
menu.Items.Add("結束進程", null, (s, e) => KillProcess(pid));
menu.Items.Add("開啟檔案位置", null, (s, e) => OpenFileLocation(item.SubItems[8].Text));
menu.Items.Add("屬性", null, (s, e) => ShowProperties(item.SubItems[8].Text));
menu.Items.Add("-");
menu.Items.Add("複製 PID", null, (s, e) => Clipboard.SetText(pid.ToString()));
menu.Show(_listView, _listView.PointToClient(Cursor.Position));
}
private void KillProcess(int pid)
{
try
{
var proc = Process.GetProcessById(pid);
proc.Kill();
proc.WaitForExit(2000);
}
catch (Exception ex)
{
MessageBox.Show($"無法結束進程: {ex.Message}");
}
}
private void OpenFileLocation(string filePath)
{
if (string.IsNullOrEmpty(filePath) || filePath == "N/A") return;
try
{
// 關鍵:使用 explorer /select 選中檔案
Process.Start("explorer.exe", $"/select,\"{filePath}\"");
}
catch (Exception ex)
{
MessageBox.Show($"無法開啟位置: {ex.Message}");
}
}
private void ShowProperties(string filePath)
{
if (string.IsNullOrEmpty(filePath) || filePath == "N/A") return;
try
{
// 使用 ShellExecute 顯示檔案屬性對話框
var psi = new ProcessStartInfo
{
FileName = filePath,
Verb = "properties",
UseShellExecute = true
};
Process.Start(psi);
}
catch { /* 屬性動詞不支援時靜默失敗 */ }
}
}
| 問題 | 解決方案 |
|---|---|
| ListView 閃爍 | 設定 DoubleBuffered = true (需反射或繼承)、BeginUpdate/EndUpdate 批次更新 |
| 刷新後選取遺失 | 記錄 SelectedPid,更新後按 PID 恢復選取 |
| 系統進程 Access Denied | try-catch 捕捉 Win32Exception,標記為系統進程 |
| 開啟檔案位置不選中檔案 | 使用 explorer /select,"path" 而非單純開啟資料夾 |
| 高 CPU 佔用 | 刷新間隔 2-5 秒、背景執行緒取資料、僅更新變更項目 |
public class CpuTracker
{
private readonly Dictionary _lastSample = new();
public float GetCpuUsage(ProcessInfo proc)
{
var now = DateTime.UtcNow;
var currentCpu = proc.TotalProcessorTime;
if (_lastSample.TryGetValue(proc.Id, out var last))
{
double cpuMs = (currentCpu - last.CpuTime).TotalMilliseconds;
double elapsedMs = (now - last.Timestamp).TotalMilliseconds;
int coreCount = Environment.ProcessorCount;
// 公式: (CPU時間差 / 經過時間) / 核心數 * 100%
float usage = (float)((cpuMs / elapsedMs) / coreCount * 100);
_lastSample[proc.Id] = (currentCpu, now);
return Math.Clamp(usage, 0, 100 * coreCount);
}
_lastSample[proc.Id] = (currentCpu, now);
return 0;
}
}
任務管理器核心在於:Process.GetProcesses() 枚舉、Process.Refresh() 更新快照、MainModule.FileName 取得路徑、explorer /select 開啟位置。UI 端關鍵是 DoubleBuffered + BeginUpdate/EndUpdate + PID 追蹤選取。現代化可改用 System.Management WMI 查詢或 PerformanceCounter 取得更豐富指標。