Saltar la navegación

Trabajando con tablas

En los siguientes ejemplos veremos cómo interaccionar con una tabla de objetos. Para ello he creado una clase denominada Personaje que contiene atributos de los tipos más comunes para ver cómo podemos personalizar su visualización y su edición. La clase Personaje es la que muestro a continuación.

Personaje.java

package javafx.eventos.clases;

public class Personaje {
	
	public enum Estrategia {
		RISA,
		MALHUMOR,
		ATAQUE
	}
	
	private String nombre;
	private int poder;
	private boolean superpoder;
	private Estrategia estrategia;
	
	public Personaje(String nombre, int poder, boolean superpoder, Estrategia estrategia) {
		this.nombre = nombre;
		this.poder = poder;
		this.superpoder = superpoder;
		this.estrategia = estrategia;
	}

	public String getNombre() {
		return nombre;
	}
	
	public void setNombre(String nombre) {
		this.nombre = nombre;
	}
	
	public int getPoder() {
		return poder;
	}
	
	public void setPoder(int poder) {
		this.poder = poder;
	}
	
	public boolean isSuperpoder() {
		return superpoder;
	}
	
	public void setSuperpoder(boolean superpoder) {
		this.superpoder = superpoder;
	}
	
	public Estrategia getEstrategia() {
		return estrategia;
	}
	
	public void setEstrategia(Estrategia estrategia) {
		this.estrategia = estrategia;
	}

	public String toString() {
		return String.format("Nombre: %s, Poder: %d, Superpoder: %b, Estrategia: %s",
				nombre, poder, superpoder, estrategia);
	}
}

También he implementado la clase anterior utilizando propiedades de JavaFX. La implementación de la misma es la siguiente:

PersonajePropiedades.java

package javafx.eventos.clases;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class PersonajePropiedades {
	
	public enum Estrategia {
		RISA,
		MALHUMOR,
		ATAQUE
	}
	
	private final StringProperty nombre = new SimpleStringProperty();
	private final IntegerProperty poder = new SimpleIntegerProperty();
	private final BooleanProperty superpoder = new SimpleBooleanProperty();
	private final ObjectProperty<Estrategia> estrategia = new SimpleObjectProperty<>();
	
	public PersonajePropiedades(String nombre, int poder, boolean superpoder, Estrategia estrategia) {
		this.nombre.set(nombre);
		this.poder.set(poder);
		this.superpoder.set(superpoder);
		this.estrategia.set(estrategia);
	}

	public final String getNombre() {
		return nombre.get();
	}
	
	public final void setNombre(String nombre) {
		this.nombre.set(nombre);
	}
	
	public final StringProperty nombreProperty() {
		return nombre;
	}
	
	public final int getPoder() {
		return poder.get();
	}
	
	public final void setPoder(int poder) {
		this.poder.set(poder);
	}
	
	public final IntegerProperty poderProperty() {
		return poder;
	}
	
	public final boolean isSuperpoder() {
		return superpoder.get();
	}
	
	public final void setSuperpoder(boolean superpoder) {
		this.superpoder.set(superpoder);
	}
	
	public final BooleanProperty superpoderProperty() {
		return superpoder;
	}
	
	public final Estrategia getEstrategia() {
		return estrategia.get();
	}
	
	public final void setEstrategia(Estrategia estrategia) {
		this.estrategia.set(estrategia);
	}
	
	public final ObjectProperty<Estrategia> estrategiaProperty() {
		return estrategia;
	}

	public String toString() {
		return String.format("Nombre: %s, Poder: %d, Superpoder: %b, Estrategia: %s",
				nombre.get(), poder.get(), superpoder.get(), estrategia.get());
	}
}

Para trabajar correctamente con una tabla debemos seguir los siguientes pasos:

  • Declarar la tabla indicando el tipo de objetos que contendrá y crearla. Al crearla podemos pasarle la lista que utilizará como modelo o luego utilizar el método setItems.
    TableView<Personaje> tvPersonajes = new TableView<>(PERSONAJES);
  • Declarar las diferentes columnas indicando el tipo de objeto de la tabla a la que pertenecerá y el tipo de objeto que contendrá esta columna y crearla pasando la etiqueta de la cabecera.
    TableColumn<Personaje, String> cNombre = new TableColumn<>("Nombre");
  • Añadir la columa a las columnas de la tabla.
    tvPersonajes.getColumns().add(cNombre);
  • Indicar a la columna cómo tendrá que visualizar cada uno de las celdas mediante el método setCellValueFactory. Para ello podemos usar la clase PropertyValueFactory que por medio de reflexión intenta invocar al método nombreProperty o al método getNombre o isNombre, siendo nombre el valor pasado en el constructor
    cNombre.setCellValueFactory(new PropertyValueFactory<>("nombre"));

