Las tablas representan uno de los elementos gráficos más completos dentro de una interfaz gráfica ya que son capaces de manejar datos con las principales acciones de una base de datos CRUD. Al mismo tiempo estos datos pueden ser representados de muy diferentes formas gracias a los modelos de pintado que se verán más adelante.

Lo primero que hay que tener en cuenta y es una de las principales diferencias con las tablas de Swing es que en javafx una tabla está compuesta por los siguientes elementos:

  • TableView
    • TableColumn

Por lo tanto una tabla está formada por columnas, donde cada una de ellas tendrá un id asignado que se utilizará para asociar una propiedad del objeto de se quiera representar en la tabla.

Al igual que pasa con los elementos que muestran datos como las listas o los spinner, la tabla se nutre de datos los cuales están dentro de una colección de tipo FXCollection.ObservableList. Por lo tanto lo primero que se debe hacer es crear una clase con el objeto que se quiere representar en la tabla el cual está compuesto por cada uno de los atributos que se quiera pero la diferencia que serán del tipo SimpleTipoProperty, lo que facilitará la asociación del elemento concreto con la columna que se quiere asociar

package fundamentos.utils;

import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;

public class AlumnoTabla {

    // se definen cada una de las propiedades que más tarde se asociarán a cada una de las columnas de la tabla
    SimpleStringProperty nombre, apellido, email;
    SimpleIntegerProperty telefono;
    SimpleBooleanProperty matriculado;

    public AlumnoTabla(String nombre, String apellido, String email, int telefono, Boolean matriculado) {
        this.nombre = new SimpleStringProperty(nombre);
        this.apellido = new SimpleStringProperty(apellido);
        this.email = new SimpleStringProperty(email);
        this.telefono = new SimpleIntegerProperty(telefono);
        this.matriculado = new SimpleBooleanProperty(matriculado);
    }

    public String getNombre() {
        return nombre.get();
    }

    public String getApellido() {
        return apellido.get();
    }

    public String getEmail() {
        return email.get();
    }

    public int getTelefono() {
        return telefono.get();
    }

    public boolean isMatriculado() {
        return matriculado.get();
    }
}

Una vez se tiene el objeto «modelo», se contruye la tabla donde a cada columna se le asocia un atributo del modelo mediante el método setCellValueFactory() al cual se le pasa un objeto de tipo PropertyValueFactory<TipoObjeto,TipoDato>() al cual se le pasa el nombre de la propiedad que se quiere asociar. En el ejemplo anterior existía una propiedad llamada matriculado, para poder asignarla a la columna correspondiente sería:

colMatriculado.setCellValueFactory(new PropertyValueFactory<AlumnoTabla,Boolean>("matriculado"));

Por último se crea la colección de datos y se asocia a la tabla

ObservableList listaAlumnos = FXCollections.observableArrayList();
listaAlumnos.addAll(new AlumnoTabla("Nombre1", "Apellido1", "email1@gmail.com", 123, true),
                new AlumnoTabla("Nombre2", "Apellido2", "emai21@gmail.com", 123, false),
                new AlumnoTabla("Nombre3", "Apellido3", "emai31@gmail.com", 123, true),
                new AlumnoTabla("Nombre4", "Apellido4", "emai41@gmail.com", 123, false),
                new AlumnoTabla("Nombre5", "Apellido5", "emai51@gmail.com", 123, false));
 
// se asocia la lista de datos a la tabla
tabla.setItems(listaAlumnos);

De esta forma la tabla directamente representa todos los datos de forma automática.

Para poder trabajar las opciones básicas de la tabla como añadir o borrar datos, se realizan las operaciones de forma directa sobre la lista asociada a la tabla junto con el método refresh() para actualizar todos los posibles cambios. Si se quiere realiza esta acción al pulsar un botón el código sería de la siguiente forma

        // añadir un elemento a la tabla
        anadirTabla.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                listaAlumnos.add(new AlumnoTabla("NombreNuevo", "ApellidoNuevo", "nuevo@gmail.com", 123,true));
                tabla.refresh();
            }
        });

        // eliminar un elemento de la tabla
        borrarTabla.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                listaAlumnos.remove(tabla.getSelectionModel().getSelectedIndex());
                tabla.refresh();
            }
        });

 

Para trabajar con la selección de la tabla se debe realizar todo desde el modelo de selección accediendo a el de la siguiente forma

tabla.getSelectionModel();

Así se está asegurado que se trabaja con los datos asociados y no con la parte gráfica. El método para poder acceder al objeto de la fila seleccionado se utiliza el método getSelectedItem()

tabla.getSelectionModel().getSelectedItem();

Si se quiere evaluar el cambio de la selección de la tabla se realiza mediante un escuchador

// evaluar la selección de la tabla según el cambio
tabla.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
    @Override
    public void changed(ObservableValue observable, Object oldValue, Object newValue) {
        AlumnoTabla seleccionado = (AlumnoTabla) newValue;
        System.out.println(seleccionado.getNombre());
        System.out.println(((AlumnoTabla)tabla.getSelectionModel().getSelectedItem()).getNombre());
    }
});

Los códigos utilizados en son:

package fundamentos.utils;

import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;

public class AlumnoTabla {


    // se definen cada una de las propiedades que más tarde se asociarán a cada una de las columnas de la tabla
    SimpleStringProperty nombre, apellido, email;
    SimpleIntegerProperty telefono;
    SimpleBooleanProperty matriculado;

    public AlumnoTabla(String nombre, String apellido, String email, int telefono, Boolean matriculado) {
        this.nombre = new SimpleStringProperty(nombre);
        this.apellido = new SimpleStringProperty(apellido);
        this.email = new SimpleStringProperty(email);
        this.telefono = new SimpleIntegerProperty(telefono);
        this.matriculado = new SimpleBooleanProperty(matriculado);
    }

