user-interface - 为什么 runAsync 会导致 Java IllegalStateException?
问题描述
我一直在尝试学习如何将 TornadoFX 与 Kotlin 一起使用,并且通常对 JavaFX 的经验很少。我一直在学习这里的教程,并进入了演示如何使用 runAsync 块的部分。教程中的示例只是一个代码片段:
val textfield = textfield()
button("Update text") {
action {
runAsync {
myController.loadText()
} ui { loadedText ->
textfield.text = loadedText
}
}
}
好吧,我决定尝试使用以下代码来实现它:
class AsyncView : View(){
val text = SimpleStringProperty();
val llabel = SimpleStringProperty("No Commit");
val controller: AsyncController by inject();
override val root = form {
fieldset {
field("Current Input"){
textfield(text);
}
label(llabel)
button("Commit") {
action {
runAsync {
controller.performWrite(text);
text = "";
} ui {
llabel.value = controller.getValue();
}
}
}
}
}
}
class AsyncController: Controller() {
private var MyValue: String = "";
fun performWrite(inputValue: String){
MyValue = inputValue;
}
fun getValue(): String {
return MyValue;
}
}
但是由于某种原因,当我单击按钮时,这会引发 java IllegalStateException:
SEVERE: Uncaught error
java.lang.IllegalStateException: Not on FX application thread; currentThread = tornadofx-thread-1
at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:236)
(下面的完整错误)
我已经尝试了所有我能想到的搜索,试图找到为什么会发生这种情况的答案。我尝试在 try/catch 块中捕获错误,但似乎没有任何效果。这里出了什么问题,如何让异步按钮事件起作用?我将 JDK8 与 Kotlin 一起使用。
预先感谢您的帮助!
完全错误:
Mar 04, 2020 1:39:37 PM tornadofx.DefaultErrorHandler uncaughtException
SEVERE: Uncaught error
java.lang.IllegalStateException: Not on FX application thread; currentThread = tornadofx-thread-1
at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:236)
at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:423)
at javafx.scene.Parent$2.onProposedChange(Parent.java:367)
at com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:113)
at com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:108)
at com.sun.javafx.scene.control.skin.LabeledSkinBase.updateChildren(LabeledSkinBase.java:575)
at com.sun.javafx.scene.control.skin.LabeledSkinBase.handleControlPropertyChanged(LabeledSkinBase.java:204)
at com.sun.javafx.scene.control.skin.ButtonSkin.handleControlPropertyChanged(ButtonSkin.java:71)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase.lambda$registerChangeListener$61(BehaviorSkinBase.java:197)
at com.sun.javafx.scene.control.MultiplePropertyChangeListenerHandler$1.changed(MultiplePropertyChangeListenerHandler.java:55)
at javafx.beans.value.WeakChangeListener.changed(WeakChangeListener.java:89)
at com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:182)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
at javafx.beans.property.StringPropertyBase.fireValueChangedEvent(StringPropertyBase.java:103)
at javafx.beans.property.StringPropertyBase.markInvalid(StringPropertyBase.java:110)
at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:144)
at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:49)
at javafx.beans.property.StringProperty.setValue(StringProperty.java:65)
at javafx.scene.control.Labeled.setText(Labeled.java:145)
at AsyncView$root$1$1$2$1$1.invoke(AsyncView.kt:21)
at AsyncView$root$1$1$2$1$1.invoke(AsyncView.kt:6)
at tornadofx.FXTask.call(Async.kt:457)
at javafx.concurrent.Task$TaskCallable.call(Task.java:1423)
at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
at java.util.concurrent.FutureTask.run(FutureTask.java)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Disconnected from the target VM, address: '127.0.0.1:55193', transport: 'socket'
Process finished with exit code 0
解决方案
答案:
您需要了解 3 件事:
1. Kotlin 简化了 Java 的 Get/Set——这一点很简单。Kotlin 做了一件很棒的事情,如果它识别出它正在使用 Java 类并且该类中的成员具有 setter/getter 函数,它将允许您访问这些成员,就好像它是直接引用一样。所以例如button.setText("some text");
现在变成button.text = "some text"
.
2. TornadoFX 构建器功能- 假设您将鼠标悬停form
在 IDE 中。您会注意到该函数包含op: Form.() → Unit
在其参数中。这意味着当您将括号附加到 时form
,如下所示:
field {
//here is some code
}
那么这些括号内的任何内容都会将接收器更改为 createdForm
而不是AsyncView
. 因此,当您在 中提到text
时textfield(text)
,您最终引用了属于该Field
对象的文本。当您编写时,controller.performWrite(text)
您最终引用了属于该Button
对象的文本,依此类推。
3. Kotlin Block Scopes - 就像上面提到的,块可以有能力改变它的接收者。但是,它不会阻止您引用自身之外的成员/函数。您只是碰巧将其命名为相同,并且参考的优先级导致了问题。您可以通过简单地将您的文本成员更改为不同的名称来解决此问题,或者:
override val root = form {
fieldset {
field("Current Input") {
textfield(this@AsyncView.text)
}
label(llabel)
button("Commit") {
action {
runAsync {
controller.performWrite(this@AsyncView.text.value)
this@AsyncView.text.value = ""
} ui {
llabel.value = controller.getValue()
}
}
}
}
}
对 .使用显式标记this
。
改进:
请看我所有的评论。这仍然没有涵盖您可以使用 TornadoFX 和 Kotlin 的强大功能做的所有事情,但这是一个开始。另外:删除所有这些分号!!!
class AsyncView : View() {
val controller: AsyncController by inject()
val inputProperty = SimpleStringProperty() //Name is descriptive and appropriate to its role
var input by inputProperty //TornadoFX-unique way to get/set property values
val valueLabelTextProperty = SimpleStringProperty("No Commit") //Name is descriptive and appropriate to its role
var valueLabelText by valueLabelTextProperty
override val root = form {
fieldset {
field("Current Input") {
textfield(inputProperty)
}
label(valueLabelTextProperty)
button("Commit") {
action {
runAsync {
controller.performWrite(input)
input = ""
controller.myValue //The last line's value gets passed to success block. Leave as little work to UI as possible
} success { value -> // ui is only included for backwards compatibility. success is replacement.
valueLabelText = value
}
}
}
}
}
}
class AsyncController : Controller() {
var myValue: String = "" //Naming should be camel-cased
private set //No need for old-school Java getters/setters. Simply private the set. Look into Kotlin get/set for more info
//If you do not plan to do more than change `myValue` in the future with this method,
//delete it and remove private set from `myValue`. You can use custom Kotlin getters/setters instead.
fun performWrite(inputValue: String) {
myValue = inputValue
}
}
推荐阅读
- ionic4 - variables.scss 中的组件变量不起作用
- android - Android Admob:缺少 google_app_id
- angular - 如何在主渲染过程之外调用电子更新器
- css - Bootstrap 4 - 溢出时多重选择不会突出显示整个项目
- java - 无法从 Hive 更改表位置
- c - 服务器没有收到来自客户端的多次写入
- go - 解码 json 值的更好方法
- r - R:如何在组 t 检验之间运行?
- amazon-web-services - 无法执行使用 Micronaut 和 Graal 构建的 AWS Lambda 函数:解码 JSON 流时出错
- python - 如何使用嵌套在另一个“for”循环中的“for”循环继续迭代 JSON 对象/列表?