admin管理员组

文章数量:1319476

I am trying to render a lot of nodes and after a certain amount, scrolling becomes very slow.

Therefore, I'm using a virtualisation approach, similar to TableView. As I have complex layout requirements (e.g. some nodes can be next to others, some need to stay on their own line), TableView is not suitable.

I've created my own component, but the layout of children doesn't work. When scrolling, one can see the light red background from the BorderPane, but the label background and the text of the label are not rendered at all.

Any ideas?

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class VirtualNodesDemo extends Application {
    @Override
    public void start(Stage primaryStage) {
        int virtualNodes = 100;
        double nodeWidth = 100;
        double nodeHeight = 100;

        StackPane root = new StackPane();

        Pane content = new Pane() {
            @Override
            protected void layoutChildren() {
                System.out.println("content.layoutChildren() ...");

                getChildren().clear();

                ScrollPane scrollPane = (ScrollPane) getParent().getParent().getParent();

                double viewportHeight = scrollPane.getViewportBounds().getHeight();
                double scrollTop = scrollPane.getVvalue() * (getPrefHeight() - viewportHeight);

                double y = 0;

                for (int i = 0; i < virtualNodes; i++) {
                    if (y + nodeHeight > scrollTop && y < scrollTop + viewportHeight) {
                        Pane pane = createRealNode(i);
                        pane.resizeRelocate(10, y, nodeWidth, nodeHeight);
                        pane.layout();
                        getChildren().add(pane);
                    } else {
                        break;
                    }
                    y += nodeHeight + 10;
                }

                setPrefHeight(y);
            }

            private Pane createRealNode(int i) {
                BorderPane borderPane = new BorderPane();
                borderPane.setManaged(false);
                Label label = new Label("Node " + i);
                label.setStyle("-fx-background-color: #aaf;");
                borderPane.setCenter(label);
                borderPane.setStyle("-fx-background-color: #faa;");
                return borderPane;
            }
        };

        ScrollPane scrollPane = new ScrollPane(content);
        scrollPane.setFitToWidth(true);
        scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
        scrollPane.vvalueProperty().addListener(observable -> content.layout());
        root.getChildren().add(scrollPane);

        Scene scene = new Scene(root, 800, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

When changing createRealNode as below, the label and label text are shown as expected. However, I need to be able to use BorderPane (and related, like StackPane, GridPane) because the nodes I need to render are quite big and have a lot of children/sub-panes.

            private Pane createRealNode(int i) {
                Label label = new Label("Node " + i);
                label.setStyle("-fx-background-color: #aaf;");
                Pane p = new Pane();
                p.setManaged(false);
                p.getChildren().add(label);
                return p;
            }

I am trying to render a lot of nodes and after a certain amount, scrolling becomes very slow.

Therefore, I'm using a virtualisation approach, similar to TableView. As I have complex layout requirements (e.g. some nodes can be next to others, some need to stay on their own line), TableView is not suitable.

I've created my own component, but the layout of children doesn't work. When scrolling, one can see the light red background from the BorderPane, but the label background and the text of the label are not rendered at all.

Any ideas?

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class VirtualNodesDemo extends Application {
    @Override
    public void start(Stage primaryStage) {
        int virtualNodes = 100;
        double nodeWidth = 100;
        double nodeHeight = 100;

        StackPane root = new StackPane();

        Pane content = new Pane() {
            @Override
            protected void layoutChildren() {
                System.out.println("content.layoutChildren() ...");

                getChildren().clear();

                ScrollPane scrollPane = (ScrollPane) getParent().getParent().getParent();

                double viewportHeight = scrollPane.getViewportBounds().getHeight();
                double scrollTop = scrollPane.getVvalue() * (getPrefHeight() - viewportHeight);

                double y = 0;

                for (int i = 0; i < virtualNodes; i++) {
                    if (y + nodeHeight > scrollTop && y < scrollTop + viewportHeight) {
                        Pane pane = createRealNode(i);
                        pane.resizeRelocate(10, y, nodeWidth, nodeHeight);
                        pane.layout();
                        getChildren().add(pane);
                    } else {
                        break;
                    }
                    y += nodeHeight + 10;
                }

                setPrefHeight(y);
            }

            private Pane createRealNode(int i) {
                BorderPane borderPane = new BorderPane();
                borderPane.setManaged(false);
                Label label = new Label("Node " + i);
                label.setStyle("-fx-background-color: #aaf;");
                borderPane.setCenter(label);
                borderPane.setStyle("-fx-background-color: #faa;");
                return borderPane;
            }
        };

        ScrollPane scrollPane = new ScrollPane(content);
        scrollPane.setFitToWidth(true);
        scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
        scrollPane.vvalueProperty().addListener(observable -> content.layout());
        root.getChildren().add(scrollPane);

        Scene scene = new Scene(root, 800, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

When changing createRealNode as below, the label and label text are shown as expected. However, I need to be able to use BorderPane (and related, like StackPane, GridPane) because the nodes I need to render are quite big and have a lot of children/sub-panes.

            private Pane createRealNode(int i) {
                Label label = new Label("Node " + i);
                label.setStyle("-fx-background-color: #aaf;");
                Pane p = new Pane();
                p.setManaged(false);
                p.getChildren().add(label);
                return p;
            }
Share Improve this question edited Jan 19 at 20:38 user3151902 asked Jan 19 at 20:32 user3151902user3151902 3,1761 gold badge22 silver badges35 bronze badges 3
  • 2 Creating and adding child nodes in a layoutChildren method seems like a code smell. That method should be placing existing child nodes, not adding or creating them. – VGR Commented Jan 19 at 20:52
  • I thought so too, but it's also how VirtualContainerBase does it. It's the only way to do virtualization and from the println statements you can see that it doesn't lead to an endless loop – user3151902 Commented Jan 19 at 21:23
  • 1 I haven’t looked at the source code (and don’t have time to now), but I don’t believe that the virtual containers create new nodes (cells) on every layout pass. That would essentially defeat the purpose. Observing the behavior when the updateItem(…) method of custom cells is incorrectly implemented suggests cells are created once, cached, and reused. Your general approach here just seems fundamentally wrong. – James_D Commented Jan 19 at 23:02
Add a comment  | 

1 Answer 1

Reset to default 2

I agree with all the comments mentioned. Don't reinvent the wheel !!

To deal with virtualization, it is not as simple as you think . Think for altering your approach to model(data objects) your items and then you can use ListView to use cell factories to render the nodes.

For the quick fix what you are encountering with, include pane.applyCss(); after you add the pane to the children. This will fix your issue and the label will be displayed in the BorderPane. But as said, this is very wrong approach.

Pane pane = createRealNode(i);
pane.resizeRelocate(10, y, nodeWidth, nodeHeight);
pane.layout();
getChildren().add(pane); 
pane.applyCss();

You can think of something like below as I mentioned above using ListView approach: (just to give you some idea how to approach)

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class VirtualNodesDemo_Update extends Application {
    private final static String CSS = "data:text/css," +
            """
                .list-view .list-cell{
                    -fx-background-color: transparent !important;
                }
    """;
    double nodeWidth = 100;
    double nodeHeight = 100;

    @Override
    public void start(Stage primaryStage) {
        ObservableList<DataDetails> data = FXCollections.observableArrayList();
        for(int i=0;i<100;i++){
            data.add(new DataDetails("Node "+i, "#aaf", "#faa", nodeWidth, nodeHeight));
        }
        ListView<DataDetails> listView = new ListView<>();
        listView.setItems(data);
        listView.setMaxWidth(Double.MAX_VALUE);
        listView.setMaxHeight(Double.MAX_VALUE);
        listView.setCellFactory(param -> new CustomListCell());

        StackPane root = new StackPane();
        root.getChildren().add(listView);
        Scene scene = new Scene(root, 800, 600);
        scene.getStylesheets().add(CSS);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }

    class CustomListCell extends ListCell<DataDetails>{

        BorderPane pane;
        Label label;

        public CustomListCell(){
            /* Build the structure as you like...*/
            pane = new BorderPane();
            label = new Label();
            pane.setCenter(label);
        }

        @Override
        protected void updateItem(DataDetails item, boolean empty) {
            super.updateItem(item, empty);
            if(item!=null && !empty){
                label.setText(item.getTitle());
                pane.setPrefSize(item.getWidth(), item.getHeight());
                pane.setMaxSize(item.getWidth(), item.getHeight());
                label.setStyle("-fx-background-color: "+item.getLabelBg());
                pane.setStyle("-fx-background-color: "+item.getDataBg());
                setGraphic(pane);
            }else{
                setGraphic(null);
            }
        }
    }
    class DataDetails{
        String title;
        String labelBg;
        String dataBg;
        double width;
        double height;

        /* Build your data object with all the details that you need to render.. */
        public DataDetails(String title, String labelBg, String dataBg, double width, double height) {
            this.title = title;
            this.labelBg = labelBg;
            this.dataBg = dataBg;
            this.width = width;
            this.height = height;
        }

        public String getTitle() {
            return title;
        }

        public String getLabelBg() {
            return labelBg;
        }

        public String getDataBg() {
            return dataBg;
        }

        public double getWidth() {
            return width;
        }

        public double getHeight() {
            return height;
        }
    }
}

本文标签: javaJavaFX layout children in custom layout componentStack Overflow