    public String getNombre() {
        return nombre.get();
    }

    public String getApellido() {
        return apellido.get();
    }

    public String getEmail() {
        return email.get();
    }

    public int getTelefono() {
        return telefono.get();
    }

    public boolean isMatriculado() {
        return matriculado.get();
    }
}
package fundamentos;

import fundamentos.utils.AlumnoTabla;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;

import java.net.URL;
import java.util.ResourceBundle;

public class Controller_tabla implements Initializable {

    @FXML
    TableView tabla;
    @FXML
    TableColumn colNombre;
    @FXML
    TableColumn colApellido;
    @FXML
    TableColumn colMail;
    @FXML
    TableColumn colTelefono;
    @FXML
    TableColumn colMatriculado;

    @FXML
    Button anadirTabla;
    @FXML
    Button borrarTabla;

    ObservableList listaAlumnos = FXCollections.observableArrayList();

    @Override
    public void initialize(URL location, ResourceBundle resources) {

        rellenarTabla();
        modificarDatos();

    }

    private void rellenarTabla() {

        listaAlumnos.addAll(new AlumnoTabla("Nombre1", "Apellido1", "email1@gmail.com", 123, true),
                new AlumnoTabla("Nombre2", "Apellido2", "emai21@gmail.com", 123, false),
                new AlumnoTabla("Nombre3", "Apellido3", "emai31@gmail.com", 123, true),
                new AlumnoTabla("Nombre4", "Apellido4", "emai41@gmail.com", 123, false),
                new AlumnoTabla("Nombre5", "Apellido5", "emai51@gmail.com", 123, false));

        // se asocia a cada columna la propierdad que se desea del objeto indicado en el PropertyValueFactory
        colNombre.setCellValueFactory(new PropertyValueFactory<AlumnoTabla, String>("nombre"));
        colApellido.setCellValueFactory(new PropertyValueFactory<AlumnoTabla, String>("apellido"));
        colMail.setCellValueFactory(new PropertyValueFactory<AlumnoTabla, String>("email"));
        colTelefono.setCellValueFactory(new PropertyValueFactory<AlumnoTabla, Integer>("telefono"));
        colMatriculado.setCellValueFactory(new PropertyValueFactory<AlumnoTabla,Boolean>("matriculado"));

        // se asocia la lista de datos a la tabla
        tabla.setItems(listaAlumnos);

        // modifica el aspecto minimo y máximo que tendrá la tabla
        tabla.setPrefWidth(600);
        tabla.setMinWidth(0);

        // modifica el aspecto que tendrán las columnas
        colNombre.prefWidthProperty().bind(tabla.widthProperty().multiply(0.25));
        colApellido.prefWidthProperty().bind(tabla.widthProperty().multiply(0.25));
        colTelefono.prefWidthProperty().bind(tabla.widthProperty().multiply(0.25));
        colMail.prefWidthProperty().bind(tabla.widthProperty().multiply(0.25));


        // añade el boton para selección de columnas
        tabla.setTableMenuButtonVisible(true);

        // muestra un elemento si la tabla está vacía
        tabla.setPlaceholder(new Label("La tabla con contiene datos"));

    }

    private void modificarDatos() {

        // añadir un elemento a la tabla
        anadirTabla.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                listaAlumnos.add(new AlumnoTabla("NombreNuevo", "ApellidoNuevo", "nuevo@gmail.com", 123,true));
                tabla.refresh();
            }
        });

        // eliminar un elemento de la tabla
        borrarTabla.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                listaAlumnos.remove(tabla.getSelectionModel().getSelectedIndex());
                tabla.refresh();
            }
        });

        // evaluar la selección de la tabla según el cambio
        tabla.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
            @Override
            public void changed(ObservableValue observable, Object oldValue, Object newValue) {
                AlumnoTabla seleccionado = (AlumnoTabla) newValue;
                System.out.println(seleccionado.getNombre());
                System.out.println(((AlumnoTabla)tabla.getSelectionModel().getSelectedItem()).getNombre());
            }
        });
    }
}
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>

<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fundamentos.Controller_tabla">
   <center>
      <TableView fx:id="tabla" editable="true" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="300.0" prefWidth="600.0" tableMenuButtonVisible="true" BorderPane.alignment="TOP_CENTER">
        <columns>
          <TableColumn fx:id="colNombre" prefWidth="75.0" text="Nombre" />
          <TableColumn fx:id="colApellido" prefWidth="75.0" text="Apellido" />
            <TableColumn fx:id="colMail" prefWidth="75.0" text="Email" />
            <TableColumn fx:id="colTelefono" prefWidth="94.0" text="Teléfono" />
            <TableColumn fx:id="colMatriculado" prefWidth="75.0" text="Matrícula" />
        </columns>
         <columnResizePolicy>
            <TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
         </columnResizePolicy>
      </TableView>
   </center>
   <bottom>
      <GridPane prefHeight="100.0" prefWidth="608.0" BorderPane.alignment="CENTER">
        <columnConstraints>
          <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
          <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
        </columnConstraints>
        <rowConstraints>
          <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
        </rowConstraints>
         <children>
            <Button fx:id="anadirTabla" mnemonicParsing="false" text="Añadir registro" GridPane.halignment="CENTER" />
            <Button fx:id="borrarTabla" mnemonicParsing="false" text="Borrar seleccionado" GridPane.columnIndex="1" GridPane.halignment="CENTER" />
         </children>
      </GridPane>
   </bottom>
</BorderPane>