JavaFX:如何绑定两个值? [英] JavaFX: How to bind two values?

查看:32
本文介绍了JavaFX:如何绑定两个值?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是新来的 :)

我有一个关于 JavaFX 绑定的小问题.我创建了作为时钟工作的任务,并返回必须在特殊标签 (label_Time) 中设置的值.此标签显示玩家在测验中的答案还剩多少秒.

问题是如何使用计时器任务自动更改标签中的值?我试图以这种方式将计时器任务()中的值链接到 label_Time 值...

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

...但它不起作用.有什么办法可以做这件事吗?

预先感谢您的回答! :)

<小时>

Controller 类中的初始化方法:

public void initialize(URL url, ResourceBundle rb) {Timer2 定时器 = 新的 Timer2();label_Time.textProperty().bind(timer.getSeconds());新线程(定时器).开始();}

任务类Timer2":

public class Timer2 extends Task{私有静态最终 int SLEEP_TIME = 1000;私有静态整数秒;私有 StringProperty 秒;公共定时器2(){Timer2.sec = 180;this.seconds = new SimpleStringProperty("180");}@Override protected StringProperty call() 抛出异常 {int 迭代;对于(迭代= 0;迭代<1000;迭代++){如果(isCancelled()){updateMessage("取消");休息;}System.out.println("TIK!" + sec);seconds.setValue(String.valueOf(sec));System.out.println("TAK!" + seconds.getValue());//从计数器中减去一秒秒--;//阻塞线程一小段时间,但一定要//检查 InterruptedException 是否取消尝试 {线程睡眠(10);} catch (InterruptedException 中断) {如果(isCancelled()){updateMessage("取消");休息;}}}返回秒数;}公共 StringProperty getSeconds(){返回 this.seconds;}}

解决方案

为什么您的应用无法运行

发生的事情是您在它自己的线程上运行任务,在任务中设置秒属性,然后绑定触发标签文本的即时更新,同时仍在任务线程上.

这违反了 JavaFX 线程处理的.有时使用 Service 来管理多个任务和 Executors 框架管理多个线程.

有一个TaskServiceExecutors 协调方法的例子:通过单个服务在每个任务中创建多个并行任务.

在每个任务中,您可以不调用 runlater、单个 runlater 调用或多个 runlater 调用.

因此有很大的灵活性.

<块引用>

或者我应该创建一个常规任务,该任务仅从其他任务中获取数据并更新 UI?

是的,如果复杂性允许,您可以使用这样的协调任务方法.在屏幕外渲染 300 个图表并将它们保存到文件 中有这种方法的示例.

I'm new guy here :)

I have a small problem which concerns binding in JavaFX. I have created Task which is working as a clock and returns value which has to be set in a special label (label_Time). This label presents how many seconds left for player's answer in quiz.

The problem is how to automatically change value in label using the timer task? I tried to link value from timer Task (seconds) to label_Time value in such a way...

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

...but it doesn't work. Is it any way to do this thing?

Thanks in advance for your answer! :)


Initialize method in Controller class:

public void initialize(URL url, ResourceBundle rb) {

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

Task class "Timer2":

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

}

解决方案

Why your app does not work

What is happening is that you run the task on it's own thread, set the seconds property in the task, then the binding triggers an immediate update of the label text while still on the task thread.

This violates a rule for JavaFX thread processing:

An application must attach nodes to a Scene, and modify nodes that are already attached to a Scene, on the JavaFX Application Thread.

This is the reason that your originally posted program does not work.


How to fix it

To modify your original program so that it will work, wrap the modification of the property in the task inside a Platform.runLater construct:

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

This ensures that when you write out to the property, you are already on the JavaFX application thread, so that when the subsequent change fires for the bound label text, that change will also occur on the JavaFX application thread.


On Property Naming Conventions

It is true that the program does not correspond to JavaFX bean conventions as Matthew points out. Conforming to those conventions is both useful in making the program more readily understandable and also for making use of things like the PropertyValueFactory which reflect on property method names to allow table and list cells to automatically update their values as the underlying property is updated. However, for your example, not following JavaFX bean conventions does not explain why the program does not work.


Alternate Solution

Here is an alternate solution to your countdown binding problem which uses the JavaFX animation framework rather than the concurrency framework. I prefer this because it keeps everything on the JavaFX application thread and you don't need to worry about concurrency issues which are difficult to understand and debug.

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


Update for additional questions on Task execution strategy

Is it possible to run more than one Task which includes a Platform.runLater(new Runnable()) method ?

Yes, you can use multiple tasks. Each task can be of the same type or a different type.

You can create a single thread and run each task on the thread sequentially, or you can create multiple threads and run the tasks in parallel.

For managing multiple tasks, you can create an overseer Task. Sometimes it is appropriate to use a Service for managing the multiple tasks and the Executors framework for managing multiple threads.

There is an example of a Task, Service, Executors co-ordination approach: Creating multiple parallel tasks by a single service In each task.

In each task you can place no runlater call, a single runlater call or multiple runlater calls.

So there is a great deal of flexibility available.

Or maybe I should create one general task which will be only take data from other Tasks and updating a UI?

Yes you can use a co-ordinating task approach like this if complexity warrants it. There is an example of such an approach in in Render 300 charts off screen and save them to files.

这篇关于JavaFX:如何绑定两个值?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