如何在tableview

时间:2017-01-07 02:04:25

标签: java javafx

这是我第一次使用JavaFX(希望是最后一次)所以我并不完全理解一切是如何工作的。我会试着总结一下我在哪里

  1. 我正在尝试让我的表突出显示特定列上的重复单元格
  2. 我需要可编辑的单元格而且没有TableCell扩展我遇到过工作,我今天大部分时间都在努力修复它们的错误无济于事。我已经放弃了这种方法。
  3. 我找到了TextFieldTableCell但是这不允许我扩展和覆盖像updateItem这样的函数。在这一点上,我没有兴趣重新实现任何此功能。
  4. 目前我所做的是以下内容:

    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()。add(“duplicate-cell”)或其他东西。我的意思是它毕竟是一张桌子......

2 个答案:

答案 0 :(得分:2)

任何涉及根据单元格的某些状态和其他数据更改表格单元格外观的问题的解决方案总是使用返回单元格的单元工厂相应地更新其外观。

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

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

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);
    }
}

和duplicate-cell-example.css:

.table-cell:duplicate {
    -fx-background-color: -fx-background ;
    -fx-background: red ;
}

答案 1 :(得分:1)

这基本上是James_D的方法,但它改善了从Ω(n²)最坏情况(n =列表大小)到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 ;
    }

}

<强>的style.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的答案中复制的,因为这样做是最好的做法。