问题
A relative Java newbie question.
I'm trying set a TableRow
's background colour based on whether or not it's selected and/or whether or not a boolean value in the data model is true.
I've found ways of doing each but not both together in the same setRowFactory
.
What I would like to end up with is this (albeit with horrible colours for example purposes only!):
How would I go about achieving that?
This is what I found wrt changing row colour based on selection. It's adapted from user James_D's answer here https://community.oracle.com/thread/3528543.
final ObservableSet<Integer> selectedRowIndexes = FXCollections.observableSet();
table.getSelectionModel().getSelectedCells().addListener((Change<? extends TablePosition> change) -> {
selectedRowIndexes.clear();
selectedRowIndexes.add( (table.getSelectionModel().getSelectedCells().get(0)).getRow() );
});
table.setRowFactory(tv -> {
TableRow<TestModel> row = new TableRow<>();
BooleanBinding selected = Bindings.createBooleanBinding(() ->
selectedRowIndexes.contains(new Integer(row.getIndex())), row.indexProperty(), selectedRowIndexes);
row.styleProperty().bind(Bindings.when(selected)
.then("-fx-background-color: green;")
.otherwise(""));
return row;
});
And this is what I found wrt changing row colour based on a cell value. It's adapted from user kleopatra's answer here TreeTableView : setting a row not editable.
table.setRowFactory(tv -> {
TableRow<TestModel> row = new TableRow<TestModel>() {
@Override
public void updateItem(TestModel testmodel, boolean empty) {
super.updateItem(testmodel, empty);
boolean locked = false;
if ( getItem() != null ) {
locked = getItem().lockedProperty().get();
setEditable( ! locked);
}
if (!isEmpty() && locked ) {
setStyle("-fx-background-color: red;");
}else{
setStyle(null);
}
}
};
return row;
});
However, I've ended up with two row factories and haven't been able to figure out how merge them into one.
If it helps, here is the MVCE I've been playing with. It has the two row factories. I haven't included my (many!) attempts to merge them as none worked.
I'm using JavaFX8 (JDK1.8.0_181), NetBeans 8.2 and Scene Builder 8.3.
package test31;
import java.util.Arrays;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.collections.ObservableSet;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.converter.BooleanStringConverter;
public class Test31 extends Application {
private Parent createContent() {
TableView<TestModel> table = new TableView<>();
ObservableList<TestModel> olTestModel = FXCollections.observableArrayList(testmodel -> new Observable[] {});
olTestModel.add(new TestModel("1", true));
olTestModel.add(new TestModel("2", false));
olTestModel.add(new TestModel("3", false));
olTestModel.add(new TestModel("4", true));
olTestModel.add(new TestModel("5", false));
TableColumn<TestModel, String> colText = new TableColumn<>("textfield");
colText.setCellValueFactory(cb -> cb.getValue().textFieldProperty());
colText.setCellFactory(TextFieldTableCell.forTableColumn());
TableColumn<TestModel, Boolean> colBoolean = new TableColumn<>("locked");
colBoolean.setCellValueFactory(cb -> cb.getValue().lockedProperty());
colBoolean.setCellFactory(TextFieldTableCell.forTableColumn(new BooleanStringConverter()));
table.getSelectionModel().setCellSelectionEnabled(true);
table.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
table.setEditable(true);
table.getColumns().addAll(Arrays.asList(colText, colBoolean));
table.setItems(olTestModel);
//****************************************************************************************
//First row factory: Set background colour based on whether or not the row is selected
final ObservableSet<Integer> selectedRowIndexes = FXCollections.observableSet();
table.getSelectionModel().getSelectedCells().addListener((Change<? extends TablePosition> change) -> {
selectedRowIndexes.clear();
selectedRowIndexes.add( (table.getSelectionModel().getSelectedCells().get(0)).getRow() );
});
table.setRowFactory(tv -> {
TableRow<TestModel> row = new TableRow<>();
BooleanBinding selected = Bindings.createBooleanBinding(() ->
selectedRowIndexes.contains(new Integer(row.getIndex())), row.indexProperty(), selectedRowIndexes);
row.styleProperty().bind(Bindings.when(selected)
.then("-fx-background-color: green;")
.otherwise(""));
return row;
});
//****************************************************************************************
//Second row factory: Set background colour based on the value of a boolean property
table.setRowFactory(tv -> {
TableRow<TestModel> row = new TableRow<TestModel>() {
@Override
public void updateItem(TestModel testmodel, boolean empty) {
super.updateItem(testmodel, empty);
boolean locked = false;
if ( getItem() != null ) {
locked = getItem().lockedProperty().get();
setEditable( ! locked);
}
if (!isEmpty() && locked ) {
setStyle("-fx-background-color: red;");
}else{
setStyle(null);
}
}
};
return row;
});
BorderPane content = new BorderPane(table);
return content;
}
public class TestModel {
private StringProperty textField;
private BooleanProperty locked;
public TestModel() {
this("", false);
}
public TestModel(
String textField,
boolean locked
) {
this.textField = new SimpleStringProperty(textField);
this.locked = new SimpleBooleanProperty(locked);
}
public String getTextField() {
return textField.get().trim();
}
public void setTextField(String textField) {
this.textField.set(textField);
}
public StringProperty textFieldProperty() {
return textField;
}
public boolean getLocked() {
return locked.get();
}
public void setLocked(boolean locked) {
this.locked.set(locked);
}
public BooleanProperty lockedProperty() {
return locked;
}
}
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setTitle("Test");
stage.setWidth(500);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
回答1:
There are a couple ways you can do this. Here's an example using external CSS and pseudo-classes:
Main.java
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>(createDummyData(100));
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
table.setRowFactory(t -> new ItemTableRow());
TableColumn<Item, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(features -> features.getValue().nameProperty());
table.getColumns().add(nameCol);
TableColumn<Item, Boolean> validCol = new TableColumn<>("Valid");
validCol.setCellValueFactory(features -> features.getValue().validProperty());
table.getColumns().add(validCol);
primaryStage.setScene(new Scene(new StackPane(table), 800, 600));
primaryStage.getScene().getStylesheets().add(getClass().getResource("Main.css").toExternalForm());
primaryStage.setTitle("JavaFX Application");
primaryStage.show();
}
private ObservableList<Item> createDummyData(int count) {
return IntStream.rangeClosed(1, count)
.mapToObj(i -> "Item #" + i)
.map(name -> new Item(name, Math.random() >= 0.5))
.collect(Collectors.toCollection(FXCollections::observableArrayList));
}
}
ItemTableRow.java
import javafx.beans.value.ChangeListener;
import javafx.beans.value.WeakChangeListener;
import javafx.css.PseudoClass;
import javafx.scene.control.TableRow;
public class ItemTableRow extends TableRow<Item> {
private static final PseudoClass VALID = PseudoClass.getPseudoClass("valid");
private final ChangeListener<Boolean> listener = (obs, oldVal, newVal) -> updateValidPseudoClass(newVal);
private final WeakChangeListener<Boolean> weakListener = new WeakChangeListener<>(listener);
public ItemTableRow() {
getStyleClass().add("item-table-row");
}
@Override
protected void updateItem(Item item, boolean empty) {
Item oldItem = getItem();
if (oldItem != null) {
oldItem.validProperty().removeListener(weakListener);
}
super.updateItem(item, empty);
if (empty || item == null) {
updateValidPseudoClass(false);
} else {
item.validProperty().addListener(weakListener);
updateValidPseudoClass(item.isValid());
}
}
private void updateValidPseudoClass(boolean active) {
pseudoClassStateChanged(VALID, active);
}
}
Item.java
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Item {
private final StringProperty name = new SimpleStringProperty(this, "name");
public final void setName(String name) { this.name.set(name); }
public final String getName() { return name.get(); }
public final StringProperty nameProperty() { return name; }
private final BooleanProperty valid = new SimpleBooleanProperty(this, "valid");
public final void setValid(boolean valid) { this.valid.set(valid); }
public final boolean isValid() { return valid.get(); }
public final BooleanProperty validProperty() { return valid; }
public Item() {}
public Item(String name, boolean valid) {
setName(name);
setValid(valid);
}
}
Main.css
.item-table-row:selected {
-fx-background-color: -fx-control-inner-background, green;
}
.item-table-row:valid {
-fx-background-color: -fx-control-inner-background, yellow;
}
.item-table-row:valid:selected {
-fx-background-color: -fx-control-inner-background, red;
}
If you prefer to only use code, change ItemTableRow
to this (and remove getStylesheets().add(...)
from Main
):
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.scene.control.TableRow;
public class ItemTableRow extends TableRow<Item> {
private final InvalidationListener listener = observable -> updateStyle();
private final WeakInvalidationListener weakListener = new WeakInvalidationListener(listener);
public ItemTableRow() {
getStyleClass().add("item-table-row");
selectedProperty().addListener(listener); // could also override updateSelected
}
@Override
protected void updateItem(Item item, boolean empty) {
Item oldItem = getItem();
if (oldItem != null) {
oldItem.validProperty().removeListener(weakListener);
}
super.updateItem(item, empty);
if (item != null) {
item.validProperty().addListener(weakListener);
}
updateStyle();
}
private void updateStyle() {
final Item item = getItem();
if (item == null || (!isSelected() && !item.isValid())) {
setStyle(null);
} else if (isSelected() && item.isValid()) {
setStyle("-fx-background-color: -fx-control-inner-background, red;");
} else if (isSelected()) {
setStyle("-fx-background-color: -fx-control-inner-background, green;");
} else if (item.isValid()) {
setStyle("-fx-background-color: -fx-control-inner-background, yellow;");
} else {
// I don't think this branch is possible, but not 100% sure
throw new AssertionError("Shouldn't be here?");
}
}
}
The -fx-control-inner-background
value is defined in modena.css
(the default stylesheet for JavaFX 8+). It gives the TableRow
that little bit of color padding.
来源:https://stackoverflow.com/questions/52885237/how-to-set-a-tablerows-background-colour-based-on-whether-or-not-its-selected