【问题标题】:JavaFX: Grouping table view with sum functionJavaFX:使用 sum 函数对表视图进行分组
【发布时间】:2017-11-01 09:53:56
【问题描述】:

我有一个包含人员的可观察列表:

ObservableList<Person> persons = FXCollections.observableArrayList();

还有一类人:

public class Person {
    private final StringProperty name = new SimpleStringProperty();
    private final IntegerProperty count = new SimpleIntegerProperty();
    // ...
}

现在我可以将它们显示在这样的表格中:

TableView<Person> table = new TableView<>(persons);

TableColumn<Person, String> name = new TableColumn<>("Name");
name.setCellValueFactory(cell -> cell.getValue().nameProperty());

TableColumn<Person, Number> count = new TableColumn<>("Count");
count.setCellValueFactory(cell -> cell.getValue().countProperty());

table.getColumns().addAll(name, count);

如果有多个同名的人,会出现多次。

现在我想在表格中只显示每个名字一次并将计数加在一起。

例如,在 SQL 中,它将是按函数分组。

如何在 JavaFX 中做到这一点?

【问题讨论】:

  • 那么不允许在 ObservableList 上重复,基于 Name 。因此,如果两个Person 具有相同的名称,那么会出现哪一个?只有一个与 number=sum numbers of all persons 一起出现?你如何用项目填充person ObservableList
  • 基础列表可能有多大?具体来说,如果基础列表有任何更改,是否可以重新计算整个表?还是在性能方面会令人望而却步? (另外,TreeTableView 在这里更合适吗?)
  • @GOXR3PLUS 该列表将在应用程序开始时填充。是的,只会出现一个,这个数字应该是计数的总和。
  • @James_D 该列表可能非常大(最多 10,000 个条目)。无法更改但已过滤 (code.makery.ch/blog/javafx-8-tableview-sorting-filtering)。
  • Nibor James_D 的答案是正确的。您还可以开发一种仅显示 300-400 个元素的机制,并且用户可以移动到下一页或上一页 :)。我已经为它做了一些东西(每页只显示 200-300 个元素,然后填充 200-300 下一个或 200-300 个上一个),但我需要在发布之前使其更通用。

标签: java javafx java-8 tableview grouping


【解决方案1】:

如果您需要在 Person 实例中的属性发生更改时更新表的解决方案,您可以这样做:

ObservableList<Person> persons = FXCollections
        .observableArrayList(p -> new Observable[] { p.nameProperty(), p.countProperty() });
ObservableList<String> uniqueNames = FXCollections.observableArrayList();

persons.addListener((Change<? extends Person> c) -> uniqueNames
        .setAll(persons.stream().map(Person::getName).distinct().collect(Collectors.toList())));

TableView<String> table = new TableView<>(uniqueNames);
TableColumn<String, String> name = new TableColumn<>("Name");
name.setCellValueFactory(n -> new SimpleStringProperty(n.getValue()));
TableColumn<String, Number> count = new TableColumn<>("Count");
count.setCellValueFactory(n -> Bindings.createIntegerBinding(() -> persons.stream()
        .filter(p -> p.getName().equals(n.getValue())).collect(Collectors.summingInt(Person::getCount)), persons));

此解决方案不是特别有效:如果基础列表中的数据发生更改(包括 Person 实例中的属性更改),所有可见单元格都将重新计算。这对于相当小的列表应该是可行的;但如果您有大量数据,您可能需要以更智能的方式处理列表 (persons.addListener(...)) 中的更改(这可能会相当复杂)。

这是一个 SSCCE。它显示两个表格:一个是包含完整人员列表的常规表格;另一个是上面设置的表格,显示“分组”列表。您可以通过填写文本字段(第二个必须是整数)并按“添加”将项目添加到主列表,或者通过选择一个项目并按删除来删除条目。 “完整表”也是可编辑的,因此您可以测试更改现有值。

import java.util.stream.Collectors;

import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
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.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.converter.IntegerStringConverter;

public class GroupedTable extends Application {

