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

5 respostas
java
X

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!

5 Respostas

A

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  pra poder itentificar melhor o erro.
X

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)

A

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

A

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;
}
X

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

Criado 17 de maio de 2016
Ultima resposta 17 de mai. de 2016
Respostas 5
Participantes 2