I am trying to do a very simple thing in JavaFX. I want to have a window with a VBox with buttons on the left, a HBox with buttons on top and the rest filled by a gridpane containing a variable amount of rectangles.
I put my three elements in a BorderBox, I calculate the width of each rectangle according to the size of the GridPane, but when I start my application, I would always have to resize my window dragging from the bottom right corner to show the full GridPane (there are approx 3 rows and 3 cols out of sight).
For this example, let the window be of a fixed size as specified. Also, with the scenes and the boxes width/height set, when I call GridPane.getHeight() and GridPane.getWidth() it shows me that is is quadratic (660x660, which should not matter, because the way I calculate, even if it wasn't, it should at least fill to the bottom or right side and not overspill).
Any ideas on how this could be fixed?
@Override
public void start(Stage primaryStage) throws Exception {
//primaryStage.setResizable(false);
BorderPane mainPane = new BorderPane();
HBox topBox = new HBox(10);
VBox leftBox = new VBox(10);
GridPane gridPane = new GridPane();
mainPane.setTop(topBox);
mainPane.setLeft(leftBox);
mainPane.setCenter(gridPane);
leftBox.setBorder(new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID,null,new BorderWidths(1))));
topBox.setBorder(new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID,null,new BorderWidths(1))));
leftBox.setPrefWidth(180);
topBox.setPrefHeight(40);
//TopBox
Button tb1 = new Button("TB 1");
Button tb2 = new Button("TB 2");
topBox.getChildren().addAll(tb1,tb2);
//LeftBox
Button lb1 = new Button("LB 1");
Button lb2 = new Button("LB 2");
Button lb3 = new Button("LB 3");
leftBox.getChildren().addAll(lb1,lb2,lb3);
Scene mainScene = new Scene(mainPane, 840,700);
primaryStage.setScene(mainScene);
primaryStage.show();
int rectangleAmountPerRowCol = 50;
double rectangleWidth = Math.min(gridPane.getWidth()/rectangleAmountPerRowCol,gridPane.getHeight()/rectangleAmountPerRowCol);
for(int k = 0; k < rectangleAmountPerRowCol; k++) {
for (int i = 0; i < rectangleAmountPerRowCol; i++) {
Rectangle r = new Rectangle(rectangleWidth, rectangleWidth);
r.setStroke(Color.RED);
r.setStrokeWidth(0.1);
gridPane.add(r, i, k);
}
}
}
I tried to change the sizes of things, tried to bind the size of the gridpane to other components, I tried to work with Column and RowConstraints, but to no effect. I just don't understand why on my readout, for ex. the width of the GridPane and the width of the left VBox add up to the window width exactly, but it doesn't do it when launched.
Thanks a lot! Thruzzd
Shape
classes, such as Rectangle
, are not resizable, so they typically don't play particularly nicely with layout panes. Since they're not resizable, the layout pane won't control their size and you are forced to somehow set the size of the rectangle yourself. This in turn requires ensuring the size of the rectangle is set at the right time during the layout pass, so that it correctly uses the size of the containing grid pane.
It's generally better to place resizable nodes (such as Region
and Control
subclasses) in layout panes, and then use the layout pane's API to configure how the layout pane resizes the node.
Here is an example using panes instead of rectangles as the child nodes of the grid pane. By calling setHgrow(...)
and setVgrow()
you can force the panes to fill as much space is available as the grid cell, forcing the grid pane to take up all the space available to it:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class GridPaneTest extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
//primaryStage.setResizable(false);
BorderPane mainPane = new BorderPane();
HBox topBox = new HBox(10);
VBox leftBox = new VBox(10);
GridPane gridPane = new GridPane();
mainPane.setTop(topBox);
mainPane.setLeft(leftBox);
mainPane.setCenter(gridPane);
leftBox.setBorder(new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID,null,new BorderWidths(1))));
topBox.setBorder(new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID,null,new BorderWidths(1))));
leftBox.setPrefWidth(180);
topBox.setPrefHeight(40);
//TopBox
Button tb1 = new Button("TB 1");
Button tb2 = new Button("TB 2");
topBox.getChildren().addAll(tb1,tb2);
//LeftBox
Button lb1 = new Button("LB 1");
Button lb2 = new Button("LB 2");
Button lb3 = new Button("LB 3");
leftBox.getChildren().addAll(lb1,lb2,lb3);
int rectangleAmountPerRowCol = 50;
for(int k = 0; k < rectangleAmountPerRowCol; k++) {
for (int i = 0; i < rectangleAmountPerRowCol; i++) {
Pane pane = new Pane();
pane.setStyle("""
-fx-background-color: red, black;
-fx-background-insets: 0, 0.1;"""
);
GridPane.setHgrow(pane, Priority.ALWAYS);
GridPane.setVgrow(pane, Priority.ALWAYS);
gridPane.add(pane, i, k);
}
}
Scene mainScene = new Scene(mainPane, 840,700);
primaryStage.setScene(mainScene);
primaryStage.show();
}
}
If you really need rectangles for some reason (though it's hard to see a use case that the rectangle fulfills that the pane does not), probably the best approach is to put each rectangle in a pane, use the API to force each pane to be the desired size, and then use a binding to make the rectangle's size the same as the pane:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class GridPaneTest extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
//primaryStage.setResizable(false);
BorderPane mainPane = new BorderPane();
HBox topBox = new HBox(10);
VBox leftBox = new VBox(10);
GridPane gridPane = new GridPane();
mainPane.setTop(topBox);
mainPane.setLeft(leftBox);
mainPane.setCenter(gridPane);
leftBox.setBorder(new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID,null,new BorderWidths(1))));
topBox.setBorder(new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID,null,new BorderWidths(1))));
leftBox.setPrefWidth(180);
topBox.setPrefHeight(40);
//TopBox
Button tb1 = new Button("TB 1");
Button tb2 = new Button("TB 2");
topBox.getChildren().addAll(tb1,tb2);
//LeftBox
Button lb1 = new Button("LB 1");
Button lb2 = new Button("LB 2");
Button lb3 = new Button("LB 3");
leftBox.getChildren().addAll(lb1,lb2,lb3);
int rectangleAmountPerRowCol = 50;
for(int k = 0; k < rectangleAmountPerRowCol; k++) {
for (int i = 0; i < rectangleAmountPerRowCol; i++) {
Rectangle r = new Rectangle();
r.setStroke(Color.RED);
r.setStrokeWidth(0.1);
Pane pane = new Pane(r);
r.widthProperty().bind(pane.widthProperty());
r.heightProperty().bind(pane.heightProperty());
GridPane.setHgrow(pane, Priority.ALWAYS);
GridPane.setVgrow(pane, Priority.ALWAYS);
gridPane.add(pane, i, k);
}
}
Scene mainScene = new Scene(mainPane, 840,700);
primaryStage.setScene(mainScene);
primaryStage.show();
}
}