web-dev-qa-db-fra.com

Modifier par programme l'apparence de la ligne TableView

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;
}

Modifier:

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?

31
E. Normous

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();
        }
    });
17
James_D

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.

16
James_D

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.

7
Jonathan Giles

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

2
negstek

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.

0
WonderWorld

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.

0
michael laudrup