    @Override
    public void start(Stage primaryStage) {
        ObservableList<Person> persons = FXCollections
                .observableArrayList(p -> new Observable[] { p.nameProperty(), p.countProperty() });
        ObservableList<String> uniqueNames = FXCollections.observableArrayList();

        persons.addListener((Change<? extends Person> c) -> uniqueNames
                .setAll(persons.stream().map(Person::getName).distinct().collect(Collectors.toList())));

        TableView<String> table = new TableView<>(uniqueNames);
        TableColumn<String, String> name = new TableColumn<>("Name");
        name.setCellValueFactory(n -> new SimpleStringProperty(n.getValue()));
        TableColumn<String, Number> count = new TableColumn<>("Count");
        count.setCellValueFactory(n -> Bindings.createIntegerBinding(() -> persons.stream()
                .filter(p -> p.getName().equals(n.getValue())).collect(Collectors.summingInt(Person::getCount)), persons));

        table.getColumns().add(name);
        table.getColumns().add(count);

        TableView<Person> fullTable = new TableView<>(persons);
        fullTable.setEditable(true);
        TableColumn<Person, String> allNamesCol = new TableColumn<>("Name");
        TableColumn<Person, Integer> allCountsCol = new TableColumn<>("Count");
        allNamesCol.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
        allNamesCol.setCellFactory(TextFieldTableCell.forTableColumn());
        allCountsCol.setCellValueFactory(cellData -> cellData.getValue().countProperty().asObject());
        allCountsCol.setCellFactory(TextFieldTableCell.forTableColumn(new IntegerStringConverter()));
        fullTable.getColumns().add(allNamesCol);
        fullTable.getColumns().add(allCountsCol);

        TextField nameTF = new TextField();
        TextField countTF = new TextField();
        Button add = new Button("Add");
        add.setOnAction(e -> {
            persons.add(new Person(nameTF.getText(), Integer.parseInt(countTF.getText())));
            nameTF.clear();
            countTF.clear();
        });
        Button delete = new Button("Delete");
        delete.setOnAction(e -> persons.remove(fullTable.getSelectionModel().getSelectedItem()));
        delete.disableProperty().bind(fullTable.getSelectionModel().selectedItemProperty().isNull());

        HBox controls = new HBox(5, new Label("Name:"), nameTF, new Label("Count:"), countTF, add, delete);
        BorderPane root = new BorderPane(new HBox(5, fullTable, table));
        root.setBottom(controls);

        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

    public static class Person {
        private final StringProperty name = new SimpleStringProperty();
        private final IntegerProperty count = new SimpleIntegerProperty();

        public Person(String name, int count) {
            setName(name);
            setCount(count);
        }


        public final StringProperty nameProperty() {
            return this.name;
        }

        public final String getName() {
            return this.nameProperty().get();
        }

        public final void setName(final String name) {
            this.nameProperty().set(name);
        }

        public final IntegerProperty countProperty() {
            return this.count;
        }

        public final int getCount() {
            return this.countProperty().get();
        }

        public final void setCount(final int count) {
            this.countProperty().set(count);
        }





    }
}

屏幕截图顺序:

【讨论】:

  • 数据(人员)将在开始时加载,不会再次更改。但我希望能够像这样过滤列表:(code.makery.ch/blog/javafx-8-tableview-sorting-filtering)。数据应基于过滤后的列表,并在过滤谓词更改时更新。
  • 您的解决方案效果很好。现在是这样的:sortedPersons.addListener((Change&lt;? extends Person&gt; c) -&gt; uniqueNames.setAll(sortedPersons.stream().map(Person::getName).distinct().collect(Collectors.toList()))); 与 setCellValueFactory 的整数绑定。是否可以通过简单的方式将姓氏添加到表中并根据姓名和姓氏进行分组/求和?
【解决方案2】:

JavaFX 是否支持 Java-8 stream API?如果是,您可以使用Collectors.groupingBy

List<Person> persons  = Arrays.asList(
        new Person("a", 10),
        new Person("b", 20),
        new Person("c", 10),
        new Person("a", 10),
        new Person("d", 20),
        new Person("b", 10),
        new Person("e", 10)
);
Map<String, Integer> sum = persons.stream().
        collect(
               Collectors.groupingBy(
                          Person::getName,
                          Collectors.summingInt(Person::getCount)
               )
         );
System.out.println(sum); // {a=20, b=30, c=10, d=20, e=10}

【讨论】:

  • 问题是,如果属性发生变化(例如persons.get(0).setCount(20)),该地图将不会更新。所以(即使你解决了你的单元格值工厂将被设置为什么的问题),这并没有真正起作用。
  • Perfecto 我试图用 Java-8 Stream 做到这一点半小时了 :)。
  • @James_D,我认为属性的变化也不会反映在表 name.setCellValueFactory(cell -&gt; cell.getValue().nameProperty()); 上。
  • @AntonBalaniuc 当然是。这就是StringProperty 的全部意义所在。表格单元格使用这些属性注册一个侦听器。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-16
  • 1970-01-01
相关资源
最近更新 更多