Программно изменить внешний вид TableView

После выполнения учебника Oracle о TableView мне было интересно, есть ли способ программно применить другой стиль CSS к выбранной строке TableView. Например, пользователь выбирает определенную строку, нажимает кнопку «Выделить», а выбранная строка получает коричневый фон, заполнение белого текста и т. Д. Я читал цвета таблиц JavaFX , обновляя внешний вид таблицы TableView и фона с помощью двух цветов в JavaFX? , но безрезультатно = /

Вот источник:

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 table = new TableView(); private final ObservableList 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("firstName")); TableColumn lastNameCol = new TableColumn("Last Name"); lastNameCol.setMinWidth(100); lastNameCol.setCellValueFactory( new PropertyValueFactory("lastName")); TableColumn emailCol = new TableColumn("Email"); emailCol.setMinWidth(200); emailCol.setCellValueFactory( new PropertyValueFactory("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(){ 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); } } } 

И application.css, из которого кнопка «Выделить выбранную строку» применяет class highlightRow к выбранной строке таблицы:

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

Редактировать:

После нескольких часов попыток, лучшее, что я мог придумать, это использовать следующий код:

 firstNameCol.setCellFactory(new Callback<TableColumn, TableCell>() { @Override public TableCell call(TableColumn personStringTableColumn) { return new TableCell() { @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 } } }; } }); 

