有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java如何修改tableview上单个单元格的属性

这是我第一次使用JavaFX(希望是最后一次),所以我不太清楚一切是如何工作的。我将简要地总结一下我的情况

  1. 我试图使我的表格突出显示特定列上的重复单元格
  2. 我需要可编辑的单元格,而不是我在工作中遇到的TableCell扩展,我今天大部分时间都在试图修复它们的bug,但毫无效果。我已经放弃了那种方法
  3. 我找到了TextFieldTableCell,但这不允许我扩展和重写像updateItem这样的函数。在这一点上,我对重新实现这些功能没有兴趣

目前我所做的工作如下:

CollectionName.setCellValueFactory(new PropertyValueFactory<>("CollectionName"));
CollectionName.setCellFactory(EditingCell.<Item>forTableColumn(this)); //At the moment this just passes though TextFieldTableCell, the parameter is totally inconsequential
CollectionName.setOnEditCommit((CellEditEvent<Item, String> t) ->
{
    ((Item) t.getTableView().getItems().get(
            t.getTablePosition().getRow())
            ).setCollectionName(t.getNewValue());
    System.out.println("Set on edit commit");
    if(isDuplicateName(t.getNewValue()))
    {
        t.getTableView().getColumns().get(t.getTablePosition().getColumn()).getStyleClass().add("duplicate-cell");
        System.out.println("Duplicate");
    }
    else
    {
        t.getTableView().getColumns().get(t.getTablePosition().getColumn()).getStyleClass().remove("duplicate-cell");
        System.out.println("Not duplicate");
    }
});

这将按预期运行,但会突出显示整个列。我需要它只突出显示特定的单元格。我希望有一种方法可以简单地称之为myTable。getCell(x,y)。getStyleClass()。添加(“复制单元格”)或其他内容。我的意思是它毕竟是一张桌子


