Tuesday, July 3, 2012

JavaFX 2.0 Layout Panes - GridPane


The GridPane is without a doubt the most powerfull and flexible layout pane in JavaFX 2.0.
It lays out its children in a flexible grid of columns and rows and is very similar to Swing´s GridBagLayout or HTML´s table model. This approach makes this pane very well suited for any kind of form (like contact forms on a website).
You have the ability to...
  • apply any Node to a cell (specified by column and row) in the GridPane
  • to let the Node span multiple columns/rows
  • to align the Node in the cell it was applied to
  • to set horizontal or vertical grow for the Node
  • and to apply a margin to be kept around the Node in the cell.
The flexibility of the GridPane also extends to a very flexible API. You can use static class methods like setColumnIndex(node, index) or setRowSpan(node, value), or you can use convenience instance methods like gridpane.add(node, column, row, columnSpan, rowSpan).

Note:
  • You don´t have to set the maximum number of columns or rows in the GridPane as it will grow automatically.
  • The size of one column is automatically determined by the widest Node in this column, the height of each row is determined by the tallest Node in the row.
The last note is probably the most important fact about the GridPane as it has to be considered for the column/row and the column span/row span of every single Node in order to get the layout you want.
For more complex layouts it is a very good idea to draw the layout on a piece of paper and to draw all lines for the columns and rows. This will ease development because you can diretly see in which cell you have to put each Node and how many rows or columns they have to span.

Lets have a look at the first simple example:

GridPane – Example 1

import javafx.application.Application;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;

/**
 * Created on: 23.06.2012
 * @author Sebastian Damm
 */
public class GridPaneExample extends Application
{
    @Override
    public void start(Stage primaryStage) throws Exception
    {
        GridPane gridPane = new GridPane();
        gridPane.setPadding(new Insets(40, 0, 0, 50));
        gridPane.setHgap(5); gridPane.setVgap(5);
        
        Scene scene = new Scene(gridPane, 300, 150);
        
        Label lbUser = new Label("Username:");
        GridPane.setHalignment(lbUser, HPos.RIGHT);
        TextField tfUser = new TextField();
        
        Label lbPass = new Label("Password:");
        GridPane.setHalignment(lbPass, HPos.RIGHT);
        PasswordField tfPass = new PasswordField();
        
        Button btLogin = new Button("Login");
        GridPane.setMargin(btLogin, new Insets(10, 0, 0, 0));

        gridPane.add(lbUser, 0, 0);
        gridPane.add(tfUser, 1, 0);
        gridPane.add(lbPass, 0, 1);
        gridPane.add(tfPass, 1, 1);
        gridPane.add(btLogin, 1, 2);
        
        primaryStage.setTitle("GridPaneExample 1");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args)
    {   Application.launch(args);   }
}

Here you can see a little login form with with two labels and two textfields for the username and the password. Additionally theres a 'login' button.
In lines 21-23 we create the GridPane and apply some padding. Furthermore you can specify a horizontal and a vertical gap to be kept between each Node. Next, take a look at line 28: The alignment of a Node inside the boundaries of the cell it was put into, can be set with the static class methods GridPane.setHalignment(Node node, HPos pos), respectively GridPane.setValignment(Node node, VPos pos).

In line 36 you can see how to put a individual margin around a single Node by using the GridPane.setMargin(Node node, Insets insets) method.
Finally in lines 38 to 42 we add each Node to the GridPane and specify the column and the row of the Node.

Your application should look like this now:



In the next example you will see, why we need to set the column span and the row span of each Node in more complex layouts. Have a look at this code:

GridPane – Example 2: User form

import javafx.application.Application;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.paint.RadialGradientBuilder;
import javafx.scene.paint.Stop;
import javafx.stage.Stage;

/**
 * Created on: 23.06.2012
 * @author Sebastian Damm
 */
public class GridPaneExample2 extends Application
{
    private final Paint background = RadialGradientBuilder.create()
            .stops(new Stop(0d, Color.TURQUOISE), new Stop(1, Color.web("3A5998")))
            .centerX(0.5d).centerY(0.5d).build();
    private final String LABEL_STYLE = "-fx-text-fill: white; -fx-font-size: 14;"
            + "-fx-effect: dropshadow(one-pass-box, black, 5, 0, 1, 1);";
    
