Skip to content

10.4 Diseño

1. VBox y HBox

El diseño en JavaFX comienza con la selección de los controles de contenedor correctos. Los dos controles de diseño que uso con más frecuencia son VBoxy HBox.

  • VBox es un contenedor que organiza a sus hijos en una pila vertical.
  • HBox ordena a sus hijos en una fila horizontal.

El poder de estos dos controles proviene de envolverlos y establecer algunas propiedades clave: alineación, hgrow y vgrow.

Este artículo demostrará estos controles a través de un proyecto de ejemplo. Una maqueta del proyecto muestra una interfaz de usuario con lo siguiente:

  • Una fila de controles superiores que contiene Actualizar Button y Cerrar sesión Hyperlink,
  • Una Tabla TableView que crecerá para ocupar el espacio vertical adicional, y
  • Un Button Cerrar.

La interfaz de usuario también presenta un Separatorpanel que divide la parte superior de la pantalla con lo que puede convertirse en un panel inferior estándar (Guardar Button, Cancelar Button, etc.) para la aplicación.

Label

1.1. Estructura

VBox será el contenedor más externo "vbox". Este será el Parent proporcionado a la Escena. El simple hecho de colocar los controles de la interfaz de usuario en este VBox permitirá que los controles, sobre todo el TableView , se estiren para adaptarse al espacio horizontal disponible. Los controles superiores, Actualizar Button y Cerrar sesión Hyperlink, están envueltos en un contenedor HBox. Del mismo modo, se envolverá el Button inferior Cerrar en un HBox, lo que permite botones adicionales.

VBox vbox = new VBox();

Button btnRefresh = new Button("Refresh");

HBox topRightControls = new HBox();
topRightControls.getChildren().add( signOutLink );

topControls.getChildren().addAll( btnRefresh, topRightControls );

TableView<Customer> tblCustomers = new TableView<>();
Separator sep = new Separator();

HBox bottomControls = new HBox();

Button btnClose = new Button("Close");

bottomControls.getChildren().add( btnClose );

vbox.getChildren().addAll(
        topControls,
        tblCustomers,
        sep,
        bottomControls
);

Esta imagen muestra la maqueta desglosada por contenedor. El padre VBox es el rectángulo azul más externo. Los HBoxes son los rectángulos interiores (rojo y verde).

Esquema de VBoxAndHBoxApp

1.2. Alineación y Hgrow

El Button Actualizar está alineado a la izquierda mientras que el Hyperlink Cerrar sesión está alineado a la derecha. Esto se logra usando dos HBoxes. controlesArriba es un HBox que contiene el Button Actualizar y también contiene un HBoxcon el Hyperlink Cerrar sesión. A medida que la pantalla se hace más ancha, el Hyperlink Cerrar sesión se desplazará hacia la derecha, mientras que el Button Actualizar mantendrá su alineación izquierda.

La alineación es la propiedad que le dice a un contenedor dónde colocar un control. controlesArriba establece la alineación en BOTTOM_LEFT. controlesArribaDerecha establece la alineación con BOTTOM_RIGHT. BOTTOM se asegura de que la línea de base del texto Actualizar coincida con la línea de base del texto Cerrar sesión.

Para que el Hyperlink Cerrar sesión se mueva hacia la derecha cuando la pantalla se ensancha, es necesario Priority.ALWAYS. Esta es una señal para que JavaFX amplíe controlesArribaDerecha. De lo contrario, controlesArriba mantendrá el espacio y controlesArribaDerecha aparecerá a la izquierda. El Hyperlink Cerrar sesión todavía estaría alineado a la derecha pero en un contenedor más estrecho.

Tenga en cuenta que setHgrow() es un método estático y no se invoca el HBox controlesArriba ni en sí mismo, controlesArribaDerecha. Esta es una faceta de la API de JavaFX que puede resultar confusa porque la mayoría de las API establece propiedades a través de setters en objetos.

