javafx:绑定未按预期工作

javafx:绑定未按预期工作,java,javafx,Java,Javafx,我试图得到一个属性total,它是通过将两个属性相乘得到的,即currentPrice和volumeHeld,其中currentPrice实际上是通过每10秒下载一次谷歌金融股票价格获得的。它每10秒自动更新一次 现在,getCurrentPrice()被初始化为0,如代码所示。10秒后,它拾取一个新值,所有这些都正常工作 但是在下面的绑定方法中,total不会在currentPrice属性更改时自动更新 totalBinding = Bindings.createDoubleBinding((

我试图得到一个属性
total
,它是通过将两个属性相乘得到的,即
currentPrice
volumeHeld
,其中
currentPrice
实际上是通过每10秒下载一次谷歌金融股票价格获得的。它每10秒自动更新一次

现在,
getCurrentPrice()
被初始化为
0
,如代码所示。10秒后,它拾取一个新值,所有这些都正常工作

但是在下面的绑定方法中,
total
不会在
currentPrice
属性更改时自动更新

totalBinding = Bindings.createDoubleBinding(() -> {
        System.out.println("current price: " + getCurrentPrice() + "vol held: " + getVolumeHeld());
        return getCurrentPrice() * getVolumeHeld();
    });

   total.bind(totalBinding);
问题:我发现在上面的
createDoubleBinding
语句中,
getCurrentPrice()
的值为0(如上所述),当其值更改时,更改不会传播到
total
属性中。我的意思是,即使当前价格已更改,
total
属性也无法从
getCurrentPrice()
中提取新值

totalBinding = Bindings.createDoubleBinding(() -> {
        System.out.println("current price: " + getCurrentPrice() + "vol held: " + getVolumeHeld());
        return getCurrentPrice() * getVolumeHeld();
    });

   total.bind(totalBinding);
因此,问题有两个方面,但我猜下面两个问题的解决方案即使不完全相同,也会相似:

  • 如何解决上述问题

  • 稍后,我将把这个
    total
    属性绑定到另一个属性,以计算出所有
    Trade
    对象的
    total
    属性的总数。这失败得很惨,它总是等于0此方法在不同的类中编写,即不在交易类中。

  • 更新:

    代码如下所示:

    class SummaryofTrade{
        ...     
        sumOfTotals = new ReadOnlyDoubleWrapper();
        sumOfTotalsBinding = Bindings.createDoubleBinding(() -> {
            double sum = 0;
            for(Trade t : this.observableListOfTrades){
                sum += t.getTotal();
            }
            return sum;         
        }, total);     // I cannot put "total" as a second parameter, as it is a property that resides in the Trade class , not this class.
        sumOfTotals.bind(sumOfTotalsBinding);
        ...
    }
    
    错误日志消息:

    Caused by: java.lang.Error: Unresolved compilation problem: 
        total cannot be resolved to a variable
    
    Caused by: java.lang.Error: Unresolved compilation problem: 
        The method totalProperty() is undefined for the type SummaryOfTrade
    
    请注意,
    sumOfTotalsBinding
    sumOfTotals
    位于另一个类中

    交易对象代码如下:

    class Trade{
          ...
          private final ReadOnlyDoubleWrapper total;
          private final ReadOnlyDoubleWrapper currentPrice;
          private DoubleProperty volumeHeld;
          public DoubleBinding totalBinding;
    
    
    
          private final ScheduledService<Number> priceService = new ScheduledService<Number>() {
            @Override
            public Task<Number> createTask(){
                return new Task<Number>() {
                    @Override
                    public Number call() throws InterruptedException, IOException {
                        return getCurrentPriceFromGoogle();
                    }
                };
            }
           };
    
        public Trade(){
           ...
           priceService.setPeriod(Duration.seconds(10));
            priceService.setOnFailed(e -> priceService.getException().printStackTrace());
            this.currentPrice   = new ReadOnlyDoubleWrapper(0);
            this.currentPrice.bind(priceService.lastValueProperty());
            startMonitoring();
            this.total          = new ReadOnlyDoubleWrapper();
            DoubleBinding totalBinding = Bindings.createDoubleBinding(() ->
              getCurrentPrice() * getVolumeHeld(),
              currentPriceProperty(), volumeHeldProperty());                
           total.bind(totalBinding);
         }
    
    
            // volume held
        public double getVolumeHeld(){
            return this.volumeHeld.get();
        }
    
        public DoubleProperty volumeHeldProperty(){
            return this.volumeHeld;
        }
    
        public void setVolumeHeld(double volumeHeld){
            this.volumeHeld.set(volumeHeld);
        }
    
            // multi-threading
        public final void startMonitoring() {
             priceService.restart();
        }
    
        public final void stopMonitoring() {
            priceService.cancel();
        }
    
            public ReadOnlyDoubleProperty currentPriceProperty(){
             return this.currentPrice.getReadOnlyProperty();
        }
    
        public final double getCurrentPrice(){
            return currentPriceProperty().get();
        }
    
            // total
        public final Double getTotal(){
            return totalProperty().getValue();
        }
    
        public ReadOnlyDoubleProperty totalProperty(){
            return this.total;
        }
    }
    
    这就是问题所在。Eclipse表示它无法识别交易对象的属性,
    totalProperty
    。错误消息如下所示

    错误日志消息:

    Caused by: java.lang.Error: Unresolved compilation problem: 
        total cannot be resolved to a variable
    
    Caused by: java.lang.Error: Unresolved compilation problem: 
        The method totalProperty() is undefined for the type SummaryOfTrade
    

    我已经指定了属性依赖项,但是Eclipse抛出了一个错误。如何解决此问题?

    由于当前价格和持有量都是属性,您可以直接绑定它们:

    total.bind(currentPriceProperty().multiply(volumeHeldProperty()));
    
    如果您确实需要使用自定义双重绑定,则首先需要提供依赖项,以便在依赖项失效后根据以下条件执行计算:

    绑定
    提供的以下帮助函数也应该可以工作:

    DoubleBinding totalBinding = Bindings.createDoubleBinding(() ->
            currentPrice.get() * volumeHeld.get(),
            currentPrice, volumeHeld);
    

    由于当前价格和持有量都是属性,您可以直接绑定它们:

    total.bind(currentPriceProperty().multiply(volumeHeldProperty()));
    
    如果您确实需要使用自定义双重绑定,则首先需要提供依赖项,以便在依赖项失效后根据以下条件执行计算:

    绑定
    提供的以下帮助函数也应该可以工作:

    DoubleBinding totalBinding = Bindings.createDoubleBinding(() ->
            currentPrice.get() * volumeHeld.get(),
            currentPrice, volumeHeld);
    

    您有一个
    可观察列表
    ,其中每个
    交易
    对象都有一个可观察的
    totalProperty()。当列表的内容更改时,或者当属于任何元素的任何单个
    totalProperty()
    s更改时,需要更新您的
    sumOfTotals

    您可以手动执行此操作:

    DoubleBinding sumOfTotalsBinding = new DoubleBinding() {
    
        {
            bind(observableListOfTrades);
            observableListOfTrades.forEach(trade -> bind(trade.totalProperty());
            observableListOfTrades.addListener((Change<? extends Trade> change) -> {
                while (change.next()) {
                    if (change.wasAdded()) {
                        change.getAddedSubList().forEach(trade -> bind(trade.totalProperty()));
                    }
                    if (change.wasRemoved()) {
                        change.getRemoved().forEach(trade -> unbind(trade.totalProperty()));
                    }
                }
            });
        }
    
        @Override
        public double computeValue() {
            return observableListOfTrades.stream().collect(Collectors.summingDouble(Trade::getTotal));
        }
    };
    
    由于现在仅绑定到
    可观察到的斯托夫特拉德
    ,当任何单个总数发生变化时,将导致重新计算

    这是一个SSCCE:

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Random;
    import java.util.function.Function;
    import java.util.stream.Collectors;
    
    import javafx.application.Application;
    import javafx.beans.Observable;
    import javafx.beans.binding.Bindings;
    import javafx.beans.binding.DoubleBinding;
    import javafx.beans.property.DoubleProperty;
    import javafx.beans.property.IntegerProperty;
    import javafx.beans.property.ReadOnlyDoubleProperty;
    import javafx.beans.property.ReadOnlyDoubleWrapper;
    import javafx.beans.property.ReadOnlyStringWrapper;
    import javafx.beans.property.SimpleDoubleProperty;
    import javafx.beans.property.SimpleIntegerProperty;
    import javafx.beans.value.ObservableValue;
    import javafx.collections.FXCollections;
    import javafx.collections.ObservableList;
    import javafx.geometry.HPos;
    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.ColumnConstraints;
    import javafx.scene.layout.GridPane;
    import javafx.scene.layout.HBox;
    import javafx.scene.layout.Priority;
    import javafx.stage.Stage;
    import javafx.util.converter.DoubleStringConverter;
    import javafx.util.converter.IntegerStringConverter;
    
    public class TradeTableExample extends Application {
    
        @Override
        public void start(Stage primaryStage) {
            TableView<Trade> table = new TableView<>();
            table.setEditable(true);
            TableColumn<Trade, String> nameCol = column("Name", trade -> new ReadOnlyStringWrapper(trade.getName()));
            TableColumn<Trade, Integer> volumeCol = column("Volume", t -> t.volumeProperty().asObject());
            TableColumn<Trade, Double> priceCol = column("Price", t -> t.priceProperty().asObject());
            TableColumn<Trade, Number> totalCol = column("Total", Trade::totalProperty);
    
            volumeCol.setCellFactory(TextFieldTableCell.forTableColumn(new IntegerStringConverter()));
            priceCol.setCellFactory(TextFieldTableCell.forTableColumn(new DoubleStringConverter()));
    
            table.getColumns().addAll(Arrays.asList(nameCol, volumeCol, priceCol, totalCol));
    
            ObservableList<Trade> data = FXCollections.observableArrayList(trade -> new Observable[] {trade.totalProperty()});
    
            DoubleBinding grandTotal = Bindings.createDoubleBinding(() -> 
                data.stream().collect(Collectors.summingDouble(Trade::getTotal)),
                data);
    
            data.addAll(createData());
            table.setItems(data);
    
            Label totalLabel = new Label();
            totalLabel.textProperty().bind(grandTotal.asString("Total: %,.2f"));
    
            TextField nameField = new TextField();
            TextField volumeField = new TextField("0");
            TextField priceField = new TextField("0.00");
    
            Button add = new Button("Add");
            add.setOnAction(e -> {
                data.add(
                    new Trade(nameField.getText(), 
                            Integer.parseInt(volumeField.getText()), 
                            Double.parseDouble(priceField.getText())));
                nameField.setText("");
                volumeField.setText("0");
                priceField.setText("0.00");
            });
    
            Button delete = new Button("Delete");
            delete.setOnAction(e -> data.remove(table.getSelectionModel().getSelectedIndex()));
            delete.disableProperty().bind(table.getSelectionModel().selectedItemProperty().isNull());
    
            HBox buttons = new HBox(5, add, delete);
    
            GridPane controls = new GridPane();
            controls.addRow(0, new Label("Name:"), nameField);
            controls.addRow(1, new Label("Volume:"), volumeField);
            controls.addRow(2, new Label("Price:"), priceField);
            controls.add(buttons, 0, 3, 2, 1);
            controls.add(totalLabel, 0, 4, 2, 1);
    
            ColumnConstraints leftCol = new ColumnConstraints();
            leftCol.setHalignment(HPos.RIGHT);
            ColumnConstraints rightCol = new ColumnConstraints();
            rightCol.setHgrow(Priority.ALWAYS);
    
            controls.getColumnConstraints().addAll(leftCol, rightCol);
    
            GridPane.setHalignment(controls, HPos.LEFT);
            GridPane.setHalignment(totalLabel, HPos.LEFT);
    
            controls.setHgap(5);
            controls.setVgap(5);
    
            BorderPane root = new BorderPane(table, null, null, controls, null);
            Scene scene = new Scene(root, 600, 600);
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        private List<Trade> createData() {
            Random rng = new Random();
            List<Trade> trades = new ArrayList<>();
            for (int i=0; i<10; i++) {
                StringBuilder name = new StringBuilder();
                for (int c = 0; c < 3; c++) {
                    name.append(Character.toString((char)(rng.nextInt(26)+'A')));
                }
                double price = rng.nextInt(100000)/100.0 ;
                int volume = rng.nextInt(10000);
                trades.add(new Trade(name.toString(), volume, price));
            }
            return trades ;
        }
    
        private <S,T> TableColumn<S,T> column(String text, Function<S, ObservableValue<T>> property) {
            TableColumn<S,T> col = new TableColumn<>(text);
            col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
            return col ;
        }
    
        public static class Trade {
            private final String name ;
            private final IntegerProperty volume = new SimpleIntegerProperty();
            private final DoubleProperty price = new SimpleDoubleProperty();
            private final ReadOnlyDoubleWrapper total = new ReadOnlyDoubleWrapper();
    
            public Trade(String name, int volume, double price) {
                this.name = name ;
                setPrice(price);
                setVolume(volume);
                total.bind(priceProperty().multiply(volumeProperty()));
            }
    
            public final String getName() {
                return name ;
            }
    
            public final IntegerProperty volumeProperty() {
                return this.volume;
            }
    
            public final int getVolume() {
                return this.volumeProperty().get();
            }
    
            public final void setVolume(final int volume) {
                this.volumeProperty().set(volume);
            }
    
            public final DoubleProperty priceProperty() {
                return this.price;
            }
    
            public final double getPrice() {
                return this.priceProperty().get();
            }
    
            public final void setPrice(final double price) {
                this.priceProperty().set(price);
            }
    
            public final ReadOnlyDoubleProperty totalProperty() {
                return this.total.getReadOnlyProperty();
            }
    
            public final double getTotal() {
                return this.totalProperty().get();
            }
    
    
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    
    import java.util.ArrayList;
    导入java.util.array;
    导入java.util.List;
    导入java.util.Random;
    导入java.util.function.function;
    导入java.util.stream.collector;
    导入javafx.application.application;
    导入javafx.beans.Observable;
    导入javafx.beans.binding.Bindings;
    导入javafx.beans.binding.DoubleBinding;
    导入javafx.beans.property.DoubleProperty;
    导入javafx.beans.property.IntegerProperty;
    导入javafx.beans.property.ReadOnlyDoubleProperty;
    导入javafx.beans.property.ReadOnlyDoubleWrapper;
    导入javafx.beans.property.ReadOnlyStringWrapper;
    导入javafx.beans.property.SimpleDoubleProperty;
    导入javafx.beans.property.SimpleIntegerProperty;
    导入javafx.beans.value.observeValue;
    导入javafx.collections.FXCollections;
    导入javafx.collections.ObservableList;
    导入javafx.geometry.HPos;
    导入javafx.scene.scene;
    导入javafx.scene.control.Button;
    导入javafx.scene.control.Label;
    导入javafx.scene.control.TableColumn;
    导入javafx.scene.control.TableView;
    导入javafx.scene.control.TextField;
    导入javafx.scene.control.cell.TextFieldTableCell;
    导入javafx.scene.layout.BorderPane;
    导入javafx.scene.layout.ColumnConstraints;
    导入javafx.scene.layout.GridPane;
    导入javafx.scene.layout.HBox;
    导入javafx.scene.layout.Priority;
    导入javafx.stage.stage;
    导入javafx.util.converter.DoubleStringConverter;
    导入javafx.util.converter.IntegerStringConverter;
    公共类TradeTableExample扩展了应用程序{
    @凌驾
    公共无效开始(阶段primaryStage){
    TableView table=新TableView();
    table.setEditable(true);
    TableColumn nameCol=column(“Name”,trade->new ReadOnlyStringWrapper(trade.getName());
    TableColumn volumeCol=列(“卷”,t->t.volumeProperty().asObject());
    TableColumn priceCol=column(“Price”,t->t.priceProperty().asObject());
    TableColumn totalCol=列(“总计”,贸易::totalProperty);
    volumeCol.setCellFactory(TextFieldTableCell.forTableColumn(新的IntegerStringConverter());
    priceCol.setCellFactory(TextFieldTableCell.forTableColumn(新的DoubleStringConverter());
    table.getColumns().addAll(Arrays.asList(nameCol、volumeCol、priceCol、totalCol));
    ObservableList data=FXCollections.observableArrayList(交易->新可观察[]{trade.totalProperty()});
    DoubleBinding grandTotal=Bindings.createDoubleBinding(()->
    data.stream().collect(collector.summingDouble(Trade::get