JavaFX app in System Tray

I am Making a Simple App using JavaFX UI, The app simply just do that:

  • has a systray icon, which when clicked shows a window, when clicked again hides it, on rightclick shows a menu with 1 "exit" item

I already Made the UI and put the App in the Sys Tray, but i can't show/hide it using Normal Actionlistener method, but i got this error:

Exception in thread "AWT-EventQueue-0" java.lang.IllegalStateException: Not on FX application thread; currentThread = AWT-EventQueue-0

here is the Code:

import java.awt.Image;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.event.ActionListener;


import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class Main extends Application{
    public static void main(String[] args) { 
        launch(args);       
    }

    @Override
    public void start(final Stage primaryStage) {
        primaryStage.setTitle("Hello World!");
        Button btn = new Button();
        btn.setText("Say 'Hello World'");
        btn.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent event) {
                System.out.println("Hello World!");            }
        });

        StackPane root = new StackPane();
        root.getChildren().add(btn);
        primaryStage.setScene(new Scene(root, 300, 250));
        primaryStage.show();

        if (SystemTray.isSupported()) {         
            SystemTray tray = SystemTray.getSystemTray();
            Image image = Toolkit.getDefaultToolkit().getImage("Germany-politcal-map.jpg");
            PopupMenu popup = new PopupMenu();
            MenuItem item = new MenuItem("Exit");

            popup.add(item);

            TrayIcon trayIcon = new TrayIcon(image, "Amr_Trial", popup);

            ActionListener listener = new ActionListener() {                
                @Override
                public void actionPerformed(java.awt.event.ActionEvent arg0) {
                    // TODO Auto-generated method stub
                    System.exit(0);                 
                }               
            };                       

            ActionListener listenerTray = new ActionListener() {                
                @Override
                public void actionPerformed(java.awt.event.ActionEvent arg0) {
                    // TODO Auto-generated method stub                  
                    primaryStage.hide();
                }                   
            };            

            trayIcon.addActionListener(listenerTray);
            item.addActionListener(listener);

            try{
              tray.add(trayIcon);
            }catch (Exception e) {
              System.err.println("Can't add to tray");
            }
          } else {
            System.err.println("Tray unavailable");
          } 
        //
    }
}

Solution 1:

Wrap the code in the actionListener which calls back to JavaFX in Platform.runLater. This will execute the code which interfaces with the JavaFX system on the JavaFX application thread rather than trying to do it on the Swing event thread (which is what is causing you issues).

For example:

ActionListener listenerTray = new ActionListener() {                
  @Override public void actionPerformed(java.awt.event.ActionEvent event) {
    Platform.runLater(new Runnable() {
      @Override public void run() {
        primaryStage.hide();
      }
    });
  }                   
};            

By default the application will shutdown when it's last window is hidden. To override this default behaviour, invoke Platform.setImplicitExit(false) before you show the first application Stage. You will then need to explicitly call Platform.exit() when you need the application to really shutdown.


I created a demo for using the AWT system tray within a JavaFX application.

Solution 2:

You should only modify the javafx classes on the javafx thread, the listeners on the tray icon are likely to be running on the swing thread. You can do this by posting a runnable to Platform#runLater like so:

Platform.runLater(new Runnable() {
    public void run() {
        primaryStage.hide();
    }
});

Solution 3:

The system tray is not supported in JavaFX yet. You could track the progress on this task under the following JIRA issue: https://bugs.openjdk.java.net/browse/JDK-8090475
The issue also provides a workaround, which could be used in JavaFX 8 to get the basic support.

The feature is not planned for JavaFX 8, so it might be released in one of the following updates or even in JavaFX 9.

Solution 4:

Shameless self-plug, but I developed a small wrapper library for JavaFX icons that use the SystemTray called FXTrayIcon.

It abstracts away all of the nasty AWT bits and eliminates having to guess which thread you should be running code on. It's available as a dependency on Maven Central.