2017-05-31 39 views
2

我有一個觀察的名單包含人數:JavaFX的:用SUM函數分組表視圖

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中做到這一點?

+0

那麼不要允許'ObservableList'上的重複,基於'Name'。所以如果兩個'人'具有相同的名字,那麼哪一個會出現?只有一個會出現'number =所有人的總數'?如何用項目填充'ObservableList'? – GOXR3PLUS

+2

底層列表可能有多大?特別是,如果底層列表有任何更改,重新計算整個表是否可以接受?或者這會令人望而卻步,在性能方面呢? (另外,這裏TreeTableView會更合適嗎?) –

+0

@ GOXR3PLUS該列表將在應用程序開始時填充。是的,只有一個會出現,數字應該是計數的總和。 – Nibor

回答

3

如果你需要一個解決方案,如果在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); 
     } 





    } 
} 

屏幕截圖的序列:

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

+0

數據(人員)將在開始時加載並不會再次更改。但我希望能夠像這樣過濾列表:(http://code.makery.ch/blog/javafx-8-tableview-sorting-filtering/)。數據應該基於過濾的列表並且在過濾器謂詞改變時更新。 – Nibor

+0

您的解決方案效果很好。現在是這樣的:'sortedPersons.addListener((Change <?extends Person> c) - > uniqueNames.setAll(sortedPersons.stream()。map(Person :: getName).distinct()。collect(Collectors.toList() )));'用setCellValueFactory的整數綁定。是否可以通過簡單的方式向表中添加姓氏並根據姓名和姓氏進行分組/求和? – Nibor

2

是否JavaFX支持Java-8stream 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} 
+2

問題是,如果屬性更改(例如'persons.get(0).setCount(20)'),那麼地圖不會更新。所以(即使你已經過去了你的單元價值工廠將要設置的問題),這實際上並不奏效。 –

+0

Perfecto我正試着用Java-8 Stream現在做半個小時:)。 – GOXR3PLUS

+0

@James_D,我認爲屬性的改變不會反映在表'name.setCellValueFactory(cell - > cell.getValue()。nameProperty());'中。 –