Search code examples
javajavafxkotlintornadofx

Kotlin FXML file controller


I am trying to make basic desktop app using Kotlin and JavaFX(TornadoFX), but I am stuck on setting up controller for FXML file. I am following guide from edvin on git LINK to git.

The program should increment number in label whenewer is the button clicked.

The problem is.. When I try to use FXML file shown below, I can't use onAction="#increment" to "connect" that file with a controller. This will compile, but there is no way to call increment function from controller.kt file. Also there is error saying "No controller specified for top level element" ..

Whenever I try to specify the controller by using fx:controller=view.Controller the code will not even compile, showing error:

javafx.fxml.LoadException: Controller value already specified. 

Could someone please help me out?

Here is FXML file:

?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<BorderPane xmlns="http://javafx.com/javafx/null" xmlns:fx="http://javafx.com/fxml/1">
    <padding>
        <Insets top="20" right="20" bottom="20" left="20"/>
    </padding>

    <center>
        <VBox alignment="CENTER" spacing="10">
            <Label text="0">
                <font>
                    <Font size="20"/>
                </font>
            </Label>
            <Button text="Click to increment" onAction="#increment"/>
        </VBox>
    </center>
</BorderPane>

Here is MyApp.kt (main file):

package app

import javafx.stage.Stage
import tornadofx.*
import view.MainView

class MyApp: App(MainView::class){
    override val primaryView = MainView::class

    override fun start(stage: Stage) {
        stage.minHeight = 400.0
        stage.minWidth = 600.0
        super.start(stage)
    }

}

Here is MainView.kt (view):

package view

import javafx.scene.layout.BorderPane
import tornadofx.*

class MainView : View() {

    override val root: BorderPane by fxml("/views/MainViewFXML.fxml" )
}

Here is Controller.kt (this should be used to control FXML file actions):

package view

class Controller {

    fun increment(){
        //code
    }

}

Solution

  • In TornadoFX, the View IS the controller. Think of View subclasses as the View Controller. React to UI events in the View, and pass business logic off to a ViewModel or a Controller subclass.

    Place your increment function in MainView and it will be called :) Remove the fx:controller=view.Controller attribute.