我有一个以常用方式由排序和筛选列表支持的TableView:
FilteredList<Filedata> filteredData = new FilteredList<>(eeModel.data, p -> true);
SortedList<Filedata> sortedList = new SortedList<>(filteredData);
table.setItems(sortedList);
sortedList.comparatorProperty().bind(table.comparatorProperty());
我需要能够在与其邻居匹配的单元格项目上设置背景颜色。我已经尝试在适当的表格单元工厂方法中执行此操作,虽然它可以正常工作,因为在按照不可预测的顺序调用单元格工厂时滚动表格会崩溃。
我猜测每次使用sortedList时最好的方法是使用sortedList,或者我是否需要在tableview和sortedlist之间插入另一个列表才能执行此操作?
任何建议都会受到最高的赞赏。
更新:
根据James_D的要求,这是目前的细胞工厂:
filename.setCellFactory(column -> {
return new TableCell<Filedata, String>() {
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
} else {
setText(item);
int index = this.getTableRow().getIndex();
String prevCell = filename.getCellData(index-1);
String nextCell = filename.getCellData(index+1);
final boolean nextCellEqual = nextCell != null && nextCell.equals(item);
final boolean prevCellEqual = prevCell != null && prevCell.equals(item);
if ( prevCellEqual ) {
setStyle( RowColour.getColour() );
System.out.println( "prevCellEqual " + index + " set to " + RowColour.getColour() );
}
else if ( nextCellEqual ) {
RowColour.nextColour();
setStyle( RowColour.getColour() );
System.out.println( "nextCellEqual " + index + " set to " + RowColour.getColour() );
}
else {
setStyle("");
}
}
}
};
});
其中RowColour是一个简单的静态类,它记录当前使用的颜色并根据请求进行切换。显然这是依赖于呼叫顺序的。但即使我消除了这一点,我认为我仍然可以在没有全局概览的情况下将非匹配单元格绘制成相同的颜色。
UPDATE - sortedList / ListChangeListener可能性
sortedList.addListener(new ListChangeListener<Object>() {
@Override
public void onChanged(Change<?> change) {
System.out.println("SortedList size: " + sortedList.size());
String thisFilename;
String prevFilename;
String nextFilename;
final int size = sortedList.size();
dupeFlags = new int[size];
int colour = 1;
for (int i = 0; i < size; i++) {
thisFilename = sortedList.get(i).getFilename();
if (i > 0) {
prevFilename = sortedList.get(i-1).getFilename();
}
else {
prevFilename = null;
}
if (i < size-1) {
nextFilename = sortedList.get(i+1).getFilename();
}
else {
nextFilename = null;
}
final boolean nextEqual = nextFilename != null && nextFilename.equals(thisFilename);
final boolean prevEqual = prevFilename != null && prevFilename.equals(thisFilename);
if ( prevEqual ) {
dupeFlags[i] = colour;
System.out.println( "pe " + i + " coloured: " + colour );
}
else if ( nextEqual ) {
colour = 3 - colour;
dupeFlags[i] = colour;
System.out.println( "ne " + i + " coloured: " + colour );
}
else {
dupeFlags[i] = 0;
System.out.println( "-- " + i + " coloured: " + 0 );
}
}
}
});
filename.setCellFactory(column -> {
return new TableCell<Filedata, String>() {
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
} else {
setText(item);
int index = this.getTableRow().getIndex();
switch (dupeFlags[index]) {
case 0:
setStyle("");
break;
case 1:
setStyle( "-fx-background-color: yellow" );
break;
case 2:
setStyle("-fx-background-color: red" );
break;
}
}
}
};
});
答案 0 :(得分:0)
正如你所提到的,这里的问题是调用事物的顺序之一。您还可能需要处理索引更改但项目不可能的情况(可能不太可能,但肯定可能)。在这种情况下,您的代码根本不会被调用。
这是(某种)更一般原则的一个例子,即子类化(在这种情况下为TableCell
)打破了封装,因为您需要知道有关超类的实现的详细信息(在这种情况下,是否为索引)在项目之前更新,反之亦然)。
<强>更新强>
这比我想象的要难一点。当然,当一个相邻单元格中的值发生变化时,单元也需要更新。由于与当前单元格相邻的单元格可能会发生变化(通过过滤,排序,甚至只是滚动),因此需要将这些值上的侦听器移动到正确的项目中。
所以我在这里采用的方法是使用一个相当小的TableCell
子类来注册一个具有item
和index
属性的侦听器。它还将每个相邻单元格的项属性包装在对象属性中,并在索引更改时更新它们。我认为这就完成了工作;我相信这种方法是正确的,但你可能需要仔细考虑,并确保你涵盖所有可能性。
我只使用css伪类作为单元格颜色,这比操纵css类更快(我被告知),并且比操作内联样式快得多。 css文件只是
高亮匹配-cells.css:
.table-cell:highlight {
-fx-background-color: salmon ;
}
示例是
import java.util.Arrays;
import java.util.Random;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class HighlightMatchingCells extends Application {
@Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
ObservableList<Item> data = createData();
FilteredList<Item> filteredData = new FilteredList<>(data, item -> true);
SortedList<Item> sortedList = new SortedList<>(filteredData);
sortedList.comparatorProperty().bind(table.comparatorProperty());
table.setItems(sortedList);
TableColumn<Item, String> nameCol = makeCol(String.class, "Name", Item::nameProperty);
TableColumn<Item, Number> value1Col = makeCol(Number.class, "Value 1", Item::value1Property);
TableColumn<Item, Number> value2Col = makeCol(Number.class, "Value 2", Item::value2Property);
TableColumn<Item, Number> value3Col = makeCol(Number.class, "Value 3", Item::value3Property);
TableColumn<Item, Number> totalCol = makeCol(Number.class, "Total", Item::totalProperty);
table.getColumns().addAll(Arrays.asList(nameCol, value1Col, value2Col, value3Col, totalCol));
Callback<TableColumn<Item, Number>, TableCell<Item, Number>> cellFactory =
col -> new MatchingAdjacentCell();
value1Col.setCellFactory(cellFactory);
value2Col.setCellFactory(cellFactory);
value3Col.setCellFactory(cellFactory);
totalCol.setCellFactory(cellFactory);
Node controls = makeControls(filteredData);
BorderPane root = new BorderPane(table, controls, null, null, null);
Scene scene = new Scene(root, 800, 600);
scene.getStylesheets().add(getClass().getResource("highlight-matching-cells.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
private ObservableList<Item> createData() {
ObservableList<Item> data = FXCollections.observableArrayList();
final Random rng = new Random();
for (int i=1; i<=100; i++) {
int value1 = rng.nextInt(10)+1;
int value2 = rng.nextInt(10)+1;
int value3 = rng.nextInt(10)+1;
String name = "Item "+i;
data.add(new Item(name, value1, value2, value3));
}
return data;
}
private HBox makeControls(FilteredList<Item> filteredData) {
TextField filterTextField = new TextField("30");
CheckBox filterCheckBox = new CheckBox("Filter");
Button updateFilterButton = new Button("Update Filter");
updateFilterButton.setOnAction(event -> {
if (filterCheckBox.isSelected()) {
int maxTotal = 30 ;
try {
maxTotal = Integer.parseInt(filterTextField.getText());
} catch (NumberFormatException exc) {
filterTextField.setText("30");
}
int max = maxTotal ;
filteredData.setPredicate(item -> item.getTotal() <= max);
} else {
filteredData.setPredicate(item -> true) ;
}
});
HBox controls = new HBox(5, filterTextField, filterCheckBox, updateFilterButton);
controls.setPadding(new Insets(5));
controls.setAlignment(Pos.CENTER);
return controls;
}
private <T> TableColumn<Item, T> makeCol(Class<T> type, String title, Function<Item, ObservableValue<T>> valueMapper) {
TableColumn<Item, T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> valueMapper.apply(cellData.getValue()));
return col ;
}
public static class MatchingAdjacentCell extends TableCell<Item, Number> {
private final ObjectProperty<ObservableValue<Number>> previousItem = new SimpleObjectProperty<>(); ;
private final ObjectProperty<ObservableValue<Number>> nextItem = new SimpleObjectProperty<>();
public MatchingAdjacentCell() {
itemProperty().addListener((obs, oldValue, newValue) -> updateCell());
indexProperty().addListener((obs, oldIndex, newIndex) -> updateCell());
// update when adjacent cells change value:
ChangeListener<Number> adjacentItemValueUpdateListener = (obs, oldValue, newValue) -> updateCell();
// register/de-register this listener when adjacent cells change:
ChangeListener<ObservableValue<Number>> adjacentItemListener = (obs, oldObservable, newObservable) -> {
if (oldObservable != null) {
oldObservable.removeListener(adjacentItemValueUpdateListener);
}
if (newObservable != null) {
newObservable.addListener(adjacentItemValueUpdateListener);
}
};
previousItem.addListener(adjacentItemListener);
nextItem.addListener(adjacentItemListener);
}
private void updateCell() {
final PseudoClass highlightClass = PseudoClass.getPseudoClass("highlight");
int index = getIndex();
if (index < 0 || index >= getTableView().getItems().size()) {
pseudoClassStateChanged(highlightClass, false);
setText(null);
previousItem.set(null);
nextItem.set(null);
} else {
boolean highlight = false ;
if (getItem() == null) {
setText("");
} else {
int cellValue = getItem().intValue();
setText(Integer.toString(cellValue));
if (index > 0) {
ObservableValue<Number> previous = getTableColumn().getCellObservableValue(index - 1);
previousItem.set(previous);
int previousCellValue = previous.getValue().intValue();
if (previousCellValue == cellValue) {
highlight = true ;
}
}
if (index < getTableView().getItems().size()-1 && (! highlight)) {
ObservableValue<Number> next = getTableColumn().getCellObservableValue(index + 1);
nextItem.set(next);
int nextCellValue = next.getValue().intValue();
if (nextCellValue == cellValue) {
highlight = true ;
}
}
pseudoClassStateChanged(highlightClass, highlight);
}
}
}
}
public static class Item implements Comparable<Item> {
private final StringProperty name = new SimpleStringProperty(this, "name", "");
private final IntegerProperty value1 = new SimpleIntegerProperty(this, "value1", 0);
private final IntegerProperty value2 = new SimpleIntegerProperty(this, "value1", 0);
private final IntegerProperty value3 = new SimpleIntegerProperty(this, "value1", 0);
private final ReadOnlyIntegerWrapper total = new ReadOnlyIntegerWrapper(this, "total", 0);
public final StringProperty nameProperty() {
return this.name;
}
public final java.lang.String getName() {
return this.nameProperty().get();
}
public final void setName(final java.lang.String name) {
this.nameProperty().set(name);
}
public final IntegerProperty value1Property() {
return this.value1;
}
public final int getValue1() {
return this.value1Property().get();
}
public final void setValue1(final int value1) {
this.value1Property().set(value1);
}
public final IntegerProperty value2Property() {
return this.value2;
}
public final int getValue2() {
return this.value2Property().get();
}
public final void setValue2(final int value2) {
this.value2Property().set(value2);
}
public final IntegerProperty value3Property() {
return this.value3;
}
public final int getValue3() {
return this.value3Property().get();
}
public final void setValue3(final int value3) {
this.value3Property().set(value3);
}
public final javafx.beans.property.ReadOnlyIntegerProperty totalProperty() {
return this.total.getReadOnlyProperty();
}
public final int getTotal() {
return this.totalProperty().get();
}
public Item(String name, int value1, int value2, int value3) {
total.bind(Bindings.createIntegerBinding(() -> getValue1() + getValue2() + getValue3(),
this.value1, this.value2, this.value3));
this.setName(name);
this.setValue1(value1);
this.setValue2(value2);
this.setValue3(value3);
}
@Override
public int compareTo(Item o) {
return Integer.compare(getTotal(), o.getTotal());
}
}
public static void main(String[] args) {
launch(args);
}
}