JavaFX属性在GUI视图范围之外的可用性

JavaFX属性在GUI视图范围之外的可用性,javafx,Javafx,在使用JavaFX(Java8)一段时间后,我发现了非常有用的概念,允许使用bean兼容的变量绑定到使用计算树的更改上进行更新,例如: class Person { StringProperty name; ... } 允许将GUI控件绑定到“模型”,以便在更改时自动更新 所以我也开始在模型中使用这个类(我的数据对象,我正在做我的函数操作)。但是JavaFX是一个单线程GUI实现,只有在JavaFX线程中进行设置时,才允许设置链接到某些GUI控件的属性。否则将引发异常:

在使用JavaFX(Java8)一段时间后,我发现了非常有用的概念,允许使用bean兼容的变量绑定到使用计算树的更改上进行更新,例如:

class Person {
    StringProperty name;
    ...
}

允许将GUI控件绑定到“模型”,以便在更改时自动更新

所以我也开始在模型中使用这个类(我的数据对象,我正在做我的函数操作)。但是JavaFX是一个单线程GUI实现,只有在JavaFX线程中进行设置时,才允许设置链接到某些GUI控件的属性。否则将引发异常:

  Exception in thread "Thread-5" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-5
如果我现在开始编写多线程代码,我最终无法使用这些属性,即使我很乐意。我无法在
Platform.runLater()
调用中封装所有更改,以将其传递给JavaFX线程

为什么JavaFX不提供线程安全的属性绑定? (还是真的?)

JavaFX属性在GUI视图范围之外的可用性

JavaFX属性绝对可以在GUI视图的范围之外使用。basic通过创建一个简单的非GUI java程序来演示这一点,该程序表示一个Bill对象,其中Bill的总金额通过JavaFX属性公开

为什么JavaFX不提供线程安全的属性绑定?(还是真的?)

JavaFX不支持线程安全属性绑定。它不支持线程安全属性绑定的原因可能是因为它不需要

尽管JavaFX内部有一个多线程体系结构,其中包含一个渲染线程、一个应用程序线程等,但在外部,它实际上只向开发人员公开单个应用程序线程。从开发人员的角度来看,开发人员将其应用程序编码为单线程系统。开发人员和内部JavaFX实现可以假设一切都在单线程环境中运行,并且编码要简单得多(有关为什么会出现这种情况的一些背景信息,请参阅和)。属性实现知道它是在这样的环境中执行的,也可以采用单线程体系结构,并跳过可能使内置线程安全控制复杂化的操作

JavaFX确实能够为生成新线程(也可以使用标准Java开发的任何并发工具)。JavaFX并发API确实能够以线程安全的方式反馈属性值(例如任务执行所完成的工作的百分比),但这是以非常特定的方式完成的。任务API有专门的方法来修改这些属性(例如),并且在内部使用线程检查和调用,以确保代码以线程安全的方式执行

因此,JavaFX并发实用程序并不是通过JavaFX属性机制的内置功能来实现线程安全,而是通过并发实用程序实现内部对非常特定且有限的属性集的显式检查和调用来实现的。即使如此,用户在使用这些实用程序时也必须非常小心,因为他们通常认为自己可以直接从并发任务中修改JavaFXGUI阶段属性(尽管文档中指出不应该这样做);如果使用标准Java并发实用程序,如
Java.util.concurrent
包,而不是
javafx.concurrent
,则需要同样小心

有人可能会创建JavaFX属性机制的扩展,以更好地在线程安全环境中工作,但迄今为止还没有人创建这样的扩展

但是JavaFX是一个单线程GUI实现,只有在JavaFX线程中进行设置时,才允许设置链接到某些GUI控件的属性

