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

C# 仿 QQ 面板延伸:水平摺疊拉幕式視窗實作

     編輯:關於C#

前言

將垂直 QQ 面板旋轉 90 度,即成為水平摺疊的「拉幕式」側邊欄。本文實作右側固定寬度按鈕列,點擊展開對應內容面板 (UserControl) 的完整架構,適用於工具箱、屬性面板、側邊導航等場景。

核心控制項:SlidePanel


using System;
using System.Drawing;
using System.Windows.Forms;

public class SlidePanel : Panel
{
    // 設定
    public string[] ModuleTitles { get; set; }
    public UserControl[] ModuleContents { get; set; }
    public int ButtonWidth { get; set; } = 25;
    public Color ButtonBackColor { get; set; } = SystemColors.Control;
    public Color ContentBackColor { get; set; } = Color.AliceBlue;
    public FlatStyle ButtonFlatStyle { get; set; } = FlatStyle.Popup;

    // 狀態
    private Button _expandedButton = null;
    private Panel _contentPanel = null;

    public SlidePanel()
    {
        this.BorderStyle = BorderStyle.None;
    }

    public void Initialize()
    {
        this.Controls.Clear();
        _expandedButton = null;
        _contentPanel = null;

        if (ModuleTitles == null) return;

        for (int i = 0; i < ModuleTitles.Length; i++)
        {
            CreateModuleButton(i);
        }
    }

    private void CreateModuleButton(int index)
    {
        var btn = new Button
        {
            Name = $"ModuleBtn_{index}",
            Text = ModuleTitles[index],
            Width = ButtonWidth,
            Height = this.Height,
            Left = this.Width - ButtonWidth * (ModuleTitles.Length - index),
            FlatStyle = ButtonFlatStyle,
            BackColor = ButtonBackColor,
            Tag = index,
            TextAlign = ContentAlignment.MiddleCenter,
            // 垂直文字 (可選)
            // AutoSize = false,
        };

        btn.Click += ModuleButton_Click;
        this.Controls.Add(btn);
    }

    private void ModuleButton_Click(object sender, EventArgs e)
    {
        Button clickedBtn = (Button)sender;
        int index = (int)clickedBtn.Tag;

        // 點擊已展開的按鈕 -> 收合
        if (_expandedButton == clickedBtn)
        {
            CollapseContent();
            _expandedButton = null;
            return;
        }

        // 展開新內容
        ExpandContent(clickedBtn, index);
    }

    private void ExpandContent(Button mainBtn, int index)
    {
        CollapseContent();

        _expandedButton = mainBtn;

        // 建立內容面板
        _contentPanel = new Panel
        {
            Name = $"ContentPanel_{index}",
            Left = ButtonWidth * (index + 1),
            Top = 0,
            Width = this.Width - ButtonWidth * ModuleTitles.Length,
            Height = this.Height,
            BackColor = ContentBackColor,
            BorderStyle = BorderStyle.FixedSingle
        };

        // 載入對應 UserControl
        if (ModuleContents != null && index < ModuleContents.Length && ModuleContents[index] != null)
        {
            var content = ModuleContents[index];
            content.Dock = DockStyle.Fill;
            _contentPanel.Controls.Add(content);
        }

        this.Controls.Add(_contentPanel);

        // 重新排列按鈕位置
        RearrangeButtons(index);
    }

    private void CollapseContent()
    {
        if (_contentPanel != null)
        {
            this.Controls.Remove(_contentPanel);
            _contentPanel.Dispose();
            _contentPanel = null;
        }

        // 恢復按鈕位置
        for (int i = 0; i < ModuleTitles.Length; i++)
        {
            if (this.Controls[$"ModuleBtn_{i}"] is Button btn)
            {
                btn.Left = ButtonWidth * i;
            }
        }
    }

    private void RearrangeButtons(int expandedIndex)
    {
        for (int i = 0; i < ModuleTitles.Length; i++)
        {
            if (this.Controls[$"ModuleBtn_{i}"] is Button btn)
            {
                if (i <= expandedIndex)
                {
                    btn.Left = ButtonWidth * i;
                }
                else
                {
                    // 往右推
                    btn.Left = this.Width - ButtonWidth * (ModuleTitles.Length - i);
                }
            }
        }
    }

