2015-09-12 78 views
0

我想有这是由两个属性,即currentPricevolumeHeld,其中currentPrice实际上是通过下载谷歌资助的股票价格,每10秒获得相乘,获得的属性total。它每10秒自动更新一次。JavaFX的:绑定工作不正常

现在getCurrentPrice()被初始化为0,如代码所示。 10秒钟后,它提供了一个新的价值,这一切工作正常。

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

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类中。

UPDATE:如下图所示

代码:

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

UPDATE 2015年9月15日:

我想阐述在这里更合理的方式我的问题。让我知道这是否没有意义。谢谢。

首先,在上述Trade class(请注意上面的代码已被更新并指定的属性依赖),每个交易对象包含一个total属性,这是currentPriceVolumeHeld产物。如果用户手动编辑当前价格和持有量的价值。 total属性将自动更新。

现在,我有一个Trade对象的ObservableList,它们每个都有一个total属性。我的目标是总结可观察列表中每个Trade对象的total属性,并将总和绑定到名为sumOfTotals的变量。这是在一个名为SummaryOfTrade的课程中完成的。每当可观察列表中任何一个交易的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是说,它不承认贸易对象的属性,totalProperty。错误消息如下所示。

错误日志消息:

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

我已经指定的属性依赖已经又Eclipse是抛出一个错误。我应该如何解决这个问题?

+0

为什么从@AlmasB回答不回答这个问题,这不是完全清楚(尤其是哪一类的属性定义中并没有区别在所有你如何编写绑定)。绑定的值保持为零,因为您没有在绑定中指定依赖关系。如果你在答案中解决它,它现在应该可以工作。也许你可以用[MCVE]更新来显示剩下的问题是什么? –

+0

从上面,我确实在'createDoubleBinding'语句中指定了第二个参数'total',即绑定中的依赖关系,但eclipse不能识别它。错误在于'total property'不在'SummaryOfTrade'类中,所以我不能引用它。如果这仍然令人困惑,我会在今晚重新更新它。 – mynameisJEFF

+0

@James_D请参阅更新,让我知道它是否没有意义。谢谢 – mynameisJEFF

回答

1

你有一个ObservableList<Trade>,其中每个Trade对象有一个可观察的totalProperty()。当该列表的内容发生变化或属于任何元素的任何个人发生变化时,您的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)); 
    } 
}; 

或者,您可以用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); 
    } 
} 
+0

您提出的第一种方法有效。不知道为什么你提出的第二种方法不起作用,因为我设法编译和运行代码,但是'sumOfTotals'总是返回0. – mynameisJEFF

+0

对我来说工作正常。查看更新。 –

+0

有趣。我会发现自己的错误在哪里。谢谢。 – mynameisJEFF

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); 
+0

所以'currentPrice.get()'的作品,但我写的'getCurrentPrice()'实例方法不? – mynameisJEFF

+0

我已经添加了关于我的问题第二部分的详细信息,并解释了它如何失败。 – mynameisJEFF

+0

请注意,在使用'Bindings.createDoubleBinding(...)'的答案中,为该方法提供了其他参数,指示需要遵守的属性。 (绑定需要知道要观察什么才能知道它何时需要更新自己。) –