如何将Java JAR应用程序运行时重新运行在系统托盘中的JavaFX编写用户界面

问题描述 投票:1回答:1

我有正在运行的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, "");
                    }
                }
            }
        });
java user-interface javafx httpserver
1个回答
2
投票

有些意见

首先,在更新后的监听器:

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

}

关于在实施例

在我的例子我保持很强的参考,而我展示加入StageActionListener被调用时TrayIcon。请注意如何在ActionListener我用Platform.runLater。对于您添加到SystemTray相关对象每一个听众(例如java.awt.MenuItem),包装将与在Platform.runLater电话的JavaFX对象进行交互的任何代码。

现在,我的示例首先推出JavaFX运行,然后添加TrayIcon。不仅是立即启动JavaFX运行,但我还预先创建的场景图和很强的参考存储它。这可能是很多不必要的开销和内存消耗。随着应用程序是指在不JavaFX运行也有一些优化你可以运行一个HTTP服务器。

  • 不要存放强烈参考Stage一旦关闭,允许它被垃圾收集。可能的选项包括: 当Stage关闭立即删除引用。这将需要你每次都重新创建的场景图。 删除引用一些任意时间Stage关闭之后。这将有某种定时器来完成(如PauseTransitionTimeline),当时间结束之前Stage已重新开放复位。 当用户请求的GUI,你会在必要时,(重新)创建场景图和(重新)与您的模型进行初始化。不要忘记任何必要的清理,如删除侦听器观察你的模型,配置了场景图的时候;任强引用任何地方将保留对象(收费)内存,导致内存泄漏。
  • 懒洋洋地推出JavaFX运行。 不要在你的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的一侧退出并重新启动被你可以使用一个单独的进程。但是,使用一个单独的进程是非常不平凡的。

© www.soinside.com 2019 - 2024. All rights reserved.