Search code examples
widgetjavafxvisitor-pattern

JavaFX walk widget tree


Is there a simple (uniform) way to recursively descend a JavaFX widget tree starting from a defined node (possibly from the Scene itself)?

The following code:

static class Visitor {
    public void visit(Node node){
        ...
    }
}

protected void walkWidgets(Node n, Visitor cb) {
    if (n instanceof Parent) {
        Parent p = (Parent) n;
        for (Node c : p.getChildrenUnmodifiable()) {
            walkWidgets(c, cb);
        }
    }
    cb.visit(n);
}

... does not work because the "children" of some containers (e.g.: SplitPane, BorderPane, etc.) are not listed in their children Property.

To overcome this I should specialize the code to allow for all the quirks of all different widgets. This is particularly annoying when You start using widget libs beyond the "standard" provision.

Am I missing something? (I surely hope so!)


Solution

  • This seems to work fine: it gets all the child nodes in the BorderPane, SplitPane and TabPane.

    import java.util.function.Consumer;
    
    import javafx.application.Application;
    import javafx.scene.Node;
    import javafx.scene.Parent;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.control.Label;
    import javafx.scene.control.ListView;
    import javafx.scene.control.SplitPane;
    import javafx.scene.control.Tab;
    import javafx.scene.control.TabPane;
    import javafx.scene.control.TextArea;
    import javafx.scene.layout.BorderPane;
    import javafx.stage.Stage;
    
    
    public class WalkComponentTree extends Application {
    
        @Override
        public void start(Stage primaryStage) {
            BorderPane root = new BorderPane();
    
            root.setTop(new Label("Title"));
    
            SplitPane splitPane = new SplitPane();
            root.setCenter(splitPane);
    
            ListView<String> list = new ListView<>();
            list.getItems().addAll("One", "Two", "Three");
    
            TabPane tabPane = new TabPane();
            Tab tab1 = new Tab();
            tab1.setContent(new TextArea());
            Tab tab2 = new Tab();
            tab2.setContent(new Label("Tab 2"));
            tabPane.getTabs().addAll(tab1, tab2);
    
            splitPane.getItems().addAll(tabPane, list);
    
            Button button = new Button("Walk tree");
            button.setOnAction(event -> walkTree(root, node -> 
                System.out.println(node.getClass())));
    
            root.setBottom(button);
    
            Scene scene = new Scene(root, 600, 400);
            primaryStage.setScene(scene);
            primaryStage.show();
    
    
        }
    
        private void walkTree(Node node, Consumer<Node> visitor) {
            visitor.accept(node);
            if (node instanceof Parent) {
                ((Parent) node).getChildrenUnmodifiable()
                    .forEach(n -> walkTree(n, visitor));
            }
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    Note you can also use node.lookupAll("*");, though this is less robust as it only works once css has been applied to the node.