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中的索引匹配。这是一个合理的假设,还是我真的需要在每个单元格上放置一个侦听器?