Las tablas, al igual que las listas, también tienen aociado un modelo de selección que nos permitirá determinar qué objeto de la lista asociada a la tabla está seleccionado actualmente.

Personaje personaje = tvPersonajes.getSelectionModel().getSelectedItem();

También, al igual que las listas, podemos editar una celda de una tabla, mediante el método setCellFactory.

El primer ejemplo muestra cómo construir una tabla para una lista de personajes. Para indicar cómo se muestra cada uno de los campos se utiliza el método setCellValueFactory para la columna dada. Este es el ejemplo más simple y su interfaz es la que sigue.

El segundo ejemplo muestra cómo podemos hacer que la tabla sea editable personalizando cómo editar cada uno de los campos según su valor. Para ello utilizamos el método setCellFactory para cada columna. Prestar especial atención a la edición del atributo poder que es entero y que sólo acepta valores entre 0 y 100, para lo cuál he creado mi propio convertidor de enteros a cadenas heredando de la clase IntegerStringConverter y redefiniendo el método fromString. Finalmente le hemos indicado a las columnas que queremos escuchar los eventos que se produzcan cuando se termine de editar una celda para así actuar en consecuencia, mediante el método setOnEditCommit. También se ha modificado la forma en que se muestra el valor lógico superpoder, mediante un CheckBoxTableCell. He tenido que escuchar los cambios que se producen en esa celda y cambiar el valor a mano, ya que dicho CheckBoxTableCell no informa de los cambios y por tanto no podemos escuchar los eventos del tipo EditCommit para esta columna.

El tercer ejemplo es el más completo ya que además de la tabla muestra una lista con el mismo modelo que el de la tabla. En este ejemplo los cambios realizados en la tabla se propagan directamente a la lista (ya que lo que se hace es modificar el modelo subyacente a ambas). Además cada elemento seleccionado en la tabla también se selecciona en la lista. Por último nos permite borrar filas y añadir filas vacías que luego podremos modificar.

El código de los tres ejemplos es el que se muestra a continuación. Os muestro la implementación utilizando la clase Personaje y utilizando la clase PersonajePropiedades.

TablaPersonajes.java

package javafx.eventos;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.eventos.clases.Personaje;
import javafx.eventos.clases.Personaje.Estrategia;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;

public class TablaPersonajes extends Application {
	
	private static final ObservableList<Personaje> PERSONAJES = FXCollections.observableArrayList(
			new Personaje("Bob Esponja", 10, false, Estrategia.RISA),
			new Personaje("Mortadelo", 60, true, Estrategia.RISA),
			new Personaje("Goku", 90, true, Estrategia.ATAQUE),
			new Personaje("El Malo", 0, false, Estrategia.MALHUMOR),
			new Personaje("Gro", 100, false, Estrategia.RISA));

