Java 在分类列表传递到tableview之前对其进行操作?
我有一个表视图,它以通常的方式由排序和筛选列表支持:Java 在分类列表传递到tableview之前对其进行操作?,java,javafx-8,Java,Javafx 8,我有一个表视图,它以通常的方式由排序和筛选列表支持: FilteredList<Filedata> filteredData = new FilteredList<>(eeModel.data, p -> true); SortedList<Filedata> sortedList = new SortedList<>(filteredData); table.setItems(sortedList); sortedList.comparat
FilteredList<Filedata> filteredData = new FilteredList<>(eeModel.data, p -> true);
SortedList<Filedata> sortedList = new SortedList<>(filteredData);
table.setItems(sortedList);
sortedList.comparatorProperty().bind(table.comparatorProperty());
我需要能够设置单元格项目的背景颜色,以匹配其邻居。我已经尝试过使用适当的表格单元工厂方法来实现这一点,虽然它可以很好地工作,但当表格滚动时,它会崩溃,因为单元格工厂是以不可预测的顺序调用的
我猜最好的方法可能是每次使用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("");
}
}
}
};
});
其中RowColor是一个简单的静态类,它记录当前使用的颜色并根据请求进行切换。很明显,这是命令依赖。但即使我排除了这一点,我想我仍然可以在没有全局概览的情况下,将不匹配的单元格绘制成相同的颜色
更新-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;
}
}
}
};
});
正如你提到的,这里的问题是事物被调用的顺序。您可能还必须处理索引发生更改的可能性,但该项可能不太可能,但肯定是可能的。在这种情况下,您的代码根本不会被调用 这是一个更一般原则的示例,即在本例中对TableCell子类化会破坏封装,因为您需要了解有关超类在本例中的实现的详细信息,不管索引是在项之前更新的,还是在项之前更新的,反之亦然 更新 这比我最初想的要难一点。当然,当一个相邻单元格中的值发生变化时,该单元格也需要更新。由于与当前单元格相邻的单元格可能会通过过滤、排序,甚至只是滚动来更改,因此发生这种情况时,需要将这些值的侦听器移动到正确的项目 因此,我在这里采用的方法是使用TableCell的一个相当小的子类,它用它的项和索引属性注册一个侦听器。它还将对象属性中每个相邻单元格的项属性打包,并在索引更改时更新这些属性。我认为这就行了;我相信这个方法是正确的,但你可能需要仔细考虑,确保你涵盖了所有的可能性 我只是对单元格颜色使用了一个css伪类,这比操纵css类要快,比操纵内联样式要快得多。css文件只是 highlight-matching-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);
}
}
使用细胞工厂可能是合适的方法。您只需对其进行编码,而无需对单元格的创建和更新时间进行任何假设。你能发布一个你尝试过的简化版本吗?不要把逻辑放在单元格中——这和Swing的渲染器一样错误。取而代之的是,有一个外部控制器,根据需要进行自我更新,并让单元格检查到controller@James\u dt。我建议放入单元格的唯一逻辑是根据显示的项目的状态确定样式。请参见下面的示例代码:单元格只观察项的状态,并相应地更改其样式伪类状态。也就是说,视图正在观察模型。我认为这不会很有效,因为彼此相邻的重复值将以相同的颜色绘制。我认为,你关于破坏封装的评论是至关重要的。我不确定在TableCell级别是否可以轻松解决冲突的减少。我正在考虑在SortedList上放置一个ListChangeListener来运行该列表,并构建一个重复列表,TableCell工厂可以选择并应用该列表。续。。。。。。我有一个暂停,虽然索引可能会更改,但项目不会更改。我不知道这是什么意思。这是否意味着我会发现将表格单元格中的项目与我在SortedList中处理的项目进行匹配不是一件小事?如果一个项目插入表格列表,即数据中;如果您的筛选器发生更改,例如,插入后项目的索引发生更改,则可能会发生这种情况。TableView的一个可能实现是为这些项目保留相同的单元格,并在正确的位置插入新的或未使用的单元格。在这种情况下,它下面的单元格将保留相同的项,但会更改它们的索引。我不认为它目前是这样实现的,但在未来它可能是这样。但我不认为这会让在列表中找到该项变得困难。我已经在包含ListChangeListener的原始帖子中添加了一些原型代码。它似乎工作正常,但取决于项目的TableRow索引与SortedList中的索引匹配。这是一个合理的假设,还是我真的需要在每个单元格上放置一个侦听器?