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

Has*_*war 5 javafx javafx-8

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

Jam*_*s_D 13

一个ObservableList将触发更新事件如果(且仅当)你创建一个带有列表提取.提取器是一个函数,它将列表的每个元素映射到一个Observables数组; 如果其中任何一个Observable更改了它们的值,则列表将触发相应的更新事件并变为无效.

所以这里的两个步骤是:

  1. 使用提取器创建列表
  2. 创建一个绑定,在列表失效时计算总计.

因此,如果您的表具有模型类,例如:

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...
}
Run Code Online (Sandbox Code Playgroud)

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

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

现在您可以创建绑定了

IntegerBinding total = Bindings.createIntegerBinding(() ->
    table.getItems().stream().collect(Collectors.summingInt(Item::getValue)),
    table.getItems());
Run Code Online (Sandbox Code Playgroud)

实现注意事项: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 ;
}
Run Code Online (Sandbox Code Playgroud)

最后,如果你想要一个标签来显示总数,你可以做类似的事情

Label totalLabel = new Label();
totalLabel.textProperty().bind(Bindings.format("Total: %d", total));
Run Code Online (Sandbox Code Playgroud)

这是一个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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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