Часть, которую я действительно не понимаю, почему я не могу сделать это из метода setOnAction для btnHighlight ? Я также попытался обновить таблицу после этого ( описанный здесь ), но, похоже, это не сработало. Кроме того, мое «решение» работает только для столбца firstNameCol , поэтому нужно установить новую фабрику ячеек для каждого столбца, чтобы применить определенный стиль, или есть более разумное решение?

    Если вы не хотите повторного использования решения, которое я разместил выше, это действительно то же самое, но использование анонимного внутреннего classа для фабрики строк вместо автономного classа. Возможно, код легче следовать, поскольку все это в одном месте. Это своего рода гибрид между решением Джонатана и моим, но автоматически обновляет основные моменты, не заставляя его сориентироваться.

    Я использовал список целых чисел, поэтому он поддерживает множественный выбор, но если вам не нужно, вы могли бы просто использовать IntegerProperty.

    Вот строчная фабрика:

      final ObservableList highlightRows = FXCollections.observableArrayList(); table.setRowFactory(new Callback, TableRow>() { @Override public TableRow call(TableView tableView) { final TableRow row = new TableRow() { @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() { @Override public void onChanged(Change change) { if (highlightRows.contains(row.getIndex())) { if (! row.getStyleClass().contains("highlightedRow")) { row.getStyleClass().add("highlightedRow"); } } else { row.getStyleClass().removeAll(Collections.singleton("highlightedRow")); } } }); return row; } }); 

    И вот некоторые кнопки могут выглядеть так:

      final Button btnHighlight = new Button("Highlight"); btnHighlight.disableProperty().bind(Bindings.isEmpty(table.getSelectionModel().getSelectedIndices())); btnHighlight.setOnAction(new EventHandler() { @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() { @Override public void handle(ActionEvent event) { highlightRows.clear(); } }); 

    Как создать фабрику строк, которая предоставляет видимый список индексов строк таблицы, которые должны быть выделены? Таким образом, вы можете просто обновить список индексами, которые вам нужно выделить: например, вызывая getSelectedIndices () на модели выбора и передавая его методу setAll (…) списка.

    Это может выглядеть примерно так:

     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 implements Callback, TableRow> { private final String styleClass ; private final ObservableList styledRowIndices ; private final Callback, TableRow> baseFactory ; public StyleChangingRowFactory(String styleClass, Callback, TableRow> baseFactory) { this.styleClass = styleClass ; this.baseFactory = baseFactory ; this.styledRowIndices = FXCollections.observableArrayList(); } public StyleChangingRowFactory(String styleClass) { this(styleClass, null); } @Override public TableRow call(TableView tableView) { final TableRow row ; if (baseFactory == null) { row = new TableRow<>(); } else { row = baseFactory.call(tableView); } row.indexProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue obs, Number oldValue, Number newValue) { updateStyleClass(row); } }); styledRowIndices.addListener(new ListChangeListener() { @Override public void onChanged(Change change) { updateStyleClass(row); } }); return row; } public ObservableList getStyledRowIndices() { return styledRowIndices ; } private void updateStyleClass(TableRow row) { final ObservableList 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)); } } } 

    Теперь вы можете сделать

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

    И в обработчике действий вашей кнопки

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

    Поскольку StyleChangingRowFactory обертывает другую фабрику строк, вы все равно можете использовать ее, если у вас уже есть пользовательская реализация фабрики строк, которую вы хотите использовать. Например:

     final StyleChangingRowFactory rowFactory = new StyleChangingRowFactory( "highlightedRow", new Callback, TableRow>() { @Override public TableRow call(TableView tableView) { final TableRow row = new TableRow(); ContextMenu menu = new ContextMenu(); MenuItem removeMenuItem = new MenuItem("Remove"); removeMenuItem.setOnAction(new EventHandler() { @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); 

    Вот полный пример кода.

    Вот уродливое решение для взлома. Во-первых, определите поле int, называемое highlightRow. Затем настройте фабрику строк в TableView:

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

    Затем добавьте следующий код в свою кнопку при действии (и именно здесь идет игра с уродливым взломом):

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

    Как только это будет сделано, вы получите выделение коричневого цвета в выбранном ряду. Разумеется, вы можете легко поддерживать несколько коричневых бликов, заменив int на список itns.

    Лучший способ найти это:

    В моем CSS

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

    В моей инициализации таблицы с SimpleBooleanProperty содержимого объекта в моем 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 row = new TableRow<>(); ChangeListener 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; }); слушателя // 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 row = new TableRow<>(); ChangeListener 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; }); 

    Код, адаптируемый из этого полного примера

    Я мог бы найти что-то, что работает:

    При добавлении этого кода, если вы нажмете кнопку, выделенная строка изменит цвет, при выборе другой строки цвет изменится на значение по умолчанию, когда вы снова нажмете кнопку, это изменит цвет новой строки на коричневый.

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

    CSS:

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

    Только проблема с этим решением заключается в том, что если вы дважды нажимаете кнопку в строке, ваша следующая выбранная строка уже коричневая. Для этого вам придется использовать отдельный файл css, иначе при запуске приложения никакие правила css не будут применяться до тех пор, пока вы не нажмете кнопку.

    Я обнаружил, что лучшим решением будет прослушивание изменений row.itemProperty (), потому что, когда вы сортируете, например, индексы изменяют индексы, поэтому строки получают уведомление автоматически.

    Interesting Posts

    Прелесть тернарного оператора против оператора if

    Экземпляр SSH на экземпляр эластичного бобового стежка

    Вычислить длину пути между узлами?

    Hibernate против JPA против JDO – плюсы и минусы каждого?

    Пример для boost shared_mutex (многократное чтение / запись)?

    Каков самый простой / лучший / самый правильный способ перебора символов строки в Java?

    В чем разница между StreamWriter.Flush () и StreamWriter.Close ()?

    Как я могу навсегда отключить указатель Dell?

    Слияние двух файлов PDF, содержащих четные и нечетные страницы книги

    Google Chrome находится на немецком языке. Как мне сделать это на английском?

    Как показывать вывод на терминал и сохранять в файл одновременно?

    Безопасно ли читать конец конца буфера на одной странице на x86 и x64?

    Папки Windows 7 «Все программы» автоматически закрываются правой кнопкой мыши

    Почему возврат 0 необязателен?

    проверка доступности данных до вызова std :: getline

    Давайте будем гением компьютера.