2013-04-01 32 views
4

我是新來的人:)JavaFX:如何綁定兩個值?

我有一個小問題,它涉及JavaFX中的綁定。我創建了一個Task,它作爲一個時鐘工作,並返回必須在特殊標籤中設置的值(label_Time)。此標籤顯示測驗中玩家答案的剩餘秒數。

問題是如何使用計時器任務自動更改標籤中的值?我想在這樣的方式()到label_Time值鏈接從計時器任務價值...

label_Time.textProperty().bind(timer.getSeconds()); 

...但它不工作。有沒有辦法做這件事?

在此先感謝您的答案! :)


初始化方法控制器類:

public void initialize(URL url, ResourceBundle rb) { 

     Timer2 timer = new Timer2(); 
     label_Time.textProperty().bind(timer.getSeconds()); 
     new Thread(timer).start(); 
} 

Task類 「定時器」:

public class Timer2 extends Task{ 

    private static final int SLEEP_TIME = 1000; 
    private static int sec; 
    private StringProperty seconds; 


    public Timer2(){ 
     Timer2.sec = 180; 
     this.seconds = new SimpleStringProperty("180"); 
    } 

    @Override protected StringProperty call() throws Exception { 


     int iterations; 

     for (iterations = 0; iterations < 1000; iterations++) { 
      if (isCancelled()) { 
       updateMessage("Cancelled"); 
       break; 
      } 

      System.out.println("TIK! " + sec); 
      seconds.setValue(String.valueOf(sec)); 
      System.out.println("TAK! " + seconds.getValue()); 

      // From the counter we subtract one second 
      sec--; 

      //Block the thread for a short time, but be sure 
      //to check the InterruptedException for cancellation 
      try { 
       Thread.sleep(10); 
      } catch (InterruptedException interrupted) { 
       if (isCancelled()) { 
        updateMessage("Cancelled"); 
        break; 
       } 
      } 
     } 
     return seconds; 
    } 

    public StringProperty getSeconds(){ 
     return this.seconds; 
    } 

} 

回答

10

爲什麼你的應用程序無法正常工作

發生的事情是,您在自己的線程上運行任務,在任務中設置seconds屬性,然後該綁定觸發標籤文本的立即更新,同時仍然在任務線程上。

這違反了JavaFX的線程處理rule

應用程序必須將節點連接場景,並修改那些已經連接到一個場景,在JavaFX應用程序線程節點。

這就是您最初發布的程序無法正常工作的原因。


如何修復

要修改原來的計劃,使其工作,包裹性修改的任務Platform.runLater構造內:

Platform.runLater(new Runnable() { 
    @Override public void run() { 
     System.out.println("TIK! " + sec); 
     seconds.setValue(String.valueOf(sec)); 
     System.out.println("TAK! " + seconds.getValue()); 
    } 
    }); 

這確保當您寫出屬性時,您已經在JavaFX應用程序線程中,以便在隨後的更改激發綁定標籤文本時,JavaFX應用程序中也會發生此更改在線程上。


專屬命名約定

該程序不對應的JavaFX豆約定馬太指出,這是事實。遵循這些約定對於使程序更容易理解以及使用像PropertyValueFactory這樣反映屬性方法名稱的東西,以允許表和列表單元格在底層屬性更新時自動更新其值。但是,對於您的示例,不遵循JavaFX bean約定並不能解釋程序無法運行的原因。


替代的解決方案

下面是一個替代的解決方案,以您的倒計時結合問題,它使用了JavaFX animation framework而非concurrency framework。我更喜歡這樣做,因爲它保留了JavaFX應用程序線程上的所有內容,您不必擔心難以理解和調試的併發問題。

countdown

import javafx.animation.*; 
import javafx.application.Application; 
import javafx.beans.*; 
import javafx.beans.binding.Bindings; 
import javafx.beans.property.*; 
import javafx.event.*; 
import javafx.geometry.Pos; 
import javafx.scene.*; 
import javafx.scene.control.*; 
import javafx.scene.layout.VBox; 
import javafx.stage.Stage; 
import javafx.util.Duration; 