1
2
3
controlesArriba.setAlignment( Pos.BOTTOM_LEFT );
HBox.setHgrow(controlesArribaDerecha, Priority.ALWAYS ); //en lugar de controlesArribaDerecha.setHgrow(Priority.ALWAYS);
controlesArribaDerecha.setAlignment( Pos.BOTTOM_RIGHT );

El Button Cerrar se envuelve en un HBox y se posiciona usando la prioridad BOTTOM_RIGHT.

controlesAbajo.setAlignment(Pos.BOTTOM_RIGHT );

1.3. Crecer

Dado que el contenedor más externo es VBox, el hijo TableView se expandirá para ocupar espacio horizontal adicional cuando se amplíe la ventana. Sin embargo, cambiar el tamaño vertical de la ventana producirá un espacio en la parte inferior de la pantalla. VBox no cambia automáticamente el tamaño de ninguno de sus elementos secundarios . Al igual que con el HBox controlesArribaDerecha, se puede configurar un indicador de crecimiento. En el caso del HBox, se trataba de una instrucción de cambio de tamaño horizontal setHgrow(). Para el contenedor VBox TableView, será setVgrow().

VBox.setVgrow( tblClientes, Priority.ALWAYS ); //también estático

1.4. Margen

Hay algunas formas de espaciar los controles de la interfaz de usuario. Nosotros usaremos la propiedad margin en varios de los contenedores para agregar espacios en blanco alrededor de los controles. Estos se configuran individualmente en lugar de usar un espacio en el VBox para que el Separador abarque todo el ancho.

1
2
3
VBox.setMargin(controlesArriba, new Insets(10.0d));
VBox.setMargin(tblClientes, new Insets(0.0d, 10.0d, 10.0d, 10.0d));
VBox.setMargin(controlesAbajo, new Insets(10.0d));

El Insets usado por tblClientes omite cualquier espacio superior para mantener el espacio uniforme. JavaFX no consolida los espacios en blanco como en el diseño web. Si el Recuadro superior se estableciera en 10.0d para el tblClientes, la distancia entre los controles superiores y el TableView sería el doble de ancha que la distancia entre cualquiera de los otros controles.

Tenga en cuenta que estos son métodos estáticos como el Priority.

Esta imagen muestra la aplicación cuando se ejecuta en su tamaño inicial de 800x600.

VBoxAndHBoxApp 800x600

Esta imagen muestra la aplicación redimensionada a un alto y ancho más pequeños.

VBoxAndHBoxApp más pequeña

1.5. Seleccione los contenedores correctos

La filosofía del diseño de JavaFX es la misma que la filosofía de Swing. Seleccione el contenedor adecuado para la tarea en cuestión. Aquí hemos mostrado los dos contenedores más versátiles: VBox y HBox. Al establecer propiedades como alineación, hgrow y vgrow, puede crear diseños increíblemente complejos mediante el anidamiento. Estos son los contenedores que más uso y, a menudo, son los únicos contenedores que necesitaras.

1.6. Código completo

El código se puede probar con un par de archivos .java. Hay un POJO para la clase Cliente utilizado por el TableView

Cliente.java

package UD09._02_VBoxHBox;

public class Cliente {
    private String nombre;
    private String apellidos;

    public Cliente(String nombre, String apellidos) {
        this.nombre = nombre;
        this.apellidos = apellidos;
    }

    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public String getApellidos() {
        return apellidos;
    }

    public void setApellidos(String apellidos) {
        this.apellidos = apellidos;
    }
}

y la subclase JavaFX completa y principal:

VBoxAndHBoxApp.java

