3

How can I set row / column sizes using weights on GridPane?

For example, if I wanted five rows where three were unknown fixed sizes, one took a third of the remaining space, and the other took two thirds of the remaining space (see image below), how could I achieve this?

What I want

I looked at using RowConstraints where two had row.setVgrow(Priority.ALWAYS); but this only allowed the remaining space to be shared equally.

I tried using percentages, as I saw in the document that if the sum of the widthPercent (or heightPercent) values total greater than 100, the values will be treated as weights. e.g. if 3 columns are each given a widthPercent of 50, then each will be allocated 1/3 of the gridpane's available width (50/(50+50+50)).

The issue is, when I tried this, I lost part of my application

Application missing

I presume this is due too the part of the documentation that says An application may freely mix the size-types of rows/columns (computed from content, fixed, or percentage). The percentage rows/columns will always be allocated space first based on their percentage of the gridpane's available space (size minus insets and gaps). The remaining space will be allocated to rows/columns given their minimum, preferred, and maximum sizes and grow priorities.


JavaFx MCVE

package sample;

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.RowConstraints;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class Main extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        GridPane root = new GridPane();
        root.setGridLinesVisible(true);

        ColumnConstraints columnOneConstraints = new ColumnConstraints();
        columnOneConstraints.setHgrow(Priority.ALWAYS);
        root.getColumnConstraints().addAll(columnOneConstraints);

        RowConstraints rowOneConstraints = new RowConstraints();
        RowConstraints rowTwoConstraints = new RowConstraints();
        rowTwoConstraints.setVgrow(Priority.ALWAYS);
        rowTwoConstraints.setPercentHeight(2000);
        RowConstraints rowThreeConstraints = new RowConstraints();
        RowConstraints rowFourConstraints = new RowConstraints();
        rowFourConstraints.setVgrow(Priority.ALWAYS);
        rowFourConstraints.setPercentHeight(1000);
        RowConstraints rowFiveConstraints = new RowConstraints();
        root.getRowConstraints().addAll(
                rowOneConstraints,
                rowTwoConstraints,
                rowThreeConstraints,
                rowFourConstraints,
                rowFiveConstraints
        );

        Background red = new Background(new BackgroundFill(Color.RED, null, null));
        Background blue = new Background(new BackgroundFill(Color.BLUE, null, null));

        Label rowOne = new Label("R1: Unknown Fixed Size");
        rowOne.backgroundProperty().set(red);
        rowOne.setMaxWidth(Double.MAX_VALUE);
        rowOne.setAlignment(Pos.CENTER);
        rowOne.setTextFill(Color.WHITE);
        rowOne.setMaxHeight(Double.MAX_VALUE);
        root.add(rowOne, 0, 0, 1, 1);

        Label rowTwo = new Label("R2: Grow 2 parts of the remaining space");
        rowTwo.backgroundProperty().set(blue);
        rowTwo.setTextFill(Color.WHITE);
        rowTwo.setAlignment(Pos.CENTER);
        rowTwo.setMaxWidth(Double.MAX_VALUE);
        rowTwo.setMaxHeight(Double.MAX_VALUE);
        root.add(rowTwo, 0, 1, 1, 1);

        Label rowThree = new Label("R3: Unknown Fixed Size");
        rowThree.backgroundProperty().set(red);
        rowThree.setTextFill(Color.WHITE);
        rowThree.setAlignment(Pos.CENTER);
        rowThree.setMaxWidth(Double.MAX_VALUE);
        rowThree.setMaxHeight(Double.MAX_VALUE);
        root.add(rowThree, 0, 2, 1, 1);

        Label rowFour = new Label("R4: Grow 1 part of the remaining space");
        rowFour.backgroundProperty().set(blue);
        rowFour.setTextFill(Color.WHITE);
        rowFour.setAlignment(Pos.CENTER);
        rowFour.setMaxWidth(Double.MAX_VALUE);
        rowFour.setMaxHeight(Double.MAX_VALUE);
        root.add(rowFour, 0, 3, 1, 1);

        Label rowFive = new Label("R5: Unknown Fixed Size");
        rowFive.backgroundProperty().set(red);
        rowFive.setTextFill(Color.WHITE);
        rowFive.setAlignment(Pos.CENTER);
        rowFive.setMaxWidth(Double.MAX_VALUE);
        rowFive.setMaxHeight(Double.MAX_VALUE);
        root.add(rowFive, 0, 4, 1, 1);

        primaryStage.setScene(new Scene(root));
        primaryStage.sizeToScene();
        primaryStage.show();
        primaryStage.setMinHeight(primaryStage.getHeight());
        primaryStage.setMinWidth(primaryStage.getWidth());
    }
}

