TreeView 節點資料來自資料庫、Web API 或檔案系統,展開時需耗時查詢。若在 UI 執行緒同步載入,介面會凍結;若直接啟動背景執行緒更新 UI,會觸發跨執行緒例外。解法:先顯示暫時節點「正在載入...」,背景執行緒查詢完成後,透過 Invoke 替換為真實子節點。
using System;
using System.Threading;
using System.Windows.Forms;
public partial class AsyncTreeViewForm : Form
{
public AsyncTreeViewForm()
{
InitializeComponent();
treeView1.AfterSelect += TreeView1_AfterSelect;
// 初始根節點
treeView1.Nodes.Add(new TreeNode("根節點") { Tag = "" });
}
private void TreeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
// 已載入過(Tag != null/空)或非葉節點不重複載入
if (e.Node.Tag != null && e.Node.Tag.ToString() != "") return;
// 1. 標記正在載入,防止重複觸發
e.Node.Tag = "Loading";
// 2. 先加入暫時節點,給使用者即時回饋
var loadingNode = new TreeNode("正在載入...") { ForeColor = Color.Gray };
e.Node.Nodes.Add(loadingNode);
e.Node.Expand(); // 展開顯示暫時節點
// 3. 背景執行緒取得資料
ThreadPool.QueueUserWorkItem(_ =>
{
// 模擬耗時查詢(實際換成 DB/Web API 呼叫)
Thread.Sleep(1500);
var children = new[] { "子節點 A", "子節點 B", "子節點 C", "子節點 D" };
// 4. 必須在 UI 執行緒更新 TreeView
treeView1.Invoke(new Action(() =>
{
// 移除暫時節點
e.Node.Nodes.Clear();
// 加入真實子節點
foreach (var name in children)
{
e.Node.Nodes.Add(new TreeNode(name) { Tag = "" }); // Tag="" 表示可再展開
}
// 標記已載入完成
e.Node.Tag = "Loaded";
}));
});
}
}
private void LoadChildren(TreeNode parent, Func
| 技巧 | 說明 |
|---|---|
Tag 狀態機 | ""=未載入, "Loading"=載入中, "Loaded"=完成,防重複請求 |
| 先加暫時節點 | 使用者立即看到回饋,體驗優於空白等待 |
Invoke/BeginInvoke | 跨執行緒安全更新 UI,Invoke 同步阻塞,BeginInvoke 非同步 |
ThreadPool.QueueUserWorkItem | 輕量背景執行緒,避免手動 new Thread 產生過多執行緒 |
BeforeExpand 事件 | 比 AfterSelect 更適合「展開時載入」語意 |
TreeView.VirtualMode(需自訂繪製),或分頁載入。Tag 物件中,避免重複查詢。CancellationToken 傳入背景任務。async/await + Task.Run,程式碼更線性易讀。private async void TreeView1_BeforeExpand(object sender, TreeViewCancelEventArgs e)
{
if (e.Node.Tag?.ToString() == "Loaded") return;
e.Node.Tag = "Loading";
e.Node.Nodes.Clear();
e.Node.Nodes.Add(new TreeNode("正在載入...") { ForeColor = Color.Gray });
e.Node.Expand();
string[] children = await Task.Run(() => GetFileSystemChildren(e.Node.FullPath));
e.Node.Nodes.Clear();
foreach (var name in children)
e.Node.Nodes.Add(new TreeNode(name) { Tag = "" });
e.Node.Tag = "Loaded";
}
TreeView 非同步載入核心在於:狀態標記防重入 + 暫時節點給回饋 + 背景查詢 + UI 執行緒安全替換。本文提供傳統 Invoke 與現代 async/await 兩種寫法,可直接套用於資料庫樹狀結構、檔案總管、組織架構圖等場景,大幅提升介面流暢度與使用者體驗。