    // 尺寸變更時重新計算
    protected override void OnResize(EventArgs e)
    {
        base.OnResize(e);
        if (_expandedButton != null)
        {
            int index = (int)_expandedButton.Tag;
            _contentPanel.Width = this.Width - ButtonWidth * ModuleTitles.Length;
            _contentPanel.Height = this.Height;
            RearrangeButtons(index);
        }
        else
        {
            for (int i = 0; i < ModuleTitles.Length; i++)
            {
                if (this.Controls[$"ModuleBtn_{i}"] is Button btn)
                {
                    btn.Height = this.Height;
                    btn.Left = this.Width - ButtonWidth * (ModuleTitles.Length - i);
                }
            }
        }
    }
}

內容 UserControl 範例


// ucPart1.cs - 簡單內容頁面
public partial class ucPart1 : UserControl
{
    public ucPart1()
    {
        InitializeComponent();
        this.BackColor = Color.White;
        
        var label = new Label
        {
            Text = "模組 1 內容區域",
            Dock = DockStyle.Fill,
            TextAlign = ContentAlignment.MiddleCenter,
            Font = new Font("微軟正黑體", 14, FontStyle.Bold)
        };
        this.Controls.Add(label);
    }
}

// ucPart2.cs - 包含控制項的功能頁面
public partial class ucPart2 : UserControl
{
    public ucPart2()
    {
        InitializeComponent();
        
        var panel = new Panel { Dock = DockStyle.Fill, Padding = new Padding(10) };
        
        var treeView = new TreeView
        {
            Dock = DockStyle.Fill,
            CheckBoxes = true
        };
        treeView.Nodes.Add("節點 1").Nodes.Add("子節點 1.1");
        treeView.Nodes.Add("節點 2").Nodes.Add("子節點 2.1");
        
        panel.Controls.Add(treeView);
        this.Controls.Add(panel);
    }
}

// ucPart3.cs - 表單頁面
public partial class ucPart3 : UserControl
{
    public ucPart3()
    {
        InitializeComponent();
        
        var tableLayout = new TableLayoutPanel
        {
            Dock = DockStyle.Fill,
            ColumnCount = 2,
            RowCount = 3,
            Padding = new Padding(20)
        };
        tableLayout.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 100));
        tableLayout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
        
        tableLayout.Controls.Add(new Label { Text = "姓名:", TextAlign = ContentAlignment.MiddleRight }, 0, 0);
        tableLayout.Controls.Add(new TextBox(), 1, 0);
        tableLayout.Controls.Add(new Label { Text = "信箱:", TextAlign = ContentAlignment.MiddleRight }, 0, 1);
        tableLayout.Controls.Add(new TextBox(), 1, 1);
        tableLayout.Controls.Add(new Label { Text = "電話:", TextAlign = ContentAlignment.MiddleRight }, 0, 2);
        tableLayout.Controls.Add(new TextBox(), 1, 2);
        
        this.Controls.Add(tableLayout);
    }
}

主表單整合


public partial class MainForm : Form
{
    private SlidePanel _slidePanel;

    public MainForm()
    {
        InitializeComponent();
        InitializeSlidePanel();
    }

    private void InitializeSlidePanel()
    {
        _slidePanel = new SlidePanel
        {
            Dock = DockStyle.Right,
            Width = 300,
            ModuleTitles = new[] { "首頁", "資料", "設定", "工具", "說明" },
            ModuleContents = new UserControl[]
            {
                new ucPart1(),
                new ucPart2(),
                new ucPart3(),
                new ucPart4(), // 自行建立
                new ucPart5()  // 自行建立
            }
        };

        _slidePanel.Initialize();
        this.Controls.Add(_slidePanel);
    }
}

進階:動畫展開效果


public class AnimatedSlidePanel : SlidePanel
{
    private Timer _animTimer;
    private int _targetWidth;
    private bool _isExpanding;
    private const int AnimStep = 20;

