JavaFX - Systray sem Stage (Erro:Not on FX application thread)

Prezados, boa tarde!

Faz tempo que não desenvolvo uma app JavaFx. Tenho que criar um systray que receberá mensagens de um serviço, via Socket. Essa parte está OK. O problema está no JFX.

Para facilitar a manutenção, pensei em organizar o código assim (só a estrutura relevante do JFX):

  • meupackage
    • menus
      • ExitMenuItemHandler.java
      • OtherMenuItemHandler.java
      • SystrayMenuItem.java
    • SystrayLauncher.java

Disso isso, um pouco de código…

SystrayLauncher.java

public class SystemTrayLauncher extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(final Stage mainStage) {

        try {
            Platform.setImplicitExit(false);
            mainStage.initStyle(StageStyle.TRANSPARENT);
            Toolkit.getDefaultToolkit();

            if (SystemTray.isSupported()) {
                SystemTray tray = SystemTray.getSystemTray();
                PopupMenu menu = new PopupMenu();

                SystrayMenuItems.createMenuItems(menu);

                Image imageIcon = ImageIO.read(new URL(SystemConfiguration.SYSTEM_ICON));
                TrayIcon icon = new TrayIcon(imageIcon, SystemConfiguration.SYSTEM_NAME, menu);

                icon.addActionListener(event -> Platform.runLater(mainStage::hide));
                tray.add(icon);

            /** Start to listen to service **/
            //not relevant

            } else {
                Alert dialog = new Alert(Alert.AlertType.ERROR);
                dialog.setTitle(null);
                dialog.setHeaderText("System tray unavailable! Click OK to finish application.");
                Optional<ButtonType> result = dialog.showAndWait();

                if (result.get() == ButtonType.OK) {
                    Platform.exit();
                }
            }

        } catch (Exception exc) {
            System.err.println(exc.getMessage());
        }
    }
}

No SystrayMenuItems eu apenas faço a inserção dos menuitems no popupmenu criado na SystrayLauncher

public static void createMenuItems(PopupMenu menu) {

    menu.add(OtherMenuItemHandler.createMenuItem());
    menu.addSeparator();
    menu.add(ExitMenuItemHandler.createMenuItem());
}

E nos Handlers, apenas faço o ActionListener. Por exemplo, na ExitMenuItemHandler:

public static MenuItem createMenuItem() {
    MenuItem exit = new MenuItem("Sair");

    exit.addActionListener(event -> {

        Alert dialog = new Alert(Alert.AlertType.CONFIRMATION);
        dialog.setTitle("test");
        dialog.setHeaderText("Confirm to exit application?");
        Optional<ButtonType> result = dialog.showAndWait();

        if (result.get() == ButtonType.OK) {
            Platform.exit();
        }
    });

    return exit;
}

O problema é que, quando eu clico no menu Sair, é exibido a exceção do título, no momento de exibir o Alert: java.lang.IllegalStateException: Not on FX application thread;

Não sei bem como resolver o problema… Tentei já colocar todo o código das outras classes (SystrayMenuItems e ExitMenuItemHandler) na SystrayLauncher, mas também retorna o mesmo erro.
Como, nessa fase, não preciso de um Stage/Scene, o que devo fazer pra funcionar?

Obrigado antecipadamente!

Olá, ao que parece a action do item não executa na thread do FX, já tentou colocar assim:

  exit.addActionListener(event -> {

  Platform.runLater(() ->{
    Alert dialog = new Alert(Alert.AlertType.CONFIRMATION);
    dialog.setTitle("test");
    dialog.setHeaderText("Confirm to exit application?");
    Optional<ButtonType> result = dialog.showAndWait();

    if (result.get() == ButtonType.OK) {
        Platform.exit();
    }
	});
});


Posta o StackTrace completo aí pra poder itentificar melhor o erro.

Olá Andrauss! Obrigado pela resposta. Coloquei o Platform.runLater() como indiciou mas não obtive sucesso. Nada acontece.
Deixando da forma como estava, eis o stacktrace:

Exception in thread "AWT-EventQueue-0" java.lang.IllegalStateException: Not on FX application thread; currentThread = AWT-EventQueue-0 at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:236) at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:423) at javafx.stage.Stage.<init>(Stage.java:241) at javafx.stage.Stage.<init>(Stage.java:227) at javafx.scene.control.HeavyweightDialog$1.<init>(HeavyweightDialog.java:52) at javafx.scene.control.HeavyweightDialog.<init>(HeavyweightDialog.java:52) at javafx.scene.control.Dialog.<init>(Dialog.java:263) at javafx.scene.control.Alert.<init>(Alert.java:245) at javafx.scene.control.Alert.<init>(Alert.java:223) at br.com.test.menus.ExitMenuItemHandler.lambda$createMenuItem$0(ExitMenuItemHandler.java:23) at java.awt.MenuItem.processActionEvent(MenuItem.java:669) at java.awt.MenuItem.processEvent(MenuItem.java:628) at java.awt.MenuComponent.dispatchEventImpl(MenuComponent.java:351) at java.awt.MenuComponent.dispatchEvent(MenuComponent.java:339) at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:761) at java.awt.EventQueue.access$500(EventQueue.java:97) at java.awt.EventQueue$3.run(EventQueue.java:709) at java.awt.EventQueue$3.run(EventQueue.java:703) at java.security.AccessController.doPrivileged(Native Method) at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:76) at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86) at java.awt.EventQueue$4.run(EventQueue.java:731) at java.awt.EventQueue$4.run(EventQueue.java:729) at java.security.AccessController.doPrivileged(Native Method) at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:76) at java.awt.EventQueue.dispatchEvent(EventQueue.java:728) at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201) at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116) at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93) at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)

Tenho um projeto com o FX em que usei SysTray e resolvi com runLater. vou dar uma olhada mais a fundo no seu código

Assim funcionou

public static MenuItem createMenuItem() {
    MenuItem exit = new MenuItem("Sair");

    exit.addActionListener(event -> {

        Platform.runLater(() -> {
            Alert dialog = new Alert(Alert.AlertType.CONFIRMATION, "Confirm to exit application?", ButtonType.YES, ButtonType.NO);
            dialog.setTitle("test");

            dialog.showAndWait() 
                    .filter(resposta -> resposta.equals(ButtonType.YES)) // Verifica se foi pressionado "Sim"
                    .ifPresent(resposta-> System.exit(0));
        });
    });

    return exit;
}
1 curtida

Andrauss, muito obrigado! Funcionou aqui pra mim também! Tive também que trabalhar com a forma como estava usando o Socket, para ficar numa thread separada!
Obrigado pela sua ajuda e tempo!
Abraços