Search code examples
javalayoutjavafxgridpane

JavaFX GridPane Dynamic Resizng of Child Nodes to Fill Assigned Area


So I am using a GridPane with the aim of having it's child notes resize to fill the available space provided to the GridPane based on the porportions assigned to the components when they were added to the grid. I have spent time on SO looking at similar questions, but none of the poposed solutions seems to work, or fit my requirements. Basically I want something like this and with dynamic resizing:

What I want (but with  dynamic sizing)

I can have that if I size the buttons min, max and pref sizes in either the CSS or code. What I find with this is that the buttons will resize a little bit with the window is resized, but even if I set the pref max of both width and height to a very large value, they still remain as shown above, i.e. they do not take up the whole screen as shown below. It's worth noting that the Magenta color is applied to the GridPane and not the AnchorPane it lives in so it's clearly taking up all the space itself but not allocating it to the children proportionally. enter image description here

If I take out the CSS specified pref, min and max dimensions and leave only the setMaxSize instructions in the Java code the windows are all drawn small. As shown below. enter image description here

Below is an SCE. (I know the code can be optimized but it's just meant to be a simple and clear example).

JAVA: package application;

import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;


public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            BorderPane root = new BorderPane();
            Scene scene = new Scene(root,400,400);
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());

            ButtonPanel2 bp = new ButtonPanel2();
            root.setCenter(bp);

            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }//end start

    public static void main(String[] args) {
        launch(args);
    }//end main
}//end class main


class ButtonPanel2 extends AnchorPane {

    GridPane grid;

    Button ba, bb, bc, bd;


    /**Construct a new button panel object.**/
    public ButtonPanel2(){
        //Create Grid and gaps
        grid = new GridPane();
        grid.setHgap(10);
        grid.setVgap(10);

        grid.prefWidthProperty().bind(this.widthProperty());
        grid.getStyleClass().add("test");

        //Init buttons
        ba = new Button("A");
        bb = new Button("B");
        bc = new Button("C");
        bd = new Button("D");

        //Apply CSS styles for size
        ba.getStyleClass().add("button1");
        bb.getStyleClass().add("buttonH2");
        bc.getStyleClass().add("buttonV2");
        bd.getStyleClass().add("button2");

        ba.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
        bb.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
        bc.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
        bd.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);

        //Add items to grid.
        //Node, colIndex, rowIndex, colSpan, rowSpan
        grid.add(ba,0,0,1,1);//
        grid.add(bb,1,0,2,1);//
        grid.add(bc,0,1,1,2);//
        grid.add(bd,1,1,2,2);//

        GridPane.setFillHeight(ba, true);
        GridPane.setFillHeight(bb, true);
        GridPane.setFillHeight(bc, true);
        GridPane.setFillHeight(bd, true);

        GridPane.setFillWidth(ba, true);
        GridPane.setFillWidth(bb, true);
        GridPane.setFillWidth(bc, true);
        GridPane.setFillWidth(bd, true);


        //anchor grid to parent container (anchor)
        AnchorPane.setTopAnchor(grid, 0.0);
        AnchorPane.setBottomAnchor(grid, 0.0);
        AnchorPane.setLeftAnchor(grid, 0.0);
        AnchorPane.setRightAnchor(grid, 0.0);

        this.getChildren().add(grid);

        this.getStyleClass().add("test");
    }//end buttonPanel2

}//end buttonPanel2

CSS:

.buttonV2{
    -fx-min-width: 50px;
    -fx-min-height:100px;
    -fx-max-width: 200px;
    -fx-max-height:100px;
    -fx-pref-width: 75px;
    -fx-pref-height:150px; 
}

.buttonH2{
    -fx-min-width: 100px;
    -fx-min-height:50px;
    -fx-max-width: 200px;
    -fx-max-height:100px;
    -fx-pref-width: 150px;
    -fx-pref-height:75px; 
}

.button1 {
    -fx-min-width: 50px;
    -fx-min-height:50px;
    -fx-max-width: 100px;
    -fx-max-height:100px;
    -fx-pref-width: 75px;
    -fx-pref-height:75px; 
}

.button2 {
    -fx-min-width: 100px;
    -fx-min-height:100px;
    -fx-max-width: 200px;
    -fx-max-height:200px;
    -fx-pref-width: 150px;
    -fx-pref-height:150px; 
}

.test{
    -fx-background-color: #ff00ff;
}

.test2{
        -fx-background-color: #00ffff;
}

Solution

  • Here is a sample layout created using SceneBuilder, with a couple of preview scenes of varying sizes. You can see that as the size changes, the proportional size of each button stays the same. This is achieved through setting 60:40 constraints on the rows and columns for the GridPane.

    sample sample-larger

    The solution also uses a tip for sizing and aligning nodes:

    To enable all of the buttons to be resized to the width of the ... pane, the maximum width of each button is set to the Double.MAX_VALUE constant, which enables a control to grow without limit.

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.scene.control.*?>
    <?import java.lang.*?>
    <?import javafx.scene.layout.*?>
    
    <GridPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.40">
      <columnConstraints>
        <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" percentWidth="40.0" prefWidth="100.0" />
        <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
      </columnConstraints>
      <rowConstraints>
        <RowConstraints minHeight="10.0" percentHeight="40.0" prefHeight="30.0" vgrow="SOMETIMES" />
        <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
      </rowConstraints>
       <children>
          <Button maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" text="Button" />
          <Button maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" text="Button" GridPane.columnIndex="1" />
          <Button maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" text="Button" GridPane.rowIndex="1" />
          <Button maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" text="Button" GridPane.columnIndex="1" GridPane.rowIndex="1" />
       </children>
    </GridPane>
    

    The above layout was crafted in about 3 minutes using SceneBuilder from Gluon. I find it a good tool to use to understand various layout types and constraints.

    Normally an AnchorPane is for fixed sized layouts. If you want a dynamically sized layout, a different layout pane type is usually preferred, which is why the answer uses only a GridPane and not an AnchorPane.

    setting the max size to be essentially Double.MAX_VALUE. I get it but that just feels like such a hack.

    Yes, it can seem that way at first, especially because the MAX_VALUE setting is only required for some controls (like buttons), but not for other layouts like StackPanes. But after you getting used to it, it never really bothered me. The kind of rule of thumb is that the layout attempts to do what you might "normally" want. Normally you don't want a button to grow to any size, but for it to remain at its preferred size. Of course that isn't always what you want, which is why you can override defaults to allow unconstrained growth as was demonstrated here.