package UD09._02_VBoxHBox;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Separator;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class VBoxAndHBoxApp extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {

        VBox vbox = new VBox();

        HBox controlesArriba = new HBox();
        VBox.setMargin( controlesArriba, new Insets(10.0d) );
        controlesArriba.setAlignment( Pos.BOTTOM_LEFT );

        Button btnActualizar = new Button("Actualizar");

        HBox topRightControls = new HBox();
        HBox.setHgrow(topRightControls, Priority.ALWAYS );
        topRightControls.setAlignment( Pos.BOTTOM_RIGHT );
        Hyperlink lnkCerrarSesion = new Hyperlink("Cerrar sesión");
        topRightControls.getChildren().add( lnkCerrarSesion );

        controlesArriba.getChildren().addAll( btnActualizar, topRightControls );

        TableView<Cliente> tblClientes = new TableView<>();
        tblClientes.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
        VBox.setMargin( tblClientes, new Insets(0.0d, 10.0d, 10.0d, 10.0d) );
        VBox.setVgrow( tblClientes, Priority.ALWAYS );

        TableColumn<Cliente, String> columnaApellidos = new TableColumn<>("Apellidos");
        columnaApellidos.setCellValueFactory(new PropertyValueFactory<>("apellidos"));

        TableColumn<Cliente, String> columnaNombre = new TableColumn<>("Nombre");
        columnaNombre.setCellValueFactory(new PropertyValueFactory<>("nombre"));

        tblClientes.getColumns().addAll( columnaApellidos, columnaNombre );

        Separator sep = new Separator();

        HBox controlesAbajo = new HBox();
        controlesAbajo.setAlignment(Pos.BOTTOM_RIGHT );
        VBox.setMargin( controlesAbajo, new Insets(10.0d) );

        Button btnCerrar = new Button("Cerrar");

        controlesAbajo.getChildren().add( btnCerrar );

        vbox.getChildren().addAll(
                controlesArriba,
                tblClientes,
                sep,
                controlesAbajo
        );

        Scene scene = new Scene(vbox );

        primaryStage.setScene( scene );
        primaryStage.setWidth( 800 );
        primaryStage.setHeight( 600 );
        primaryStage.setTitle("Aplicación con VBox y HBox");
        primaryStage.setOnShown( (evt) -> loadTable(tblClientes) );
        primaryStage.show();
    }

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

    private void loadTable(TableView<Cliente> tblCustomers) {
        tblCustomers.getItems().add(new Cliente("David", "Martinez"));
        tblCustomers.getItems().add(new Cliente("Ada", "Lovelace"));
        tblCustomers.getItems().add(new Cliente("Alan", "Turing"));
    }
}

2. StackPane

El layout StackPane coloca a sus hijos uno encima de otro. El último Node agregado es el más alto. Por defecto StackPane alineará los hijos usando Pos.CENTER, como se puede ver en la siguiente imagen, donde están los 3 hijos (en orden de creación): Rectangle, Circle y Button.

Ejemplo de StackPane

Esta imagen fue producida por el siguiente fragmento:

StackPaneApp.java

package UD09._03_StackPane;

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class StackPaneApp extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        StackPane pane = new StackPane(
                new Rectangle(200, 100, Color.BLACK),
                new Circle(40, Color.RED),
                new Button("Hello StackPane")
        );

        //pane.setAlignment(Pos.CENTER_LEFT);

        stage.setScene(new Scene(pane, 300, 300));
        stage.show();
    }

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

Podemos cambiar la alineación predeterminada descomentando la linea pane.setAlignment(Pos.CENTER_LEFT);para producir el siguiente efecto:

Cambio de alineación de StackPane

3. Posicionamiento absoluto con panel (Pane)

Contenedores como VBox o BorderPane alinear y distribuir a sus hijos. La superclase Pane también es un contenedor, pero no impone un orden a sus hijos. Los hijos se posicionan a sí mismos a través de propiedades como x, centerX y layoutX. Esto se llama posicionamiento absoluto y es una técnica para colocar un Shape o un Node en un lugar determinado de la pantalla.

Esta captura de pantalla muestra una vista Acerca de. La vista Acerca de contiene un Hyperlink en el medio de la pantalla "Acerca de esta aplicación". La vista Acerca de utiliza varias formas JavaFX para formar un diseño que se recorta para que parezca una tarjeta de presentación.

Captura de pantalla de Aplicación Pane

3.1. Tamaño del panel

A diferencia de la mayoría de los contenedores, Pane cambia de tamaño para adaptarse a su contenido y no al revés. Esta imagen es una captura de pantalla de Scenic View tomada antes de agregar el Arco inferior derecho. El Pane es el área resaltada en amarillo. Tenga en cuenta que no ocupa la totalidad Stage.

