JavaFx Bindings:有没有办法将值绑定到observable List?

时间:2015-04-22 01:12:36

标签: javafx javafx-8

我有表视图它的内容是包含数字的可观察列表,我有一个文本字段,应该在表中显示这些值的总和是否有任何方法将这些文本字段绑定到数字的总和物业。 注意:用户可以编辑此列表中的值,可以添加更多元素,可以删除一些元素如何使用javafx绑定正确绑定这些数字的总和而不是通过旧时尚方式迭代列表并总结手动编号,每次更改都重复一次。

2 个答案:

答案 0 :(得分:13)

如果(且仅当)您使用update events创建列表,ObservableList将触发extractor。提取器是一个函数,它将列表的每个元素映射到Observables的数组;如果这些Observable中的任何一个更改了它们的值,则列表将触发相应的更新事件并变为无效。

所以这里的两个步骤是:

  1. 使用提取器创建列表
  2. 创建一个绑定,用于在列表失效时计算总计。
  3. 因此,如果您的表有一个模型类,例如:

    public class Item {
    
        private final IntegerProperty value = new SimpleIntegerProperty();
    
        public IntegerProperty valueProperty() {
            return value ;
        }
    
        public final int getValue() {
            return valueProperty().get();
        }
    
        public final void setValue(int value) {
            valueProperty().set(value);
        }
    
        // other properties, etc...
    }
    

    然后使用以下命令创建表:

    TableView<Item> table = new TableView<>();
    table.setItems(FXCollections.observableArrayList(item -> 
        new Observable[] { item.valueProperty() }));
    

    现在您可以使用

    创建绑定
    IntegerBinding total = Bindings.createIntegerBinding(() ->
        table.getItems().stream().collect(Collectors.summingInt(Item::getValue)),
        table.getItems());
    

    实现说明:上面createIntegerBinding的两个参数是一个计算int值的函数,以及要观察的任何值。如果任何观察到的值(此处只有一个,table.getItems())无效,则重新计算该值。请记住,我们创建了table.getItems(),因此如果任何项valueProperty()更改,它将无效。作为第一个参数的函数使用lambda表达式和Java 8 Streams API,它大致相当于

    () -> {
        int totalValue = 0 ;
        for (Item item : table.getItems()) {
            totalValue = totalValue + item.getValue();
        }
        return totalValue ;
    }
    

    最后,如果您希望标签显示总数,您可以执行类似

    的操作
    Label totalLabel = new Label();
    totalLabel.textProperty().bind(Bindings.format("Total: %d", total));
    

    这是一个SSCCE:

    import java.util.function.Function;
    import java.util.stream.Collectors;
    import java.util.stream.IntStream;
    
    import javafx.application.Application;
    import javafx.beans.Observable;
    import javafx.beans.binding.Bindings;
    import javafx.beans.binding.IntegerBinding;
    import javafx.beans.property.IntegerProperty;
    import javafx.beans.property.SimpleIntegerProperty;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;
    import javafx.beans.value.ObservableValue;
    import javafx.collections.FXCollections;
    import javafx.geometry.Pos;
    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.cell.TextFieldTableCell;
    import javafx.scene.layout.BorderPane;
    import javafx.scene.layout.HBox;
    import javafx.scene.layout.Priority;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;
    import javafx.util.StringConverter;
    import javafx.util.converter.DefaultStringConverter;
    import javafx.util.converter.NumberStringConverter;
    
    public class TotallingTableView extends Application {
    
        @Override
        public void start(Stage primaryStage) {
    
            TableView<Item> table = new TableView<>();
            table.setEditable(true);
    
            table.getColumns().add(
                    column("Item", Item::nameProperty, new DefaultStringConverter()));
    
            table.getColumns().add(
                    column("Value", Item::valueProperty, new NumberStringConverter()));
    
            table.setItems(FXCollections.observableArrayList(
                    item -> new Observable[] {item.valueProperty() }));
    
            IntStream.rangeClosed(1, 20)
                .mapToObj(i -> new Item("Item "+i, i))
                .forEach(table.getItems()::add);
    
            IntegerBinding total = Bindings.createIntegerBinding(() -> 
                table.getItems().stream().collect(Collectors.summingInt(Item::getValue)),
                table.getItems());
    
            Label totalLabel = new Label();
            totalLabel.textProperty().bind(Bindings.format("Total: %d", total));
    
            Button add = new Button("Add item");
            add.setOnAction(e -> 
                table.getItems().add(new Item("New Item", table.getItems().size() + 1)));
    
            Button remove = new Button("Remove");
            remove.disableProperty().bind(
                    Bindings.isEmpty(table.getSelectionModel().getSelectedItems()));
    
            remove.setOnAction(e -> 
                table.getItems().remove(table.getSelectionModel().getSelectedItem()));
    
            HBox buttons = new HBox(5, add, remove);
            buttons.setAlignment(Pos.CENTER);
            VBox controls = new VBox(5, totalLabel, buttons);
            VBox.setVgrow(totalLabel, Priority.ALWAYS);
            totalLabel.setMaxWidth(Double.MAX_VALUE);
            totalLabel.setAlignment(Pos.CENTER_RIGHT);
    
            BorderPane root = new BorderPane(table, null, null, controls, null);
            Scene scene = new Scene(root, 800, 600);
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        private <S,T> TableColumn<S,T> column(String title, 
                Function<S, ObservableValue<T>> property, StringConverter<T> converter) {
            TableColumn<S,T> col = new TableColumn<>(title);
            col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
    
            col.setCellFactory(TextFieldTableCell.forTableColumn(converter));
    
            return col ;
        }
    
        public static class Item {
            private final StringProperty name = new SimpleStringProperty();
            private final IntegerProperty value = new SimpleIntegerProperty();
    
            public Item(String name, int value) {
                setName(name);
                setValue(value);
            }
    
            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 valueProperty() {
                return this.value;
            }
    
            public final int getValue() {
                return this.valueProperty().get();
            }
    
            public final void setValue(final int value) {
                this.valueProperty().set(value);
            }
    
    
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    请注意,这不是最有效的可能实现,而是(IMHO)保持代码最干净的实现。如果表中有大量项目,则通过迭代重新计算总数并将它们相加可能会非常昂贵。另一种方法是监听列表中的添加/删除更改。添加项目时,将其值添加到总计中,并使用value属性注册一个侦听器,如果值更改,则更新总计。从列表中删除项目时,从value属性中删除侦听器,并从总计中减去该值。这样可以避免从头开始不断重新计算,但代码更难以破译。

答案 1 :(得分:0)

我只是在ObservableList中添加一个监听器,并在任何时候更改时更新标签。

list.addListener((obs, oldValue, newValue) -> {  
    textfield.setText(list.stream().mapToInt(x -> x.intValue()).sum(); 
});

这样的事情应该有效