首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java 桌面應用系統托盤進階實戰:截圖工具架構設計與跨平台兼容

     編輯:Java 程式設計入門

前言

本文在上一篇基礎托盤教程基礎上,構建一個具備桌面截圖核心功能的「EasyCapture」工具,展示托盤應用的完整工程架構:主窗口生命週期管理、托盤控制器解耦、截圖邏輯佔位、系統外觀適配、資源加載、關閉/最小化策略、單擊/雙擊語義區分。代碼採用 MVC 分層,便於功能擴展。

一、項目結構

com.b510.tray
├── DesktopCapture.java    // 主窗口控制器 (View + Controller)
├── MyTray.java            // 托盤組件封裝 (Model)
└── resources/
    └── image/xiaomai.png  // 托盤圖標

二、核心類設計

1. DesktopCapture - 主窗口與應用入口

package com.b510.tray;

import javax.swing.*;
import java.awt.event.*;

public class DesktopCapture extends JFrame {
    private static final long serialVersionUID = 1L;
    private MyTray tray;
    private boolean iconed = false; // 標記是否主動最小化到托盤

    public DesktopCapture() {
        super("EasyCapture");
        initUI();
        initTray();
        initWindowListeners();
        pack();
        setSize(350, 230);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); // 交由監聽器處理
        setResizable(false);
        setVisible(true);
    }

    private void initUI() {
        // 實際項目:工具欄、設置面板、歷史記錄列表等
        JLabel label = new JLabel("易捕獲截圖工具", SwingConstants.CENTER);
        label.setFont(label.getFont().deriveFont(18f));
        add(label);
    }

    private void initTray() {
        tray = new MyTray(this);
    }

    private void initWindowListeners() {
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowIconified(WindowEvent e) { // 點最小化按鈕
                hideToTray();
            }
            @Override
            public void windowClosing(WindowEvent e) { // 點關閉按鈕
                int opt = JOptionPane.showConfirmDialog(
                    DesktopCapture.this,
                    "確定最小化到托盤?選「否」退出程序。",
                    "提示", JOptionPane.YES_NO_OPTION
                );
                if (opt == JOptionPane.YES_OPTION) {
                    hideToTray();
                } else {
                    shutdown();
                }
            }
        });
    }

    // 業務邏輯:截圖佔位
    public void capture() {
        // TODO: 實現全屏截圖、區域選取、編輯標註、保存/複製/上傳
        System.out.println(">> 觸發截圖邏輯");
        // 示例:Robot.createScreenCapture + Swing 選區組件
    }

    private void hideToTray() {
        if (!iconed) {
            iconed = true;
            setVisible(false);
            // tray.getTrayIcon().displayMessage("EasyCapture", "已最小化到托盤", TrayIcon.MessageType.INFO);
        }
    }

    public void restoreFromTray() {
        iconed = false;
        setVisible(true);
        setExtendedState(JFrame.NORMAL); // 確保非最大化/圖標化狀態
        toFront(); // 置頂
    }

    private void shutdown() {
        if (tray != null) tray.dispose();
        System.exit(0);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            try {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            } catch (Exception ignored) {}
            new DesktopCapture();
        });
    }
}

2. MyTray - 托盤封裝組件

