joshwalker
joshwalker

Reputation: 41

JavaFX BorderPane Layout (Stretch Left and Right Pane)

I am using a BorderPane to implement a layout in JavaFX.

Suppose the BorderPane's maxPrefWidth is set to 1000. There is a button in the left pane, and a button in the right pane. In the center pane, there is another button with an unknown size.

Is there a way to tell the left and right pane to automatically grow (horizontally) until the center node's is hit?

Upvotes: 3

Views: 4047

Answers (2)

Basil Bourque
Basil Bourque

Reputation: 340070

BorderPane expands middle area, by design

Your intentions do not mesh with the design intentions of BorderPane.

To quote the Javadoc:

The top and bottom children will be resized to their preferred heights and extend the width of the border pane. The left and right children will be resized to their preferred widths and extend the length between the top and bottom nodes. And the center node will be resized to fill the available space in the middle. Any of the positions may be null.

This means:

  • The center expands to take all extra space.
  • The top and bottom take maximum width and their preferred height.
  • The left and right areas take their preferred width and maximum height.

Imagine the middle as a box with a strongman inside pushing up, down, and out.

diagram of the five areas of a BorderPane showing how the center expands while the outer areas collapse their width or height

This logic is often quite handy for many business apps. The outside areas are often used for navigation, breadcrumbs, menu bar, tool bar, status bar, and so on. The inner area then holds the main content of interest. Given such usage, it makes sense to allocate only necessary space to the outer areas, and allocate most space to the inner content area.

For example, here is an entire example app using JavaFX 14.

screenshot of example JavaFX app using BorderPane layout to show how the colored center area grows to take all extra space

In the center area of this example app, we place an HBox layout containing a single button. We set the background color of that layout to cornflowerblue color to make visible how the content of the center area expands to take all extra space.

package work.basil.example;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ToolBar;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

import java.time.ZonedDateTime;

/**
 * JavaFX App
 */
public class App extends Application
{

    @Override
    public void start ( Stage stage )
    {
        // Widgets
        ToolBar toolBar = new ToolBar();
        Button button = new Button( "Click Me" );
        toolBar.getItems().add( button );

        Button buttonLeft = new Button( "Left" );
        Button buttonRight = new Button( "Right" );

        HBox appContent = new HBox( new Button( "Bonjour le monde" ) );
        appContent.setStyle( "-fx-background-color: cornflowerblue;" );

        HBox statusBar = new HBox( new Label( "Status goes here. Now: " + ZonedDateTime.now() ) );

        // Arrange
        BorderPane borderPane = new BorderPane();
        borderPane.setTop( toolBar );
        borderPane.setLeft( buttonLeft );
        borderPane.setCenter( appContent );
        borderPane.setRight( buttonRight );
        borderPane.setBottom( statusBar );

        var scene = new Scene( borderPane , 1000 , 1000 );
        stage.setScene( scene );
        stage.show();
    }

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

Choose another layout

As commented on the Question, you should be using a different layout manager given your intentions.

You might be able to get by with a HBox. For maximum control, you will need to invest some time into mastering the GridPane.

GridPane

Your Question is not completely clear. If what you want is for the center content to be fixed width of 500 pixels while the left and right are flexible, being allocated any extra space proportionally, then use GridPane while setting the ColumnConstraints to Priority.SOMETIMES or Priority.ALWAYS on the left and right cells.

Here is a complete example app.

We put one button, each nested in a colored HBox, in each cell of the single-row GridPane. The colors dramatize the sizing behavior being shown here. Alternatively, you could drop the colored HBox, instead calling gridPane.setStyle( "-fx-grid-lines-visible: true ;" ) to show border lines around each cell.

package work.basil.example;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ToolBar;
import javafx.scene.layout.*;
import javafx.stage.Stage;

import java.time.ZonedDateTime;

/**
 * JavaFX App
 */
public class App extends Application
{

