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
|
1 Answer
Reset to default 2I 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
版权声明:本文标题:java - JavaFX layout children in custom layout component - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1742059729a2418507.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
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:52updateItem(…)
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