每次建立新 WinForms 專案都要重複「新增表單 → 拉控制項 → 寫事件」的樣板程式碼。若不使用 Visual Studio 設計器 (或需跨平台編譯),掌握以程式碼完整建構表單架構的能力極為關鍵。本文手把手教你打造一個可重複使用的 MyWinApp 範本類別,涵蓋表單屬性、選單、工具列、狀態列、TreeView/Label/Button 控制項、完整事件處理與資源釋放。
// 編譯指令: csc MyWinApp.cs /r:System.dll /r:System.Windows.Forms.dll /r:System.Drawing.dll
using System;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using System.ComponentModel;
public class MyWinApp : Form
{
// === 欄位:控制項與狀態 ===
private Label _lblHint;
private Button _btnDemo;
private TreeView _treeView;
private ImageList _imgList;
private readonly string _imageFolder;
// 選單
private MainMenu _mainMenu;
private MenuItem _mnuFile, _mnuFileNew, _mnuFileOpen, _mnuFileSave, _mnuFileSaveAs, _mnuFileExit;
private MenuItem _mnuSub, _mnuSubItem;
// 工具列
private ToolBar _toolBar;
private ToolBarButton _tbbSep1, _tbbNew, _tbbOpen, _tbbSave, _tbbSep2, _tbbPrint;
// 狀態列
private StatusBar _statusBar;
private StatusBarPanel _sbpHelp, _sbpTime;
public MyWinApp()
{
// 圖片資料夾相對路徑 (需專案建置後複製 Images 資料夾至輸出目錄)
_imageFolder = Path.Combine(Application.StartupPath, "Images") + Path.DirectorySeparatorChar;
InitializeComponent();
}
private void InitializeComponent()
{
// 1. 表單基本屬性
this.Text = "My Windows Application Template";
this.Width = 600;
this.Height = 450;
this.StartPosition = FormStartPosition.CenterScreen;
this.Icon = LoadIcon("app.ico");
// 2. 圖片清單 (供工具列按鈕使用)
_imgList = new ImageList { ImageSize = new Size(16, 16), ColorDepth = ColorDepth.Depth32Bit };
_imgList.Images.Add(LoadImage("new.bmp")); // Index 0
_imgList.Images.Add(LoadImage("open.gif")); // Index 1
_imgList.Images.Add(LoadImage("save.bmp")); // Index 2
_imgList.Images.Add(LoadImage("print.gif")); // Index 3
// 3. 主選單
_mainMenu = new MainMenu();
_mnuFile = new MenuItem("&File");
_mnuFileNew = NewMenuItem("&New", Shortcut.CtrlN, MnuFileNew_Click);
_mnuFileOpen = NewMenuItem("&Open", Shortcut.CtrlO, MnuFileOpen_Click);
_mnuFileSave = NewMenuItem("&Save", Shortcut.CtrlS, MnuFileSave_Click);
_mnuFileSaveAs = NewMenuItem("Save &As", null, MnuFileSaveAs_Click);
_mnuSub = new MenuItem("&With Submenu");
_mnuSubItem = NewMenuItem("Su&bmenu", MnuSubItem_Click);
_mnuFileExit = NewMenuItem("E&xit", null, MnuFileExit_Click);
_mnuSub.MenuItems.Add(_mnuSubItem);
_mnuFile.MenuItems.AddRange(new MenuItem[] {
_mnuFileNew, _mnuFileOpen, _mnuFileSave, _mnuFileSaveAs,
_mnuSub, new MenuItem("-"), _mnuFileExit });
_mainMenu.MenuItems.Add(_mnuFile);
this.Menu = _mainMenu;
// 4. 工具列
_toolBar = new ToolBar
{
Appearance = ToolBarAppearance.Flat,
ImageList = _imgList,
ButtonSize = new Size(24, 24),
ShowToolTips = true
};
_tbbSep1 = NewToolBarButton(ToolBarButtonStyle.Separator);
_tbbNew = NewToolBarButton("New Document", 0);
_tbbOpen = NewToolBarButton("Open Document", 1);
_tbbSave = NewToolBarButton("Save", 2);
_tbbSep2 = NewToolBarButton(ToolBarButtonStyle.Separator);
_tbbPrint = NewToolBarButton("Print", 3);
_toolBar.Buttons.AddRange(new ToolBarButton[] {
_tbbSep1, _tbbNew, _tbbOpen, _tbbSave, _tbbSep2, _tbbPrint });
_toolBar.ButtonClick += ToolBar_ButtonClick;
this.Controls.Add(_toolBar);
// 5. 一般控制項
_treeView = new TreeView
{
Left = 20, Top = 60, Width = 200, Height = 300,
Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left
};
_treeView.Nodes.Add("Root").Nodes.Add("Child 1");
_treeView.Nodes[0].Nodes.Add("Child 2");
_treeView.ExpandAll();
_lblHint = new Label
{
Left = 240, Top = 60, Width = 300, Height = 40,
Text = "Click the button below to test event handling.",
Font = new Font("Segoe UI", 10)
};
_btnDemo = new Button
{
Left = 240, Top = 110, Width = 120, Height = 35,
Text = "Click Me",
FlatStyle = FlatStyle.System
};
_btnDemo.Click += BtnDemo_Click;
this.Controls.AddRange(new Control[] { _treeView, _lblHint, _btnDemo });
// 6. 狀態列
_statusBar = new StatusBar { ShowPanels = true };
_sbpHelp = new StatusBarPanel
{
BorderStyle = StatusBarPanelBorderStyle.Sunken,
Text = "Press F1 for Help",
AutoSize = StatusBarPanelAutoSize.Spring
};
_sbpTime = new StatusBarPanel
{
BorderStyle = StatusBarPanelBorderStyle.Raised,
Text = DateTime.Today.ToLongDateString(),
ToolTipText = DateTime.Now.ToLongTimeString(),
AutoSize = StatusBarPanelAutoSize.Contents
};
_statusBar.Panels.AddRange(new StatusBarPanel[] { _sbpHelp, _sbpTime });
this.Controls.Add(_statusBar);
// 7. 表單關閉事件
this.Closing += MyWinApp_Closing;
}
// === Helper Methods ===
private Icon LoadIcon(string name)
{
string path = Path.Combine(_imageFolder, name);
return File.Exists(path) ? new Icon(path) : SystemIcons.Application;
}
private Image LoadImage(string name)
{
string path = Path.Combine(_imageFolder, name);
return File.Exists(path) ? Image.FromFile(path) : new Bitmap(16, 16);
}
private MenuItem NewMenuItem(string text, Shortcut shortcut, EventHandler handler)
{
var mi = new MenuItem(text, handler);
if (shortcut != 0) mi.Shortcut = shortcut;
return mi;
}
private ToolBarButton NewToolBarButton(string tooltip, int imageIndex)
{
return new ToolBarButton
{
ToolTipText = tooltip,
ImageIndex = imageIndex
};
}
private ToolBarButton NewToolBarButton(ToolBarButtonStyle style)
{
return new ToolBarButton { Style = style };
}
// === 事件處理器 ===
private void MnuFileNew_Click(object sender, EventArgs e) => ShowMsg("File → New");
private void MnuFileOpen_Click(object sender, EventArgs e) => ShowMsg("File → Open");
private void MnuFileSave_Click(object sender, EventArgs e) => ShowMsg("File → Save");
private void MnuFileSaveAs_Click(object sender, EventArgs e) => ShowMsg("File → Save As");
private void MnuSubItem_Click(object sender, EventArgs e) => ShowMsg("Submenu → Submenu Item");
private void MnuFileExit_Click(object sender, EventArgs e) => this.Close();
private void ToolBar_ButtonClick(object sender, ToolBarButtonClickEventArgs e)
{
int idx = _toolBar.Buttons.IndexOf(e.Button);
// Index: 0=Sep, 1=New, 2=Open, 3=Save, 4=Sep, 5=Print
string[] names = { "", "New", "Open", "Save", "", "Print" };
if (idx >= 0 && idx < names.Length && names[idx] != "")
ShowMsg($"Toolbar → {names[idx]}");
}
private void BtnDemo_Click(object sender, EventArgs e)
{
ShowMsg("Button clicked! Thank you.");
}
private void MyWinApp_Closing(object sender, CancelEventArgs e)
{
if (MessageBox.Show("確定要離開嗎?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No)
e.Cancel = true;
}
private void ShowMsg(string msg) => MessageBox.Show(msg, "Event Triggered", MessageBoxButtons.OK, MessageBoxIcon.Information);
// === 進入點 ===
[STAThread]
public static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MyWinApp());
}
}
Form,建構子呼叫 InitializeComponent()。Main() 標記 [STAThread],呼叫 Application.Run(new MyWinApp()) 進入訊息迴圈。Application.StartupPath 取得執行檔目錄,搭配 Path.DirectorySeparatorChar 跨平台相容。ImageList 集中管理工具列圖示,ToolBarButton.ImageIndex 參考索引。LoadIcon/LoadImage 檔案不存在時回傳系統預設圖示/空白 Bitmap,避免啟動崩潰。MainMenu → MenuItem 階層結構。new MenuItem(text, handler, shortcut) 一次設定文字、事件、快捷鍵。new MenuItem("-")。MenuItem.MenuItems.Add(child)。ToolBar + ToolBarButton,ButtonClick 事件統一處理所有按鈕。ToolBarButtonStyle.Separator 產生分隔間距。ShowToolTips = true 顯示 ToolTipText。StatusBar + StatusBarPanel 多面板。AutoSize.Spring 自動填滿剩餘寬度 (通常放提示文字)。AutoSize.Contents 依內容寬度 (通常放時間)。Left/Top/Width/Height 或 Location/Size。Anchor 屬性控制控制項隨表單縮放行為 (範例 TreeView 固定左上、隨高度延伸)。TableLayoutPanel / FlowLayoutPanel 容器。Click += Handler。ButtonClick,透過 Buttons.IndexOf(e.Button) 判斷哪個按鈕。Closing 事件可取消關閉 (e.Cancel = true)。Dispose(),釋放 ImageList 圖片、Icon、Menu 等非受控資源。Dispose(bool disposing) 覆寫中手動 Controls.Clear() 並逐一 Dispose()。MyWinApp.cs。專案 → 匯出範本 → 項目範本,勾選「自動匯入至 VS」。.vstemplate XML 放入 %USERPROFILE%\Documents\Visual Studio 2022\Templates\ProjectTemplates。手刻 WinForms 範本不僅能擺脫設計器依賴,更能深入理解控制項樹、事件路由、資源管理等核心機制。將常用架構 (選單、工具列、狀態列、錨點配置、基底表單類別) 封裝為可重複使用的程式碼範本,是提升團隊開發效率、確保 UI 風格一致性的關鍵實務。