企業級權限管理系統的功能維護模組,使用 `TreeView` 展示階層式功能選單,支援節點增刪改、拖曳重組、資料庫同步、明細編輯。資料來源為 Oracle 資料庫,透過 `DataTable` 快取實現離線操作與 UI 即時回饋。
private void InitTreeView(TreeNodeCollection nodes, string parentId) {
DataView view = new DataView(funcDataTable);
view.RowFilter = $"上級功能ID = {parentId}";
foreach (DataRowView row in view) {
TreeNode node = new TreeNode {
Text = row["功能名稱"].ToString(),
Tag = row["功能ID"].ToString()
};
nodes.Add(node);
// 遞迴處理子節點
InitTreeView(node.Nodes, node.Tag.ToString());
}
}
private void SM_FunctionMaintenance_Load(object sender, EventArgs e) {
InitTreeView(trv_Function.Nodes, ROOT_NODE_VALUE); // "0"
trv_Function.SelectedNode = trv_Function.Nodes[0];
trv_Function.Focus();
trv_Function.CollapseAll();
}
private void trv_Function_AfterSelect(object sender, TreeViewEventArgs e) {
DataView view = new DataView(funcDataTable);
view.RowFilter = $"功能ID = {e.Node.Tag}";
foreach (DataRowView row in view) {
txt_Tag.Text = row["功能標識"].ToString();
txt_Name.Text = row["功能名稱"].ToString();
txt_Detail.Text = row["功能簡述"].ToString();
txt_BelongTo.Text = row["所屬窗體"].ToString();
cbo_Type.SelectedValue = row["功能類別"].ToString();
txt_FunEntity.Text = row["功能體"].ToString();
ckb_IsInterface.Checked = row["有無界面"].ToString() == "1";
}
}
private void btn_AddRoot_Click(object sender, EventArgs e) {
var dlg = new SM_AddNewFunction(0, DIC_funcType); // 父ID=0
dlg.ShowDialog();
if (dlg.NewFuncItem.FuncID != -1) {
// 1. 更新 DataTable 快取
DataRow row = funcDataTable.NewRow();
row["功能ID"] = dlg.NewFuncItem.FuncID;
row["功能標識"] = dlg.NewFuncItem.FuncTag;
// ... 其他欄位
funcDataTable.Rows.Add(row);
// 2. 更新 TreeView
TreeNode node = new TreeNode(dlg.NewFuncItem.FuncName) { Tag = dlg.NewFuncItem.FuncID.ToString() };
trv_Function.Nodes.Add(node);
trv_Function.SelectedNode = node;
}
trv_Function.Focus();
}
private void btn_AddSon_Click(object sender, EventArgs e) {
if (trv_Function.SelectedNode == null) { MessageBox.Show("請選擇父節點"); return; }
long parentId = long.Parse(trv_Function.SelectedNode.Tag.ToString());
var dlg = new SM_AddNewFunction(parentId, DIC_funcType);
dlg.ShowDialog();
if (dlg.NewFuncItem.FuncID != -1) {
// 更新 DataTable
DataRow row = funcDataTable.NewRow();
// ... 填入欄位,含 上級功能ID = parentId
funcDataTable.Rows.Add(row);
// 更新 TreeView
TreeNode node = new TreeNode(dlg.NewFuncItem.FuncName) { Tag = dlg.NewFuncItem.FuncID.ToString() };
trv_Function.SelectedNode.Nodes.Add(node);
trv_Function.SelectedNode = node;
}
trv_Function.Focus();
}
private void btn_Modify_Click(object sender, EventArgs e) {
if (trv_Function.SelectedNode == null) return;
long id = long.Parse(trv_Function.SelectedNode.Tag.ToString());
FunctionTable item = QueryFuncItem(id);
var dlg = new SM_EditFunction(item, DIC_funcType);
dlg.ShowDialog();
if (dlg.IsEdit) {
// 1. 更新 DataTable
DataView view = new DataView(funcDataTable);
view.RowFilter = $"功能ID = {id}";
foreach (DataRowView row in view) {
row["功能標識"] = dlg.NewFuncItem.FuncTag;
row["功能名稱"] = dlg.NewFuncItem.FuncName;
// ... 其他欄位
}
// 2. 更新 TreeView 顯示文字
trv_Function.SelectedNode.Text = dlg.NewFuncItem.FuncName;
// 3. 同步明細區
txt_Name.Text = dlg.NewFuncItem.FuncName;
// ... 其他控制項
}
trv_Function.Focus();
}
private void btn_Remove_Click(object sender, EventArgs e) {
if (trv_Function.SelectedNode == null) return;
if (MessageBox.Show("確定刪除?") != DialogResult.Yes) return;
// 1. 決定刪除後焦點節點
TreeNode nextFocus = CalculateNextFocusNode();
// 2. 收集待刪除節點 ID(含所有後代)
var ids = CollectNodeIds(trv_Function.SelectedNode);
// 3. 執行資料庫刪除(交易)
foreach (long id in ids) DeleteFuncItem(id);
// 4. 更新 DataTable
foreach (long id in ids) {
for (int i = 0; i < funcDataTable.Rows.Count; i++) {
if (funcDataTable.Rows[i]["功能ID"].ToString() == id.ToString()) {
funcDataTable.Rows[i].Delete();
break;
}
}
funcDataTable.AcceptChanges();
}
// 5. 移除 TreeView 節點
trv_Function.Nodes.Remove(trv_Function.SelectedNode);
trv_Function.SelectedNode = nextFocus;
trv_Function.Focus();
}
// 收集節點及所有後代 ID
private long[] CollectNodeIds(TreeNode node) {
var list = new List { long.Parse(node.Tag.ToString()) };
foreach (TreeNode child in node.Nodes)
list.AddRange(CollectNodeIds(child));
return list.ToArray();
}
private void trv_Function_ItemDrag(object sender, ItemDragEventArgs e) {
if (e.Button == MouseButtons.Left)
DoDragDrop(e.Item, DragDropEffects.Move);
}
private void trv_Function_DragDrop(object sender, DragEventArgs e) {
TreeNode moveNode = (TreeNode)e.Data.GetData(typeof(TreeNode));
Point pt = trv_Function.PointToClient(new Point(e.X, e.Y));
TreeNode targetNode = trv_Function.GetNodeAt(pt);
if (IsDragValid(targetNode, moveNode)) {
if (targetNode != moveNode) {
trv_Function.Nodes.Remove(moveNode);
if (targetNode == null)
trv_Function.Nodes.Insert(trv_Function.Nodes.Count, moveNode); // 移至根
else
targetNode.Nodes.Add(moveNode); // 移至目標子節點
// 更新 DataTable
string newParentId = targetNode?.Tag.ToString() ?? ROOT_NODE_VALUE;
DataView view = new DataView(funcDataTable);
view.RowFilter = $"功能ID = {moveNode.Tag}";
foreach (DataRowView row in view)
row["上級功能ID"] = newParentId;
// 資料庫同步(交易)
SyncParentIdToDatabase(moveNode.Tag.ToString(), newParentId);
trv_Function.SelectedNode = moveNode;
}
}
}
// 防止拖曳到自己的子孫節點(會造成循環)
private bool IsDragValid(TreeNode target, TreeNode source) {
while (target != null) {
if (target == source) return false;
target = target.Parent;
}
return true;
}
private void trv_Function_DragEnter(object sender, DragEventArgs e) => e.Effect = DragDropEffects.Move;
private void trv_Function_DragOver(object sender, DragEventArgs e) {
Point pt = trv_Function.PointToClient(new Point(e.X, e.Y));
TreeNode node = trv_Function.GetNodeAt(pt);
trv_Function.SelectedNode = node;
trv_Function.Focus();
}
private void DeleteFuncItem(long funcId, string funcName) {
using (var conn = new OracleConnection(connStr))
using (var tran = conn.BeginTransaction()) {
try {
var cmd = conn.CreateCommand();
cmd.Transaction = tran;
cmd.CommandText = $"DELETE FROM 功能 WHERE 功能ID = {funcId}";
cmd.ExecuteNonQuery();
cmd.CommandText = $"DELETE FROM 角色功能 WHERE 功能ID = {funcId}";
cmd.ExecuteNonQuery();
cmd.CommandText = $"DELETE FROM 操作員授權 WHERE 功能ID = {funcId}";
cmd.ExecuteNonQuery();
cmd.CommandText = $"UPDATE 界面定制 SET 功能ID=NULL, 節點名稱='{funcName}(已收回)' WHERE 功能ID={funcId}";
cmd.ExecuteNonQuery();
tran.Commit();
} catch {
tran.Rollback();
throw;
}
}
}
private void SyncParentIdToDatabase(string funcId, string newParentId) {
using (var conn = new OracleConnection(connStr))
using (var tran = conn.BeginTransaction()) {
try {
var cmd = conn.CreateCommand();
cmd.Transaction = tran;
cmd.CommandText = $"UPDATE 功能 SET 上級功能ID={newParentId} WHERE 功能ID={funcId}";
cmd.ExecuteNonQuery();
tran.Commit();
} catch {
tran.Rollback();
throw;
}
}
}
| 模式 | 應用處 | 效益 |
|---|---|---|
| DataTable 離線快取 | funcDataTable | 減少 DB 往返、支援 RowFilter 快速查詢 |
| 遞迴建樹 | InitTreeView | 任意深度階層自動建構 |
| Tag 屬性綁定實體 ID | TreeNode.Tag | UI 與資料模型雙向對應 |
| 拖曳事件組合 | ItemDrag/DragEnter/DragOver/DragDrop | 完整拖曳體驗 |
| 循環檢查 | IsDragValid | 防止樹狀結構破壞 |
| 交易包裝 | DeleteFuncItem/SyncParentId | 資料一致性保證 |
| 焦點管理 | CalculateNextFocusNode | 操作流暢不中斷 |
此實作展示了 WinForms `TreeView` 進階應用的完整模式:從資料載入、樹狀建構、CRUD 操作、拖曳重組到資料庫交易同步。核心在於 **UI 樹狀結構與資料關係模型的強一致性維護**,透過 `Tag` 綁定 ID、`RowFilter` 快速定位、遞迴處理子樹、交易保證原子性。此模式廣泛適用於選單管理、組織架構、分類目錄等階層式資料維護場景。