Falta el arco inferior

A continuación se muestra una captura de pantalla tomada después de agregar la esquina inferior derecha Arc. Esto Arcse colocó más cerca del borde inferior derecho del archivo Stage. Esto obliga al Panel a estirarse para acomodar los contenidos expandidos.

Pane crece para adaptarse al arco inferior añadido

3.2. El panel (Pane)

El contenedor más externo de la vista Acerca de es un VBox cuyo único contenido es el archivo Pane. El VBox se utiliza para encajar en el conjunto Stage y proporciona un fondo.

1
2
3
4
5
6
7
8
VBox vbox = new VBox();
vbox.setPadding( new Insets( 10 ) );
vbox.setBackground(
    new Background(
        new BackgroundFill(Color.BLACK, new CornerRadii(0), new Insets(0))
        ));

Pane p = new Pane();

3.3. Las formas (Shape)

En la parte superior izquierda de la pantalla, hay un grupo de 4 Arcos y 1 Círculo. Este código posiciona largeArc en (0,0) a través de los argumentos centerX y centerY en el constructor Arc. Observa que backgroundArc también se coloca en (0,0) y aparece debajo de largeArc. Pane no intenta eliminar el conflicto de formas superpuestas y, en este caso, lo que se busca es la superposición. smArc1 se coloca en (0,160), que está abajo en el eje Y. smArc2 está posicionado en (160,0) que está justo en el eje X. smCircle se coloca a la misma distancia que smArc1 y smArc2, pero en un ángulo de 45 grados.

Arc largeArc = new Arc(0, 0, 100, 100, 270, 90);
largeArc.setType(ArcType.ROUND);

Arc backgroundArc = new Arc(0, 0, 160, 160, 270, 90 );
backgroundArc.setType( ArcType.ROUND );

Arc smArc1 = new Arc( 0, 160, 30, 30, 270, 180);
smArc1.setType(ArcType.ROUND);

Circle smCircle = new Circle(160/Math.sqrt(2.0), 160/Math.sqrt(2.0), 30,Color.web("0xF2A444"));

Arc smArc2 = new Arc( 160, 0, 30, 30, 180, 180);
smArc2.setType(ArcType.ROUND);

En la parte inferior derecha el Arc se coloca en función de la altura total del archivo Stage. Los 20 restados de la altura son los 10 píxeles de Insets (Margen) de VBox (10 para la izquierda + 10 para la derecha).

1
2
3
4
5
Arc medArc = new Arc(568-20, 320-20, 60, 60, 90, 90);
medArc.setType(ArcType.ROUND);

primaryStage.setWidth(568);
primaryStage.setHeight(320);

3.4. El hipervínculo

El Hyperlink está posicionado compensado el centro (284,160) que es el ancho y alto de Stage ambos dividido por dos. Esto coloca el texto del Hyperlinken el cuadrante inferior derecho de la pantalla, por lo que se necesita un desplazamiento basado en el ancho y el alto de Hyperlink. Las dimensiones del Hyperlink no están disponibles hasta que se muestra la pantalla, por lo que se realiza un ajuste posterior a la visualización de la posición.

1
2
3
4
5
6
Hyperlink hyperlink = new Hyperlink("About this App");

primaryStage.setOnShown( (evt) -> {
     hyperlink.setLayoutX( 284 - (hyperlink.getWidth()/3) );
     hyperlink.setLayoutY( 160 - hyperlink.getHeight() );
});

El Hyperlinkno está colocado en el verdadero centro de la pantalla. El valor de layoutX se basa en una operación de división por tres que lo aleja de las formas de la esquina superior izquierda.

3.5. Orden Z

Como se mencionó anteriormente, Pane admite la superposición de los hijos. Esta imagen muestra la vista Acerca de con profundidad.

VBoxAndHBoxApp 800x600

El orden z en este ejemplo está determinado por el orden en que se agregan los elementos secundarios al archivo Pane. backgroundArc está oscurecido por elementos agregados más tarde, más notablemente largeArc. Para reorganizar los elementos secundarios, use los métodos toFront() y toBack() después de agregar los elementos al archivo Pane.