package com.b510.tray;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class MyTray implements ActionListener, MouseListener {
    private final DesktopCapture frame;
    private final SystemTray systemTray;
    private final TrayIcon trayIcon;
    private final PopupMenu popup = new PopupMenu();
    private final MenuItem miCapture = new MenuItem("截圖");
    private final MenuItem miShow = new MenuItem("顯示主窗口");
    private final MenuItem miExit = new MenuItem("退出");

    public MyTray(DesktopCapture frame) {
        this.frame = frame;
        // 1. 載入圖標(classpath 方式,支援 JAR 打包)
        Image icon = loadIcon("/image/xiaomai.png");
        if (icon == null) {
            System.err.println("托盤圖標加載失敗,功能不可用");
            return;
        }

        // 2. 檢查系統托盤支援
        if (!SystemTray.isSupported()) {
            System.err.println("當前系統不支援 SystemTray");
            return;
        }
        systemTray = SystemTray.getSystemTray();

        // 3. 構建彈出菜單
        buildPopupMenu();

        // 4. 創建並註冊 TrayIcon
        trayIcon = new TrayIcon(icon, "EasyCapture - 單擊截圖", popup);
        trayIcon.setImageAutoSize(true); // 高 DPI 自適應
        trayIcon.addMouseListener(this); // 單擊/雙擊事件

        try {
            systemTray.add(trayIcon);
        } catch (AWTException e) {
            e.printStackTrace();
        }
    }

    private Image loadIcon(String path) {
        java.net.URL url = getClass().getResource(path);
        if (url == null) return null;
        return new ImageIcon(url).getImage();
    }

    private void buildPopupMenu() {
        miCapture.addActionListener(this);
        miShow.addActionListener(this);
        miExit.addActionListener(this);
        popup.add(miCapture);
        popup.add(miShow);
        popup.add(miExit);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        Object src = e.getSource();
        if (src == miShow) {
            frame.restoreFromTray();
        } else if (src == miCapture) {
            frame.capture();
        } else if (src == miExit) {
            frame.shutdown();
        }
    }

    // === MouseListener: 單擊托盤圖標觸發截圖 ===
    @Override
    public void mouseClicked(MouseEvent e) {
        // 僅左鍵單擊(非右鍵彈出菜單)觸發
        if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 1) {
            frame.capture();
        }
    }
    @Override public void mousePressed(MouseEvent e) {}
    @Override public void mouseReleased(MouseEvent e) {}
    @Override public void mouseEntered(MouseEvent e) {}
    @Override public void mouseExited(MouseEvent e) {}

    public void dispose() {
        if (systemTray != null && trayIcon != null) {
            systemTray.remove(trayIcon);
        }
    }
}

三、關鍵設計決策解析

決策點方案理由
圖標加載getClass().getResource()支援 IDE 運行、JAR 打包、模塊化運行時,避免相對路徑失效
主窗口關閉策略DO_NOTHING_ON_CLOSE + 確認對話框防止用戶誤關閉,符合托盤應用「關閉=隱藏」直覺
最小化行為直接隱藏到托盤減少干擾,任務欄不佔位
托盤點擊語義左鍵單擊=截圖,右鍵=菜單高頻操作零點擊成本,參考 Snipaste/QQ 截圖交互
高 DPI 適配setImageAutoSize(true)Windows 縮放 125%/150%、Mac Retina 下圖標不模糊
資源清理dispose() 移除托盤圖標避免進程退出後圖標殘留(Windows 常見)
系統外觀UIManager.getSystemLookAndFeelClassName()原生風格,用戶無違和感

四、截圖核心邏輯擴展指南(TODO 實現)

public void capture() {
    // 1. 隱藏主窗口(如可見)
    boolean wasVisible = isVisible();
    if (wasVisible) setVisible(false);

    // 2. 獲取全屏圖像
    BufferedImage screen = new Robot().createScreenCapture(
        new Rectangle(Toolkit.getDefaultToolkit().getScreenSize())
    );

    // 3. 彈出選區框架(全屏透明窗口 + MouseListener 拖拽選區)
    //    參考開源項目:ScreenShot (GitHub), ShareX 邏輯
    //    選區完成後 -> 截取子圖 -> 進入編輯模式 (標註、文字、馬賽克)

    // 4. 編輯完成後:保存文件 / 複製到剪貼板 / 上傳圖床
    // Toolkit.getDefaultToolkit().getSystemClipboard().setContents(...)

    // 5. 恢復主窗口狀態
    if (wasVisible) setVisible(true);
}

五、跨平台兼容性補丁

  • Linux (GNOME/KDE):需安裝 libappindicator3-1,部分桌面環境需啟用 AppIndicator 支援;若 SystemTray.isSupported() 返回 false,降級為狀態欄圖標庫。
  • macOS:SystemTray 映射到菜單欄右側,圖標需為模板圖像(僅 alpha 通道),建議提供 .png 並設置 trayIcon.setImageAutoSize(true)。
  • Windows 高 DPI:JDK 9+ 自動感知,JDK 8 需清單文件 dpiAware=true 或啟動參數 -Dsun.java2d.dpiaware=true。

六、總結

本例展示了生產級托盤應用的骨架:主窗口與托盤解耦、生命週期精細管理、資源加載規範化、交互語義符合用戶習慣、跨平台降級策略。在此基礎上接入 Robot 截圖、Swing/JavaFX 編輯器、剪貼板、圖床上傳,即可構建商業級截圖工具。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved