问题描述
一个相对的Java新手问题。
A relative Java newbie question.
我正在尝试设置 TableRow
的背景颜色基于是否选中它和/或数据模型中的布尔值是否为真。
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.
我找到了各种方法,但不是两者都在一起相同的 setRowFactory
。
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?
这是我发现根据选择改变行颜色的原因。它改编自用户James_D的答案 。
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;
});
这就是我发现wrt根据单元格值改变行颜色。它改编自用户kleopatra的答案。
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.
如果有帮助,这是我一直在玩的MVCE。它有两排工厂。我没有包括我的(很多!)尝试合并它们,因为没有工作。
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.
我正在使用JavaFX8(JDK1.8.0_181),NetBeans 8.2和Scene Builder 8.3。
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);
}
}
推荐答案
有几种方法可以做到这一点。以下是使用外部CSS和伪类的示例:
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;
}
如果您更喜欢使用代码,将 ItemTableRow
更改为此(并从<$ c中删除 getStylesheets()。add(...)
$ c> Main ):
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?");
}
}
}
-fx-control-inner-background
值在 modena.css
(JavaFX 8+的默认样式表)。它为 TableRow
提供了一点颜色填充。
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.
这篇关于如何在JavaFX8 TableView中根据是否选择了TableRow的背景颜色和/或数据模型中的值来设置它们?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!