p.getChildren().addAll( backgroundArc, largeArc, smArc1, smCircle, smArc2, hyperlink, medArc );
vbox.getChildren().add( p );

Al comenzar con JavaFX, es tentador construir un diseño absoluto. Tenga en cuenta que los diseños absolutos son frágiles y, a menudo, se rompen cuando se cambia el tamaño de la pantalla (como en este caso) o cuando se agregan elementos durante la fase de mantenimiento del software. Sin embargo, existen buenas razones para utilizar el posicionamiento absoluto. El juego es uno de esos usos. En un juego, puede ajustar la coordenada (x,y) de una Shape para mover una pieza del juego por la pantalla.

3.6. Código completo

Esta es la aplicación JavaFX completa y principal.

PaneApp.java

package UD09._04_Pane;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Hyperlink;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.Border;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.scene.shape.Circle;
import javafx.scene.text.Font;
import javafx.stage.Stage;

public class PaneApp extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {

        VBox vbox = new VBox();
        vbox.setPadding( new Insets( 10 ) );
        vbox.setBackground(
            new Background(
                new BackgroundFill(Color.BLACK, new CornerRadii(0), new Insets(0))
                ));

        Pane p = new Pane();

        Arc largeArc = new Arc(0, 0, 100, 100, 270, 90);
        largeArc.setFill(Color.web("0x59291E"));
        largeArc.setType(ArcType.ROUND);

        Arc backgroundArc = new Arc(0, 0, 160, 160, 270, 90 );
        backgroundArc.setFill( Color.web("0xD96F32") );
        backgroundArc.setType( ArcType.ROUND );

        Arc smArc1 = new Arc( 0, 160, 30, 30, 270, 180);
        smArc1.setFill(Color.web("0xF2A444"));
        smArc1.setType(ArcType.ROUND);

        Circle smCircle = new Circle(
            160/Math.sqrt(2.0), 160/Math.sqrt(2.0), 30,Color.web("0xF2A444")
            );

        Arc smArc2 = new Arc( 160, 0, 30, 30, 180, 180);
        smArc2.setFill(Color.web("0xF2A444"));
        smArc2.setType(ArcType.ROUND);

        Hyperlink hyperlink = new Hyperlink("Acerca de esta App");
        hyperlink.setFont( Font.font(36) );
        hyperlink.setTextFill( Color.web("0x3E6C93") );
        hyperlink.setBorder( Border.EMPTY );

        Arc medArc = new Arc(568-20, 320-20, 60, 60, 90, 90);
        medArc.setFill(Color.web("0xD9583B"));
        medArc.setType(ArcType.ROUND);

        p.getChildren().addAll( backgroundArc, largeArc, smArc1, smCircle,
            smArc2, hyperlink, medArc );

        vbox.getChildren().add( p );

        Scene scene = new Scene(vbox);
        scene.setFill(Color.BLACK);

        primaryStage.setTitle("Aplicación Pane");
        primaryStage.setScene( scene );
        primaryStage.setWidth( 568 );
        primaryStage.setHeight( 320 );
        primaryStage.setOnShown( (evt) -> {
             hyperlink.setLayoutX( 284 - (hyperlink.getWidth()/3) );
             hyperlink.setLayoutY( 160 - hyperlink.getHeight() );
        });
        primaryStage.show();
    }

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

4. GridPane

Los formularios en las aplicaciones comerciales a menudo usan un diseño que imita un registro de base de datos. Para cada columna de una tabla, se agrega un encabezado en el lado izquierdo que coincide con un valor de fila en el lado derecho. JavaFX tiene un control de propósito especial llamado GridPane para este tipo de diseño que mantiene los contenidos alineados por fila y columna. GridPane también admite expansión para diseños más complejos.

La siguiente captura de pantalla muestra un diseño básico de GridPane. En el lado izquierdo del formulario, hay una columna de nombres de campo: Correo-e, Prioridad, Problema, Descripción. En el lado derecho del formulario, hay una columna de controles que mostrará el valor del campo correspondiente. Los nombres de campo son de tipo Label y los controles de valor son una mezcla que incluye TextField, TextArea y ComboBox.

Ejemplo de GridPane

El siguiente código muestra los objetos creados para el formulario. "vbox" es la raíz del Sceney también contendrá el ButtonBar en la base del formulario.

VBox vbox = new VBox();
GridPane gp = new GridPane();

Label lblTitle = new Label("Tiquet de soporte");

Label lblEmail = new Label("Correo-e");
TextField tfEmail = new TextField();

Label lblPriority = new Label("Prioridad");
ObservableList<String> priorities = FXCollections.observableArrayList("Media", "Alta", "Baja");
ComboBox<String> cbPriority = new ComboBox<>(priorities);

Label lblProblem = new Label("Problema");
TextField tfProblem = new TextField();

Label lblDescription = new Label("Descripción");
TextArea taDescription = new TextArea();

GridPane tiene un método útil setGridLinesVisible() que muestra la estructura de la cuadrícula y los espacios. Es especialmente útil en diseños más complejos donde se involucra la expansión porque los espacios en las asignaciones de filas/columnas pueden causar cambios en el diseño.

Lineas de estructura

4.1. Espaciado

Como contenedor, GridPane tiene una propiedad de relleno que se puede configurar para rodear el contenedor GridPane con espacios en blanco. setPadding() tomará un objeto Inset como parámetro. En este ejemplo, se aplican 10 píxeles de espacio en blanco a todos los lados, por lo que se usa un constructor de formato corto para Inset.

Dentro de GridPane, vgap y hgap controlan los espacios. El hgap se establece en 4 para mantener los campos cerca de sus valores. vgap es un poco más grande para ayudar con la navegación del mouse.

1
2
3
gp.setPadding( new Insets(10) );
gp.setHgap( 4 );
gp.setVgap( 8 );

Para mantener consistente la parte inferior del formulario, Priority.ALWAYS se establece a en la clase VBox sobre el GridPane (recuerda que es estático). Sin embargo, esto no cambiará el tamaño de las filas individuales. Para especificaciones de cambio de tamaño individuales, debes usar ColumnConstraints y RowConstraints.

VBox.setVgrow(gp, Priority.ALWAYS );

4.2. Adición de elementos

A diferencia de los contenedores como BorderPane o HBox, los nodos deben especificar su posición dentro del contenedor GridPane. Esto se hace con el método add() en GridPane y no con el método add() en una propiedad secundaria del contenedor. El método add() de GridPane recibe una posición de columna de base cero y una posición de fila de base cero. En este código ponemos dos declaraciones en la misma línea para facilitar la lectura.

1
2
3
4
5
gp.add( lblTitle,       1, 1);  // empty item at 0,0
gp.add( lblEmail,       0, 2); gp.add(tfEmail,        1, 2);
gp.add( lblPriority,    0, 3); gp.add( cbPriority,    1, 3);
gp.add( lblProblem,     0, 4); gp.add( tfProblem,     1, 4);
gp.add( lblDescription, 0, 5); gp.add( taDescription, 1, 5);

lblTitle se coloca en la segunda columna de la primera fila. No hay ninguna entrada en la primera columna de la primera fila.

Las adiciones posteriores se presentan por parejas. Los objetos de nombre de campo Label se colocan en la primera columna (índice de columna=0) y los controles de valor se colocan en la segunda columna (índice de columna=1). Las filas se agregan por el segundo valor incrementado. Por ejemplo, lblPriority se coloca en la cuarta fila junto con su ComboBox.

GridPane es un contenedor importante en el diseño de aplicaciones empresariales JavaFX. Cuando tenga un requisito de pares de nombre/valor, GridPaneserá una manera más fácil de organizar la estructura del formulario.

4.3. Código completo

La siguiente clase es el código completo del ejemplo. Esto incluye la definición de la ButtonBarque no se presentó en las secciones anteriores enfocadas en GridPane.

GridPaneApp.java

package UD09._05_GridPane;

import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class GridPaneApp extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {

        VBox vbox = new VBox();

        GridPane gp = new GridPane();
        gp.setPadding(new Insets(10));
        gp.setHgap(4);
        gp.setVgap(8);

        VBox.setVgrow(gp, Priority.ALWAYS);

        Label lblTitle = new Label("Tiquet de soporte");

        Label lblEmail = new Label("Correo-e");
        TextField tfEmail = new TextField();

        Label lblPriority = new Label("Prioridad");
        ObservableList<String> priorities
                = FXCollections.observableArrayList("Media", "Alta", "Baja");
        ComboBox<String> cbPriority = new ComboBox<>(priorities);

        Label lblProblem = new Label("Problema");
        TextField tfProblem = new TextField();

        Label lblDescription = new Label("Descripción");
        TextArea taDescription = new TextArea();

        gp.add(lblTitle, 1, 1);  // empty item at 0,0
        gp.add(lblEmail, 0, 2);
        gp.add(tfEmail, 1, 2);
        gp.add(lblPriority, 0, 3);
        gp.add(cbPriority, 1, 3);
        gp.add(lblProblem, 0, 4);
        gp.add(tfProblem, 1, 4);
        gp.add(lblDescription, 0, 5);
        gp.add(taDescription, 1, 5);

        Separator sep = new Separator(); // hr

        ButtonBar buttonBar = new ButtonBar();
        buttonBar.setPadding(new Insets(10));

        Button saveButton = new Button("Guardar");
        Button cancelButton = new Button("Cancelar");

        buttonBar.setButtonData(saveButton, ButtonBar.ButtonData.OK_DONE);
        buttonBar.setButtonData(cancelButton, ButtonBar.ButtonData.CANCEL_CLOSE);

        buttonBar.getButtons().addAll(saveButton, cancelButton);

        vbox.getChildren().addAll(gp, sep, buttonBar);

        //para mostrar las lineas de estructura descomenta la siguiente linea
        //gp.setGridLinesVisible(true);

        Scene scene = new Scene(vbox);

        primaryStage.setTitle("Grid Pane App");
        primaryStage.setScene(scene);
        primaryStage.setWidth(736);
        primaryStage.setHeight(414);
        primaryStage.show();

    }

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

5. GridPane Spanning (expansión)

Para formularios más complejos implementados con GridPane, se admite la expansión. La expansión permite que un control reclame el espacio de columnas vecinas (colspan) y filas vecinas (rowspan). Esta captura de pantalla muestra un formulario que amplía el ejemplo de la sección anterior. El diseño de dos columnas de la versión anterior se reemplazó por un diseño de varias columnas. Los campos como Problema y Descripción conservan la estructura original. Pero se agregaron controles a las filas que anteriormente contenían solo Correo electrónico y Prioridad.

Aplicación GridaPanev2

Al activar las líneas de la cuadrícula, observe que la cuadrícula anterior de dos columnas se reemplaza con una cuadrícula de seis columnas. La tercera fila que contiene seis elementos (3 pares de nombre de campo/valor) dicta la estructura. El resto del formulario utilizará la expansión para completar el espacio en blanco.

Lineas de estructura para GridPanev2

Hay un poco más de Vgap para ayudar al usuario a seleccionar los controles ComboBox. Como en la versión anterior, los controles se agregan al GridPane con el método add(). Se especifica una columna y una fila. Repasa los índices ya que no es evidente, ya que se espera que se llenen los vacíos mediante el contenido expandido.

Las definiciones de expansión se establecen mediante un método estático en GridPane. Hay un método similar para hacer la expansión de filas. El título ocupará 5 columnas, al igual que el problema y la descripción. El correo electrónico comparte una fila con el contrato, pero ocupará más columnas. La tercera fila de ComboBoxes es un conjunto de tres pares de campo/valor, cada uno de los cuales ocupa una columna.

1
2
3
4
GridPane.setColumnSpan( lblTitle, 5 );
GridPane.setColumnSpan( tfEmail, 3 );
GridPane.setColumnSpan( tfProblem, 5 );
GridPane.setColumnSpan( taDescription, 5 );

