我有正在运行的Java HTTP服务器的Java应用程序。这个Java应用程序应连续运行。我不想在程序运行在第一次时打开的JavaFX GUI。
正如我所说的,应用程序应该连续运行。用户应该能够通过点击系统托盘图标,随时打开用户界面。还是应该能够关闭该界面中的交叉按钮。
我用Platform.setImplicitExit (false)
不从按下接口上的交叉按钮来停止Java应用程序。
如果用户希望再次看到的画面,我想按系统托盘重新呈现在屏幕上。
我要显示和隐藏用户界面,但不关闭Java程序。什么是我在等待你的帮助的最佳实践。
相关代码如下所示。
public class Gui extends Application {
@Override
public void start(Stage stage) throws Exception {
Platform.setImplicitExit(false);
Platform.runLater(new Runnable() {
@Override
public void run() {
try {
new Gui().start(new Stage());
} catch (Exception e) {
e.printStackTrace();
}
}
});
Scene scene = new Scene(new StackPane());
LoginManager loginManager = new LoginManager(scene);
loginManager.showLoginScreen();
stage.setScene(scene);
stage.show();
// stage.setOnCloseRequest(e -> Platform.exit());
}
}
大类
public static void main(String[] args) throws IOException, Exception, FileNotFoundException {
ServerSocket ss = null;
try {
ss = new ServerSocket(9090);
if (ss != null) {
ss.close();
}
} catch (BindException e) {
System.out.println("Sikke Node Server is already running.");
System.exit(0);
}
launchh();
}
在主类中的方法
private static void createAndShowGUI() {
if (SystemTray.isSupported()) {
final PopupMenu popup = new PopupMenu();
final TrayIcon trayIcon = new TrayIcon(createImage("/sikke24.gif", "Sikke Node "), "Sikke Node Server",
popup);
trayIcon.setImageAutoSize(true);
final SystemTray tray = SystemTray.getSystemTray();
final int port = Integer.parseInt(_System.getConfig("rpcport").get(0));
// Create a popup menu components
MenuItem aboutItem = new MenuItem("About");
Menu displayMenu = new Menu("Display");
MenuItem infoItem = new MenuItem("Info");
MenuItem noneItem = new MenuItem("None");
MenuItem exitItem = new MenuItem("Exit Sikke Node Server");
// Add components to popup menu
popup.add(aboutItem);
popup.addSeparator();
popup.add(displayMenu);
displayMenu.add(infoItem);
displayMenu.add(noneItem);
popup.add(exitItem);
trayIcon.setPopupMenu(popup);
try {
tray.add(trayIcon);
} catch (AWTException e) {
System.out.println("Sikke Node Icon could not be added.");
return;
}
trayIcon.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
/*
* JOptionPane.showMessageDialog(null,
* "Server started successfully. The server works on port number:" + port);
*/
Application.launch(Gui.class, "");
}
});
aboutItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null,
"Server started successfully. The server works on port number:" + port);
}
});
ActionListener listener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
MenuItem item = (MenuItem) e.getSource();
System.out.println(item.getLabel());
if ("Error".equals(item.getLabel())) {
trayIcon.displayMessage("Sikke Node Server", "This is an error message",
TrayIcon.MessageType.ERROR);
} else if ("Warning".equals(item.getLabel())) {
trayIcon.displayMessage("Sikke Node Server", "This is a warning message",
TrayIcon.MessageType.WARNING);
} else if ("Info".equals(item.getLabel())) {
// GUI runs
trayIcon.displayMessage("Sikke Node Server", "This is an info message",
TrayIcon.MessageType.INFO);
} else if ("None".equals(item.getLabel())) {
trayIcon.displayMessage("Sikke Node Server", "This is an ordinary message",
TrayIcon.MessageType.NONE);
}
}
};
trayIcon.displayMessage("Sikke Node Server", "Sikke Node Server started successfully on port : " + port,
TrayIcon.MessageType.INFO);
infoItem.addActionListener(listener);
noneItem.addActionListener(listener);
exitItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
tray.remove(trayIcon);
System.exit(0);
}
});
}
}
当心这里
Application.launch(Gui.class, "");
任务栏图标的ActionListener更新
trayIcon.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 1) {
if (Platform.isFxApplicationThread()) {
Platform.runLater(new Runnable() {
@Override
public void run() {
try {
new Gui().start(new Stage());
} catch (Exception e) {
e.printStackTrace();
}
}
});
} else {
Application.launch(Gui.class, "");
}
}
}
});
首先,在更新后的监听器:
trayIcon.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 1) {
if (Platform.isFxApplicationThread()) {
Platform.runLater(new Runnable() {
@Override public void run() { /* OMITTED FOR BREVITY */ }
});
} else {
Application.launch(Gui.class, "");
}
}
}
});
你检查Platform.isFxApplicationThread
,如果为真,则调用Platform.runLater
。调用Platform.runLater
时间表对JavaFX应用程序线程中执行的动作;如果你已经在该线程有没有必要(典型值)来调用Platform.runLater
。当然,由于isFxApplicationThread
是AWT的一部分,将调用AWT相关话题在听者SystemTray
永远不会返回true。这意味着else
分公司将始终被调用,这是一个问题,因为你不能叫Application.launch
不止一次在单个JVM实例;这样做在IllegalStateException
结果被抛出。
此外,在您的start
方法:
@Override
public void start(Stage stage) throws Exception {
Platform.setImplicitExit(false);
Platform.runLater(new Runnable() {
@Override
public void run() {
try {
new Gui().start(new Stage());
} catch (Exception e) {
e.printStackTrace();
}
}
});
/* SOME CODE OMITTED FOR BREVITY */
}
这Platform.runLater
通话应该是造成“循环”。当你调用start
你安排Runnable
在通过Platform.runLater
通话一段时间以后运行。这里面Runnable
你打电话new Gui().start(new Stage())
。什么,做的就是调用再次start
(上Gui
的新实例),这将再次调用Platform.runLater
,这将再次调用new Gui().start(new Stage())
,这将再次打电话start
,你这...的想法。
需要注意的是Application.launch(Gui.class)
将创建Gui
的一个实例,并调用start
与您的主要Stage
。但正如上面提到的,launch
只能被调用一次。从概念上讲,Application
子类代表了整个应用程序。应该有理想的永远只能是这个类的一个实例。
SystemTray
下面是一个使用SystemTray
(重新)打开一个JavaFX窗口的一个小例子。不显示窗口,直到用户点击(双击,至少在Windows上)的托盘图标。
import java.awt.AWTException;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.image.BufferedImage;
import java.util.function.Predicate;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
public class Main extends Application {
private Stage primaryStage;
private boolean iconAdded;
@Override
public void start(Stage primaryStage) throws AWTException {
if (SystemTray.isSupported()) {
installSystemTray();
Platform.setImplicitExit(false);
StackPane root = new StackPane(new Label("Hello, World!"));
primaryStage.setScene(new Scene(root, 500, 300));
primaryStage.setTitle("JavaFX Application");
primaryStage.setOnCloseRequest(this::promptUserForDesiredAction);
this.primaryStage = primaryStage;
} else {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText(null);
alert.setContentText("SystemTray is not supported. Will exit application.");
alert.showAndWait();
Platform.exit();
}
}
@Override
public void stop() {
if (iconAdded) {
SystemTray tray = SystemTray.getSystemTray();
for (TrayIcon icon : tray.getTrayIcons()) {
tray.remove(icon);
}
}
}
private void promptUserForDesiredAction(WindowEvent event) {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.initOwner((Window) event.getSource());
alert.setTitle("Choose Action");
alert.setHeaderText(null);
alert.setContentText("Would you like to exit or hide the application?");
// Use custom ButtonTypes to give more meaningful options
// than, for instance, OK and CANCEL
ButtonType exit = new ButtonType("Exit");
ButtonType hide = new ButtonType("Hide");
alert.getDialogPane().getButtonTypes().setAll(exit, hide);
alert.showAndWait().filter(Predicate.isEqual(exit)).ifPresent(unused -> Platform.exit());
}
private void installSystemTray() throws AWTException {
TrayIcon icon = new TrayIcon(createSystemTrayIconImage(), "Show JavaFX Application");
// On Windows 10, this listener is invoked on a double-click
icon.addActionListener(e -> Platform.runLater(() -> {
if (primaryStage.isShowing()) {
primaryStage.requestFocus();
} else {
primaryStage.show();
}
}));
SystemTray.getSystemTray().add(icon);
iconAdded = true;
}
// Creates a simple red circle as the TrayIcon image. This is here
// to avoid needing an image resource for the example.
private BufferedImage createSystemTrayIconImage() {
Circle circle = new Circle(6.0, Color.FIREBRICK);
Scene scene = new Scene(new Group(circle), Color.TRANSPARENT);
return SwingFXUtils.fromFXImage(circle.snapshot(null, null), null);
}
}
在我的例子我保持很强的参考,而我展示加入Stage
的ActionListener
被调用时TrayIcon
。请注意如何在ActionListener
我用Platform.runLater
。对于您添加到SystemTray
相关对象每一个听众(例如java.awt.MenuItem
),包装将与在Platform.runLater
电话的JavaFX对象进行交互的任何代码。
现在,我的示例首先推出JavaFX运行,然后添加TrayIcon
。不仅是立即启动JavaFX运行,但我还预先创建的场景图和很强的参考存储它。这可能是很多不必要的开销和内存消耗。随着应用程序是指在不JavaFX运行也有一些优化你可以运行一个HTTP服务器。
Stage
一旦关闭,允许它被垃圾收集。可能的选项包括:
当Stage
关闭立即删除引用。这将需要你每次都重新创建的场景图。
删除引用一些任意时间Stage
关闭之后。这将有某种定时器来完成(如PauseTransition
或Timeline
),当时间结束之前Stage
已重新开放复位。
当用户请求的GUI,你会在必要时,(重新)创建场景图和(重新)与您的模型进行初始化。不要忘记任何必要的清理,如删除侦听器观察你的模型,配置了场景图的时候;任强引用任何地方将保留对象(收费)内存,导致内存泄漏。Application
子类的任何服务器初始化/运行的逻辑。尤其是,不要把你的main
方法在类,因为它会indirectly launch the JavaFX runtime。
在第一个请求,以显示GUI使用Application.launch
。
注:我相信launch
呼叫必须在一个单独的线程来放。调用launch
线程不会返回,直到JavaFX运行退出。由于TrayIcon
听众呼吁AWT线程这将导致该线程被阻塞,这将不会是好的。
在后续请求中只显示窗口,必要时重新创建。你怎么去这取决于你的架构。一种选择是让你Application
类那种被通过Application.launch
设置懒惰单的;你会得到一个参考,并调用以显示窗口(FX线程)的方法。这两种方法都将保持JavaFX运行活着,一经推出,直到整个应用程序退出。你可以在技术上独立退出JavaFX运行,但正如前面提到的,调用Application.launch
不止一次是错误的;如果你这样做,你将无法直到整个应用程序重新启动再次显示GUI。如果你真的要允许应用程序的JavaFX的一侧退出并重新启动被你可以使用一个单独的进程。但是,使用一个单独的进程是非常不平凡的。