Search code examples
javafxeventtriggerpane

JavaFX: Trigger on Pane not visible / destroyed


I want to fill a Pane varPane with variable content. The implementation looks similar to this

Pane varPane = new Pane();
// ..

someProperty.addListener(( obsv, oldV, newV) -> {
    varPane.getChildren().clear();    // Remove old Property Pane Content
    Pane propPane = getNewPane(newV);     // Get new content
    varPane.getChildren().add(propPane);  // add Content
});

The Pane is generated like:

public Pane getNewPane(Object newV){
       Pane myPane = new Pane();

       // Add dummy Content
        myPane.getChildren().add(new Label(newV.toString()))

        // Here I need some listener
        // somthing like [not working]:
        myPane.setOnClosed( System.out.println("Pane closed: " + newV.toString() );

        return myPane;
}

As indicated above, I need to perform some cleanup after the pane is not used anymore. However I couldn't find to correct way to implement such a listener.

The listener shall be triggered if:

  • the content of varPane is changed and the old Pane is not visible anymore, OR
  • if varPane is destroyed (e.g. on Platform.exit())

I could require that on these events an function is called for the cleanup. However I think it would be safer, if the pane detects such events on its own.


Solution

  • First note that varPane.getChildren().removeAll() doesn't do what you think. You need

    varPane.getChildren().clear();
    

    You can check if the pane's parentProperty() changes to null:

    myPane.parentProperty().addListener((obs, oldParent, newParent) -> {
        if (newParent == null) {
             System.out.println("Pane closed: " + newV);
        }
    });
    

    Checking if the window containing the pane is hidden (closed) is considerably harder. You can create the following chain of listeners (basically listening to the pane's sceneProperty() in order to listen to the scene's windowProperty() in order to listener to the window's showingProperty()):

    ChangeListener<Boolean> showingListener = (obs, wasShowing, isNowShowing) -> {
        if (! isNowShowing) {
            System.out.println("Window containing "+newV+" hidden");
        }
    };
    ChangeListener<Window> windowListener = (obs, oldWin, newWin) -> {
        if (oldWin != null) {
            oldWin.showingProperty().removeListener(showingListener);
        }
        if (newWin != null) {
            newWin.showingProperty().addListener(showingListener);
        }
    };
    ChangeListener<Scene> sceneListener = (obs, oldScene, newScene) -> {
        if (oldScene != null) {
            oldScene.windowProperty().removeListener(windowListener);
            if (oldScene.getWindow() != null) {
                oldScene.getWindow().showingProperty().removeListener(showingListener);
            }
        }
        if (newScene != null) {
            newScene.windowProperty().addListener(windowListener);
            if (newScene.getWindow() != null) {
                newScene.getWindow().showingProperty().addListener(showingListener);
            }
        }
    };
    myPane.sceneProperty().addListener(sceneListener);
    

    You may prefer to use a bindings framework, such as the one that is part of ReactFX. Using ReactFX, you can cover both cases with

    EventStreams.changesOf(Val
        .flatMap(myPane.sceneProperty(), Scene::windowProperty)
        .flatMap(Window::showingProperty)
        .orElseConst(false))
        .filter(c -> ! c.getNewValue())
        .observe(v -> System.out.println("Pane closed: "+newV));