Modifier par programmation la TableView ligne apparence
après avoir fait un tutoriel Oracle sur TableView , je me demandais s'il y avait un moyen d'appliquer programmatiquement un style CSS différent à la ligne TableView sélectionnée. Par exemple, l'utilisateur sélectionne une certaine ligne, clique sur le bouton "surligner" et la ligne sélectionnée obtient le fond brun, le remplissage de texte blanc, etc. J'ai lu le JavaFX table colorsview , Updating TableView row apparence et arrière-plan 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", "jacob.smith@example.com"),
new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
new Person("Ethan", "Williams", "ethan.williams@example.com"),
new Person("Emma", "Jones", "emma.jones@example.com"),
new Person("Michael", "Brown", "michael.brown@example.com")
);
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 la demande .css à partir duquel le bouton "surligner la rangée sélectionnée" applique la classe surligneur à la rangée 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;
}
Edit:
après plusieurs heures d'essai, la meilleure chose que j'ai pu trouver est ce 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 est pourquoi je ne peux pas faire cela de l'intérieur de la méthode setOnAction
de la btnHighlight
? J'ai également essayé de rafraîchir la table après ( décrit ici ), mais il ne semble pas fonctionner. En outre, ma "solution" ne fonctionne que pour la colonne firstNameCol
, est-ce que l'on doit définir une nouvelle usine de cellules pour chaque colonne afin d'appliquer un certain style, ou y a-t-il une solution plus intelligente?
6 réponses
si vous ne voulez pas la réutilisabilité de la solution que j'ai posté ci-dessus, c'est vraiment la même chose mais en utilisant une classe interne anonyme pour l'usine row au lieu d'une classe autonome. Peut-être que le code est plus facile à suivre car tout est en un seul endroit. C'est une sorte d'hybride entre la solution de Jonathan et la mienne, mais va automatiquement mettre à jour les highlights sans forcer avec une sorte.
j'ai utilisé une liste d'entiers donc il soutient la sélection multiple, mais si vous Je n'ai pas besoin que vous pourriez évidemment juste utiliser une Integrerproperty à la place.
, Voici la ligne de l'usine:
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();
}
});
Que Diriez-vous de créer une usine de rangée qui expose une liste observable des index des rangées de table qui doivent être mis en évidence? De cette façon, vous pouvez simplement mettre à jour la liste avec les index que vous avez besoin de mettre en évidence: par exemple en appelant getSelectedIndices() sur le modèle de sélection et en la passant à setAll(...) méthode.
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 vous pouvez le faire
final StyleChangingRowFactory<Person> rowFactory = new StyleChangingRowFactory<>("highlightedRow");
table.setRowFactory(rowFactory);
Et dans l'action handler de votre bouton Faire
rowFactory.getStyledRowIndices().setAll(table.getSelectionModel().getSelectedIndices());
parce que StyleChangingRowFactory enveloppe une autre usine de rangée, vous pouvez toujours l'utiliser si vous avez déjà une application d'usine de rangée personnalisée que vous voulez 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 piratage moche. Tout d'abord, définissez un champ int appelé highlightedRow. Ensuite, définissez une usine de rang sur la 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");
}
}
};
}
});
puis ajouter le code suivant dans votre bouton sur l'action (et c'est là que le hack laid 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 que cela est fait, vous obtenez la surbrillance brune sur la ligne sélectionnée. Vous pouvez bien sûr facilement prendre en charge plusieurs points saillants bruns en remplaçant l'int par une liste de Mii.
La meilleure façon que j'ai trouver pour faire ceci:
dans mon CSS
.table-row-cell:feederChecked{
-fx-background-color: #06FF00;
}
dans mon initialisation de table avec une simple propriété D'un contenu 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 ligne différente la couleur change de nouveau par défaut, lorsque vous appuyez sur le bouton à nouveau, il change la couleur de la nouvelle ligne en brun.
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;
}
Seul problème avec cette solution est que si vous appuyez sur le bouton deux fois de suite, votre prochaine ligne la sélection est déjà marron. Vous devrez utiliser un fichier css séparé pour cela, sinon au démarrage de l'application aucune règle css ne sera appliquée jusqu'à ce que vous appuyez sur le bouton.
j'ai trouvé que la meilleure solution serait d'écouter row.itemProperty () change parce que lorsque vous triez par exemple les lignes changent les index, ainsi les lignes sont notifiées automatiquement.