Alternativamente, una variación del método add() tendrá argumentos columnSpan y rowSpan para evitar la subsiguiente llamada al método estático.

Este ejemplo ampliado GridPanedemostró la expansión de columnas. La misma capacidad está disponible para la expansión de filas, lo que permitiría que un control reclame espacio vertical adicional. La expansión mantiene los controles alineados incluso en los casos en que varía el número de elementos en una fila (o columna) determinada.

5.1. Código completo

El siguiente es el código completo para el ejemplo de GridPane de expansión.

GridPaneAppv2.java

Package UD09._05_GridPane;

import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class GridPaneAppv2 extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {

        VBox vbox = new VBox();

        GridPane gp = new GridPane();
        gp.setPadding(new Insets(10));
        gp.setHgap(4);
        gp.setVgap(10);

        VBox.setVgrow(gp, Priority.ALWAYS);

        Label lblTitle = new Label("Tiquet de soporte");

        Label lblEmail = new Label("Correo-e");
        TextField tfEmail = new TextField();

        Label lblContract = new Label("Contrato");
        TextField tfContract = new TextField();

        Label lblPriority = new Label("Prioridad");
        ObservableList<String> priorities
                = FXCollections.observableArrayList("Media", "Alta", "Baja");
        ComboBox<String> cbPriority = new ComboBox<>(priorities);

        Label lblSeverity = new Label("Severidad");
        ObservableList<String> severities
                = FXCollections.observableArrayList("Bloqueante", "Salvable", "No importa");
        ComboBox<String> cbSeverity = new ComboBox<>(severities);

        Label lblCategory = new Label("Categoria");
        ObservableList<String> categories
                = FXCollections.observableArrayList("Defecto", "Nueva funcionalidad");
        ComboBox<String> cbCategory = new ComboBox<>(categories);

        Label lblProblem = new Label("Problema");
        TextField tfProblem = new TextField();

        Label lblDescription = new Label("Descripción");
        TextArea taDescription = new TextArea();

        gp.add(lblTitle, 1, 0);  // empty item at 0,0

        gp.add(lblEmail, 0, 1);
        gp.add(tfEmail, 1, 1);
        gp.add(lblContract, 4, 1);
        gp.add(tfContract, 5, 1);

        gp.add(lblPriority, 0, 2);
        gp.add(cbPriority, 1, 2);
        gp.add(lblSeverity, 2, 2);
        gp.add(cbSeverity, 3, 2);
        gp.add(lblCategory, 4, 2);
        gp.add(cbCategory, 5, 2);

        gp.add(lblProblem, 0, 3);
        gp.add(tfProblem, 1, 3);
        gp.add(lblDescription, 0, 4);
        gp.add(taDescription, 1, 4);

        //Expansiones
        GridPane.setColumnSpan(lblTitle, 5);
        GridPane.setColumnSpan(tfEmail, 3);
        GridPane.setColumnSpan(tfProblem, 5);
        GridPane.setColumnSpan(taDescription, 5);

        Separator sep = new Separator(); // hr

        ButtonBar buttonBar = new ButtonBar();
        buttonBar.setPadding(new Insets(10));

        Button saveButton = new Button("Guardar");
        Button cancelButton = new Button("Cancelar");

        buttonBar.setButtonData(saveButton, ButtonBar.ButtonData.OK_DONE);
        buttonBar.setButtonData(cancelButton, ButtonBar.ButtonData.CANCEL_CLOSE);

        buttonBar.getButtons().addAll(saveButton, cancelButton);

        vbox.getChildren().addAll(gp, sep, buttonBar);

        //para mostrar las lineas de estructura descomenta la siguiente linea
        gp.setGridLinesVisible(true);

        Scene scene = new Scene(vbox);

        primaryStage.setTitle("Grid Pane App");
        primaryStage.setScene(scene);
        primaryStage.setWidth(736);
        primaryStage.setHeight(414);
        primaryStage.show();

    }

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