    @Override
    public void start(Stage primaryStage) throws Exception
    {
        Scene scene = new Scene(createGridPane(), 370, 250, background);
        primaryStage.setTitle("GridPaneExample 2 - User form");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    private GridPane createGridPane()
    {
        GridPane gridPane = new GridPane();
        gridPane.setPadding(new Insets(20, 0, 20, 20));
        gridPane.setHgap(7); gridPane.setVgap(7);
        
        Label lbFirstName = new Label("First Name:");
        lbFirstName.setStyle(LABEL_STYLE);
        GridPane.setHalignment(lbFirstName, HPos.RIGHT);
        TextField tfFirstName = new TextField();
        
        Label lbLastName = new Label("Last Name:");
        lbLastName.setStyle(LABEL_STYLE);
        GridPane.setHalignment(lbLastName, HPos.RIGHT);
        TextField tfLastName = new TextField();
        
        Label lbCity = new Label("City:");
        lbCity.setStyle(LABEL_STYLE);
        GridPane.setHalignment(lbCity, HPos.RIGHT);
        TextField tfCity = new TextField();
        
        Label lbStreetNr = new Label("Street/Nr.:");
        lbStreetNr.setStyle(LABEL_STYLE);
        GridPane.setHalignment(lbStreetNr, HPos.RIGHT);
        TextField tfStreet = new TextField();
        tfStreet.setPrefColumnCount(14);
        GridPane.setColumnSpan(tfStreet, 2);
        TextField tfNumber = new TextField();
        tfNumber.setPrefColumnCount(3);
        
        Label lbNotes = new Label("Notes:");
        lbNotes.setStyle(LABEL_STYLE);
        GridPane.setHalignment(lbNotes, HPos.RIGHT);
        TextArea taNotes = new TextArea();
        taNotes.setPrefColumnCount(5);
        taNotes.setPrefRowCount(5);
        GridPane.setColumnSpan(taNotes, 3);
        GridPane.setRowSpan(taNotes, 2);    
        
        ImageView imageView = new ImageView(new Image(getClass()
                .getResourceAsStream("person.png"), 0, 65, true, true));
        GridPane.setHalignment(imageView, HPos.LEFT);
        GridPane.setColumnSpan(imageView, 2);
        GridPane.setRowSpan(imageView, 3);
        
//        gridPane.setGridLinesVisible(true);
        
        gridPane.add(lbFirstName, 0, 0); gridPane.add(tfFirstName, 1, 0);
        gridPane.add(imageView, 2, 0); gridPane.add(lbLastName, 0, 1);
        gridPane.add(tfLastName, 1, 1); gridPane.add(lbCity, 0, 2);
        gridPane.add(tfCity, 1, 2); gridPane.add(lbStreetNr, 0, 3);
        gridPane.add(tfStreet, 1, 3); gridPane.add(tfNumber, 3, 3);
        gridPane.add(lbNotes, 0, 4); gridPane.add(taNotes, 1, 4);
        
        return gridPane;
    }
    
    public static void main(String[] args)
    {   Application.launch(args);   }
}

In this example we create a user form with different inputs and an image. To make the application appear a little nicer, i created a RadialGradient for the background of the Scene and applied a white font color and a little dropshadow to each label.

The application should look like this:


Compared to the previous example, the first difference occurs in line 64.
With GridPane.setColumnSpan(tfStreet, 2); i tell this TextField to occupy two columns. This is needed, because i want this textfield to be a little wider (see line 63) than the other textfields. Otherwise the second column would be as wide as this textfield and therefore stretch the smaller ones.
The TextArea (starting at line 71) and the ImageView (line 77) span across multiple columns and rows.
Next, take a look at line 83. If you remove the comment lines and start the application, it should look like this:



As you can see, this method makes all grid lines (including the horizontal and vertical gap between each Node visible which can be a great help if your Nodes arent aligned the way you want it.
I don´t know how many times i wished for a method like this during the time i learned Swing and the GridBagLayout and i bet that i´m not the only one ;)

Finally, please remove all lines, where column span or row span are specified (lines 64, 74, 75, 80, 81). This will help you to understand the necessity of column span and row span.


You can see, that each Node occupies one single cell and that the layout is pretty messed up because the width/height of each column/row depend on the widest/tallest child Node.



GridPane – Example 3: The setConstraints method

The instance method add "only" provides two versions, one with the Node, the column and the row, and one with additional column span and row span. Other properties like the alignment or the grow have to be set with dedicated class methods like GridPane.setHalignment like in the first two examples.

But theres another nice way: the GridPane.setConstraints(...)method.

At the moment (JavaFX 2.2) there are five overloaded versions of this method from
setConstraints(Node child, int columnIndex, int rowIndex)

to

setConstraints(Node child, int columnIndex, int rowIndex, int columnspan, int rowspan, HPos halignment, VPos valignment, Priority hgrow, Priority vgrow, Insets margin).

This is pretty similiar to Swing´s GridBagConstraints but here you don´t have to create a dedicated object and reuse it for multiple graphical objects.

If you apply the constraints to every Node like this, you can simply add the Nodes to the GridPane´s collections of children.

With this approach the code of the second example looks like this:

  
    private GridPane createGrid()
    {
        GridPane gridPane = new GridPane();
        gridPane.setPadding(new Insets(20, 0, 20, 20));
        gridPane.setHgap(7); gridPane.setVgap(7);
        
        Label lbFirstName = new Label("First Name:");
        lbFirstName.setStyle(LABEL_STYLE);
        GridPane.setConstraints(lbFirstName, 0, 0, 1, 1, HPos.RIGHT, VPos.CENTER);
        TextField tfFirstName = new TextField();
        GridPane.setConstraints(tfFirstName, 1, 0);
        
        Label lbLastName = new Label("Last Name:");
        lbLastName.setStyle(LABEL_STYLE);
        GridPane.setConstraints(lbLastName, 0, 1, 1, 1, HPos.RIGHT, VPos.CENTER);
        TextField tfLastName = new TextField();
        GridPane.setConstraints(tfLastName, 1, 1);
        
        Label lbCity = new Label("City:");
        lbCity.setStyle(LABEL_STYLE);
        GridPane.setConstraints(lbCity, 0, 2, 1, 1, HPos.RIGHT, VPos.CENTER);
        
        TextField tfCity = new TextField();
        GridPane.setConstraints(tfCity, 1, 2);
        
        Label lbStreetNr = new Label("Street/Nr.:");
        lbStreetNr.setStyle(LABEL_STYLE);
        GridPane.setConstraints(lbStreetNr, 0, 3, 1, 1, HPos.RIGHT, VPos.CENTER);
        
        TextField tfStreet = new TextField();
        tfStreet.setPrefColumnCount(14);
        GridPane.setConstraints(tfStreet, 1, 3, 2, 1);
        
        TextField tfNumber = new TextField();
        tfNumber.setPrefColumnCount(3);
        GridPane.setConstraints(tfNumber, 3, 3);
        
        Label lbNotes = new Label("Notes:");
        lbNotes.setStyle(LABEL_STYLE);
        GridPane.setConstraints(lbNotes, 0, 4, 1, 1, HPos.RIGHT, VPos.CENTER);
        
        TextArea taNotes = new TextArea();
        taNotes.setPrefColumnCount(5);
        taNotes.setPrefRowCount(5);
        GridPane.setConstraints(taNotes, 1, 4, 3, 2);
                
        ImageView imageView = new ImageView(new Image(getClass()
                .getResourceAsStream("person.png"), 0, 65, true, true));
        GridPane.setConstraints(imageView, 2, 0, 3, 3, HPos.LEFT, VPos.CENTER);
        
        gridPane.getChildren().addAll(lbFirstName, tfFirstName, imageView
                , lbLastName, tfLastName, lbCity, tfCity, lbStreetNr, tfStreet
                , tfNumber, lbNotes, taNotes);
        return gridPane;
    }

You can see the usage of the overloaded setConstraints(...) methods and how you can simply add the Nodes to the GridPane in lines 51-53.

I hope i could provide a good introduction to the GridPane in JavaFX 2.0. Feel free to add comments and post questions.

14 comments:

  1. good man!
    this app run in browser ok?

    ReplyDelete
  2. luving ur tutorials.. waiting for more topics

    ReplyDelete
  3. A very good and practical example of GridPane.

    ReplyDelete
  4. good and easy stuff

    ReplyDelete
  5. hey friends please tell me about javafx in briefly because i don't know what we can do using javafx like desktop application or web application so please please tell me my e-mail :jaygori143@gmail.com

    ReplyDelete
    Replies
    1. http://www.javafxapps.in/tutorial/Getting-started-with-your-first-JavaFX-Application.html

      Delete
  6. http://www.javafxapps.in/tutorial/Getting-started-with-your-first-JavaFX-Application.html

    ReplyDelete
  7. as I can put an image on a background gridpane?

    ReplyDelete