Как исправить «IllegalStateException» при использовании пользовательской JavaFXBuilderFactory

Прежде всего, я использую Kotlin в своем проекте JavaFX.

Я пытаюсь реализовать свой собственный узел JavaFX, который расширяет Canvas.

class BrikkCanvas(width: Double, height: Double, private val rows: Int, private val cols: Int) : Canvas(width, height)

Я также хочу иметь возможность добавлять BrikkCanvas непосредственно в файл FXML, например так

<BrikkCanvas fx:id="myCanvas" width="100.0" height="100.0" rows="1" cols="1" />

В моем классе нет конструктора по умолчанию, поэтому включение его в FXML нетривиально.

Однако я узнал, что вы можете реализовать собственный BuilderFactory, поэтому я сделал:

class BrikkCanvasBuilderFactory : BuilderFactory {
    private val defaultBuilderFactory = JavaFXBuilderFactory()

    override fun getBuilder(clazz: Class<*>): Builder<*> =
            if (clazz == BrikkCanvas::class.java) BrikkCanvasBuilder()
            else defaultBuilderFactory.getBuilder(clazz)

    private class BrikkCanvasBuilder : Builder<BrikkCanvas> {
        var width: Double = 0.0
        var height: Double = 0.0
        var rows: Int = 0
        var cols: Int = 0

        override fun build(): BrikkCanvas = BrikkCanvas(width, height, rows, cols)
    }
}

В классе App, который расширяет Application, я использую свой BrikkCanvasBuilderFactory следующим образом:

fun startTheGame(playerName: String) {
            val loader = FXMLLoader(App::class.java.getResource("game.fxml"), null, BrikkCanvasBuilderFactory())
            val scene = Scene(loader.load())
            val controller = loader.getController<GameController>()
            primaryStage.scene = scene
}

Однако, когда я запускаю приложение и нажимаю кнопку, которая вызывает startTheGame, я получаю следующую ошибку:

Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
...
Caused by: java.lang.reflect.InvocationTargetException
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:567)
...
Caused by: javafx.fxml.LoadException: 
/path to project/target/classes/game.fxml:21
    at App$Companion.startTheGame(App.kt:18)
...
Caused by: java.lang.IllegalStateException: defaultBuilderFactory.getBuilder(clazz) must not be null
    at controller.BrikkCanvasBuilderFactory.getBuilder(BrikkCanvasBuilderFactory.kt:12)

game.fxml:21 = fx:controller="controller.GameController" App$Companion.startTheGame(App.kt:18) = val scene = Scene(loader.load()) BrikkCanvasBuilderFactory.kt:12 = else defaultBuilderFactory.getBuilder(clazz)

Обратите внимание, что я еще даже не включил теги <BrikkCanvas ... /> в файл FXML.

Я почти уверен, что мне не хватает чего-то действительно простого, что, вероятно, связано с тем, что я использую Kotlin вместо Java.


person pjaro    schedule 19.08.2019    source источник


Ответы (1)


Насколько я понимаю, интерфейсы BuilderFactory и Builder — это пережитки прошлого (по существу устаревшие), и их следует избегать в новом коде — делать вид, что их не существует. Вместо этого вы должны аннотировать параметры конструктора с помощью @NamedArg. аннотация.

class BrikkCanvas(
    @NamedArg("width") width: Double,
    @NamedArg("height") height: Double,
    @param:NamedArg("rows") private val rows: Int,
    @param:NamedArg("cols") private val cols: Int
) : Canvas(width, height)

Затем FXMLLoader сможет сказать, какой атрибут FXML соответствует какому параметру, и, таким образом, может установить свойства через конструктор.

person Slaw    schedule 19.08.2019