我想要一个属性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()
获取新值。
所以这个问题是双重的,但我猜我下面两个问题的解决方案是相似的,如果不完全相同的话:
如何解决上述问题?
稍后,我将把这个total
属性绑定到另一个属性,以计算出所有total
个对象的Trade
属性的总和。这很失败,它总是等于0. 这个方法写在不同的类中,即不在Trade类中。
更新
代码如下所示:
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
请注意,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;
}
}
更新2015年9月15日:
我试图在这里以合乎逻辑的方式阐述我的问题。如果这没有意义,请告诉我。感谢。
首先,在上面的Trade class
中(请注意上面的代码已更新并且指定了属性依赖性),每个Trade对象都包含一个total
属性, currentPrice
和VolumeHeld
的乘积。如果用户手动编辑当前价格和持有量的值。 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正在抛出错误。我该如何解决这个问题?
答案 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);
}
}