Почему runAsyn c вызывает исключение Java IllegalStateException? - PullRequest
0 голосов
/ 05 марта 2020

Я пытался научиться использовать TornadoFX с Kotlin и у меня очень мало опыта работы с JavaFX в целом. Я следовал учебному пособию здесь и попал в ту часть, где демонстрируется, как использовать блок runAsyn c. Пример в руководстве - просто фрагмент кода:

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, но, похоже, ничего не работает. Что здесь не так и как заставить работать событие кнопки asyn c? Я использую 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

1 Ответ

1 голос
/ 05 марта 2020

Ответ:
Вам необходимо понять 3 вещи:
1. Kotlin Упрощение Java Получение / установка - Это просто. Kotlin делает эту замечательную вещь, когда он распознает, что он использует класс Java и что член в этом классе имеет функции установщика / получателя, он позволит вам получить доступ к этим членам, как если бы это была прямая ссылка. Так, например, button.setText("some text"); теперь становится button.text = "some text".

2. Функции компоновщика TornadoFX - Допустим, вы наведите курсор мыши на form в вашей IDE. Вы заметите, что функция включает op: Form.() → Unit в свои параметры. Это означает, что когда вы прикрепляете скобки к form, например:

field {
  //here is some code
}

, тогда все, что находится внутри этих скобок, изменит получателя на созданный Form вместо 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
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...