javafx NullPointerException with controlsfx Notifi

2019-02-24 04:37发布

I want to develop an application that uses controlsfx Notifications to show some notifications in system tray mode. In normal mode my application works well and notification can be shown successfully.but when I hide stage in system tray , NullPointerException occurs. I don't know how i can fix this problem.

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<WindowEvent>() {
            @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);
}

}

And this is my controller Class:

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

}

2条回答
Viruses.
2楼-- · 2019-02-24 05:20

I realize that this exception occurs when the stage go to hide mode and the notification component cannot find stage when notification needs to show in stage. After searching in internet I find two solution for this problem.

Solution 1:

Open the stage and show notification. In this way we should check that if the stage was hidden, open it , and show notification. To do this we must add this condition in CreateNotification Method:

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

Solution 2:

In this solution we create a dummy stage and set its opacity to zero and after that, hide the main stage. I find this solution at this link and put the code in here:

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

As I mention bellow, We should call this method before hiding the main stage:

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

I implement this two solution and every things works well. If you have a better solution for this problem please put here

You can download the complete Netbeans project from my Dropbox

查看更多
祖国的老花朵
3楼-- · 2019-02-24 05:23

I could not figure out why hamid's first solution was not working for me, until I debugged the Notifications creation. I found out, that beside the need of the Window to be isShowing it has to be isFocused too!

My solution is to call something like this method before Notifications.show():

private void focusStage() {
    final Stage stage = (Stage) button.getScene().getWindow();
    if (!stage.isShowing()) {
        stage.show();
    }
    if (!stage.isFocused()) {
        stage.requestFocus();
    }
}
查看更多
登录 后发表回答