GUI-Basteleien machen Spaß – Wie man JavaFX Applikationen in das System Tray verbannt

JavaFX ist seit dem großen Umstieg auf 2.0 eine spaßige Sache. Anstelle einer hässlichen Zwischensprache (ja ich meine dich, JavaFX-Script!) kann nun der Entwickler nun seine altbewährten POJOs verwenden (Plain Old Java Objects). Wer sich als Outsiderkünstler versteht, im Sinne von  „außerhalb der Programmierung“ die GUI zu entwerfen, kann sich auch mit FXML austoben, was nichts anderes als XML ist, welches von der JavaFX Runtime geparsed wird. WPF lässt grüßen!

Anwendungen mit JavaFX zu basteln macht mir Spaß. Ich bin zwar kein Picasso, allerdings wäre der Kubismus sowieso nichts für Businessanwendungen. WPF, Silverlight, QT und andere Sachen machen zwar auch Spaß, da bei uns im Haus auf Entwicklerseiten allerdings die Kaffeebohnen von der Insel (Java) bevorzugt werden, gehe ich auch erst mal nur auf Dinge bezüglich Java ein (und anderen Themen, die bei mir den Eindruck erwecken, dass sie bei uns im Haus wichtig sind).

Vor kurzem wurde mir die Aufgabe zu Teil, ein bahnbrechendes, die Industrie revolutionierendes und alles andere in den Schatten stellendes Produkt zu entwickeln (ist es nicht schön zu träumen?). Und eine der Anforderungen war, etwas flach ausgedrückt:

–          Das Teil soll eine tolle GUI haben!

Das Endprodukt ist eine Java-Applikation. Für Java kann man oberflächentechnisch viele tolle Dinge machen, WENN es sich um eine Webapplikation handelt. Was den Desktop angeht: „meh“! Es gibt auch Anwendungen, die mit Swing und AWT nett aussehen, und man kann ja was weiß ich für tolle Themes/Designs anwenden. Trotzdem finde ich das meiste einfach nur: „meh!“.

Und hier kommt nun Oracle und sagt: „Verzweifle nicht! Nimm das hier und mach damit voll tolles Zeug!“ und drückt einem JavaFX 2.0 (die Versionsnummer ist wichtig) in die Hand. Und es macht Spaß. Und es lässt sich schnell damit arbeiten. Und es hat voll viele tolle Features! Und, und, und…

Bei so vielen „und“s braucht es was? Genau! Es braucht auch „aber“s! Und nach dem ganzen belanglosen Intro kommt nun ein aber, bei dem dieser Blog aushelfen möchte:

Aber JavaFX bietet in der aktuellen Version keine Unterstützung für den System Tray an!

[Der Vollständigkeit halber: Der System Tray (je nach Desktop heißt das Ding anders) ist der Bereich in der Taskleiste, in denen manche Anwendungen „ausgelagert“ zu sein scheinen. Der eine kennt es als „Taskbar Notification Area“, der andere als Infobereich und ich nenne den Bereich so wie viele andere auch „System Tray“.]

Der System Tray ist definitiv nicht überlebensnotwendig, wenn man allerdings so was wie einen Launcher hat, welcher verschiedene Module bietet, oder wenn eine Anwendung einen Prozess im Hintergrund ausführt und erst mal den Desktop nicht braucht, dann verlangt es die Sitte eigentlich schon, dass man auf den Tray ausweicht.

Und da haben wir auch schon das Dilemma! Wir haben ‘ne tolle Technologie und an einer Banalität scheitert die Sache.

In den Tiefen und Weiten des Internets wird auch schon das eine oder andere Mal auf das Problem eingegangen. Zwei Theorien gefallen mir dabei besonders gut:

  1. Auf die nächste Version von JavaFX warten, da soll das mit dabei sein. Der große Nachteil darin ist, dass wir das erst mit dem Release von Java 8 erleben werden. Und um ehrlich zu sein, habe ich keine Lust auf meinen Ruhestand zu warten, um den Blog zu beenden. Java 8 hat immerhin mehr Verspätungen als die Deutsche Bahn.
  2. Man benutzt etwas, das man schon zur Verfügung hat. Und genau so machen wir es. Mit Hilfe von AWT packen wir unsere JavaFX-Fenster in den Infobereich.

