Applying CSS and generating a layout pass will force the creation of any nodes managed by the CSS or the control's skin.
The layout pass application can be for the entire scene graph if you apply it to the root of the scene graph. Or just the part that has changed (is dirty) if applied to a subtree of the scene graph.
Here is an example based on the code in your question:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class MainApp extends Application {
@Override
public void start(Stage stage) throws Exception {
Button button = new Button("click");
StackPane root = new StackPane(button);
button.setOnAction(event -> {
SplitPane sp = new SplitPane();
TabPane tabs1 = new TabPane();
TabPane tabs2 = new TabPane();
sp.getItems().addAll(tabs1, tabs2);
root.getChildren().add(sp);
// force a layout pass.
sp.applyCss();
sp.layout();
System.out.println(tabs1.getParent());
System.out.println(tabs2.getParent());
});
Scene scene = new Scene(root);
stage.setTitle("JavaFX and Gradle");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
With these changes, the code which outputs the parent node of the tab panes prints out a non-null value (a reference to the enclosing split-pane skin). It is important to note that it is not a reference to the enclosing SplitPane control instance, but merely the visual representation of the control (the skin). This is an important distinction. It answers your question, but the answer may not be as directly applicable to your application as you had thought it might be.
Advice on this approach
The rest here is background info and generic advice on API usage in JavaFX, so ignore if not relevant to your particular case.
In general, I'd advise keeping references and structures for the relationships between important items separately, rather than relying on the parent/child relationships of nodes. That way you can deal with things at a higher level (e.g. the control classes themselves) rather than underlying nodes for skins and nodes which are created within the skin (which are fragile relationships and may change between JavaFX versions and skin implementations, breaking your code).
For controls, they usually have APIs to describe their relationships independent of the scene graph. Making use of these where you can is preferred to querying scene graph relationships for things like parent/child relationships. For example, you can get all the children of a split pane using the [splitPane.getItems()] call. Or, all of the tabs in a TabPane using the [tabPane.getTabs()] call. Note, a tab isn't even a node, so using the scene graph to try to find or manage it isn't appropriate. Similar object relationships outside of the scene graph are maintained for other controls, like menu items. Complex controls such as color pickers can have combo boxes that feature pop-ups that even have nodes in different pop-up windows featuring entirely different scene graphs from the parent nodes.
Usually, if you use FXML, the important references are injected into the application code within the controller, so there isn't additional work you need to do. If you are dynamically changing the structure of the existing scene graph (which it sounds like you are doing), then you might need to do some more work and create some custom data structures to manage references and sync them up with what is in the scene graph.