共 (2) 个答案

  1. # 1 楼答案

    任何涉及根据单元格项的特定状态和其他数据更改表单元格外观的问题的解决方案都是使用单元格工厂,该工厂返回相应更新其外观的单元格

    您尝试的方法的问题在于忽略了表视图重用单元格的事实。例如,如果表格包含大量数据且用户滚动,则不会创建新的单元格,但滚动到视图外的单元格将被重新用于滚动到视图中的新项目。由于发生这种情况时不更新单元格的样式,滚动会突出显示错误的单元格

    这里的逻辑有点复杂,因为每个单元格基本上都必须观察列中的所有值(无论它们当前是否显示)。我认为这里最简单的解决方案是独立地维护一个ObservableSet,它保存一个重复条目的列表,并让单元格观察到这一点。这是一个实现。您可能可以将其分解为一个单独的类,用于cell factory(或一些方便的东西),以使其更加优雅和可重用

    import java.util.HashSet;
    import java.util.List;
    import java.util.Set;
    import java.util.stream.Collectors;
    
    import javafx.application.Application;
    import javafx.beans.Observable;
    import javafx.beans.binding.Bindings;
    import javafx.beans.binding.BooleanBinding;
    import javafx.beans.property.SimpleIntegerProperty;
    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.css.PseudoClass;
    import javafx.scene.Scene;
    import javafx.scene.control.TableColumn;
    import javafx.scene.control.TableView;
    import javafx.scene.control.cell.TextFieldTableCell;
    import javafx.stage.Stage;
    import javafx.util.StringConverter;
    
    public class HighlightDuplicateTableCells extends Application {
    
        // create an observable list that fires events if the dataProperty of any elements change:
    
        private final ObservableList<Item> items = 
                FXCollections.observableArrayList(item -> new Observable[]{item.dataProperty()});
    
        // collection of strings that are duplicated in the data properties of all the items:
    
        private final ObservableSet<String> duplicateData = FXCollections.observableSet();
    
        private static final PseudoClass DUPLICATE_PC = PseudoClass.getPseudoClass("duplicate");
    
        private final StringConverter<String> identityStringConverter = new StringConverter<String>() {
    
            @Override
            public String toString(String object) {
                return object;
            }
    
            @Override
            public String fromString(String string) {
                return string;
            }
    
        };
    
        @Override
        public void start(Stage primaryStage) {
    
            // listener to maintain collection of duplicates:
            items.addListener((Change<? extends Item> change) -> updateDuplicateData());
    
            TableView<Item> table = new TableView<>();
            table.setEditable(true);
            table.setItems(items);
    
            TableColumn<Item, Number> idColumn = new TableColumn<>("Id");
            idColumn.setCellValueFactory(cellData -> new SimpleIntegerProperty(cellData.getValue().getId()));
    
            TableColumn<Item, String> dataColumn = new TableColumn<>("Data");
            dataColumn.setCellValueFactory(cellData -> cellData.getValue().dataProperty());
    
            dataColumn.setCellFactory(tc -> {
    
                TextFieldTableCell<Item, String> cell = new TextFieldTableCell<Item, String>(identityStringConverter) {
    
                    // boolean binding that indicates if the current item is contained in the duplicateData set:
                    private BooleanBinding duplicate = Bindings.createBooleanBinding(
                            () -> duplicateData.contains(getItem()),
                            duplicateData, itemProperty());
    
                    // anonymous constructor just updates CSS pseudoclass if above binding changes:
                    {
                        duplicate.addListener((obs, wasDuplicate, isNowDuplicate) -> 
                            pseudoClassStateChanged(DUPLICATE_PC, isNowDuplicate));
                    }
                };
    
                return cell ;
            });
    
            table.getColumns().add(idColumn);
            table.getColumns().add(dataColumn);
    
            // note best to minimize changes to items.
            // creating a temp list and using items.setAll(...) achieves this:
    
            List<Item> tmp = new ArrayList<>();
            for (int i = 1 ; i <= 70; i++) {
                char c = (char)('@' + (i % 60));
                String data = Character.toString(c) ;
                tmp.add(new Item(i, data));
            }
    
            items.setAll(tmp);
    
            Scene scene = new Scene(table, 600, 600);
            scene.getStylesheets().add("duplicate-cell-example.css");
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        private void updateDuplicateData() {
    
            // TODO: may not be most efficient implementation
    
            // all data:
            List<String> data = items.stream().map(Item::getData).collect(Collectors.toList());
            // unique data:
            Set<String> uniqueData = new HashSet<>(data);
            // remove unique values from data:
            uniqueData.forEach(data::remove);
            // remaining values are duplicates: replace contents of duplicateData with these:
            duplicateData.clear();
            duplicateData.addAll(data);
        }
    
        public static class Item {
            private final int id ;
            private final StringProperty data = new SimpleStringProperty();
    
            public Item(int id, String data) {
                this.id = id ;
                setData(data);
            }
    
            public final StringProperty dataProperty() {
                return this.data;
            }
    
    
            public final String getData() {
                return this.dataProperty().get();
            }
    
    
            public final void setData(final String data) {
                this.dataProperty().set(data);
            }
    
            public int getId() {
                return id ;
            }
    
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    还有重复单元的例子。css:

    .table-cell:duplicate {
        -fx-background-color: -fx-background ;
        -fx-background: red ;
    }
    
  2. # 2 楼答案

    这基本上是James_D的方法,但它将更新所需的时间从Ω(n²)最坏情况(n=list size)提高到O(m),其中m是更改的数量(1用于属性更新;列表更新时添加/删除的元素数量)

    这种性能是通过将发生次数存储在ObservableMap<String, Integer>中来实现的:

    private final ObservableMap<String, Integer> valueOccuranceCounts = FXCollections.observableHashMap();
    
    private final ChangeListener<String> changeListener = (observable, oldValue, newValue) -> {
        valueOccuranceCounts.computeIfPresent(oldValue, REMOVE_UPDATER);
        valueOccuranceCounts.merge(newValue, 1, ADD_MERGER);
    };
    
    private static final BiFunction<Integer, Integer, Integer> ADD_MERGER = (oldValue, newValue) -> oldValue + 1;
    private static final BiFunction<String, Integer, Integer> REMOVE_UPDATER = (key, value) -> {
        int newCount = value - 1;
        // remove mapping, if the value would become 0
        return newCount == 0 ? null : newCount;
    };
    
    private final ListChangeListener<Item> listChangeListener = (ListChangeListener.Change<? extends Item> c) -> {
        while (c.next()) {
            if (c.wasRemoved()) {
                for (Item r : c.getRemoved()) {
                    // decrease count and remove listener
                    this.valueOccuranceCounts.computeIfPresent(r.getData(), REMOVE_UPDATER);
                    r.dataProperty().removeListener(this.changeListener);
                }
            }
            if (c.wasAdded()) {
                for (Item a : c.getAddedSubList()) {
                    // increase count and add listener
                    this.valueOccuranceCounts.merge(a.getData(), 1, ADD_MERGER);
                    a.dataProperty().addListener(this.changeListener);
                }
            }
        }
    };
    
    private final ObservableList<Item> items;
    
    {
        items = FXCollections.observableArrayList();
        items.addListener(listChangeListener);
    }
    
    private static final PseudoClass DUPLICATE = PseudoClass.getPseudoClass("duplicate");
    private static final String FIRST_COLUMN_CLASS = "first-column";
    
    @Override
    public void start(Stage primaryStage) throws Exception {
        TableView<Item> tableView = new TableView<>(items);
    //    tableView.getSelectionModel().setCellSelectionEnabled(true);
        tableView.setEditable(true);
    
        TableColumn<Item, String> column = new TableColumn<>("data");
        column.setCellValueFactory(cellData -> cellData.getValue().dataProperty());
    
        column.setCellFactory(col -> new TextFieldTableCell<Item, String>() {
    
            // boolean binding that indicates if the current item is contained in the duplicateData set:
            private final BooleanBinding duplicate = Bindings.createBooleanBinding(
                    () -> valueOccuranceCounts.getOrDefault(getItem(), 1) >= 2,
                    valueOccuranceCounts, itemProperty());
    
            // anonymous constructor just updates CSS pseudoclass if above binding changes:
            {
                duplicate.addListener((observable, oldValue, newValue)
                        -> pseudoClassStateChanged(DUPLICATE, newValue));
            }
        });
    
        TableColumn<Item, Number> idColumn = new TableColumn<>("id");
        idColumn.setCellValueFactory(cellData -> new SimpleIntegerProperty(cellData.getValue().getId()));
    
        tableView.getColumns().addAll(idColumn, column);
        tableView.getColumns().addListener((Observable observable) -> {
            // keep style class marking the cells of the column as
            // belonging to the first column up to date 
            if (tableView.getColumns().get(0) == column) {
                if (!column.getStyleClass().contains(FIRST_COLUMN_CLASS)) {
                    column.getStyleClass().add(FIRST_COLUMN_CLASS);
                }
            } else {
                column.getStyleClass().remove(FIRST_COLUMN_CLASS);
            }
        });
    
        // note best to minimize changes to items.
        // creating a temp list and using items.setAll(...) achieves this:
        final int count = 70;
        List<Item> tmp = Arrays.asList(new Item[count]);
        for (int i = 0; i < count; i++) {
            tmp.set(i, new Item(Integer.toString(i % 60)));
        }
    
        items.setAll(tmp);
    
        Scene scene = new Scene(tableView);
        scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static class Item {
    
        private static int counter = 0;
    
        private final StringProperty data;
        private final int id = counter++;
    
        public Item(String data) {
            this.data = new SimpleStringProperty(data);
        }
    
        public final StringProperty dataProperty() {
            return this.data;
        }
    
        public final String getData() {
            return this.dataProperty().get();
        }
    
        public final void setData(final String data) {
            this.dataProperty().set(data);
        }
    
        public int getId() {
            return id ;
        }
    
    }
    

    风格。css

    .table-row-cell:filled .table-cell:duplicate {
        -fx-background: yellow;
        -fx-background-color: -fx-table-cell-border-color, -fx-background;
    }
    
    .table-view:focused .table-row-cell:filled .table-cell:duplicate:focused {
        -fx-background-color: -fx-background, -fx-cell-focus-inner-border, -fx-background;
    }
    
    /* keep use the same background colors normally used for focused table rows */
    .table-view:focused .table-row-cell:filled:focused .table-cell:duplicate {
        -fx-background-color: -fx-background, -fx-cell-focus-inner-border, -fx-background;
        /* frame only at top & bottom sides */
        -fx-background-insets: 0, 1 0 1 0, 2 0 2 0;
    }
    
    .table-view:focused .table-row-cell:filled:focused .table-cell.first-column:duplicate {
        /* frame only for top, left and bottom sides*/
        -fx-background-insets: 0, 1 0 1 1, 2 0 2 2;
    }
    
    .table-row-cell:filled .table-cell:duplicate:selected,
    .table-row-cell:filled:selected .table-cell:duplicate {
        -fx-background: turquoise;
    }
    

    请注意,有些部分(创建和填充表格,创建列)是从@James_D的答案中复制的,因为这样做只是最佳实践