JavaFX 应用程序在系统托盘中

20

我正在使用JavaFX UI制作一个简单的应用程序,该应用程序仅执行以下操作:

  • 有一个系统托盘图标,单击时显示一个窗口,再次单击时隐藏它,在右键单击时显示带有1个“退出”项的菜单

我已经制作了UI并将应用程序放在了Sys Tray中,但我无法使用常规Actionlistener方法来显示/隐藏它,而是出现了以下错误:

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

以下是代码:

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");
          } 
        //
    }
}
5个回答

22
将代码放在actionListener中,通过Platform.runLater回调到JavaFX。这将在JavaFX应用程序线程上执行与JavaFX系统交互的代码,而不是尝试在Swing事件线程上执行(这就是导致问题的原因)。
例如:
ActionListener listenerTray = new ActionListener() {                
  @Override public void actionPerformed(java.awt.event.ActionEvent event) {
    Platform.runLater(new Runnable() {
      @Override public void run() {
        primaryStage.hide();
      }
    });
  }                   
};            

默认情况下,当应用程序的最后一个窗口被隐藏时,它会关闭。为了覆盖这种默认行为,在显示第一个应用程序舞台之前,请调用Platform.setImplicitExit(false)。然后,当您需要真正关闭应用程序时,您将需要显式调用Platform.exit()
我创建了一个在JavaFX应用程序中使用AWT系统托盘的演示

是的,我刚在文档页面找到了,非常感谢。但是当我隐藏它时,即使我使用show()也不会再次出现。 - amrlab
更新答案以解释隐式/显式退出行为。 - jewelsea
非常感谢,我一直在寻找这个!! - amrlab
如果你真的只需要一个窗口而且它不是非常复杂的话,你应该坚持使用awt/swing,并避免使用JavaFx,直到它支持托盘图标。 - ed22

4

你应该只在javafx线程上修改javafx类,托盘图标上的监听器很可能在swing线程上运行。你可以通过向Platform#runLater发布一个可运行对象来实现这一点:

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

1
系统托盘在JavaFX中尚未得到支持。您可以在以下JIRA问题下跟踪此任务的进展:https://bugs.openjdk.java.net/browse/JDK-8090475
该问题还提供了一个解决方法,可用于在JavaFX 8中获得基本支持。
该功能不计划在JavaFX 8中发布,因此可能会在以下更新或甚至在JavaFX 9中发布。

5
希望能够无需注册就能查看Jira的内容。 - aphex

1

有些自夸,但我为JavaFX图标开发了一个小型包装库,用于使用SystemTray的FXTrayIcon

它抽象了所有令人讨厌的AWT部分,并消除了猜测应该在哪个线程上运行代码的需要。 它可以作为Maven Central上的依赖项使用。


太棒了,这比所有的awt混乱好多了。对于我在这个问题上的解决方案,这是迄今为止最好的选择。 - 4673_j

0

我解决了你的问题。JavaFX与AWT。我有一个应用程序的示例,当你单击左键时显示和隐藏。我真的希望它对你有用。

import java.awt.AWTException;
import java.awt.Image;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.net.URL;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.application.Platform;
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 MainApp2 extends Application {

int stateWindow = 1;

@Override
public void start(final Stage stage) throws Exception {

//Check the SystemTray is supported
if (!SystemTray.isSupported()) {
    System.out.println("SystemTray is not supported");
    return;
}

URL url = System.class.getResource("/image/yourImage.png");
Image image = Toolkit.getDefaultToolkit().getImage(url);

//image dimensions must be 16x16 on windows, works for me
final TrayIcon trayIcon = new TrayIcon(image, "application name");

final SystemTray tray = SystemTray.getSystemTray();

//Listener left clic XD
trayIcon.addMouseListener(new MouseAdapter() {
    @Override
    public void mouseClicked(MouseEvent event) {
        if (event.getButton() == MouseEvent.BUTTON1) {
            Platform.runLater(new Runnable() {
                @Override
                public void run() {
                    if (stateWindow == 1) {
                        stage.hide();
                        stateWindow = 0;
                    } else if (stateWindow == 0) {
                        stage.show();
                        stateWindow = 1;
                    }
                }
            });
        }

    }
});

try {
    tray.add(trayIcon);
} catch (AWTException e) {
    System.out.println("TrayIcon could not be added.");
}

stage.setTitle("Hello man!");
Button btn = new Button();
btn.setText("Say 'Hello man'");
btn.setOnAction(new EventHandler<ActionEvent>() {

    @Override
    public void handle(ActionEvent event) {
        System.out.println("Hello man!");
    }
});
StackPane root = new StackPane();
root.getChildren().add(btn);
stage.setScene(new Scene(root, 300, 250));
Platform.setImplicitExit(false);
stage.show();

}

/**
 * The main() method is ignored in correctly deployed JavaFX application.
 * main() serves only as fallback in case the application can not be
 * launched through deployment artifacts, e.g., in IDEs with limited FX
 * support. NetBeans ignores main().
 *
 * @param args the command line arguments
 */
public static void main(String[] args) {
launch(args);
}
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接