Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 230
D-Dᴙum Avatar asked Feb 04 '17 15:02

D-Dᴙum


1 Answers

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());
    }
}
like image 163
James_D Avatar answered Sep 21 '22 13:09

James_D