	@Override
	public void start(Stage escenarioPrincipal) {
		try {
			VBox raiz = new VBox();
			raiz.setPadding(new Insets(40));
			raiz.setSpacing(10);
			raiz.setAlignment(Pos.CENTER);
			
			Label lbPersonajes = new Label("Personajes");
			lbPersonajes.setFont(Font.font(20));
			TableView<Personaje> tvPersonajes = new TableView<>(PERSONAJES);
			
			TableColumn<Personaje, String> cNombre = new TableColumn<>("Nombre");
			TableColumn<Personaje, Integer> cPoder = new TableColumn<>("Poder");
			TableColumn<Personaje, Boolean> cSuperpoder = new TableColumn<>("Super Poder");
			TableColumn<Personaje, Estrategia> cEstrategia = new TableColumn<>("Estrategia");
			
			tvPersonajes.getColumns().add(cNombre);
			tvPersonajes.getColumns().add(cPoder);
			tvPersonajes.getColumns().add(cSuperpoder);
			tvPersonajes.getColumns().add(cEstrategia);
			
			cNombre.setMinWidth(100);
			cNombre.setCellValueFactory(new PropertyValueFactory<>("nombre")); 
			cPoder.setMinWidth(20);
			cPoder.setCellValueFactory(new PropertyValueFactory<>("poder"));
			cSuperpoder.setMinWidth(40);
			cSuperpoder.setCellValueFactory(new PropertyValueFactory<>("superpoder"));
			cEstrategia.setMinWidth(60);
			cEstrategia.setCellValueFactory(new PropertyValueFactory<>("estrategia"));
			
			raiz.getChildren().addAll(lbPersonajes, tvPersonajes);
			
			Scene escena = new Scene(raiz, 455, 250);
			escenarioPrincipal.setTitle("Tabla personajes");
			escenarioPrincipal.setScene(escena);
			escenarioPrincipal.show();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}

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

}

TablaPersonajesPropiedades.java

package javafx.eventos;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.eventos.clases.PersonajePropiedades.Estrategia;
import javafx.eventos.clases.PersonajePropiedades;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;

public class TablaPersonajesPropiedades extends Application {
	
	private static final ObservableList<PersonajePropiedades> PERSONAJES = FXCollections.observableArrayList(
			new PersonajePropiedades("Bob Esponja", 10, false, Estrategia.RISA),
			new PersonajePropiedades("Mortadelo", 60, true, Estrategia.RISA),
			new PersonajePropiedades("Goku", 90, true, Estrategia.ATAQUE),
			new PersonajePropiedades("El Malo", 0, false, Estrategia.MALHUMOR),
			new PersonajePropiedades("Gro", 100, false, Estrategia.RISA));

	@Override
	public void start(Stage escenarioPrincipal) {
		try {
			VBox raiz = new VBox();
			raiz.setPadding(new Insets(40));
			raiz.setSpacing(10);
			raiz.setAlignment(Pos.CENTER);
			
			Label lbPersonajes = new Label("Personajes");
			lbPersonajes.setFont(Font.font(20));
			TableView<PersonajePropiedades> tvPersonajes = new TableView<>(PERSONAJES);
			
			TableColumn<PersonajePropiedades, String> cNombre = new TableColumn<>("Nombre");
			TableColumn<PersonajePropiedades, Integer> cPoder = new TableColumn<>("Poder");
			TableColumn<PersonajePropiedades, Boolean> cSuperpoder = new TableColumn<>("Super Poder");
			TableColumn<PersonajePropiedades, Estrategia> cEstrategia = new TableColumn<>("Estrategia");
			
			tvPersonajes.getColumns().add(cNombre);
			tvPersonajes.getColumns().add(cPoder);
			tvPersonajes.getColumns().add(cSuperpoder);
			tvPersonajes.getColumns().add(cEstrategia);
			
			cNombre.setMinWidth(100);
			cNombre.setCellValueFactory(fila -> fila.getValue().nombreProperty());
			cPoder.setMinWidth(20);
			cPoder.setCellValueFactory(fila -> fila.getValue().poderProperty().asObject());
			cSuperpoder.setMinWidth(40);
			cSuperpoder.setCellValueFactory(fila -> fila.getValue().superpoderProperty());
			cEstrategia.setMinWidth(60);
			cEstrategia.setCellValueFactory(fila -> fila.getValue().estrategiaProperty());
			
			raiz.getChildren().addAll(lbPersonajes, tvPersonajes);
			
			Scene escena = new Scene(raiz, 455, 250);
			escenarioPrincipal.setTitle("Tabla personajes");
			escenarioPrincipal.setScene(escena);
			escenarioPrincipal.show();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}

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

}

TablaPersonajesPersonalizada.java

package javafx.eventos;

import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.eventos.clases.Personaje;
import javafx.eventos.clases.Personaje.Estrategia;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.ChoiceBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.converter.IntegerStringConverter;

public class TablaPersonajesPersonalizada extends Application {
	
	private static final ObservableList<Personaje> PERSONAJES = FXCollections.observableArrayList(
			new Personaje("Bob Esponja", 10, false, Estrategia.RISA),
			new Personaje("Mortadelo", 60, true, Estrategia.RISA),
			new Personaje("Goku", 90, true, Estrategia.ATAQUE),
			new Personaje("El Malo", 0, false, Estrategia.MALHUMOR),
			new Personaje("Gro", 100, false, Estrategia.RISA));
	
	private class ConversorEnteroCadena extends IntegerStringConverter  {
		@Override
		public Integer fromString(String value) {
			int entero;
			try {
				entero = Integer.parseInt(value);
				entero = (entero < 0 || entero > 100) ? 0 : entero;
			} catch (Exception e) {
				entero = 0;
			}
			return entero;
		}
    }

	@Override
	public void start(Stage escenarioPrincipal) {
		try {
			VBox raiz = new VBox();
			raiz.setPadding(new Insets(40));
			raiz.setSpacing(10);
			raiz.setAlignment(Pos.CENTER);
			
			Label lbPersonajes = new Label("Personajes");
			lbPersonajes.setFont(Font.font(20));
			TableView<Personaje> tvPersonajes = new TableView<>(PERSONAJES);
			
			TableColumn<Personaje, String> cNombre = new TableColumn<>("Nombre");
			TableColumn<Personaje, Integer> cPoder = new TableColumn<>("Poder");
			TableColumn<Personaje, Boolean> cSuperpoder = new TableColumn<>("Super Poder");
			TableColumn<Personaje, Estrategia> cEstrategia = new TableColumn<>("Estrategia");
			
			tvPersonajes.getColumns().add(cNombre);
			tvPersonajes.getColumns().add(cPoder);
			tvPersonajes.getColumns().add(cSuperpoder);
			tvPersonajes.getColumns().add(cEstrategia);
			tvPersonajes.setEditable(true);
			
			cNombre.setMinWidth(100);
			cNombre.setCellValueFactory(new PropertyValueFactory<>("nombre"));
			cNombre.setCellFactory(TextFieldTableCell.forTableColumn());
			cNombre.setOnEditCommit(e -> System.out.println(e.getNewValue()));
			cPoder.setMinWidth(20);
			cPoder.setCellValueFactory(new PropertyValueFactory<>("poder"));
			cPoder.setCellFactory(fila -> new TextFieldTableCell<>(new ConversorEnteroCadena()));
			cPoder.setOnEditCommit(e -> System.out.println(e.getNewValue()));
			cSuperpoder.setMinWidth(40);
			cSuperpoder.setCellValueFactory(fila -> {
				BooleanProperty superpoderProperty = new SimpleBooleanProperty(fila.getValue().isSuperpoder());
				superpoderProperty.addListener((observable, oldValue, newValue) -> {
					fila.getValue().setSuperpoder(newValue);
					System.out.println(newValue);
				});
				return superpoderProperty;
			});
			cSuperpoder.setCellFactory(fila -> new CheckBoxTableCell<>());
			cEstrategia.setMinWidth(60);
			cEstrategia.setCellValueFactory(new PropertyValueFactory<>("estrategia"));
			cEstrategia.setCellFactory(fila -> new ChoiceBoxTableCell<Personaje, Estrategia>(Estrategia.values()));
			cEstrategia.setOnEditCommit(e -> System.out.println(e.getNewValue()));
			
			raiz.getChildren().addAll(lbPersonajes, tvPersonajes);
			
			Scene escena = new Scene(raiz, 455, 250);
			escenarioPrincipal.setTitle("Tabla personajes");
			escenarioPrincipal.setScene(escena);
			escenarioPrincipal.show();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}

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

}

TablaPersonajesPropiedadesPersonalizada.java

package javafx.eventos;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.eventos.clases.PersonajePropiedades;
import javafx.eventos.clases.PersonajePropiedades.Estrategia;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.ChoiceBoxTableCell;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.converter.IntegerStringConverter;

public class TablaPersonajesPropiedadesPersonalizada extends Application {
	
	private static final ObservableList<PersonajePropiedades> PERSONAJES = FXCollections.observableArrayList(
			new PersonajePropiedades("Bob Esponja", 10, false, Estrategia.RISA),
			new PersonajePropiedades("Mortadelo", 60, true, Estrategia.RISA),
			new PersonajePropiedades("Goku", 90, true, Estrategia.ATAQUE),
			new PersonajePropiedades("El Malo", 0, false, Estrategia.MALHUMOR),
			new PersonajePropiedades("Gro", 100, false, Estrategia.RISA));
	
	private class ConversorEnteroCadena extends IntegerStringConverter  {
		@Override
		public Integer fromString(String value) {
			int entero;
			try {
				entero = Integer.parseInt(value);
				entero = (entero < 0 || entero > 100) ? 0 : entero;
			} catch (Exception e) {
				entero = 0;
			}
			return entero;
		}
    }

	@Override
	public void start(Stage escenarioPrincipal) {
		try {
			VBox raiz = new VBox();
			raiz.setPadding(new Insets(40));
			raiz.setSpacing(10);
			raiz.setAlignment(Pos.CENTER);
			
			Label lbPersonajes = new Label("Personajes");
			lbPersonajes.setFont(Font.font(20));
			TableView<PersonajePropiedades> tvPersonajes = new TableView<>(PERSONAJES);
			
			TableColumn<PersonajePropiedades, String> cNombre = new TableColumn<>("Nombre");
			TableColumn<PersonajePropiedades, Integer> cPoder = new TableColumn<>("Poder");
			TableColumn<PersonajePropiedades, Boolean> cSuperpoder = new TableColumn<>("Super Poder");
			TableColumn<PersonajePropiedades, Estrategia> cEstrategia = new TableColumn<>("Estrategia");
			
			tvPersonajes.getColumns().add(cNombre);
			tvPersonajes.getColumns().add(cPoder);
			tvPersonajes.getColumns().add(cSuperpoder);
			tvPersonajes.getColumns().add(cEstrategia);
			tvPersonajes.setEditable(true);
			
			cNombre.setMinWidth(100);
			cNombre.setCellValueFactory(fila -> fila.getValue().nombreProperty());
			cNombre.setCellFactory(TextFieldTableCell.forTableColumn());
			cNombre.setOnEditCommit(e -> System.out.println(e.getNewValue()));
			cPoder.setMinWidth(20);
			cPoder.setCellValueFactory(fila -> fila.getValue().poderProperty().asObject());
			cPoder.setCellFactory(fila -> new TextFieldTableCell<>(new ConversorEnteroCadena()));
			cPoder.setOnEditCommit(e -> System.out.println(e.getNewValue()));
			cSuperpoder.setMinWidth(40);
			cSuperpoder.setCellValueFactory(fila -> {
				fila.getValue().superpoderProperty().addListener((observable, oldValue, newValue) -> {
					fila.getValue().setSuperpoder(newValue);
					System.out.println(newValue);
				});
				return fila.getValue().superpoderProperty();
			});
			cSuperpoder.setCellFactory(fila -> new CheckBoxTableCell<>());
			cEstrategia.setMinWidth(60);
			cEstrategia.setCellValueFactory(fila -> fila.getValue().estrategiaProperty());
			cEstrategia.setCellFactory(fila -> new ChoiceBoxTableCell<PersonajePropiedades, Estrategia>(Estrategia.values()));
			cEstrategia.setOnEditCommit(e -> System.out.println(e.getNewValue()));
			
			raiz.getChildren().addAll(lbPersonajes, tvPersonajes);
			
			Scene escena = new Scene(raiz, 455, 250);
			escenarioPrincipal.setTitle("Tabla personajes");
			escenarioPrincipal.setScene(escena);
			escenarioPrincipal.show();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}

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

}

TablaPersonajesCompleta.java

package javafx.eventos;

import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.eventos.clases.Personaje;
import javafx.eventos.clases.Personaje.Estrategia;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.ChoiceBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.converter.IntegerStringConverter;

public class TablaPersonajesCompleta extends Application {
	
	private static final ObservableList<Personaje> PERSONAJES = FXCollections.observableArrayList(
			new Personaje("Bob Esponja", 10, false, Estrategia.RISA),
			new Personaje("Mortadelo", 60, true, Estrategia.RISA),
			new Personaje("Goku", 90, true, Estrategia.ATAQUE),
			new Personaje("El Malo", 0, false, Estrategia.MALHUMOR),
			new Personaje("Gro", 100, false, Estrategia.RISA));
	
	private class ConversorEnteroCadena extends IntegerStringConverter  {
		@Override
		public Integer fromString(String value) {
			int entero;
			try {
				entero = Integer.parseInt(value);
				entero = (entero < 0 || entero > 100) ? 0 : entero;
			} catch (Exception e) {
				entero = 0;
			}
			return entero;
		}
    }
	
	private TableView<Personaje> tvPersonajes;
	private ListView<Personaje> lvPersonajes;
	
	private void filaCambiada(Personaje personaje) {
		if (personaje != null) {
			System.out.println("Fila cambiada: " + personaje);
		}
	}
	
	private void filaSeleccionada(Personaje personaje) {
		if (personaje != null) {
			lvPersonajes.getSelectionModel().select(personaje);
			System.out.println("Fila seleccionada: " + personaje);
		}
	}
	
	private void borrarFilaSeleccionada() {
		Personaje personaje = tvPersonajes.getSelectionModel().getSelectedItem();
		if (personaje != null) {
			PERSONAJES.remove(personaje);
			System.out.println("Fila borrada: " + personaje);
		}
	}
	
	private void anadirPersonajeVacio() {
		Personaje personaje = new Personaje("Cámbiame", 0, false, Estrategia.MALHUMOR);
		PERSONAJES.add(personaje);
		System.out.println("Personaje añadido: " + personaje);
	}

	@Override
	public void start(Stage escenarioPrincipal) {
		try {
			VBox raiz = new VBox(10);
			raiz.setPadding(new Insets(20));
			raiz.setAlignment(Pos.CENTER);
			
			Label lbPersonajes = new Label("Personajes");
			lbPersonajes.setFont(Font.font("Arial", 30));
			
			HBox hbPersonajes = new HBox(20);
			hbPersonajes.setPadding(new Insets(10));
			
			tvPersonajes = new TableView<>(PERSONAJES);
			
			TableColumn<Personaje, String> cNombre = new TableColumn<>("Nombre");
			TableColumn<Personaje, Integer> cPoder = new TableColumn<>("Poder");
			TableColumn<Personaje, Boolean> cSuperpoder = new TableColumn<>("Super Poder");
			TableColumn<Personaje, Estrategia> cEstrategia = new TableColumn<>("Estrategia");
			
			tvPersonajes.getColumns().add(cNombre);
			tvPersonajes.getColumns().add(cPoder);
			tvPersonajes.getColumns().add(cSuperpoder);
			tvPersonajes.getColumns().add(cEstrategia);
			tvPersonajes.setEditable(true);
			tvPersonajes.setMinWidth(360);
			
			cNombre.setMinWidth(100);
			cNombre.setCellValueFactory(new PropertyValueFactory<>("nombre"));
			cNombre.setCellFactory(TextFieldTableCell.forTableColumn());
			cNombre.setOnEditCommit(e -> {
				int indice = PERSONAJES.indexOf(e.getRowValue());
				e.getRowValue().setNombre(e.getNewValue());
				PERSONAJES.set(indice, e.getRowValue());
				filaCambiada(e.getRowValue());
			});
			cPoder.setMinWidth(20);
			cPoder.setCellValueFactory(new PropertyValueFactory<>("poder"));
			cPoder.setCellFactory(TextFieldTableCell.<Personaje, Integer>forTableColumn(new ConversorEnteroCadena()));
			cPoder.setOnEditCommit(e -> {
				int indice = PERSONAJES.indexOf(e.getRowValue());
				e.getRowValue().setPoder(e.getNewValue());
				PERSONAJES.set(indice, e.getRowValue());
				filaCambiada(e.getRowValue());
			});
			cSuperpoder.setMinWidth(40);
						cSuperpoder.setCellValueFactory(new PropertyValueFactory<>("superpoder"));
			cSuperpoder.setCellFactory(superPoder -> new CheckBoxTableCell<>());
			cSuperpoder.setCellValueFactory(fila -> {
				BooleanProperty superpoderProperty = new SimpleBooleanProperty(fila.getValue().isSuperpoder());
				superpoderProperty.addListener((observable, oldValue, newValue) -> {
					int indice = PERSONAJES.indexOf(fila.getValue());
					fila.getValue().setSuperpoder(newValue);
					PERSONAJES.set(indice, fila.getValue());
					filaCambiada(fila.getValue());
				});
				return superpoderProperty;
			});
			cEstrategia.setMinWidth(60);
			cEstrategia.setCellValueFactory(new PropertyValueFactory<>("estrategia"));
			cEstrategia.setCellFactory(estrategia -> new ChoiceBoxTableCell<Personaje, Estrategia>(Estrategia.values()));
			cEstrategia.setOnEditCommit(e -> {
				int indice = PERSONAJES.indexOf(e.getRowValue());
				e.getRowValue().setEstrategia(e.getNewValue());
				PERSONAJES.set(indice, e.getRowValue());
				filaCambiada(e.getRowValue());
			});
			tvPersonajes.getSelectionModel().selectedItemProperty().addListener((ob, oldValue, newValue) -> filaSeleccionada(newValue));

			lvPersonajes = new ListView<>(PERSONAJES);
			lvPersonajes.setMinWidth(500);

			hbPersonajes.getChildren().addAll(tvPersonajes, lvPersonajes);
			
			Button btBorrar = new Button("Borrar fila seleccionada");
			btBorrar.setStyle("-fx-font: 20 arial; -fx-base: #dc143c;");
			btBorrar.setOnAction(e -> borrarFilaSeleccionada());
			Button btAnadir = new Button("Añadir Personaje");
			btAnadir.setStyle("-fx-font: 22 arial; -fx-base: #b6e7c9;");
			btAnadir.setOnAction(e -> anadirPersonajeVacio());
			
			raiz.getChildren().addAll(lbPersonajes, hbPersonajes, btBorrar, btAnadir);
			
			Scene escena = new Scene(raiz, 950, 450);
			escenarioPrincipal.setTitle("Tabla Personajes completa");
			escenarioPrincipal.setScene(escena);
			escenarioPrincipal.show();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}

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

}

TablaPersonajesPropiedadesCompleta.java

package javafx.eventos;

import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.eventos.clases.PersonajePropiedades;
import javafx.eventos.clases.PersonajePropiedades.Estrategia;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.ChoiceBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.converter.IntegerStringConverter;

public class TablaPersonajesPropiedadesCompleta extends Application {
	
