Некоторые наблюдения
Во-первых, в обновленном слушателе:
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
и, , если true , затем вызываете Platform.runLater
.Вызов Platform.runLater
планирует действие, выполняемое в потоке приложений JavaFX ;если вы уже участвуете в этой теме, нет необходимости (обычно) вызывать Platform.runLater
.Конечно, isFxApplicationThread
никогда не вернет true, потому что SystemTray
является частью AWT и вызовет слушателя в потоке, связанном с AWT.Это означает, что всегда будет вызываться ветвь 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
), оберните любой код, который будет взаимодействовать с объектами JavaFX, в вызове Platform.runLater
.
Теперь мой пример запускает среду выполнения JavaFX сначала , а затем добавляет TrayIcon
.Мало того, что исполняемая среда JavaFX запущена немедленно, но я также предварительно создаю граф сцены и сохраняю сильную ссылку на него.Это может быть много ненужных накладных расходов и потребления памяти.Поскольку ваше приложение представляет собой HTTP-сервер, который может работать без среды выполнения JavaFX, вы можете выполнить некоторые оптимизации.
Не сохраняйте сильную ссылку на Stage
после закрытия, что позволяетэто будет сбор мусора.Возможные варианты:
Немедленно удалите ссылку, когда Stage
закрыт.Это потребует от вас пересоздания графа сцены каждый раз.
Удалите ссылку через произвольное время после закрытия Stage
.Это можно сделать с помощью какого-либо таймера (например, PauseTransition
или Timeline
), который сбрасывается, когда Stage
был вновь открыт до истечения времени.
Когда пользователь запрашивает графический интерфейс, вы, при необходимости, (заново) создаете граф сцены и (заново) инициализируете его с вашей моделью.Не забывайте о любой необходимой очистке, такой как удаление слушателей, наблюдающих вашу модель, при утилизации графа сцены;любые сильные ссылки в любом месте сохранят объект (ы) в памяти, что приведет к утечке памяти.
Ленивый запуск среды выполнения JavaFX.
В вашем подклассе Application
нет логики инициализации / запуска сервера.В частности, не помещайте ваш метод main
в этот класс, поскольку он косвенно запустит среду выполнения JavaFX .
При первом запросе, чтобы показать использование GUIApplication.launch
.
Примечание: я считаю, что вызов launch
должен быть сделан в отдельном потоке.Поток, вызывающий launch
, не возвращается, пока не завершится среда выполнения JavaFX.Поскольку слушатели TrayIcon
вызываются в потоке AWT, это приведет к тому, что этот поток будет заблокирован, что было бы нехорошо.
При последующих запросах просто отображать окно, воссоздавая его при необходимости.То, как вы это сделаете, зависит от вашей архитектуры.Один из вариантов - сделать класс Application
своего рода ленивым синглтоном, который устанавливается через Application.launch
;вы получите ссылку и вызовете метод для отображения окна (в потоке FX).
Оба параметра будут поддерживать время выполнения JavaFX после запускаДо тех пор, пока не выйдет все приложение.Технически вы можете выйти из среды выполнения JavaFX независимо, но, как упоминалось ранее, вызов Application.launch
более одного раза является ошибкой;если вы сделаете это, вы не сможете снова отобразить графический интерфейс, пока все приложение не будет перезапущено.Если вы действительно хотите разрешить стороне приложения JavaFX выходить и перезапускаться, вы можете использовать отдельный процесс.Однако использование отдельного процесса очень нетривиально.