Das Beispiel zeigt wie ein Tray-Icon mit einer Badge eingerichtet werden kann.

Die Klasse java.awt.SystemTray stellt Methoden bereit, um ein Tray-Icon einzurichten. Java gibt jedoch keine Möglichkeit vor, um hierfür auch eine Badge, einen kleinen roten Kreis für Anmerkungen, einzurichten. Sie muss 'manuell' gezeichnet werden.

Im Beispiel wird ein kleines GUI erzeugt, das nur aus einem JFrame mit einem Button zum Beenden der Application besteht. Im System-Tray, unter MacOSX der Fensterleiste oben-rechts, zeigt das Programm ein eigenes Icon, auf dem innerhalb einer Badge ein Countdown heruntergezählt wird.

Ein Tray-Icon mit Badge

Programmaufbau

Der Konstruktor dient zum Aufruf von init() [27]. In der Methode werden die Oberfläche und das Tray-Icon eingerichtet. Um eine Badge mit ihrem dynamischen Zähler unabhängig und parallel zur Anzeige des Hauptfensters zu ermöglichen, muss dies nebenläufig erfolgen. Dies wird durch ein ExecutorService-Objekt gewährleistet, das einen Thread bereitstellt, in dem showTrayIcon() aufgerufen wird [37]. Der Methode werden hierbei drei Parameter übergeben: ein PopupMenu, das vorher gebildet wurde, ein numerischer Startwert für den Countdown der Badge und ein boolscher Wert. Ist dieser true wird der Zahlenwert auf der Badge statisch angezeigt, bei false dient er als Startwert eines Countdowns. Sobald sie nicht mehr benötigt werden, müssen die benötigten Resourcen des Threads durch shutdown() wieder freigegeben werden [40].
Da nicht jedes Betriebssystem ein System-Tray bereitstellt, sollte dies vorher durch die statische Methode SystemTray.isSupported() abgefragt werden [35].

import java.awt.AWTException;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class MacOSXTrayIconMitBadge {

    private static final String ICON_PATH = "/img/icon512.png";

    public MacOSXTrayIconMitBadge() {
        init();
    }

    private void init() {
        PopupMenu menu = new PopupMenu("Application");
        menu.add("Test");
        menu.add("Test 2");
        ExecutorService executor = Executors.newCachedThreadPool();
        if (SystemTray.isSupported()) {
            executor.execute(() -> {
                showTrayIcon(menu, 15, false);
            });
        }
        executor.shutdown();
        JButton butt = new JButton("Ende");
        butt.addActionListener(e -> System.exit(0));
        JFrame frame = new JFrame("Tray-Demo");
        frame.setLayout(new FlowLayout());
        frame.add(butt);
        frame.setSize(200, 80);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private void showTrayIcon(PopupMenu menu, int number, boolean isFixed) {
        SystemTray tray = SystemTray.getSystemTray();
        Image icon = createTrayIcon(ICON_PATH, number);
        TrayIcon trayIcon = new TrayIcon(icon, "Tray Demo", menu);
        try {
            tray.add(trayIcon);
        } catch (AWTException e) {
        }
        if (!isFixed) {
            while (number > -1) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                icon = createTrayIcon(ICON_PATH, --number);
                trayIcon.setImage(icon);
            }
            tray.remove(trayIcon);
        }
    }

    private Image loadImage(String imgPath) {
        Image img = null;
        try {
            img = ImageIO.read(getClass().getResource(imgPath));
        } catch (IOException e) {
            System.err.println("Dock-Icon kann nicht geladen werden!");
        }
        return img;
    }

    private Image createTrayIcon(String imgPath, int num) {
        BufferedImage img = (BufferedImage) loadImage(imgPath);
        int imgwidth = img.getWidth();
        int fontSize = (int) (imgwidth / 2.5);
        int imgx = (int) (imgwidth / 2);
        Graphics2D g2d = (Graphics2D) img.getGraphics();
        g2d.setFont(new Font(Font.SANS_SERIF, Font.BOLD, fontSize));
        g2d.setColor(Color.RED);
        int fontx = imgx;
        int fonty = (int) (imgwidth * 0.9);
        g2d.fillArc(imgx, imgx, imgx, imgx, 0, 360);
        if (num < 10)
            fontx = imgx + fontSize / 3;
        g2d.setColor(Color.WHITE);
        g2d.drawString(Integer.valueOf(num).toString(), fontx, fonty);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            ImageIO.write(img, "png", baos);
        } catch (IOException e) {
            e.printStackTrace();
        }
        byte[] bArr = baos.toByteArray();
        ImageIcon imgTmp = new ImageIcon(bArr);
        return imgTmp.getImage();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new MacOSXTrayIconMitBadge());
    }
}