问题的标题是“GUI视图范围外JavaFX属性的可用性”,但您描述的特定问题是其中一个非常具体的子集,我认为您有以下几点:

  • 模型类上的属性
  • 模型类中的属性可以由JavaFX应用程序线程或其他用户线程读取和写入
  • 模型类中的属性可以绑定到活动GUI元素,例如TextField中的文本或屏幕上显示的标签
  • 开箱即用这是行不通的,因为JavaFX系统假定javafxgui组件的属性只在JavaFX应用程序线程上读取或修改。此外,在内部,为了支持绑定和更改侦听器,属性本身需要维护要修改的侦听器和绑定属性的列表,这些列表可能假定它们只能从单个线程访问或修改。您可以通过使用
    Platform.runLater
    包装调用,确保每次读取或写入都在单个线程上进行,从而解决这个问题,但从您的问题来看,这正是您试图避免的代码类型

    对于我在上面几点中概述的用例,没有其他解决方案和
    平台。必须使用runLater
    包装。通过提供属性访问和更新的门面方法(类似于JavaFX并发任务实现),您可以潜在地隐藏runLater调用的一些复杂性和样板文件,但这样的系统实现起来可能有点复杂(特别是如果您希望为整个属性/绑定子系统实现通用解决方案,而不是为Task等几个特定属性实现专用解决方案)

    那么JavaFX属性是什么呢

    现有的主要用例用于支持JavaFXGUI应用程序的基于绑定的GUI编程模型
      Exception in thread "Thread-5" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-5
    
    class SafePublishProperty[T](init: T) {
      val writable = new ReadOnlyObjectWrapper[T](init)
      def readOnly: ObjectExpression[T] = writable.getReadOnlyProperty
    }
    
    class ThreadSafeBooleanProperty(init: Boolean) {
      protected val property = new ReadOnlyBooleanWrapper(init)
    
      def value: BooleanExpression = property.getReadOnlyProperty
    
      def setValue(value: Boolean): Future[Boolean] = {
        val promise = Promise[Boolean]
        if (Platform.isFxApplicationThread) {
          property.setValue(value)
          promise.success(true)
        }
        else
          try {
            Platform.runLater(() => {
              property.setValue(value)
              promise.success(true)
            })
          } catch {
            case _: IllegalStateException =>
              property.setValue(value)
              promise.success(true)
          }
        promise.future
      }
    }
    
    class ThreadSafeObjectProperty[T](init: T) {
      protected val property = new SafePublishProperty[T](init)
    
      def value: ObjectExpression[T] = property.readOnly
    
      def setValue(value: T): Future[Boolean] = {
        val promise = Promise[Boolean]
        if (Platform.isFxApplicationThread) {
          property.writable.setValue(value)
          promise.success(true)
        }
        else {
          try {
            Platform.runLater(() => {
              property.writable.setValue(value)
              promise.success(true)
            })
          } catch {
            case _: IllegalStateException =>
              property.writable.setValue(value)
              promise.success(true)
          }
        }
        promise.future
      }
    }
    
    object ThreadSafePropertySetter {
      def execute(function: () => Unit): Future[Boolean] = {
        val promise = Promise[Boolean]
        if (Platform.isFxApplicationThread) {
          function.apply()
          promise.success(true)
        }
        else {
          try {
            Platform.runLater(() => {
              function.apply()
              promise.success(true)
            })
          } catch {
            case ex: IllegalStateException =>
              function.apply()
              promise.success(true)
          }
        }
        promise.future
      }
    }
    
      class SomeExample {
        private val propertyP = new ThreadSafeBooleanProperty(true)
    
        def property: BooleanExpression = propertyP.value
    
        private class Updater extends Actor {
          override def receive: Receive = {
            case update: Boolean =>
              propertyP.setValue(update)
          }
        }
      }
    
    public <T> ChangeListener<T> getFxListener(Consumer<T> consumer) {
        return (observable, oldValue, newValue) -> {
            if (Platform.isFxApplicationThread()) {
                consumer.accept(newValue);
            }
            else {
                Platform.runLater(() -> consumer.accept(newValue));
            }
        };
    }
    
    // domain object with JavaFX property members
    private User user;
    
    @FXML
    private TextField userName;
    
    @FXML
    protected void initialize() {
        user.nameProperty().addListener(getFxListener(this::setUserName));
    }
    
    public void setUserName(String value) {
        userName.setText(value);
    }