public class CountdownTimer extends Application { 
    @Override public void start(final Stage stage) throws Exception { 
    final CountDown  countdown  = new CountDown(10); 
    final CountDownLabel countdownLabel = new CountDownLabel(countdown); 

    final Button   countdownButton = new Button(" Start "); 
    countdownButton.setOnAction(new EventHandler<ActionEvent>() { 
     @Override public void handle(ActionEvent t) { 
     countdownButton.setText("Restart"); 
     countdown.start(); 
     } 
    }); 

    VBox layout = new VBox(10); 
    layout.getChildren().addAll(countdownLabel, countdownButton); 
    layout.setAlignment(Pos.BASELINE_RIGHT); 
    layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 20; -fx-font-size: 20;"); 

    stage.setScene(new Scene(layout)); 
    stage.show(); 
    } 

    public static void main(String[] args) throws Exception { 
    launch(args); 
    } 
} 

class CountDownLabel extends Label { 
    public CountDownLabel(final CountDown countdown) { 
    textProperty().bind(Bindings.format("%3d", countdown.timeLeftProperty())); 
    } 
} 

class CountDown { 
    private final ReadOnlyIntegerWrapper timeLeft; 
    private final ReadOnlyDoubleWrapper timeLeftDouble; 
    private final Timeline    timeline; 

    public ReadOnlyIntegerProperty timeLeftProperty() { 
    return timeLeft.getReadOnlyProperty(); 
    } 

    public CountDown(final int time) { 
    timeLeft  = new ReadOnlyIntegerWrapper(time); 
    timeLeftDouble = new ReadOnlyDoubleWrapper(time); 

    timeline = new Timeline(
     new KeyFrame(
     Duration.ZERO,   
     new KeyValue(timeLeftDouble, time) 
    ), 
     new KeyFrame(
     Duration.seconds(time), 
     new KeyValue(timeLeftDouble, 0) 
    ) 
    ); 

    timeLeftDouble.addListener(new InvalidationListener() { 
     @Override public void invalidated(Observable o) { 
     timeLeft.set((int) Math.ceil(timeLeftDouble.get())); 
     } 
    }); 
    } 

    public void start() { 
    timeline.playFromStart(); 
    } 
} 

更新任務執行戰略

其他問題是否可以運行多個任務,其中包含一個Platform.runLater(new Runnable())方法?

是的,您可以使用多個任務。每個任務可以是相同類型或不同類型。

您可以創建單個線程並按順序在線程上運行每個任務,也可以創建多個線程並以並行方式運行任務。

要管理多個任務,您可以創建一個監督Task。有時可以使用Service來管理多個任務,並使用Executors框架來管理多個線程。

有一個例子TaskServiceExecutors協調方式:Creating multiple parallel tasks by a single service In each task

在每項任務中,您可以不撥打runlater電話,撥打一個runlater電話或撥打多個runlater電話。

所以有很大的靈活性可用。

或者我應該創建一個僅從其他任務獲取數據並更新UI的常規任務?

是的,如果複雜性保證,您可以使用這樣的協調任務方法。在Render 300 charts off screen and save them to files中有這樣一種方法的例子。

+0

非常感謝!你已經啓發了我的想法:D 我有與此相關的其他問題: 是否有可能運行多個包含Platform.runLater(新的Runnable())方法的任務?或者,也許我應該創建一個僅從其他任務獲取數據並更新UI的常規任務? – Wicia

+0

更新的答案與其他問題的信息。可能最好在新問題中提出任何新問題。 – jewelsea

-1

你的「定時器」類不符合的JavaFX豆約定:

public String getSeconds(); 
public void setSeconds(String seconds); 
public StringProperty secondsProperty(); 
+2

雖然generllay是正確的,但這不能回答問題,並且更適合作爲對問題的評論。 –