Après avoir effectué un tutoriel Oracle sur TableView , je me demandais s’il était possible d’appliquer par programme un style CSS différent à la ligne TableView sélectionnée. Par exemple, l'utilisateur sélectionne une certaine ligne, clique sur le bouton "Mettre en surbrillance" et la ligne sélectionnée devient un fond brun, un remplissage de texte blanc, etc. J'ai lu les couleurs de la vue table JavaFX , Mise à jour de la ligne apparence et Fond avec 2 couleurs en JavaFX? , mais en vain = /
Voici la source:
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);
}
}
}
Et le application.css à partir duquel le bouton "Mettre en évidence la ligne sélectionnée" applique la classe de mise en surbrillance à la ligne de tableau sélectionnée:
.highlightedRow {
-fx-background-color: brown;
-fx-background-insets: 0, 1, 2;
-fx-background: -fx-accent;
-fx-text-fill: -fx-selection-bar-text;
}
Après plusieurs heures d’essais, la meilleure chose à faire est this en utilisant le code ci-dessous:
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 partie que je ne comprends pas vraiment, c'est pourquoi je ne peux pas faire cela depuis la méthode setOnAction
de btnHighlight
? J'ai également essayé d'actualiser la table après ( décrit ici ), mais cela n'a pas semblé fonctionner. De plus, ma "solution" ne fonctionne que pour la colonne firstNameCol
; vous devez donc définir une nouvelle fabrique de cellules pour chaque colonne afin d'appliquer un certain style, ou existe-t-il une solution plus intelligente?
Si vous ne voulez pas que la solution que j'ai publiée ci-dessus puisse être réutilisée, il s'agit en réalité de la même chose, mais l'utilisation d'une classe interne anonyme pour la fabrique de lignes au lieu d'une classe autonome. Peut-être que le code est plus facile à suivre car tout est au même endroit. C'est un peu un hybride entre la solution de Jonathan et la mienne, mais mettra automatiquement à jour les points forts sans le forcer.
J'ai utilisé une liste d'entiers afin qu'il prenne en charge la sélection multiple, mais si vous n'en avez pas besoin, vous pouvez évidemment simplement utiliser IntegerProperty à la place.
Voici l'usine en rangée:
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;
}
});
Et voici à quoi pourraient ressembler certains boutons:
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();
}
});
Pourquoi ne pas créer une fabrique de lignes qui présente une liste observable des index des lignes de la table à mettre en surbrillance? De cette façon, vous pouvez simplement mettre à jour la liste avec les index que vous devez surligner: par exemple, en appelant getSelectedIndices () sur le modèle de sélection et en le passant à la méthode setAll (...) de la liste.
Cela pourrait ressembler à quelque chose comme:
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));
}
}
}
Maintenant tu peux faire
final StyleChangingRowFactory<Person> rowFactory = new StyleChangingRowFactory<>("highlightedRow");
table.setRowFactory(rowFactory);
Et dans le gestionnaire d'action de votre bouton, faites
rowFactory.getStyledRowIndices().setAll(table.getSelectionModel().getSelectedIndices());
Dans la mesure où StyleChangingRowFactory enveloppe une autre fabrique de lignes, vous pouvez toujours l'utiliser si vous avez déjà une implémentation de fabrique de lignes personnalisée à utiliser. Par exemple:
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);
Ici est un exemple de code complet.
Voici une solution de hack moche. Tout d’abord, définissez un champ int appelé highlightRow. Ensuite, définissez une fabrique de lignes sur le 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");
}
}
};
}
});
Ajoutez ensuite le code suivant à votre bouton lors de l’action (et c’est là que le pirate laide entre en jeu):
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);
}
});
Une fois cela fait, vous obtenez la surbrillance marron sur la ligne sélectionnée. Bien sûr, vous pouvez facilement prendre en charge plusieurs points forts bruns en remplaçant l’int avec une liste d’itns.
La meilleure façon que je trouve de faire cela:
Dans mon CSS
.table-row-cell:feederChecked{
-fx-background-color: #06FF00;
}
Dans mon initialisation de table avec un SimpleBooleanProperty d'un contenu d'objet dans mon 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;
});
Code adapté de cet exemple complet
J'ai peut-être trouvé quelque chose qui fonctionne:
Avec ce code ajouté, si vous appuyez sur le bouton, la ligne en surbrillance change de couleur. Lorsque vous sélectionnez une autre ligne, la couleur redevient la valeur par défaut. Lorsque vous appuyez à nouveau sur le bouton, la couleur de la nouvelle ligne devient brune.
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;
}
Le seul problème avec cette solution est que si vous appuyez sur le bouton deux fois de suite, la prochaine ligne sélectionnée est déjà brune. Pour cela, vous devez utiliser un fichier css séparé. Sinon, au démarrage de l'application, aucune règle css ne sera appliquée jusqu'à ce que vous appuyiez sur le bouton.
J'ai trouvé que la meilleure solution serait d'écouter les modifications de row.itemProperty (), car lorsque vous triez par exemple les lignes, les index changent, les lignes sont donc automatiquement notifiées.