make how example component java javafx-2 tableview

java - how - Cambia programáticamente la apariencia de la fila TableView



jtable component in java (6)

¿Qué tal crear una fábrica de filas que expone una lista observable de los índices de las filas de la tabla que se van a resaltar? De esta forma, simplemente puede actualizar la lista con los índices que necesita resaltar: por ejemplo, llamando al getSelectedIndices () en el modelo de selección y pasándolo al método setAll (...) de la lista.

Esto podría ser algo así como:

import java.util.Collections; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.scene.control.TableRow; import javafx.scene.control.TableView; import javafx.util.Callback; public class StyleChangingRowFactory<T> implements Callback<TableView<T>, TableRow<T>> { private final String styleClass ; private final ObservableList<Integer> styledRowIndices ; private final Callback<TableView<T>, TableRow<T>> baseFactory ; public StyleChangingRowFactory(String styleClass, Callback<TableView<T>, TableRow<T>> baseFactory) { this.styleClass = styleClass ; this.baseFactory = baseFactory ; this.styledRowIndices = FXCollections.observableArrayList(); } public StyleChangingRowFactory(String styleClass) { this(styleClass, null); } @Override public TableRow<T> call(TableView<T> tableView) { final TableRow<T> row ; if (baseFactory == null) { row = new TableRow<>(); } else { row = baseFactory.call(tableView); } row.indexProperty().addListener(new ChangeListener<Number>() { @Override public void changed(ObservableValue<? extends Number> obs, Number oldValue, Number newValue) { updateStyleClass(row); } }); styledRowIndices.addListener(new ListChangeListener<Integer>() { @Override public void onChanged(Change<? extends Integer> change) { updateStyleClass(row); } }); return row; } public ObservableList<Integer> getStyledRowIndices() { return styledRowIndices ; } private void updateStyleClass(TableRow<T> row) { final ObservableList<String> rowStyleClasses = row.getStyleClass(); if (styledRowIndices.contains(row.getIndex()) ) { if (! rowStyleClasses.contains(styleClass)) { rowStyleClasses.add(styleClass); } } else { // remove all occurrences of styleClass: rowStyleClasses.removeAll(Collections.singleton(styleClass)); } } }

Ahora puedes hacer

final StyleChangingRowFactory<Person> rowFactory = new StyleChangingRowFactory<>("highlightedRow"); table.setRowFactory(rowFactory);

Y en el manejador de acciones de tu botón haz

rowFactory.getStyledRowIndices().setAll(table.getSelectionModel().getSelectedIndices());

Debido a que StyleChangingRowFactory ajusta otra fábrica de filas, aún puede usarla si ya tiene una implementación de fábrica de filas personalizada que desea usar. Por ejemplo:

final StyleChangingRowFactory<Person> rowFactory = new StyleChangingRowFactory<Person>( "highlightedRow", new Callback<TableView<Person>, TableRow<Person>>() { @Override public TableRow<Person> call(TableView<Person> tableView) { final TableRow<Person> row = new TableRow<Person>(); ContextMenu menu = new ContextMenu(); MenuItem removeMenuItem = new MenuItem("Remove"); removeMenuItem.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { table.getItems().remove(row.getItem()); } }); menu.getItems().add(removeMenuItem); row.contextMenuProperty().bind( Bindings.when(row.emptyProperty()) .then((ContextMenu) null) .otherwise(menu)); return row; } }); table.setRowFactory(rowFactory);

Here hay un ejemplo de código completo.

Después de hacer un tutorial de Oracle sobre TableView , me preguntaba si hay una forma de aplicar de forma programática diferentes estilos CSS a la fila seleccionada de TableView. Por ejemplo, el usuario selecciona una fila determinada, hace clic en el botón "Resaltar" y la fila seleccionada obtiene fondo marrón, relleno de texto blanco, etc. He leído los colores de la tabla JavaFX , la actualización de la apariencia de la fila TableView y el Fondo con 2 colores en JavaFX. , pero fue en vano = /

Aquí está la fuente:

import javafx.application.Application; import javafx.beans.property.SimpleStringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.control.Button; 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 TableViewSample extends Application { private TableView<Person> table = new TableView<Person>(); private final ObservableList<Person> data = FXCollections.observableArrayList( new Person("Jacob", "Smith", "[email protected]"), new Person("Isabella", "Johnson", "[email protected]"), new Person("Ethan", "Williams", "[email protected]"), new Person("Emma", "Jones", "[email protected]"), new Person("Michael", "Brown", "[email protected]") ); public static void main(String[] args) { launch(args); } @Override public void start(Stage stage) { Scene scene = new Scene(new Group()); stage.setTitle("Table View Sample"); stage.setWidth(450); stage.setHeight(600); final Label label = new Label("Address Book"); label.setFont(new Font("Arial", 20)); TableColumn firstNameCol = new TableColumn("First Name"); firstNameCol.setMinWidth(100); firstNameCol.setCellValueFactory( new PropertyValueFactory<Person, String>("firstName")); TableColumn lastNameCol = new TableColumn("Last Name"); lastNameCol.setMinWidth(100); lastNameCol.setCellValueFactory( new PropertyValueFactory<Person, String>("lastName")); TableColumn emailCol = new TableColumn("Email"); emailCol.setMinWidth(200); emailCol.setCellValueFactory( new PropertyValueFactory<Person, String>("email")); table.setItems(data); table.getColumns().addAll(firstNameCol, lastNameCol, emailCol); final Button btnHighlight = new Button("Highlight selected row"); btnHighlight.setMaxWidth(Double.MAX_VALUE); btnHighlight.setPrefHeight(30); btnHighlight.setOnAction(new EventHandler<ActionEvent>(){ public void handle(ActionEvent e){ // this is where the CSS should be applied } }); final VBox vbox = new VBox(); vbox.setSpacing(5); vbox.setPadding(new Insets(10, 0, 0, 10)); vbox.getChildren().addAll(label, table, btnHighlight); ((Group) scene.getRoot()).getChildren().addAll(vbox); stage.setScene(scene); stage.show(); } public static class Person { private final SimpleStringProperty firstName; private final SimpleStringProperty lastName; private final SimpleStringProperty email; private Person(String fName, String lName, String email) { this.firstName = new SimpleStringProperty(fName); this.lastName = new SimpleStringProperty(lName); this.email = new SimpleStringProperty(email); } public String getFirstName() { return firstName.get(); } public void setFirstName(String fName) { firstName.set(fName); } public String getLastName() { return lastName.get(); } public void setLastName(String fName) { lastName.set(fName); } public String getEmail() { return email.get(); } public void setEmail(String fName) { email.set(fName); } } }

Y la aplicación.css desde la cual el botón "Resaltar la fila seleccionada" aplica la clase highlightRow a la fila de la tabla seleccionada:

.highlightedRow { -fx-background-color: brown; -fx-background-insets: 0, 1, 2; -fx-background: -fx-accent; -fx-text-fill: -fx-selection-bar-text; }

Editar:

Después de varias horas de intentarlo, lo mejor que puedo hacer es usar el siguiente código:

firstNameCol.setCellFactory(new Callback<TableColumn<Person, String>, TableCell<Person, String>>() { @Override public TableCell<Person, String> call(TableColumn<Person, String> personStringTableColumn) { return new TableCell<Person, String>() { @Override protected void updateItem(String name, boolean empty) { super.updateItem(name, empty); if (!empty) { if (name.toLowerCase().startsWith("e") || name.toLowerCase().startsWith("i")) { getStyleClass().add("highlightedRow"); } setText(name); } else { setText("empty"); // for debugging purposes } } }; } });

La parte que realmente no entiendo es ¿por qué no puedo hacer eso desde dentro del método setOnAction de btnHighlight ? También intenté actualizar la tabla después ( descrita aquí ), pero no pareció funcionar. Además, mi "solución" solo funciona para la primera columna firstNameCol , por lo que uno tiene que establecer una nueva fábrica de células para cada columna con el fin de aplicar un cierto estilo, ¿o existe una solución más inteligente?


Aquí hay una solución de hack fea. En primer lugar, defina un campo int llamado markedRow. Luego configure una fábrica de filas en TableView:

table.setRowFactory(new Callback<TableView<Person>, TableRow<Person>>() { @Override public TableRow<Person> call(TableView<Person> param) { return new TableRow<Person>() { @Override protected void updateItem(Person item, boolean empty) { super.updateItem(item, empty); if (getIndex() == highlightedRow) { getStyleClass().add("highlightedRow"); } else { getStyleClass().remove("highlightedRow"); } } }; } });

A continuación, agregue el siguiente código en su botón de acción (y aquí es donde entra en juego el truco feo):

btnHighlight.setOnAction(new EventHandler<ActionEvent>(){ public void handle(ActionEvent e){ // set the highlightedRow integer to the selection index highlightedRow = table.getSelectionModel().getSelectedIndex(); // force a tableview refresh - HACK List<Person> items = new ArrayList<>(table.getItems()); table.getItems().setAll(items); } });

Una vez hecho esto, obtienes el resaltado marrón en la fila seleccionada. Por supuesto, podría admitir fácilmente múltiples reflejos marrones reemplazando el int con una lista de itns.


Descubrí que la mejor solución sería escuchar los cambios de row.itemProperty () porque al ordenar, por ejemplo, las filas cambian los índices, por lo que las filas se notifican automáticamente.


La mejor manera que encuentro para hacer esto:

En mi CSS

.table-row-cell:feederChecked{ -fx-background-color: #06FF00; }

En mi tabla de inicialización con un SimpleBooleanProperty de un contenido de Objeto en mi ObservableList:

// The pseudo classes feederChecked that were defined in the css file. PseudoClass feederChecked = PseudoClass.getPseudoClass("feederChecked"); // Set a rowFactory for the table view. tableView.setRowFactory(tableView -> { TableRow<Feeder> row = new TableRow<>(); ChangeListener<Boolean> changeListener = (obs, oldFeeder, newFeeder) -> { row.pseudoClassStateChanged(feederChecked, newFeeder); }; row.itemProperty().addListener((obs, previousFeeder, currentFeeder) -> { if (previousFeeder != null) { previousFeeder.feederCheckedProperty().removeListener(changeListener); } if (currentFeeder != null) { currentFeeder.feederCheckedProperty().addListener(changeListener); row.pseudoClassStateChanged(feederChecked, currentFeeder.getFeederChecked()); } else { row.pseudoClassStateChanged(feederChecked, false); } }); return row; });

Código de adaptación de este ejemplo completo


Pude haber encontrado algo que funciona:

Con este código agregado, si presiona el botón, la fila resaltada cambia de color, cuando selecciona una fila diferente, el color vuelve a ser el predeterminado, cuando presiona el botón nuevamente, cambia el color de la nueva fila a marrón.

final String css = getClass().getResource("style.css").toExternalForm(); final Scene scene = new Scene(new Group()); btnHighlight.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent e) { scene.getStylesheets().add(css); } }); table.getSelectionModel().selectedIndexProperty() .addListener(new ChangeListener<Number>() { @Override public void changed(ObservableValue<? extends Number> ov, Number t, Number t1) { scene.getStylesheets().remove(css); } });

css:

.table-row-cell:selected { -fx-background-color: brown; -fx-text-inner-color: white; }

El único problema con esta solución es que si presiona el botón dos veces seguidas, la siguiente fila seleccionada ya estará marrón. Tendría que usar un archivo css separado para esto, de lo contrario al inicio de la aplicación no se aplicarán reglas CSS hasta que presione el botón.


Si no quiere la reutilización de la solución que publiqué anteriormente, esto es realmente lo mismo pero usando una clase interna anónima para la fábrica de filas en lugar de una clase independiente. Tal vez el código es más fácil de seguir, ya que todo está en un solo lugar. Es una especie de híbrido entre la solución de Jonathan y la mía, pero actualizará automáticamente los aspectos más destacados sin forzarlo con un tipo.

Utilicé una lista de enteros para que sea compatible con la selección múltiple, pero si no la necesita, obviamente puede usar una propiedad IntegerProperty.

Aquí está la fábrica de filas:

final ObservableList<Integer> highlightRows = FXCollections.observableArrayList(); table.setRowFactory(new Callback<TableView<Person>, TableRow<Person>>() { @Override public TableRow<Person> call(TableView<Person> tableView) { final TableRow<Person> row = new TableRow<Person>() { @Override protected void updateItem(Person person, boolean empty){ super.updateItem(person, empty); if (highlightRows.contains(getIndex())) { if (! getStyleClass().contains("highlightedRow")) { getStyleClass().add("highlightedRow"); } } else { getStyleClass().removeAll(Collections.singleton("highlightedRow")); } } }; highlightRows.addListener(new ListChangeListener<Integer>() { @Override public void onChanged(Change<? extends Integer> change) { if (highlightRows.contains(row.getIndex())) { if (! row.getStyleClass().contains("highlightedRow")) { row.getStyleClass().add("highlightedRow"); } } else { row.getStyleClass().removeAll(Collections.singleton("highlightedRow")); } } }); return row; } });

Y aquí están algunos botones:

final Button btnHighlight = new Button("Highlight"); btnHighlight.disableProperty().bind(Bindings.isEmpty(table.getSelectionModel().getSelectedIndices())); btnHighlight.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { highlightRows.setAll(table.getSelectionModel().getSelectedIndices()); } }); final Button btnClearHighlight = new Button("Clear Highlights"); btnClearHighlight.disableProperty().bind(Bindings.isEmpty(highlightRows)); btnClearHighlight.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { highlightRows.clear(); } });