系統托盤是桌面應用常駐後台、提供快速入口的標準機制。Windows 稱「任務列狀態區」、Gnome 稱「通知區」、KDE 稱「系統托盤」。Java 6 引入 java.awt.SystemTray 與 java.awt.TrayIcon,**純 Java 跨平台實現**,無需 JNI/JNA。本文完整示範托盤圖示、右鍵選單、雙擊還原、氣球通知、圖示閃爍等核心功能。
| 類別 | 角色 | 關鍵方法 |
|---|---|---|
SystemTray | 系統托盤單例 | getSystemTray(), add(TrayIcon), remove(TrayIcon), isSupported() |
TrayIcon | 托盤圖示實體 | setImage(), setToolTip(), setPopupMenu(), displayMessage(), addMouseListener() |
PopupMenu | 右鍵彈出選單 | add(MenuItem) |
MenuItem | 選單項目 | addActionListener() |
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class SystemTrayDemo extends JFrame {
private final SystemTray tray;
private final TrayIcon trayIcon;
private final ImageIcon iconNormal, iconAlert;
private final Timer flashTimer;
public SystemTrayDemo() {
super("系統托盤示範");
// 1. 檢查平台支援
if (!SystemTray.isSupported()) {
JOptionPane.showMessageDialog(this, "當前平台不支援系統托盤", "錯誤", JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
tray = SystemTray.getSystemTray();
// 2. 載入圖示(請替換為實際路徑)
iconNormal = new ImageIcon(getClass().getResource("/icons/tray-normal.png"));
iconAlert = new ImageIcon(getClass().getResource("/icons/tray-alert.png"));
if (iconNormal.getImageLoadStatus() != MediaTracker.COMPLETE) {
// 回退:建立簡單圖示
iconNormal = createFallbackIcon(Color.BLUE);
iconAlert = createFallbackIcon(Color.RED);
}
// 3. 建立右鍵選單
PopupMenu popup = new PopupMenu();
MenuItem openItem = new MenuItem("開啟主視窗");
MenuItem exitItem = new MenuItem("退出程式");
MenuItem aboutItem = new MenuItem("關於");
popup.add(openItem); popup.add(exitItem); popup.add(aboutItem);
// 4. 建立 TrayIcon
trayIcon = new TrayIcon(iconNormal.getImage(), "Java 托盤示範", popup);
trayIcon.setImageAutoSize(true); // 自動縮放適應托盤尺寸
// 5. 註冊事件
// 雙擊托盤圖示 → 還原視窗
trayIcon.addMouseListener(new MouseAdapter() {
@Override public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) restoreWindow();
}
});
// 選單事件
openItem.addActionListener(e -> restoreWindow());
exitItem.addActionListener(e -> System.exit(0));
aboutItem.addActionListener(e -> trayIcon.displayMessage("關於", "Java SystemTray Demo v1.0", TrayIcon.MessageType.INFO));
// 6. 視窗最小化 → 隱藏到托盤
addWindowStateListener(e -> {
if ((e.getNewState() & Frame.ICONIFIED) != 0) minimizeToTray();
});
// 7. 圖示閃爍定時器(模擬新訊息提醒)
flashTimer = new Timer(500, e -> {
trayIcon.setImage(trayIcon.getImage() == iconNormal.getImage() ? iconAlert.getImage() : iconNormal.getImage());
});
// UI 佈局
setLayout(new BorderLayout());
JButton minimizeBtn = new JButton("最小化到托盤");
minimizeBtn.addActionListener(e -> minimizeToTray());
add(minimizeBtn, BorderLayout.NORTH);
add(new JLabel("關閉視窗不退出,程式常駐托盤", SwingConstants.CENTER), BorderLayout.CENTER);
setSize(300, 200);
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // 關閉鈕改為最小化
addWindowListener(new WindowAdapter() {
@Override public void windowClosing(WindowEvent e) { minimizeToTray(); }
});
setLocationRelativeTo(null);
}
private void minimizeToTray() {
try {
tray.add(trayIcon);
setVisible(false);
// 可選:顯示氣球提示
trayIcon.displayMessage("已最小化", "程式在托盤持續運行", TrayIcon.MessageType.INFO);
} catch (AWTException ex) {
ex.printStackTrace();
}
}
private void restoreWindow() {
tray.remove(trayIcon);
setVisible(true);
setExtendedState(Frame.NORMAL); // 還原最大化/最小化狀態
toFront(); // 置頂
flashTimer.stop(); // 停止閃爍
trayIcon.setImage(iconNormal.getImage());
}
private ImageIcon createFallbackIcon(Color c) {
BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
g2.setColor(c);
g2.fillOval(2, 2, 12, 12);
g2.dispose();
return new ImageIcon(img);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); }
catch (Exception ignored) {}
new SystemTrayDemo().setVisible(true);
});
}
}
if (!SystemTray.isSupported()) {
// Headless 伺服器、某些 Linux 無桌面環境、舊版 macOS
// 應優雅降級:改用通知庫、日誌、或僅在視窗模式運行
}
getClass().getResource("/icons/...") 打包進 JAR 內,避免外部檔案丟失。trayIcon.setImageAutoSize(true) 自動選擇。// 最小化 → 托盤
addWindowStateListener(e -> {
if ((e.getNewState() & Frame.ICONIFIED) != 0) minimizeToTray();
});
// 關閉鈕 → 托盤(而非退出)
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter() {
@Override public void windowClosing(WindowEvent e) { minimizeToTray(); }
});
trayIcon.displayMessage("標題", "內容訊息", TrayIcon.MessageType.INFO);
// MessageType: NONE, INFO, WARNING, ERROR
// Windows: 原生氣球提示
// Linux (Gnome/KDE): 通知守護進程
// macOS: Java 9+ 支援原生通知,舊版可能無效果
Timer flashTimer = new Timer(500, e ->
trayIcon.setImage(current == normal ? alert : normal));
// 收到訊息啟動,使用者還原視窗停止
整個 JVM 僅有一個 SystemTray 實例,**不可自行 new**,透過 getSystemTray() 取得。多視窗應用共享同一托盤圖示,或根據業務建立多個 TrayIcon 加入同一托盤。
| 平台 | 托盤支援 | 氣球通知 | 圖示自動縮放 | 注意事項 |
|---|---|---|---|---|
| Windows 10/11 | ✅ 完美 | ✅ 原生 | ✅ | 任務列設定可隱藏圖示 |
| Ubuntu (Gnome) | ✅ 需 libappindicator | ✅ notify-osd | ✅ | Wayland 可能需額外權限 |
| Ubuntu (KDE) | ✅ 原生 | ✅ | ✅ | |
| macOS | ⚠️ Java 6-8 有限,Java 9+ 較好 | ⚠️ Java 9+ 原生 | ⚠️ | 菜單列右側,非 Dock |
| Headless Server | ❌ | ❌ | ❌ | isSupported()=false |
createFallbackIcon 驗證邏輯。MenuItem 需加 ActionListener,而非 MouseListener。mouseClicked 觸發兩次 → 檢查 getClickCount() == 2。libappindicator3-1、gir1.2-appindicator3-0.1。src/main/resources 包含圖示,打包為 Fat JAR 時資源路徑正確。SystemTray + TrayIcon 是 Java **純跨平台** 托盤方案,無需原生庫。現代替代方案:若需更豐富的托盤交互(自定義渲染、動畫、觸控支援),可考慮 Tray 庫(基於 JNA/JNI),但會引入原生依賴。純 Java 專案首選標準 API。