首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

WinForms 左右分欄界面進階實現:TreeView + 自訂分割器的輕量級方案

     編輯:關於C語言

一、方案對比與設計思路

上篇使用 `SplitContainer` 實現左右分欄,功能完整但代碼較多。本文提供更輕量的方案:

  • 父視窗啟用 MDI:`IsMdiContainer = true`
  • 頂部 `MenuStrip` + `MdiWindowListItem` 自動管理 Windows 選單
  • 左側 `TreeView` 設 `Dock = Left` 作為導航
  • 一個寬度 3px 的 `Panel` 作為**自訂分割器**(紅色背景、VSplit 游標)
  • 滑鼠事件手動實現拖曳調整 TreeView 寬度

優點:代碼極簡、無 SplitContainer 開銷、子視窗最大化時標題列**自動併入父視窗選單列**(標準 MDI 行為),視覺更原生。

二、視覺化設計器設置

  1. 父視窗 `IsMdiContainer = true`
  2. `MenuStrip` 加入 `WindowsToolStripMenuItem`,設 `MdiWindowListItem = WindowsToolStripMenuItem`
  3. `TreeView` 設 `Dock = Left`、`Margin = 0`
  4. `Panel` 設 `Width = 3`、`BackColor = Red`、`Cursor = VSplit`、`Anchor = Top, Bottom, Left`

三、核心代碼實現

1. 建構函式:初始化分割器位置與事件訂閱

public ParentForm()
{
    InitializeComponent();

    // 分割器滑鼠事件
    panel1.MouseDown += Panel1_MouseDown;
    panel1.MouseMove += Panel1_MouseMove;
    panel1.MouseUp += Panel1_MouseUp;

    // 初始位置:緊貼 TreeView 右側
    panel1.Top = menuStrip1.Height;
    panel1.Left = treeView1.Left + treeView1.Width;
    panel1.Height = panel1.Parent.Height;

    // 視窗大小改變時同步分割器高度
    Resize += ParentForm_Resize;
}

2. 分割器拖曳邏輯(三個滑鼠事件)

private bool _isDragging = false;

private void Panel1_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left) _isDragging = true;
}

private void Panel1_MouseMove(object sender, MouseEventArgs e)
{
    if (_isDragging)
    {
        panel1.Left += e.X; // 累加滑鼠水平位移
    }
}

private void Panel1_MouseUp(object sender, MouseEventArgs e)
{
    if (_isDragging)
    {
        panel1.Left += e.X;
        _isDragging = false;
        // 關鍵:同步 TreeView 寬度
        treeView1.Width = panel1.Left;
    }
}

3. 視窗 Resize 同步分割器高度

private void ParentForm_Resize(object sender, EventArgs e)
{
    panel1.Height = panel1.Parent.Height;
}

4. TreeView 雙擊節點開啟子視窗

private void ParentForm_Load(object sender, EventArgs e)
{
    LoadMenuNodes(); // 動態載入導航節點
}

private void LoadMenuNodes()
{
    treeView1.Nodes.Clear();
    var root = treeView1.Nodes.Add("Root");
    var rand = new Random();

    for (int i = 1; i <= 10; i++)
    {
        var section = root.Nodes.Add($"Section-{i}");
        int maxNodes = rand.Next(1, 10);
        for (int n = 1; n <= maxNodes; n++)
        {
            section.Nodes.Add($"Level-{i}-{n}");
        }
    }
    root.ExpandAll();
}

private void TreeView1_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
{
    // 僅葉子節點(無子節點)觸發開啟
    if (e.Node.Nodes.Count == 0)
        ShowChildForm<ChildForm>();
}

5. 通用子視窗開啟方法

private void ShowChildForm<TForm>() where TForm : Form, new()
{
    var child = new TForm
    {
        MdiParent = this,
        Name = "ChildForm - " + DateTime.Now.Millisecond,
        Text = "ChildForm - " + DateTime.Now.Millisecond
    };
    child.Show();
}

四、完整代碼整合

using System;
using System.Windows.Forms;

namespace WinFormTest
{
    public partial class ParentForm : Form
    {
        private bool _isDragging = false;

        public ParentForm()
        {
            InitializeComponent();
            panel1.MouseDown += Panel1_MouseDown;
            panel1.MouseMove += Panel1_MouseMove;
            panel1.MouseUp += Panel1_MouseUp;
            panel1.Top = menuStrip1.Height;
            panel1.Left = treeView1.Left + treeView1.Width;
            panel1.Height = panel1.Parent.Height;
            Resize += ParentForm_Resize;
        }

        private void Panel1_MouseDown(object sender, MouseEventArgs e) 
            => _isDragging = e.Button == MouseButtons.Left;

        private void Panel1_MouseMove(object sender, MouseEventArgs e)
        {
            if (_isDragging) panel1.Left += e.X;
        }

        private void Panel1_MouseUp(object sender, MouseEventArgs e)
        {
            if (_isDragging)
            {
                panel1.Left += e.X;
                _isDragging = false;
                treeView1.Width = panel1.Left;
            }
        }

        private void ParentForm_Resize(object sender, EventArgs e)
            => panel1.Height = panel1.Parent.Height;

        private void ParentForm_Load(object sender, EventArgs e)
            => LoadMenuNodes();

        private void LoadMenuNodes()
        {
            treeView1.Nodes.Clear();
            var root = treeView1.Nodes.Add("Root");
            var rand = new Random();
            for (int i = 1; i <= 10; i++)
            {
                var section = root.Nodes.Add($"Section-{i}");
                int max = rand.Next(1, 10);
                for (int n = 1; n <= max; n++)
                    section.Nodes.Add($"Level-{i}-{n}");
            }
            root.ExpandAll();
        }

        private void TreeView1_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            if (e.Node.Nodes.Count == 0) ShowChildForm<ChildForm>();
        }

        private void ShowChildForm<TForm>() where TForm : Form, new()
        {
            var child = new TForm { MdiParent = this, Name = "ChildForm - " + DateTime.Now.Millisecond };
            child.Text = child.Name;
            child.Show();
        }
    }
}

五、效果展示與兩種方案差異

TreeView + 自訂分割器效果圖

關鍵差異:

特性SplitContainer 方案 (上篇)TreeView + Panel 方案 (本文)
子視窗最大化標題列保留在 Panel2 內部**併入父視窗選單列** (標準 MDI)
代碼量較多 (事件處理、選單同步)極少 (利用內建 MdiWindowListItem)
分割器功能內建、專業手動實現、輕量
擴展性高 (Panel1 可放任意控制項)中 (TreeView 固定左側)

六、選型建議

  • **需標準 MDI 行為、代碼極簡、導航固定為 TreeView** → 本方案最佳。
  • **需靈活佈局、Panel1 放複雜控制項、自訂選單同步邏輯** → SplitContainer 方案更適合。

七、後續展望

下一步將研究 WinForms **外掛式編程架構**(動態載入 DLL、介面隔離、依賴注入),實現模組化業務擴展,敬請期待。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved