將垂直 QQ 面板旋轉 90 度,即成為水平摺疊的「拉幕式」側邊欄。本文實作右側固定寬度按鈕列,點擊展開對應內容面板 (UserControl) 的完整架構,適用於工具箱、屬性面板、側邊導航等場景。
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);
}
}
}
}
}
// 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 | 使用縮放感知單位,避免硬編碼像素 |
新專案建議使用現代 UI 框架:
DockPanel + Expander + DataTemplate,宣告式 XAML,內建動畫NavigationView 內建側邊欄、自適應顯示模式 (Compact/Minimal/Left/Top)FlyoutPage 或 Shell 側邊導航拉幕式面板核心邏輯:固定寬度按鈕列 + 動態內容面板 + 按鈕位置重算。封裝為 SlidePanel 控制項,透過 ModuleTitles/ModuleContents 陣列資料驅動,支援動畫、尺寸自適應、鍵盤導航。內容頁面採用 UserControl 封裝,達成高內聚低耦合。現代桌面開發建議遷移至 WPF/WinUI/MAUI 獲得原生側邊導航控制項與更豐富的視覺體驗。