需求
- 視窗最小化時隱藏至工作列通知區 (系統托盤),不佔用工作列空間。
- 托盤圖示右鍵選單提供「開啟」、「退出」功能。
- 雙擊托盤圖示還原視窗。
- 應用程式關閉時正確釋放托盤圖示資源。
核心元件
NotifyIcon:系統托盤圖示元件 (元件工具箱 → Common Controls)
ContextMenuStrip:右鍵選單
- 表單事件:
Resize、FormClosing、Load
完整程式碼
public partial class MainForm : Form
{
private NotifyIcon _trayIcon;
private ContextMenuStrip _trayMenu;
public MainForm()
{
InitializeComponent();
InitializeTrayIcon();
}
private void InitializeTrayIcon()
{
// 1. 建立右鍵選單
_trayMenu = new ContextMenuStrip();
_trayMenu.Items.Add("開啟主視窗", null, (s, e) => ShowWindow());
_trayMenu.Items.Add("退出", null, (s, e) => ExitApplication());
// 2. 建立 NotifyIcon
_trayIcon = new NotifyIcon
{
Icon = this.Icon ?? SystemIcons.Application, // 使用表單圖示或預設
Text = "我的應用程式",
Visible = false, // 預設隱藏,最小化時顯示
ContextMenuStrip = _trayMenu
};
// 3. 雙擊圖示還原視窗
_trayIcon.DoubleClick += (s, e) => ShowWindow();
// 4. 表單事件訂閱
this.Resize += MainForm_Resize;
this.FormClosing += MainForm_FormClosing;
this.Load += MainForm_Load;
}
private void MainForm_Load(object sender, EventArgs e)
{
// 確保圖示資源已載入
if (_trayIcon.Icon == null && this.Icon != null)
_trayIcon.Icon = this.Icon;
}
private void MainForm_Resize(object sender, EventArgs e)
{
if (this.WindowState == FormWindowState.Minimized)
{
this.Hide(); // 隱藏視窗與工作列按鈕
_trayIcon.Visible = true; // 顯示托盤圖示
_trayIcon.ShowBalloonTip(1000, "最小化至托盤", "程式仍在背景執行", ToolTipIcon.Info);
}
}
private void ShowWindow()
{
this.Show();
this.WindowState = FormWindowState.Normal;
this.Activate(); // 取得焦點
_trayIcon.Visible = false;
}
private void ExitApplication()
{
_trayIcon.Visible = false; // 先隱藏圖示,避免殘留
_trayIcon.Dispose(); // 釋放非受控資源
Application.Exit(); // 結束訊息迴圈
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
// 若使用者點右上角 X,預設關閉應用程式
// 若想改為最小化至托盤,取消註解下列兩行並註解 ExitApplication() 呼叫:
// if (e.CloseReason == CloseReason.UserClosing) { e.Cancel = true; this.WindowState = FormWindowState.Minimized; }
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_trayIcon?.Dispose();
_trayMenu?.Dispose();
}
base.Dispose(disposing);
}
}
進階功能擴充
1. 自訂托盤圖示動畫 / 狀態指示
private readonly Icon[] _animationIcons = new Icon[8]; // 載入 8 幀圖示
private int _animIndex = 0;
private System.Windows.Forms.Timer _animTimer;
private void StartAnimation()
{
_animTimer = new System.Windows.Forms.Timer { Interval = 100 };
_animTimer.Tick += (s, e) =>
{
_trayIcon.Icon = _animationIcons[_animIndex++ % _animationIcons.Length];
};
_animTimer.Start();
}
private void StopAnimation()
{
_animTimer?.Stop();
_trayIcon.Icon = this.Icon; // 恢復預設
}
2. 氣球提示 (Balloon Tip) 事件處理
_trayIcon.BalloonTipClicked += (s, e) => ShowWindow(); // 點擊氣球提示還原
_trayIcon.BalloonTipClosed += (s, e) => { /* 記錄關閉原因 e.CloseReason */ };
3. 單一實例應用程式 (配合 Mutex)
static class Program
{
[STAThread]
static void Main()
{
using var mutex = new Mutex(true, "Global\\MyUniqueAppId", out bool createdNew);
if (!createdNew)
{
// 已有實例執行中,傳送訊息喚醒或直接返回
MessageBox.Show("程式已在執行中", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
常見問題與解決
| 問題 | 原因 | 解決 |
| 托盤圖示不顯示 | Icon 屬性為 null、Visible=false、DPI 縮放導致圖示過小 | 指派有效 .ico、確保 Visible=true、提供多尺寸圖示 (16/32/48/256px) |
| 關閉視窗後托盤圖示殘留 (鬼影) | 未在 FormClosing/Dispose 呼叫 _trayIcon.Visible = false; _trayIcon.Dispose(); | 確實釋放資源,參考上方 Dispose 模式 |
| 雙擊圖示無反應 | 未訂閱 DoubleClick 事件、或被 ContextMenuStrip 攔截 | 確認事件註冊、ContextMenuStrip 不會吞噬雙擊 |
| 最小化後無法還原 | this.Show() 前未設定 WindowState = Normal | 依序呼叫 Show() → WindowState = Normal → Activate() |
| 高 DPI 模糊 | 圖示解析度不足 | 在 .ico 內內建 256x256 PNG 壓縮格式,Windows 10/11 自動選用 |
WPF / .NET Core 3.0+ 專案注意事項
NotifyIcon 屬於 System.Windows.Forms,WPF 專案需參考 System.Windows.Forms.dll。
- .NET Core / .NET 5+ 需在專案檔加入
<UseWindowsForms>true</UseWindowsForms>。
- WPF 原生替代方案:
Hardcodet.Wpf.TaskbarNotification (NuGet),純 WPF 實作、支援 MVVM、豐富動畫/樣式。
最佳實務清單
- [ ] 表單圖示 (
this.Icon) 設定有效 .ico 檔案 (含 16x16, 32x32, 48x48, 256x256)
- [ ]
NotifyIcon.Visible 預設 false,最小化時設 true,還原/結束時設 false
- [ ]
Dispose 釋放 NotifyIcon、ContextMenuStrip、Timer
- [ ] 提供右鍵選單「開啟」、「退出」,雙擊圖示還原
- [ ] 單一實例保護 (Mutex) 避免多開
- [ ] 應用程式真正退出時呼叫
Application.Exit() 而非 Environment.Exit()