    public AnimatedSlidePanel()
    {
        _animTimer = new Timer { Interval = 10 };
        _animTimer.Tick += AnimTimer_Tick;
    }

    private void ExpandContent(Button mainBtn, int index)
    {
        CollapseContent();

        _expandedButton = mainBtn;

        _contentPanel = new Panel
        {
            Name = $"ContentPanel_{index}",
            Left = ButtonWidth * (index + 1),
            Top = 0,
            Width = 0, // 從 0 開始
            Height = this.Height,
            BackColor = ContentBackColor
        };

        if (ModuleContents != null && index < ModuleContents.Length && ModuleContents[index] != null)
        {
            var content = ModuleContents[index];
            content.Dock = DockStyle.Fill;
            _contentPanel.Controls.Add(content);
        }

        this.Controls.Add(_contentPanel);

        _targetWidth = this.Width - ButtonWidth * ModuleTitles.Length;
        _isExpanding = true;
        _animTimer.Start();

        RearrangeButtons(index);
    }

    private void AnimTimer_Tick(object sender, EventArgs e)
    {
        if (_isExpanding)
        {
            _contentPanel.Width += AnimStep;
            if (_contentPanel.Width >= _targetWidth)
            {
                _contentPanel.Width = _targetWidth;
                _animTimer.Stop();
            }
        }
        else
        {
            _contentPanel.Width -= AnimStep;
            if (_contentPanel.Width <= 0)
            {
                _contentPanel.Width = 0;
                _animTimer.Stop();
                CollapseContent();
            }
        }
    }

    private void CollapseContent()
    {
        if (_contentPanel != null)
        {
            _isExpanding = false;
            _animTimer.Start(); // 啟動收合動畫
        }
    }
}

資料驅動模式:從設定檔載入


// SlideConfig.json
{
  "ButtonWidth": 30,
  "Modules": [
    { "Title": "儀表板", "ContentType": "DashboardControl" },
    { "Title": "報表", "ContentType": "ReportControl" },
    { "Title": "管理", "ContentType": "AdminControl" }
  ]
}

// 載入邏輯
public void LoadConfig(string jsonPath)
{
    var json = File.ReadAllText(jsonPath);
    var config = JsonSerializer.Deserialize(json);
    
    ButtonWidth = config.ButtonWidth;
    ModuleTitles = config.Modules.Select(m => m.Title).ToArray();
    
    // 透過反射或工廠建立內容控制項
    ModuleContents = config.Modules.Select(m => 
        ControlFactory.Create(m.ContentType) as UserControl).ToArray();
    
    Initialize();
}

最佳實踐總結

面向建議
內容隔離每個模組獨立 UserControl,邏輯封裝
生命週期收合時 Dispose 內容面板,釋放資源
狀態保持需保持狀態時,隱藏而非 Dispose (Visible=false)
動畫平滑使用 Timer 漸變 Width,或 WPF/WinUI 原生動畫
鍵盤導航支援 Tab/Enter/方向鍵操作按鈕
高 DPI使用縮放感知單位,避免硬編碼像素

現代化替代:WPF DockPanel / WinUI NavigationView

新專案建議使用現代 UI 框架:

  • WPF:DockPanel + Expander + DataTemplate,宣告式 XAML,內建動畫
  • WinUI 3:NavigationView 內建側邊欄、自適應顯示模式 (Compact/Minimal/Left/Top)
  • MAUI:跨平台 FlyoutPage 或 Shell 側邊導航

總結

拉幕式面板核心邏輯:固定寬度按鈕列 + 動態內容面板 + 按鈕位置重算。封裝為 SlidePanel 控制項,透過 ModuleTitles/ModuleContents 陣列資料驅動,支援動畫、尺寸自適應、鍵盤導航。內容頁面採用 UserControl 封裝,達成高內聚低耦合。現代桌面開發建議遷移至 WPF/WinUI/MAUI 獲得原生側邊導航控制項與更豐富的視覺體驗。

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