javajavafx

How to make equal width for two columns from different GridPane in JavaFX?


I want to do something like this:

enter image description here

using TitledPane with GridPane inside. In every GridPane I have two columns - label column and control column. And I want the label column in the top TitlePane to have the same width as the bottom one. Because otherwise it looks terrible. And of course, I can't use fixed width in pixels because label texts depend on user language.

This is my code:

public class MyGridPanes extends Application {

    @Override
    public void start(Stage stage) {
        GridPane gridPane1 = new GridPane();
        gridPane1.add(new Label("One two three"), 0, 0);
        gridPane1.add(new TextField(), 1, 0);
        gridPane1.setHgap(20);
        var titledPane1 = new TitledPane("Top", gridPane1);
        titledPane1.setCollapsible(false);

        GridPane gridPane2 = new GridPane();
        gridPane2.setHgap(20);
        gridPane2.add(new Label("Four five six seven"), 0, 0);
        gridPane2.add(new TextField(), 1, 0);
        var titledPane2 = new TitledPane("Bottom", gridPane2);
        titledPane2.setCollapsible(false);

        Scene scene = new Scene(new VBox(titledPane1, titledPane2), 400, 200);
        stage.setScene(scene);
        stage.show();
    }

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

And this is my result:

enter image description here

Could anyone say how to do it?


Solution

  • +1 for @SedJ601 & @jewelsea suggestions.

    But if you have many labels/gridPanes to handle or if it is hard to maintain all labels in one list or if you don't want any custom control, you can try the below approach.

    The general idea is: we go through all the nodes of first column in all gridPanes and keep track of the their widths to determine the max width. And this maxWidth is set as the ColumnConstraint minWidth to all gridPanes.

    You can have a static utility method as below, where you just pass the gridPanes:

    /**
     * Builds a common first column constraints for the provided gridPanes.
     * @param gridPanes
     */
    private static void buildFirstColumnConstraint(GridPane... gridPanes) {
        /* Column constraint key. */
        final String COLUMN_INDEX_CONSTRAINT = "gridpane-column";
    
        /* Checks if the node is a first column node or not. */
        Predicate<Node> isFirstColumn = node ->{
            Integer constraint = (Integer) node.getProperties().get(COLUMN_INDEX_CONSTRAINT);
            return constraint != null && constraint == 0;
        };
    
        /* Keep track of the max width. */
        DoubleProperty maxWidth = new SimpleDoubleProperty();
        ChangeListener<Number> widthListener = (obs, old, val) -> {
            if (val.doubleValue() > maxWidth.get()) {
                maxWidth.set(val.doubleValue());
            }
        };
    
        // Bind the minWidth to the calculated width
        final ColumnConstraints constraint = new ColumnConstraints();
        constraint.minWidthProperty().bind(maxWidth);
    
        // Go through each gridPane and set the first constraint
        Stream.of(gridPanes).forEach(gridPane -> {
            gridPane.getColumnConstraints().add(0,constraint);
    
            // Filter for all first column children and add the widthListener to them
            gridPane.getChildren().stream().filter(isFirstColumn)
                    .map(node -> (Region) node)
                    .forEach(region -> region.widthProperty().addListener(widthListener));
        });
    }
    

    Below is the full demo code:

    enter image description here

    import javafx.application.Application;
    import javafx.beans.property.DoubleProperty;
    import javafx.beans.property.SimpleDoubleProperty;
    import javafx.beans.value.ChangeListener;
    import javafx.scene.Node;
    import javafx.scene.Scene;
    import javafx.scene.control.Label;
    import javafx.scene.control.TextField;
    import javafx.scene.control.TitledPane;
    import javafx.scene.layout.ColumnConstraints;
    import javafx.scene.layout.GridPane;
    import javafx.scene.layout.Region;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;
    
    import java.util.function.Predicate;
    import java.util.stream.Stream;
    
    public class GridPane_Demo extends Application {
    
        @Override
        public void start(Stage stage) {
    
    
            GridPane gridPane1 = buildGrid("One", "One Two");
            var titledPane1 = new TitledPane("Top", gridPane1);
            titledPane1.setCollapsible(false);
    
            GridPane gridPane2 = buildGrid("One Two Three", "One Two Three Four");
            var titledPane2 = new TitledPane("Bottom", gridPane2);
            titledPane2.setCollapsible(false);
    
            buildFirstColumnConstraint(gridPane1, gridPane2);
            Scene scene = new Scene(new VBox(titledPane1, titledPane2), 400, 220);
            stage.setScene(scene);
            stage.show();
        }
    
        private GridPane buildGrid(String label1, String label2) {
            GridPane gridPane = new GridPane();
            gridPane.setHgap(20);
            gridPane.setVgap(5);
            gridPane.addRow(0, new Label(label1), new TextField());
            gridPane.addRow(1, new Label(label2), new TextField());
            return gridPane;
        }
    
        /**
         * Builds a common first column constraints for the provided gridPanes.
         *
         * @param gridPanes
         */
        private static void buildFirstColumnConstraint(GridPane... gridPanes) {
            /* Column constraint key. */
            final String COLUMN_INDEX_CONSTRAINT = "gridpane-column";
    
            /* Checks if the node is a first column node or not. */
            Predicate<Node> isFirstColumn = node -> {
                Integer constraint = (Integer) node.getProperties().get(COLUMN_INDEX_CONSTRAINT);
                return constraint != null && constraint == 0;
            };
    
            /* Keep track of the max width. */
            DoubleProperty maxWidth = new SimpleDoubleProperty();
            ChangeListener<Number> widthListener = (obs, old, val) -> {
                if (val.doubleValue() > maxWidth.get()) {
                    maxWidth.set(val.doubleValue());
                }
            };
    
            // Bind the minWidth to the calculated width
            final ColumnConstraints constraint = new ColumnConstraints();
            constraint.minWidthProperty().bind(maxWidth);
    
            // Go through each gridPane and set the first constraint
            Stream.of(gridPanes).forEach(gridPane -> {
                gridPane.getColumnConstraints().add(0, constraint);
    
                // Filter for all first column children and add the widthListener to them
                gridPane.getChildren().stream().filter(isFirstColumn)
                        .map(node -> (Region) node)
                        .forEach(region -> region.widthProperty().addListener(widthListener));
            });
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }