Как перезапустить пользовательский интерфейс, написанный с javafx в системном трее, когда java jar-приложение работает - PullRequest
0 голосов
/ 01 февраля 2019

У меня есть Java-приложение, работающее на Java-сервере http.Это 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, "");

Обновлен TrayIcon 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, "");
                    }
                }
            }
        });

1 Ответ

0 голосов
/ 04 февраля 2019

Некоторые наблюдения

Во-первых, в обновленном слушателе:

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 выходить и перезапускаться, вы можете использовать отдельный процесс.Однако использование отдельного процесса очень нетривиально.

...