D-Dᴙum
D-Dᴙum

Reputation: 7890

Detect When A Node is Visible in a Scene

I am trying to find a way to detect (or receive notification) that a Node has been added to a Scene and is visible.

I am creating Node objects off the main JavaFx thread and add them to the Stage and Scene using Platform.runLater(). However I would like the Node object to receive notification that is has been added to the Scene and is visible, for example I wish to trigger an animation to start.

I can't seem to find any property or method to add a listener to capture such an event. Any suggestions?

Upvotes: 7

Views: 6750

Answers (2)

Buddha
Buddha

Reputation: 4476

You can add a listener to the children property of container node into which you are adding the new node.

grid.getChildren().addListener((ListChangeListener<? super Node>)  change -> {
   System.out.println(change.getList().get(0).getTypeSelector());
});

change.getList().get(0) returns the first node that is added to grid object.

After James's comment, I have looked up and yes, it is possible to do it from node's perspective as well. You can listen to parentProeprty's changes on the node. Following snippet shows the way to do it.

Button b = new Button("Test");
b.parentProperty().addListener((observable, oldValue, newValue) ->        {
    System.out.println("added to a container " + newValue);
});

answerPane.getChildren().add(b);

Upvotes: 0

James_D
James_D

Reputation: 209330

The third-party JavaFX library ReactFX has a mechanism for this, and this exact use case is cited in the blog. In short, you can do

Val<Boolean> showing = Val.flatMap(node.sceneProperty(), Scene::windowProperty)
    .flatMap(Window::showingProperty);

and then of course

showing.addListener((obs, wasShowing, isNowShowing) -> {
    if (isNowShowing) {
        // node is showing
    } else {
        // node is not showing
    }
});

The standard library has a version of this, but it is very badly written. (It is not typesafe, has no compile-time checking that the properties exist, and also pipes a lot of unnecessary warnings to standard error if any of the properties in the "chain" are null, even though the API docs indicate this is a supported use case.) If you want to do this with the standard JavaFX library, you can do

BooleanBinding showing = Bindings.selectBoolean(node.sceneProperty(), "window", "showing");

and then use the binding the same way as above.

Finally, you could do all this by hand, but it gets a bit ugly to manage the listeners properly:

BooleanProperty showing = new SimpleBooleanProperty();

ChangeListener<Window> windowListener = (obs, oldWindow, newWindow) -> {
    showing.unbind();
    if (newWindow != null) {
        showing.bind(newWindow.showingProperty());
    } else {
        showing.set(false);
    }
};

ChangeListener sceneListener = (obs, oldScene, newScene) -> {
    showing.unbind();
    if (oldScene != null) {
        oldScene.windowProperty().removeListener(windowListener);
    }
    if (newScene == null) {
        showing.set(false);
    } else {
        newScene.windowProperty().addListener(windowListener);
        if (newScene.getWindow() == null) {
            showing.set(false);
        } else {
            showing.bind(newScene.getWindow().showingProperty());
        }
    }
};

node.sceneProperty().addListener(sceneListener);
if (node.getScene() == null) {
    showing.set(false);
} else {
    node.getScene().windowProperty().add(windowListener);
    if (node.getScene().getWindow() == null) {
        showing.set(false);
    } else {
        showing.bind(node.getScene().getWindow().showingProperty());
    }
}

Upvotes: 5

Related Questions