What I'm after could be produced with Swing's GridBagLayout, but obviously this can't be used with GridPane.

package sample;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.WindowConstants;

import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;

public class Main {

    public static void main(String[] args) {
        JFrame frame = new JFrame();
        JPanel panel = new JPanel(new GridBagLayout());

        GridBagConstraints constraints = new GridBagConstraints();
        constraints.fill = GridBagConstraints.BOTH;
        constraints.weightx = 1;

        JLabel rowOne = new JLabel("R1: Unknown Fixed Size", SwingConstants.CENTER);
        rowOne.setBackground(Color.RED);
        rowOne.setForeground(Color.WHITE);
        rowOne.setOpaque(true);
        constraints.gridy = 0;
        constraints.weighty = 0;
        panel.add(rowOne, constraints);

        JLabel rowTwo = new JLabel("R2: Grow 2 parts of remaining space", SwingConstants.CENTER);
        rowTwo.setBackground(Color.BLUE);
        rowTwo.setForeground(Color.WHITE);
        rowTwo.setOpaque(true);
        constraints.gridy = 1;
        constraints.weighty = 2;
        panel.add(rowTwo, constraints);

        JLabel rowThree = new JLabel("R3: Unknown Fixed Size", SwingConstants.CENTER);
        rowThree.setBackground(Color.RED);
        rowThree.setForeground(Color.WHITE);
        rowThree.setOpaque(true);
        constraints.gridy = 2;
        constraints.weighty = 0;
        panel.add(rowThree, constraints);

        JLabel rowFour = new JLabel("R4: Grow 1 part of the remaining space", SwingConstants.CENTER);
        rowFour.setBackground(Color.BLUE);
        rowFour.setForeground(Color.WHITE);
        rowFour.setOpaque(true);
        constraints.gridy = 3;
        constraints.weighty = 1;
        panel.add(rowFour, constraints);

        JLabel rowFive = new JLabel("R5: Unknown Fixed Size", SwingConstants.CENTER);
        rowFive.setBackground(Color.RED);
        rowFive.setForeground(Color.WHITE);
        rowFive.setOpaque(true);
        constraints.gridy = 4;
        constraints.weighty = 0;
        panel.add(rowFive, constraints);

        frame.getContentPane().add(panel);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.pack();
        frame.setMinimumSize(frame.getSize());
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}
Dan
  • 7,286
  • 6
  • 49
  • 114
  • 1
    didn't try - but would follow your presumption. you might try a custom layout which is mostly the same as gridPane (extend or c&p) with reversed sequence: move calc of space for relatve size to the end. – kleopatra Aug 15 '20 at 15:59
  • @kleopatra Thanks for the advice. Will look into :) – Dan Aug 15 '20 at 20:41

2 Answers2

4

As far as I know it is not possible to achieve what your example shows with just the default JavaFX GridPane. The standard answer of the professionals in this forum would be: Create your own custom (GridBag like) layout. ;-P

If you want to work with the default options given by JavaFX you could use a combination of e. g. BorderPane and GridPane. It is not exactly what your example shows, but maybe it comes close to it. You need to decide, if it is good enough for your needs.

Here the example:

When the first and the last row should have their fixed height values, you can use a BorderPane as the root. For the center you can have a GridPane with two rows which have percentage based values. The second row of it can then contain e. g. a BorderPane (fixed height for top node) as well and so on.

FXML:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>