	private static final ObservableList<PersonajePropiedades> PERSONAJES = FXCollections.observableArrayList(
			new PersonajePropiedades("Bob Esponja", 10, false, Estrategia.RISA),
			new PersonajePropiedades("Mortadelo", 60, true, Estrategia.RISA),
			new PersonajePropiedades("Goku", 90, true, Estrategia.ATAQUE),
			new PersonajePropiedades("El Malo", 0, false, Estrategia.MALHUMOR),
			new PersonajePropiedades("Gro", 100, false, Estrategia.RISA));
	
	private class ConversorEnteroCadena extends IntegerStringConverter  {
		@Override
		public Integer fromString(String value) {
			int entero;
			try {
				entero = Integer.parseInt(value);
				entero = (entero < 0 || entero > 100) ? 0 : entero;
			} catch (Exception e) {
				entero = 0;
			}
			return entero;
		}
    }
	
	private TableView<PersonajePropiedades> tvPersonajes;
	private ListView<PersonajePropiedades> lvPersonajes;
	
	private void filaCambiada(PersonajePropiedades personaje) {
		if (personaje != null) {
			System.out.println("Fila cambiada: " + personaje);
		}
	}
	
	private void filaSeleccionada(PersonajePropiedades personaje) {
		if (personaje != null) {
			lvPersonajes.getSelectionModel().select(personaje);
			System.out.println("Fila seleccionada: " + personaje);
		}
	}
	
	private void borrarFilaSeleccionada() {
		PersonajePropiedades personaje = tvPersonajes.getSelectionModel().getSelectedItem();
		if (personaje != null) {
			PERSONAJES.remove(personaje);
			System.out.println("Fila borrada: " + personaje);
		}
	}
	
	private void anadirPersonajeVacio() {
		PersonajePropiedades personaje = new PersonajePropiedades("Cámbiame", 0, false, Estrategia.MALHUMOR);
		PERSONAJES.add(personaje);
		System.out.println("Personaje añadido: " + personaje);
	}