Tray-Icon mit Tooltip und Menu

In showTrayIcon() wird zunächst das System-Tray durch SystemTray.getSystemTray() ermittelt und instanziert [53]. Unter Übergabe des Pfades zur Bilddatei und dem numerischen Wert für die Badge wird dann ein Image-Objekt erzeugt [54], das im nächsten Schritt Verwendung findet, um das TrayIcon zu erzeugen [55]. Dem Konstruktor werden hierbei neben dem Image-Objekt noch ein Tooltiptext und das PopupMenu übergeben. Tooltip und Menu sind optional, da der Konstruktor dreifach überladen ist. Das Menu enthält hier zwei Elemente, Test und Test 2. Beide bleiben der Übersichtlichkeit halber funktionslos, können jedoch nach dem Start des Programms durch Klick auf das Tray-Icon bestaunt werden.

Ein Tray-Icon mit Kontextmenu und Badge

Zeichnen des Tray-Icons

Da die Java-Bibliotheken keine Möglichkeit bieten, ein Tray-Icon mit einer Badge zu versehen, muss diese selbst gezeichnet werden. Dazu wird in createTrayIcon() zunächst die Icondatei geladen [85]. Dies geschieht mit Hilfe der ImageIO-API unter Übergabe des Dateipfades in der Methode loadImage() [74]. Sie liefert ein Image-Objekt, das zum BufferedImage gecastet wird, um die nachfolgenden Schritte etwas zu vereinfachen.

Erzeugen der Badge

Das hier verwendete Icon ist quadratisch. Zur Dimensionierung und Positionierung der Badge und der verwendeten Schrift wird die Seitenlänge des Icons herangezogen [86]. Unter Verwendung des ermittelten Wertes werden ein Kreis [94] und anschließend der String der der Methode übergebenen Zahl [98] gezeichnet. Da die kleine Schriftgröße empfindlich hinsichtlich der Positionierung ist, wird hierbei noch zwischen ein- und zweistelligen Zahlen unterschieden [95].
Der Zeichenvorgang selbst findet auf dem Graphics-Kontext des BufferedImage statt [89]. Alle Größen richten sich somit nach den Dimensionen der Ausgangsdatei und nicht denjenigen des skalierten Tray-Icons!
Die auf diese Weise erzeugte Graphik wird in einen ByteArrayOutputStream geschrieben [102], aus dem das erzeugte Array extrahiert [106] und schließlich an den Konstruktor eines ImageIcon übergeben wird [107]. Zurückgegeben wird dann das von diesem gelieferte Image-Objekt [108].

Anzeigen des Tray-Icons

Das Image-Objekt wird wie oben beschrieben zur Erzeugung des Tray-Icons an den entsprechenden Konstruktor übergeben. Innerhalb eines try-catch-Blocks wird das Icon auf das Tray gesetzt [57].
Wird das Icon mit Countdown angezeigt, so wird nach dessen Ablauf auch das Icon aus dem Tray entfernt [70].

Wenn Ihnen javabeginners.de gefällt, freue ich mich über eine Spende an diese gemeinnützigen Organisationen.