Dann legen wir mal los!

Wir beginnen ganz normal indem wir von Application erben (wegen JavaFX 2) und in unserer main-Methode launch(args) aufrufen. Auch, dass wir start() implementieren ist noch Routine. Um die Sache einfach zu halten nehmen wir ein einfaches Fenster mit dem Inhalt „Hello World“.

Alles, was uns interessiert findet innerhalb der if-Bedignung ab. In der Abfrage überprüfen wir als erstes, ob das System ein SystemTray überhaupt anbietet. Ansonsten können wir uns das nicht nur sparen, sondern rennen auch noch in eine dementsprechende Exception rein, wenn wir es versuchen.

Den Aufruf Platform.setImplicitExit(false) machen wir, weil JavaFX die Angewohnheit hat, sich zu beenden, wenn das letzte vorhandene Fenster verschwunden ist. Durch die Spielereien im SystemTray könnten wir also einen Teil unserer Anwendung, den JavaFX-Thread, unbeabsichtigt beenden.

Und um es zumindest ein bisschen übersichtlich zu gestalten habe ich die eigentliche Zauberei in die Methode setTrayIcon() ausgelagert.

In der Methode setTrayIcon(), die es nur gibt, damit start() nicht so übermäßig aufgebläht wird, lassen wir uns als erstes eine Referenz auf das SystemTray geben und laden das gewünschte Icon, welches wir verwenden wollen. Kleiner Tipp am Rande: [Pfad zur Bilddatei] ist nur als Platzhalter für den String gedacht. 😉

Nun findet die eigentliche Magie statt. Und wie man sehen kann, passiert in unserem Beispiel nicht viel. Wir verwenden 2 Listener: Einer um das Fenster sichtbar zu machen und einer um die Anwendung zu beenden.

Der ActionListener listenerShow zeigt ein wichtiges Detail. Die Anwendung besteht an dieser Stelle schon aus 2 Threads. Und zwar dem JavaFX-Thread und dem AWT-Thread. GUI-Anwendungen haben die Angewohnheit, dass die Benutzeroberfläche über einen Hauptthread verwaltet wird. Der Versuch aus einem anderem Thread heraus irgendeine Änderung zu machen, und mag sie noch so trivial wirken, wird mit einer Exception belohnt und das war’s dann auch schon für die Anwendung.

Damit das Fenster wieder erscheint, brauchen wir nur window.show() aufrufen (wichtig: window muss final sein). Damit wir nicht mit einer Access-Violation verprügelt werden, müssen wir das in ein Platform.runLater() verpacken. Damit hätten wir auch schon einen tollen Einstiegspunkt für Themen wie „Multithreading in GUI-Awendungen“. Um an dieser Stelle nicht zu weit vom Thema abzuweichen sagen wir einfach: Auf diese Weise geben wir JavaFX Anweisungen außerhalb des dessen Threads. Und da wir uns im AWT-Thread befinden, muss das sein.

Der Listener listenerClose ist schon weniger spannend. Mit diesem Listener werden wir über das SystemTray die Anwendung beenden.

Anschließend brauchen wir noch ein neues Handle. Wenn wir das Fenster „schließen“, wollen wir es in Wirklichkeit verstecken.

Alles, was jetzt noch fehlt sind Eingabemöglichkeiten. Dafür erstellen wir ein Popup-Menu und fügen zwei Einträge hinzu. Den Einträgen müssen dann nur noch die Listener hinzugefügt werden und schon darf unser lustiges kleines Icon (das hängt von der gewählten Grafik ab) es sich im SystemTray gemütlich machen.

