系統托盤 是桌面應用常見的「最小化到托盤、後台常駐、右鍵菜單交互」功能。Java 自 JDK 6 起通過 java.awt.SystemTray、TrayIcon、PopupMenu 提供原生支持。本文提供完整可運行的 Maven 專案結構、核心邏輯解析、關鍵 API 詳解及常見坑點指南。
src/main/java/org/pdp/frame/
├── MainFrame.java // 主窗體 + 托盤邏輯
└── resources/
└── systray.jpg // 托盤圖標 (16x16 或 24x24 建議)
運行效果:
if (!SystemTray.isSupported()) {
System.err.println("當前系統不支持 SystemTray!");
return; // 或降級處理
}
注意:部分 Linux 桌面環境(如無托盤的 Window Manager)會返回 false,需做降級預案。
URL imgUrl = getClass().getResource("/systray.jpg"); // 放在 resources 根目錄
if (imgUrl == null) throw new IllegalStateException("找不到托盤圖標資源!");
Image image = new ImageIcon(imgUrl).getImage();
避免使用相對路徑 new File("..."),打包成 JAR 後會失效。
PopupMenu popup = new PopupMenu(); // 必須用 java.awt.PopupMenu
MenuItem showItem = new MenuItem("顯示窗體");
MenuItem exitItem = new MenuItem("退出系統");
popup.add(showItem);
popup.add(exitItem);
關鍵點:TrayIcon 構造函數要求 PopupMenu (AWT),而非 JPopupMenu (Swing)。混用會導致菜單不顯示或異常。
TrayIcon trayIcon = new TrayIcon(image, "電話薄系統", popup);
trayIcon.setImageAutoSize(true); // 自適應縮放
SystemTray tray = SystemTray.getSystemTray();
try {
tray.add(trayIcon);
} catch (AWTException e) {
logger.error("托盤圖標註冊失敗", e);
// 降級:最小化到任務欄、通知用戶等
}
// 菜單「置於托盤」→ 隱藏窗體
if ("置於托盤".equals(cmd)) setVisible(false);
// 托盤菜單「顯示窗體」→ 顯示並置頂
showItem.addActionListener(e -> {
setVisible(true);
toFront(); // 關鍵:確保窗體在最上層
});
exitItem.addActionListener(e -> {
tray.remove(trayIcon); // 移除托盤圖標,避免殘留
System.exit(0);
});
package org.pdp.frame;
import java.awt.*;
import java.awt.event.*;
import java.net.URL;
import javax.swing.*;
public class MainFrame extends JFrame implements ActionListener {
private static final long serialVersionUID = 1L;
private JMenuBar menuBar;
private JMenu sysMenu;
private String[] menuNames = {"置於托盤", "系統退出"};
public MainFrame() {
super("電話薄");
initUI();
initSystemTray();
setSize(500, 400);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // 防止直接關閉繞過托盤
addWindowListener(new WindowAdapter() {
@Override public void windowClosing(WindowEvent e) { setVisible(false); }
});
}
private void initUI() {
sysMenu = new JMenu("系統窗體");
for (String name : menuNames) {
JMenuItem mi = new JMenuItem(name);
mi.addActionListener(this);
sysMenu.add(mi);
}
menuBar = new JMenuBar();
menuBar.add(sysMenu);
setJMenuBar(menuBar);
}
@Override
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand();
if ("置於托盤".equals(cmd)) setVisible(false);
else if ("系統退出".equals(cmd)) System.exit(0);
}
private void initSystemTray() {
if (!SystemTray.isSupported()) return;
URL imgUrl = getClass().getResource("/systray.jpg");
if (imgUrl == null) return;
Image image = new ImageIcon(imgUrl).getImage();
PopupMenu popup = new PopupMenu();
MenuItem show = new MenuItem("顯示窗體");
MenuItem exit = new MenuItem("退出系統");
show.addActionListener(ev -> { setVisible(true); toFront(); });
exit.addActionListener(ev -> { SystemTray.getSystemTray().remove(trayIcon); System.exit(0); });
popup.add(show); popup.add(exit);
TrayIcon trayIcon = new TrayIcon(image, "電話薄系統", popup);
trayIcon.setImageAutoSize(true);
try { SystemTray.getSystemTray().add(trayIcon); }
catch (AWTException ex) { ex.printStackTrace(); }
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new MainFrame().setVisible(true));
}
}
| 問題 | 原因 | 解決方案 |
|---|---|---|
| 托盤圖標不顯示/菜單無反應 | 用了 Swing 的 JPopupMenu | 必須使用 java.awt.PopupMenu + MenuItem |
| 打包 JAR 後圖標丟失 | 用相對文件路徑加載 | 用 ClassLoader.getResource() 讀取 classpath 資源 |
| 點擊關閉按鈕程序直接退出 | 默認 EXIT_ON_CLOSE | 設置 DO_NOTHING_ON_CLOSE,窗口關閉事件中執行 setVisible(false) |
| Linux 下托盤不支持 | 桌面環境無 SystemTray | 啟動時檢查 isSupported(),降級為最小化到任務欄或後台運行 |
| 多次點擊產生多個托盤圖標 | 重複調用 tray.add() | 單例模式管理 TrayIcon,或添加前先 remove |
trayIcon.displayMessage("標題", "內容", TrayIcon.MessageType.INFO)trayIcon.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { setVisible(true); toFront(); } } })trayIcon.setImage(newImage)、trayIcon.setToolTip("新提示")