javafx:绑定无法按预期工作

时间:2015-09-12 06:52:17

标签: java javafx

我想要一个属性total,它是通过将两个属性相乘得到的,即currentPricevolumeHeld,其中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()获取新值。

所以这个问题是双重的,但我猜我下面两个问题的解决方案是相似的,如果不完全相同的话:

  1. 如何解决上述问题?

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

  3. 更新

    代码如下所示:

    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
    

    请注意,sumOfTotalsBindingsumOfTotals住在另一个班级。

    以下贸易对象代码:

    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;
        }
    }
    

    更新2015年9月15日:

    我试图在这里以合乎逻辑的方式阐述我的问题。如果这没有意义,请告诉我。感谢。

    首先,在上面的Trade class中(请注意上面的代码已更新并且指定了属性依赖性),每个Trade对象都包含一个total属性, currentPriceVolumeHeld的乘积。如果用户手动编辑当前价格和持有量的值。 total属性将自动更新。

    现在,我有一个ObservableList of Trade对象,每个对象都有一个total属性。我的目标是在可观察列表中总结每个Trade对象的total属性,并将总和绑定到名为sumOfTotals的变量。这是在名为SummaryOfTrade的类中完成的。每当Observable列表中任何一个Trades的total属性发生更改时,sumOfTotals属性也应自动更改。

    class SummaryofTrade{
        ...     
        // within constructor, we have
        sumOfTotals = new ReadOnlyDoubleWrapper();
        sumOfTotalsBinding = Bindings.createDoubleBinding(() -> {
            double sum = 0;
            for(Trade t : this.observableListOfTrades){
                sum += t.getTotal();
            }
            return sum;         
        }, totalProperty());    
        sumOfTotals.bind(sumOfTotalsBinding);
        ...
    }
    

    这就是问题所在.Eclipse表示它无法识别Trade对象的属性totalProperty。错误消息如下所示。

    错误日志消息:

    Caused by: java.lang.Error: Unresolved compilation problem: 
        The method totalProperty() is undefined for the type SummaryOfTrade
    

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

2 个答案:

答案 0 :(得分:5)

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

total.bind(currentPriceProperty().multiply(volumeHeldProperty()));

如果您绝对需要使用自定义双重绑定,则首先需要提供依赖项,以便在依赖项根据documentation失效后执行计算:

DoubleBinding totalBinding = new DoubleBinding() {

     {
         super.bind(currentPrice, volumeHeld);
     }

     @Override
     protected double computeValue() {
         return currentPrice.get() * volumeHeld.get();
     }
 };

Bindings提供的以下帮助函数也应该有效:

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

答案 1 :(得分:2)

您有一个ObservableList<Trade>,其中每个Trade对象都有一个可观察的totalProperty()。当该列表的内容发生变化时,或者属于任何元素的任何单个sumOfTotals发生变化时,您的totalProperty()都需要更新。

您可以手动执行此操作:

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));
    }
};

或者,您可以使用extractor创建列表。当属于元素的任何指定属性发生更改时,这将导致列表触发更新通知(从而将其标记为无效):

ObservableList<Trade> observableListOfTrades = 
    FXCollections.observableArrayList(trade -> new Observable[] { trade.totalProperty() });

然后你就可以了

sumOfTotals.bind(Bindings.createDoubleBinding(() ->
    observableListOfTrades.stream().collect(Collectors.summingDouble(Trade::getTotal)),
    observableListOfTrades);

因为现在只绑定observableListOfTrades会导致任何单个总计更改时重新计算。

这是一个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);
    }
}