带有controlsfx通知组件的javafx NullPointerException

我想开发一个应用程序,它使用controlsfx Notifications在系统托盘模式下显示一些通知。 在正常模式下,我的应用程序运行良好,通知可以成功显示。但是当我在系统托盘中隐藏阶段时,会发生NullPointerException。 我不知道如何解决这个问题。

import java.awt.AWTException; 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.application.Platform; import javafx.event.EventHandler; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; import javafx.stage.WindowEvent; public class TryIconNotification extends Application { private boolean firstTime; private TrayIcon trayIcon; @Override public void start(Stage stage) throws Exception { firstTime = true; Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml")); Scene scene = new Scene(root); createTrayIcon(stage); firstTime = true; Platform.setImplicitExit(false); stage.setScene(scene); stage.show(); } public void createTrayIcon(final Stage stage) { if (SystemTray.isSupported()) { // get the SystemTray instance SystemTray tray = SystemTray.getSystemTray(); // load an image java.awt.Image image = null; image = Toolkit.getDefaultToolkit().getImage("icons\\iconify.png"); stage.setOnCloseRequest(new EventHandler() { @Override public void handle(WindowEvent t) { hide(stage); } }); // create a action listener to listen for default action executed on the tray icon final ActionListener closeListener = new ActionListener() { @Override public void actionPerformed(java.awt.event.ActionEvent e) { stage.hide(); } }; ActionListener showListener = new ActionListener() { @Override public void actionPerformed(java.awt.event.ActionEvent e) { Platform.runLater(new Runnable() { @Override public void run() { stage.show(); } }); } }; // create a popup menu PopupMenu popup = new PopupMenu(); MenuItem showItem = new MenuItem("Open app"); showItem.addActionListener(showListener); popup.add(showItem); MenuItem closeItem = new MenuItem("Exit"); closeItem.addActionListener(closeListener); popup.add(closeItem); /// ... add other items // construct a TrayIcon trayIcon = new TrayIcon(image, "Systray", popup); // set the TrayIcon properties trayIcon.addActionListener(showListener); // ... // add the tray image try { tray.add(trayIcon); } catch (AWTException e) { System.err.println(e); } // ... } } public void showProgramIsMinimizedMsg() { //only in first time show the message if (firstTime) { trayIcon.displayMessage("System Tray", "Iconified", TrayIcon.MessageType.INFO); firstTime = false; } } private void hide(final Stage stage) { Platform.runLater(new Runnable() { @Override public void run() { if (SystemTray.isSupported()) { stage.hide(); showProgramIsMinimizedMsg(); } else { System.exit(0); System.out.println("Not Support Sys Tray"); } } }); } /** * @param args the command line arguments */ public static void main(String[] args) { launch(args); } } 

这是我的控制器类:

 import java.net.URL; import java.util.ResourceBundle; import java.util.Timer; import java.util.TimerTask; import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.Label; import javafx.stage.Stage; import org.controlsfx.control.Notifications; public class FXMLDocumentController implements Initializable { @FXML private Label label; @FXML private void handleButtonAction(ActionEvent event) { Stage stage = (Stage) label.getScene().getWindow(); stage.hide(); } public void createNotification() { Notifications.create() .text("This is a Notification") .title("Notifications") .showInformation(); } @Override public void initialize(URL url, ResourceBundle rb) { Timer timer = new Timer(); timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { Platform.runLater(()->createNotification()); } }, 5000, 10000); } } 

我意识到当阶段进入隐藏模式并且通知需要在阶段显示时通知组件找不到阶段时会发生此exception。 在互联网上搜索后,我找到了解决这个问题的两个方案

解决方案1:

打开舞台并显示通知。 通过这种方式,我们应该检查是否隐藏了舞台,打开它并显示通知。 为此,我们必须在CreateNotification方法中添加此条件:

 Stage stage = (Stage) button.getScene().getWindow(); if (!stage.isShowing()){ stage.show(); } 

解决方案2:

在这个解决方案中,我们创建一个虚拟舞台并将其不透明度设置为零,然后隐藏主舞台。 我在这个链接找到了这个解决方案并将代码放在这里 :

 public void createDummyStage() { Stage dummyPopup = new Stage(); dummyPopup.initModality(Modality.NONE); // set as utility so no iconification occurs dummyPopup.initStyle(StageStyle.UTILITY); // set opacity so the window cannot be seen dummyPopup.setOpacity(0d); // not necessary, but this will move the dummy stage off the screen final Screen screen = Screen.getPrimary(); final Rectangle2D bounds = screen.getVisualBounds(); dummyPopup.setX(bounds.getMaxX()); dummyPopup.setY(bounds.getMaxY()); // create/add a transparent scene final Group root = new Group(); dummyPopup.setScene(new Scene(root, 1d, 1d, Color.TRANSPARENT)); // show the dummy stage dummyPopup.show(); } 

正如我提到的那样,我们应该在隐藏主阶段之前调用此方法:

  @FXML public void handleSysTryAction(ActionEvent event) { Stage stage = (Stage) button.getScene().getWindow(); createDummyStage(); stage.hide(); } 

我实现了这两个解决方案,每一件事都运作良好。 如果您有更好的解决方案来解决这个问题请放在这里

您可以从我的Dropbox下载完整的Netbeans项目

我无法弄清楚为什么hamid的第一个解决方案不能为我工作,直到我调试Notifications创建。 我发现,除了窗口的需要之外, isShowing它必须是isFocused

我的解决方案是在Notifications.show()之前调用类似这样的方法:

 private void focusStage() { final Stage stage = (Stage) button.getScene().getWindow(); if (!stage.isShowing()) { stage.show(); } if (!stage.isFocused()) { stage.requestFocus(); } }