	@Override
	public void start(Stage escenarioPrincipal) {
		try {
			VBox raiz = new VBox(10);
			raiz.setPadding(new Insets(20));
			raiz.setAlignment(Pos.CENTER);
			
			Label lbPersonajes = new Label("Personajes");
			lbPersonajes.setFont(Font.font("Arial", 30));
			
			HBox hbPersonajes = new HBox(20);
			hbPersonajes.setPadding(new Insets(10));
			
			tvPersonajes = new TableView<>(PERSONAJES);
			
			TableColumn<PersonajePropiedades, String> cNombre = new TableColumn<>("Nombre");
			TableColumn<PersonajePropiedades, Integer> cPoder = new TableColumn<>("Poder");
			TableColumn<PersonajePropiedades, Boolean> cSuperpoder = new TableColumn<>("Super Poder");
			TableColumn<PersonajePropiedades, Estrategia> cEstrategia = new TableColumn<>("Estrategia");
			
			tvPersonajes.getColumns().add(cNombre);
			tvPersonajes.getColumns().add(cPoder);
			tvPersonajes.getColumns().add(cSuperpoder);
			tvPersonajes.getColumns().add(cEstrategia);
			tvPersonajes.setEditable(true);
			tvPersonajes.setMinWidth(360);
			
			cNombre.setMinWidth(100);
			cNombre.setCellValueFactory(fila -> fila.getValue().nombreProperty());
			cNombre.setCellFactory(TextFieldTableCell.forTableColumn());
			cNombre.setOnEditCommit(e -> {
				int indice = PERSONAJES.indexOf(e.getRowValue());
				e.getRowValue().setNombre(e.getNewValue());
				PERSONAJES.set(indice, e.getRowValue());
				filaCambiada(e.getRowValue());
			});
			cPoder.setMinWidth(20);
			cPoder.setCellValueFactory(fila -> fila.getValue().poderProperty().asObject());
			cPoder.setCellFactory(fila -> new TextFieldTableCell<>(new ConversorEnteroCadena()));
			cPoder.setOnEditCommit(e -> {
				int indice = PERSONAJES.indexOf(e.getRowValue());
				e.getRowValue().setPoder(e.getNewValue());
				PERSONAJES.set(indice, e.getRowValue());
				filaCambiada(e.getRowValue());
			});
			cSuperpoder.setMinWidth(40);
			cSuperpoder.setCellFactory(superPoder -> new CheckBoxTableCell<>());
			cSuperpoder.setCellValueFactory(fila -> {
				BooleanProperty superpoderProperty = new SimpleBooleanProperty(fila.getValue().isSuperpoder());
				superpoderProperty.addListener((observable, oldValue, newValue) -> {
					int indice = PERSONAJES.indexOf(fila.getValue());
					fila.getValue().setSuperpoder(newValue);
					PERSONAJES.set(indice, fila.getValue());
					filaCambiada(fila.getValue());
				});
				return superpoderProperty;
			});
			cEstrategia.setMinWidth(60);
			cEstrategia.setCellValueFactory(new PropertyValueFactory<>("estrategia"));
			cEstrategia.setCellFactory(estrategia -> new ChoiceBoxTableCell<PersonajePropiedades, Estrategia>(Estrategia.values()));
			cEstrategia.setOnEditCommit(e -> {
				int indice = PERSONAJES.indexOf(e.getRowValue());
				e.getRowValue().setEstrategia(e.getNewValue());
				PERSONAJES.set(indice, e.getRowValue());
				filaCambiada(e.getRowValue());
			});
			tvPersonajes.getSelectionModel().selectedItemProperty().addListener((ob, oldValue, newValue) -> filaSeleccionada(newValue));

			lvPersonajes = new ListView<>(PERSONAJES);
			lvPersonajes.setMinWidth(500);

			hbPersonajes.getChildren().addAll(tvPersonajes, lvPersonajes);
			
			Button btBorrar = new Button("Borrar fila seleccionada");
			btBorrar.setStyle("-fx-font: 20 arial; -fx-base: #dc143c;");
			btBorrar.setOnAction(e -> borrarFilaSeleccionada());
			Button btAnadir = new Button("Añadir Personaje");
			btAnadir.setStyle("-fx-font: 22 arial; -fx-base: #b6e7c9;");
			btAnadir.setOnAction(e -> anadirPersonajeVacio());
			
			raiz.getChildren().addAll(lbPersonajes, hbPersonajes, btBorrar, btAnadir);
			
			Scene escena = new Scene(raiz, 950, 450);
			escenarioPrincipal.setTitle("Tabla Personajes completa");
			escenarioPrincipal.setScene(escena);
			escenarioPrincipal.show();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}

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

}