    @Override
    public void start ( Stage stage )
    {
        // Widgets
        Button buttonLeft = new Button( "Left" );
        HBox left = new HBox( buttonLeft );
        left.setStyle( "-fx-background-color: Salmon;" );

        Button buttonCenter = new Button( "Center" );
        HBox center = new HBox( buttonCenter );
        center.setStyle( "-fx-background-color: CornflowerBlue;" );

        Button buttonRight = new Button( "Right" );
        HBox right = new HBox( buttonRight );
        right.setStyle( "-fx-background-color: MediumSeaGreen;" );

        // GridPane
        GridPane gridPane = new GridPane();
        gridPane.addRow( 0 , left , center , right );  // Add these widgets in first row (annoying zero-based counting means index 0 is row 1).
        gridPane.setStyle( "-fx-grid-lines-visible: true ;" );  // Add lines to the edges of each cell (row/column) in the grid. Useful for learning and debugging. https://docs.oracle.com/javafx/2/api/javafx/scene/doc-files/cssref.html#gridpane

        // Grid constraints
        ColumnConstraints column1 = new ColumnConstraints();
        column1.setHgrow( Priority.SOMETIMES ); // Extra space alloted to this column.

        ColumnConstraints column2 = new ColumnConstraints( 500 ); // Fixed width of 500 pixels.

        ColumnConstraints column3 = new ColumnConstraints();
        column3.setHgrow( Priority.SOMETIMES );// Extra space alloted to this column.

        gridPane.getColumnConstraints().addAll( column1 , column2 , column3 ); // first column gets any extra width

        // Render
        var scene = new Scene( gridPane , 1000 , 150 );
        stage.setScene( scene );
        stage.setTitle( "Example of JavaFX GridPane, by Basil Bourque" );
        stage.show();
    }


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

Screenshot of the app running. Notice how the left and right get remaining space of 250 pixels each. We set the window (Stage) to 1,000 pixels, and fixed the width of the center piece to 500 pixels. That leaves 500 pixels remaining to allocate. Both left and right cells were set to the same priority level, so they split the space evenly between them: 500/2 = 250 pixels each.

Screenshot of JavaFX 14 app display a GridPane of 3 columns and one row where the left and right cells get any extra space after the center takes 500 pixels in width.

If the user narrows the width of the window to 600 pixels, the left and right cells will be 50 pixels each: 600 - 500 = 100, 100/2 = 50 pixels each.

enter image description here

Upvotes: 2

c0der
c0der

Reputation: 18812

Generally I would recommend posting code rather than attempting to describe it. For example this mre can represent your question:

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

public class Main extends Application {
    @Override
    public void start(Stage stage) throws Exception   {

        Button leftBtn = new Button("Left");
        Button centerBtn = new Button("Center");
        centerBtn.widthProperty().addListener((obs, oldValue,newValue)-> {
                //change the logic as needed
                leftBtn.setPrefWidth(newValue.doubleValue() >= 600 ? 200 : 250);
        });
        centerBtn.setPrefSize(600, 0);
        Button rightBtn = new Button("Right");
        Pane root = new BorderPane(centerBtn, null, rightBtn, null, leftBtn);
        root.setPrefSize(1000, 150);
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();
    }

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

This also makes helping much easier. For example a solution with HBox as proposed in the comments just requires minor changes in the mre (action box added to the center button to change its width):

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;

public class Main extends Application {

    private static final double MIN = 300, MAX = 700, DELTA = 100;
    private Button centerBtn;
    @Override
    public void start(Stage stage) throws Exception   {

        Button leftBtn = new Button("Left");
        HBox.setHgrow(leftBtn, Priority.NEVER);

        centerBtn = new Button("Click to change width");
        HBox.setHgrow(leftBtn, Priority.NEVER);
        centerBtn.widthProperty().addListener((obs, oldValue,newValue)-> {
            //change the logic as needed
            leftBtn.setPrefWidth(newValue.doubleValue() >= 600 ? 200 : 250);
        });
        centerBtn.setPrefSize(600, 0);
        centerBtn.setOnAction(e-> changeCenterBtnWidth());
        Button rightBtn = new Button("Right");
        HBox.setHgrow(rightBtn, Priority.ALWAYS);
        Pane root = new HBox(leftBtn,centerBtn,rightBtn);
        root.setPrefSize(1000, 150);
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();
    }

    private void changeCenterBtnWidth() {
        double newWidth = centerBtn.getWidth() +  DELTA;
        newWidth = newWidth < MAX ? newWidth : MIN;
        centerBtn.setPrefWidth(newWidth);
    }

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

Demonstrating a solution based on GridPane rquires only some samll changes:

    public void start(Stage stage) throws Exception   {

        Button leftBtn = new Button("Left");

        centerBtn = new Button("Click to change width");
        centerBtn.widthProperty().addListener((obs, oldValue,newValue)-> {
            //change the logic as needed
            leftBtn.setPrefWidth(newValue.doubleValue() >= 600 ? 200 : 250);
        });
        centerBtn.setPrefSize(600, 0);
        centerBtn.setOnAction(e-> changeCenterBtnWidth());
        Button rightBtn = new Button("Right");

        GridPane root = new GridPane();
        root.add(leftBtn,0, 0);
        root.add(centerBtn,1, 0);
        root.add(rightBtn,2, 0);
        root.setPrefSize(1000, 150);
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();
   }

Upvotes: 1

Related Questions