Jai
Jai

Reputation: 8363

JavaFX: How to center a TilePane but place the TilePane children from left-to-right?

Like what the title suggest, I'm trying to center a TilePane in its parent container, but at the same time, I want the child nodes of the TilePane to be placed from left-to-right.

@Override
public void start(final Stage primaryStage) throws Exception {
    final VBox root = new VBox();
    final Scene sc = new Scene(root);
    primaryStage.setScene(sc);
    
    final TilePane tp = new TilePane();
    root.getChildren().add(tp);
    
    tp.setPrefColumns(3);
    tp.setAlignment(Pos.TOP_LEFT);
    tp.setStyle("-fx-background-color: green;");
    root.setAlignment(Pos.CENTER);
    root.setFillWidth(true);

    Stream.iterate(0, i -> i + 1).limit(5).forEach(i -> {
        Region r = new Region();
        r.setPrefSize(200, 200);
        r.setStyle("-fx-background-color: red; -fx-border-color: blue; -fx-border-width: 1;");

        tp.getChildren().add(r);
    });

    primaryStage.show();
}

This creates a window like this:

On launch

Increasing window width causes this:

Increased width

Continuing to increase window height causes this:

Increased height

Setting root.setFillWidth(false) causes:

Fill width = false

What can I do to make sure that the whole TilePane remains centered while the Region child nodes continues to be placed from left on each row?

Update

To make it more clear, the TilePane should, during a resize, tries to fit in as many tiles (regions in this example) in all rows except the top row. In other words, there should not be any green space at the right side of the first row. Alternatively, the green space should be equal at both the left and right side. Additionally, the tiles must be placed from left to right. Setting TilePane.setAlignment(Pos.CENTER) would place any "leftover" tiles at the center of the last row, which is not acceptable.

Upvotes: 2

Views: 1777

Answers (1)

fabian
fabian

Reputation: 82461

Set the preferred size of the tiles via TilePane. Prevent the parent from resizing the TilePane beyond it's prefered size by setting fillWidth to false and use a listener to the width property of the VBox to set the prefColumns property:

@Override
public void start(Stage primaryStage) {
    final VBox root = new VBox();
    final Scene sc = new Scene(root);
    primaryStage.setScene(sc);

    final TilePane tp = new TilePane();
    tp.setPrefTileWidth(200);
    tp.setPrefTileHeight(200);
    root.getChildren().add(tp);

    tp.setPrefColumns(3);
    tp.setAlignment(Pos.TOP_LEFT);
    tp.setStyle("-fx-background-color: green;");
    root.setAlignment(Pos.CENTER);
    root.setFillWidth(false);

    // set prefColumns from a listener instead of a binding
    // to prevent the initial value from being set to 0
    root.widthProperty().addListener((o, oldValue, newValue) -> {
        // allow as many columns as fit the parent but keep it in
        // range [1, childCount]
        tp.setPrefColumns(Math.min(tp.getChildren().size(),
                Math.max(1, (int) (newValue.doubleValue() / tp.getPrefTileWidth()))));
    });

    Stream.iterate(0, i -> i + 1).limit(5).forEach(i -> {
        Region r = new Region();
        r.setStyle("-fx-background-color: red; -fx-border-color: blue; -fx-border-width: 1;");

        tp.getChildren().add(r);
    });

    primaryStage.show();
}

Update by OP

I was amazed by this approach, and I tried something slightly more dynamic.

root.widthProperty().addListener((o, oldValue, newValue) -> {
    double maxWidth = tp.getChildren()
                        .stream()
                        .filter(n -> n instanceof Region)
                        .map(n -> ((Region) n).getWidth())
                        .max(Double::compareTo)
                        .orElse(0d);

    tp.setPrefColumns(Math.min(tp.getChildren().size(),
            Math.max(1, (int) (newValue.doubleValue() / maxWidth))));
});

Stream.iterate(0, i -> i + 1).limit(5).forEach(i -> {
    Region r = new Region();
    Random random = new Random();
    r.setPrefSize(random.nextInt(150) + 50, random.nextInt(150) + 50);
    System.out.println(r.getPrefWidth());
    System.out.println(r.getPrefHeight());
    r.setStyle("-fx-background-color: red; -fx-border-color: blue; -fx-border-width: 1;");

    tp.getChildren().add(r);
});

This removes the need to set a static width/height for each tile.

While this works in this basic example, I have yet tried this on more complex tile children.

Upvotes: 3

Related Questions