2015-09-12 108 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(...)'的答案中,爲該方法提供了其他參數,指示需要遵守的屬性。 (綁定需要知道要觀察什麼才能知道它何時需要更新自己。) –