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?

31
demandé sur Community 2013-12-03 15:37:04

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();
        }
    });
16
répondu James_D 2013-12-09 16:08:27

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.

15
répondu James_D 2013-12-06 14:50:12

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.

7
répondu Jonathan Giles 2013-12-05 20:19:20

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

2
répondu negstek 2015-10-26 15:14:37

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.

0
répondu WonderWorld 2013-12-06 19:08:48

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.

0
répondu michael laudrup 2015-08-19 17:16:25