Und wer sich jetzt denkt:“Boah ist das lahm! Ich will mit dem SystemTray lieber XYZ machen!“ kann sich freuen. Der Code enthält alles, was man als Grundlage braucht, um seine eigenen diabolischen Meisterwerke zu verwirklichen.

Veröffentlicht in Java Getagged mit: , , , , , , , ,
4 commenti su “GUI-Basteleien machen Spaß – Wie man JavaFX Applikationen in das System Tray verbannt
  1. andre sagt:

    Danke! Hat mir geholfen 🙂

    Habe es allerdings ausgelagert um es Übersichtlicher zu halten.

    Aufruf:
    if (SystemTray.isSupported()) {
    Platform.setImplicitExit(false);
    SystemTrayIcon.setTrayIcon(stage);
    }
    Klasse: http://pastie.org/private/y600rfqhc8is27zdw0kx7q

    • Oliver Baumann sagt:

      Es freut mich, dass es dir geholfen hat.
      Es auszulagern macht natürlich Sinn. Bei Beispielcode versuche ich mich normalerweise auf das wesentliche zu beschränken, um nicht mehr Quellcode
      im Blog zu haben als notwendig. Man möchte ja den Leser nicht unnötig viel Quellcode zumuten um auf den Punkt zu kommen.
      Wenn das jetzt allerdings zu unübersichtlich war, dann werde ich wohl zukünftig schauen müssen, den Code etwas besser zu kapseln.

      Vielen Dank für das Feedback.
      Mit freundlichen Grüßen,

      Oliver Baumann

      • andre sagt:

        Hey,
        habe nochmal einige Änderungen gemacht und jetzt wird bei mir kein Bild mehr angezeigt.

        So wollte ich das Bild laden: Image image = Toolkit.getDefaultToolkit().getImage(„src/resources/icon.png“);

        im ordner „src“ habe ich einen ordner „resources“ und dieser beinhaltet die datei „icon.png“.
        ich verstehe nicht warum das bild Leer bleibt. Es gibt zwar ein Tray Icon im System Tray, allerdings komplett leerer Inhalt.

        Hast du eine Idee?

        • Oliver Baumann sagt:

          Entschuldige bitte die lange Wartezeit.

          Ich habe nun eine Weile herumprobiert, da es sich bei mir zuerst auch geweigert hatte, das Icon im Systemtray anzuzeigen. Allerdings nicht nur, wenn das Bild im Projektverzeichnis war, sondern auch mit einem Systempfad (beliebiger Ordern, in meinem Fall auf C:).

          Ich habe die Zeile, in der das Bild geladen wird dadurch getauscht:
          Image image = Toolkit.getDefaultToolkit().getImage(ClassLoader.getSystemClassLoader().getResource(„resources/icon.png“));
          Das Bild liegt in einem Ordner namnes „resources“ im „src“-Ordner.

          Danach wurde es kurios. Es wollte nicht auftauchen.
          Für das Projekt unter „Build Path -> configure build path… -> libraries (tab)“ habe ich die JRE ausgewählt und bin auf edit gegangen. Nachdem ich unter Execution Environment explizit eine JavaSE ausprobiert hatte, ging es nun endlich.

          Mir stellt sich nun die Frage, wieso dies bei mir vorerst schief ging (den Code von vor einem Jahr hatte ich nicht mehr und musste deshalb ein neues Pojekt anlegen).

          In diesem Fall war Java überraschenderweise Schuld. Wie es allerdings zu dieser eigenartigen Konfiguration kommen konnte, kann ich jetzt allerdings nicht nachvollziehen.

          Ich hoffe, dass es dir weiter hilft.

          Kleine Randnotiz: Wenn du .ico-Dateien für Icons verwenden willst, dann könnte es sein, dass diese Icons nicht erscheinen, da dieses Format anscheinend nicht unterstützt wird.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *

*

* Copy This Password *

* Type Or Paste Password Here *