<BorderPane prefHeight="600.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1">
   <top>
      <HBox alignment="CENTER" style="-fx-background-color: lightgreen;" BorderPane.alignment="CENTER">
         <children>
            <Label text="&quot;R1&quot;" />
         </children>
      </HBox>
   </top>
   <bottom>
      <HBox alignment="CENTER" style="-fx-background-color: lightgreen;" BorderPane.alignment="CENTER">
         <children>
            <Label text="&quot;R5&quot;" />
         </children>
      </HBox>
   </bottom>
   <center>
      <GridPane BorderPane.alignment="CENTER">
        <columnConstraints>
          <ColumnConstraints halignment="CENTER" hgrow="SOMETIMES" />
        </columnConstraints>
        <rowConstraints>
          <RowConstraints percentHeight="60.0" vgrow="SOMETIMES" />
          <RowConstraints percentHeight="40.0" vgrow="SOMETIMES" />
        </rowConstraints>
         <children>
            <HBox alignment="CENTER" style="-fx-background-color: tomato;">
               <children>
                  <Label text="&quot;R2&quot;" />
               </children>
            </HBox>
            <BorderPane GridPane.rowIndex="1">
               <center>
                  <HBox alignment="CENTER" style="-fx-background-color: tomato;">
                     <children>
                        <Label text="&quot;R4&quot;" />
                     </children>
                  </HBox>
               </center>
               <top>
                  <HBox alignment="CENTER" style="-fx-background-color: lightgreen;" BorderPane.alignment="CENTER">
                     <children>
                        <Label text="&quot;R3&quot;" />
                     </children>
                  </HBox>
               </top>
            </BorderPane>
         </children>
      </GridPane>
   </center>
</BorderPane>

preview

anko
  • 1,628
  • 1
  • 6
  • 14
  • Thanks for the answer. I will likely try modifying the layout – Dan Aug 15 '20 at 20:41
  • 1
    Scratch that, I just found out MigLayout was supported in JavaFX lol. I'm going to use that instead, but thanks for the help – Dan Aug 15 '20 at 21:41
3

If you are able to use 3rd party libraries, in my case I was, MigLayout is a good option.

It made it simple to implement the layout I wanted and it also supports FXML.

package sample;

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import org.tbee.javafx.scene.layout.MigPane;

public class Main extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        MigPane root = new MigPane(
                "fill, debug, wrap, gap 0, insets 0",
                "[fill, grow]",
                "[fill][fill, grow 2][fill][fill, grow 1][fill]"
        );

        Background red = new Background(new BackgroundFill(Color.RED, null, null));
        Background blue = new Background(new BackgroundFill(Color.BLUE, null, null));

        Label rowOne = new Label("R1: Unknown Fixed Size");
        rowOne.backgroundProperty().set(red);
        rowOne.setAlignment(Pos.CENTER);
        rowOne.setTextFill(Color.WHITE);
        root.add(rowOne);

        Label rowTwo = new Label("R2: Grow 2 parts of the remaining space");
        rowTwo.backgroundProperty().set(blue);
        rowTwo.setTextFill(Color.WHITE);
        rowTwo.setAlignment(Pos.CENTER);
        root.add(rowTwo);

        Label rowThree = new Label("R3: Unknown Fixed Size");
        rowThree.backgroundProperty().set(red);
        rowThree.setTextFill(Color.WHITE);
        rowThree.setAlignment(Pos.CENTER);
        root.add(rowThree);

        Label rowFour = new Label("R4: Grow 1 part of the remaining space");
        rowFour.backgroundProperty().set(blue);
        rowFour.setTextFill(Color.WHITE);
        rowFour.setAlignment(Pos.CENTER);
        root.add(rowFour);

        Label rowFive = new Label("R5: Unknown Fixed Size");
        rowFive.backgroundProperty().set(red);
        rowFive.setTextFill(Color.WHITE);
        rowFive.setAlignment(Pos.CENTER);
        root.add(rowFive);

        primaryStage.setScene(new Scene(root));
        primaryStage.sizeToScene();
        primaryStage.show();
        primaryStage.setMinHeight(primaryStage.getHeight());
        primaryStage.setMinWidth(primaryStage.getWidth());
    }
}
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Label?>
<?import org.tbee.javafx.scene.layout.fxml.MigPane?>

<MigPane layout="fill, gap 10 10, debug, wrap"
         cols="fill, grow"
         rows="[][grow 2][][grow 1][]">

    <Label text="Row One"
           alignment="CENTER" />

    <Label text="Row Two"
           alignment="CENTER" />

    <Label text="Row Three"
           alignment="CENTER" />

    <Label text="Row Four"
           alignment="CENTER" />

    <Label text="Row Five"
           alignment="CENTER" />
</MigPane>
Dan
  • 7,286
  • 6
  • 49
  • 114