JavaFX:任务的两个绑定属性

JavaFX:任务的两个绑定属性,java,java-8,javafx-8,Java,Java 8,Javafx 8,我有一个JavaFX8应用程序,希望允许一个任务修改两个不同的UI元素。据我所知,如果要修改单个标签,我可以使用mylabel.textProperty().bind(mytask.messageProperty())绑定到标签,并在任务中使用updateMessage() 如何使用两种不同的任意类型执行此操作?我已经看过concurrency和JavaFX文档中的示例,但对我来说,它们并没有很好地解释这一点 我知道任务本身具有消息(字符串)、进度(双倍/长)、标题(字符串)和值(用户定义)属性

我有一个JavaFX8应用程序,希望允许一个任务修改两个不同的UI元素。据我所知,如果要修改单个标签,我可以使用mylabel.textProperty().bind(mytask.messageProperty())绑定到标签,并在任务中使用updateMessage()

如何使用两种不同的任意类型执行此操作?我已经看过concurrency和JavaFX文档中的示例,但对我来说,它们并没有很好地解释这一点

我知道任务本身具有消息(字符串)、进度(双倍/长)、标题(字符串)和值(用户定义)属性,但如果我想要两个或多个任意类型的属性来控制UI元素,该怎么办?(并希望避免使用runLater()

我可以在任务上创建任意属性吗?我觉得我错过了一些明显的东西。

建议

只有在任务中需要自定义特性的特性样式界面时,才能使用以下解决方案。通常,许多应用程序不需要这样的接口,只需一次调用而不公开自定义属性就足够了

解决方案

您可以使用与用于的相同的习语。我将复制并粘贴相关代码到这个答案中。请注意,此解决方案将“合并更新,这样我们就不会淹没事件队列”,方法是使用。这个解决方案并不违背JavaFX的一般绑定特性,如果使用得太频繁,也不会导致主线程出现大量消息。但是,由于它合并了更新,因此并非对属性的每次更新都会触发属性更改。每次最多只触发一次属性更改

对其他问题的答复


这是来自类任务的源代码

对。这是

所以你是说唯一的方法是用附加属性扩展Task类,就像上面Task中所做的那样

若您希望自定义任务中的自定义属性可以同时修改,那个么是的,您需要对该任务进行子类化。但这与将自定义属性添加到您定义的任何其他类(或扩展另一个现有类以添加属性)没有太大区别。唯一的区别是额外的机制,以确保执行发生在正确的线程上,并在需要时合并

第二个话题,您似乎一开始就说偶尔调用runLater是一种可以接受的方式

是的,这是在任务和JavaFXUI线程之间传递消息的推荐方式(如中所示)


这些属性提供了任务和对象之间的松散耦合,这些对象可能通过一个实例依赖于任务。如果不需要松耦合,那么就不需要特别的属性(尽管它们有时很有用,而且绑定起来也很简单,因为JavaFXAPI的其余部分,例如标签的文本,都是基于属性的)。

实现这一点的方法是使用
Platform.runLater()
。为什么要避免显而易见的解决方案?假设您的意思是使用runLater是显而易见的解决方案,那么使用它似乎违背了JavaFX的一般绑定特性,并且如果使用太频繁,可能会导致主线程收到大量消息。您只需声明一个属性并从UI线程更新它,使用
Platform.runLater()
。如果您的代码可能频繁调用
Platform.runLater()
,那么@jewelsea的答案显示了一种抑制和合并频繁调用的技术。如果可能的话,另一个选项是使
值的类型
属性封装您需要的所有数据,并调用
updateValue()
(实现为在UI线程上不太频繁地运行)。然后只需使用自定义绑定到
valueProperty()
。这是来自类任务的源代码?所以你是说唯一的方法是用附加属性扩展Task类,就像上面Task中所做的那样?第二个话题,您似乎一开始就说偶尔调用runLater是一种可以接受的方式?
private final StringProperty message = new SimpleStringProperty(this, "message", "");
@Override public final String getMessage() { checkThread(); return message.get(); }
@Override public final ReadOnlyStringProperty messageProperty() { checkThread(); return message; }

/**
 * Used to send message updates in a thread-safe manner from the subclass
 * to the FX application thread. AtomicReference is used so as to coalesce
 * updates such that we don't flood the event queue.
 */
private AtomicReference<String> messageUpdate = new AtomicReference<>();

/**
 * Updates the <code>message</code> property. Calls to updateMessage
 * are coalesced and run later on the FX application thread, so calls
 * to updateMessage, even from the FX Application thread, may not
 * necessarily result in immediate updates to this property, and
 * intermediate message values may be coalesced to save on event
 * notifications.
 * <p>
 *     <em>This method is safe to be called from any thread.</em>
 * </p>
 *
 * @param message the new message
 */
protected void updateMessage(String message) {
    if (isFxApplicationThread()) {
        this.message.set(message);
    } else {
        // As with the workDone, it might be that the background thread
        // will update this message quite frequently, and we need
        // to throttle the updates so as not to completely clobber
        // the event dispatching system.
        if (messageUpdate.getAndSet(message) == null) {
            runLater(new Runnable() {
                @Override public void run() {
                    final String message = messageUpdate.getAndSet(null);
                    Task.this.message.set(message);
                }
            });
        }
    }
}

// This method exists for the sake of testing, so I can subclass and override
// this method in the test and not actually use Platform.runLater.
void runLater(Runnable r) {
    Platform.runLater(r);
}

// This method exists for the sake of testing, so I can subclass and override
// this method in the test and not actually use Platform.isFxApplicationThread.
boolean isFxApplicationThread() {
    return Platform.isFxApplicationThread();
}