以编程方式更改TableView行外观

在做了关于TableView的Oracle教程之后,我想知道是否有一种方法可以以编程方式将不同的CSS样式应用于选定的TableView行。 例如,用户select一个特定的行,单击“突出显示”button,选定的行获得棕色背景,白色文本填充等。我已经阅读了JavaFX tableview颜色 ,在JavaFX中 更新TableView行外观和2种颜色的背景? ,但无济于事= /

这是来源:

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

从“高亮选中的行”button的application.css中,将高亮的行类应用于所选的表格行:

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

我不明白的部分是为什么我不能从setOnAction方法中做到这btnHighlight ? 之后我也尝试刷新桌面( 这里描述 ),但似乎没有工作。 此外,我的“解决scheme”只适用于firstNameCol列,所以必须为每个列设置新的单元格工厂以应用某种风格,还是有更聪明的解决scheme?

如果你不希望我上面发布的解决scheme的可重用性,这是真正的同样的事情,但使用行工厂,而不是一个独立的类的匿名内部类。 也许代码更容易遵循,因为它在一个地方。 这是乔纳森的解决scheme和我之间的混合,但会自动更新亮点,而不强迫它sorting。

我使用了一个整数列表,所以它支持多个select,但如果你不需要,你可以显然只是使用一个IntegerProperty来代替。

这是行工厂:

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

下面是一些button的样子:

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

如何创build一个行工厂,暴露出一个可观察的表行的索引列表,这些行将被突出显示? 这样,您可以简单地使用需要突出显示的索引来更新列表:例如,通过调用select模型上的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<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)); } } } 

现在你可以做了

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

并在你的button的动作处理程序做

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

因为StyleChangingRowFactory包装另一行工厂,所以如果您已经有了您想要使用的自定义行工厂实现,仍然可以使用它。 例如:

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

这是一个完整的代码示例。

这是一个丑陋的黑客解决scheme。 首先,定义一个名为highlightedRow的int字段。 然后在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"); } } }; } }); 

然后添加下面的代码在你的button的行动(这是丑陋的黑客进场):

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

一旦完成,你会得到所选行的棕色突出显示。 你当然可以轻松地支持多个棕色亮点,用int列表replaceint列表。

我发现要做到这一点的最佳方式是:

在我的CSS

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

在我的表中初始化一个ObservableList中Object对象的SimpleBooleanProperty:

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

代码适应这个完整的例子

我可能会find一些可行的方法:

添加此代码后,如果按下button,突出显示的行将更改颜色,则当您select不同的行时,颜色将变回默认值,再次按下该button时,会将新行的颜色更改为棕色。

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

只有这个解决scheme的问题是,如果你连续按两次button,你select的下一行已经是棕色的。 你将不得不使用一个单独的CSS文件,否则在应用程序的启动,将不会应用CSS的规则,直到你按下button。

我发现最好的解决scheme是监听row.itemProperty()的更改,因为当您对行进行sorting时会更改索引,因此行会自动得到通知。