Deprecated: Creation of dynamic property Markdown_Shortcode_Plugin::$cache is deprecated in /var/www/0/138308/www/wp-content/plugins/markdown-shortcode/markdown-shortcode.php on line 25

Warning: Cannot modify header information - headers already sent by (output started at /var/www/0/138308/www/wp-content/plugins/markdown-shortcode/markdown-shortcode.php:25) in /var/www/0/138308/www/wp-includes/rest-api/class-wp-rest-server.php on line 1893

Warning: Cannot modify header information - headers already sent by (output started at /var/www/0/138308/www/wp-content/plugins/markdown-shortcode/markdown-shortcode.php:25) in /var/www/0/138308/www/wp-includes/rest-api/class-wp-rest-server.php on line 1893

Warning: Cannot modify header information - headers already sent by (output started at /var/www/0/138308/www/wp-content/plugins/markdown-shortcode/markdown-shortcode.php:25) in /var/www/0/138308/www/wp-includes/rest-api/class-wp-rest-server.php on line 1893

Warning: Cannot modify header information - headers already sent by (output started at /var/www/0/138308/www/wp-content/plugins/markdown-shortcode/markdown-shortcode.php:25) in /var/www/0/138308/www/wp-includes/rest-api/class-wp-rest-server.php on line 1893

Warning: Cannot modify header information - headers already sent by (output started at /var/www/0/138308/www/wp-content/plugins/markdown-shortcode/markdown-shortcode.php:25) in /var/www/0/138308/www/wp-includes/rest-api/class-wp-rest-server.php on line 1893

Warning: Cannot modify header information - headers already sent by (output started at /var/www/0/138308/www/wp-content/plugins/markdown-shortcode/markdown-shortcode.php:25) in /var/www/0/138308/www/wp-includes/rest-api/class-wp-rest-server.php on line 1893

Warning: Trying to access array offset on value of type null in /var/www/0/138308/www/wp-content/plugins/markdown-shortcode/parsedown/ParsedownExtra.php on line 243

Warning: Trying to access array offset on value of type null in /var/www/0/138308/www/wp-content/plugins/markdown-shortcode/parsedown/ParsedownExtra.php on line 243

Deprecated: preg_match(): Passing null to parameter #2 ($subject) of type string is deprecated in /var/www/0/138308/www/wp-content/plugins/markdown-shortcode/parsedown/ParsedownExtra.php on line 243

Warning: Cannot modify header information - headers already sent by (output started at /var/www/0/138308/www/wp-content/plugins/markdown-shortcode/markdown-shortcode.php:25) in /var/www/0/138308/www/wp-includes/rest-api/class-wp-rest-server.php on line 1893

Warning: Cannot modify header information - headers already sent by (output started at /var/www/0/138308/www/wp-content/plugins/markdown-shortcode/markdown-shortcode.php:25) in /var/www/0/138308/www/wp-includes/rest-api/class-wp-rest-server.php on line 1893
{"id":33,"date":"2017-01-02T19:00:35","date_gmt":"2017-01-02T17:00:35","guid":{"rendered":"https:\/\/edvin.town\/?p=33"},"modified":"2022-03-16T16:10:41","modified_gmt":"2022-03-16T14:10:41","slug":"leveraging-tornadofx-to-the-fullest","status":"publish","type":"post","link":"https:\/\/edvin.town\/leveraging-tornadofx-to-the-fullest\/","title":{"rendered":"Leveraging TornadoFX to the fullest"},"content":{"rendered":"

Carl Walker recently wrote an insightful post on TableView Binding in Kotlin<\/a> where he uses TornadoFX <\/a>to create a nice looking TableView with a couple of buttons that are disabled depending on the state of the currently selected item.<\/p>\n\"\"\n

He also wrote the same program in plain Java<\/a> and the post illustrates how TornadoFX can greatly reduce boiler plate code.<\/p>\n

Coming from a strong JavaFX background, Carl's approach is very reasonable, but I'd like to show you that we can improve upon it even more by leveraging some more TornadoFX features.<\/p>\n

TornadoFX is all about reducing boiler plate, while increasing readability and maintainability.<\/p>\n

First I'll post the complete application as Carl created it, then I'll walk through each element I feel we can improve upon and finally post the full source for the modified app.<\/p>\n

Here is the initial application without imports:<\/p>\n

data class Item(val sku : String, val descr : String, val price : Float, val taxable : Boolean)\n\nclass TableSelectView : View(\"TableSelectApp\") {\n\n    private val items = FXCollections.observableArrayList(\n        Item(\"KBD-0455892\", \"Mechanical Keyboard\", 100.0f, true),\n        Item(\"145256\", \"Product Docs\", 0.0f, false),\n        Item(\"OR-198975\", \"O-Ring (100)\", 10.0f, true)\n    )\n\n    var tblItems : TableView<Item> by singleAssign()\n    var btnInventory : Button by singleAssign()\n    var btnCalcTax : Button by singleAssign()\n\n    override val root = vbox {\n        tblItems = tableview(items) {\n\n            column(\"SKU\", Item::sku)\n            column(\"Item\", Item::descr)\n            column(\"Price\", Item::price)\n            column(\"Taxable\", Item::taxable)\n\n            prefWidth = 667.0\n            prefHeight = 376.0\n\n            columnResizePolicy = CONSTRAINED_RESIZE_POLICY\n\n            vboxConstraints {\n                vGrow = Priority.ALWAYS\n            }\n        }\n        hbox {\n            btnInventory = button(\"Inventory\")\n            btnCalcTax = button(\"Tax\")\n\n            spacing = 8.0\n        }\n\n        padding = Insets(10.0)\n        spacing = 10.0\n    }\n\n    init {\n\n        btnInventory.disableProperty().bind( tblItems.selectionModel.selectedItemProperty().isNull )\n\n        btnCalcTax.disableProperty().bind(\n                tblItems.selectionModel.selectedItemProperty().isNull().or(\n                    Bindings.select<Boolean>(\n                            tblItems.selectionModel.selectedItemProperty(),\n                            \"taxable\"\n                    ).isEqualTo( false )\n                )\n        )\n    }\n}\n\nclass TableSelectApp : App(TableSelectView::class)<\/code><\/pre>\n

Syntactic Sugar<\/h2>\n

We'll start with something simple and probably insignificant, but we want to attack every piece of boiler plate, so I'll mention it anyway.<\/p>\n

In JavaFX, you're used to setting properties on objects as you create them. A typical example is the spacing<\/code> property on VBox<\/code> and HBox<\/code> containers. The original sample does:<\/p>\n

vbox {\n    spacing = 8.0\n}<\/code><\/pre>\n

The box builders take spacing as a parameter, so you can write vbox(8.0)<\/code> or vbox(spacing = 8.0)<\/code> if you prefer. OK, that was low hanging fruit, but bear with me.<\/p>\n

Next up is actually a feature we added to TornadoFX, but that also has a shorthand alternative. Configuring the constraints inside a VBox<\/code> can be done inside a vboxConstraints<\/code> block, but when you only configure a single constraint you're better off just using this shorthand:<\/p>\n

vgrow = Priority.ALWAYS<\/code><\/pre>\n

Let's tackle the last minor issue before we move on to bigger fish. Setting preferred width and height can be done in a single statement:<\/p>\n

setPrefSize(667.0, 376.0)<\/code><\/pre>\n

Builder Encapsulation<\/h2>\n

I have a golden rule I always try to follow: Whenever possible, avoid references to other ui elements<\/em>. This reduces coupling, but more importantly it means that you will configure a single ui element in just one place. It's almost always less code as well. The original sample uses the singleAssign<\/code> delegate that makes sure we only assign a value to the variable once. This is the original code:<\/p>\n

var tblItems : TableView<Item> by singleAssign()\nvar btnInventory : Button by singleAssign()\nvar btnCalcTax : Button by singleAssign()<\/code><\/pre>\n

When these objects are created inside the builders, they are assigned to these variables:<\/p>\n

tblItems = tableview(items)\nbtnInventory = button(\"Inventory\")\nbtnCalcTax = button(\"Tax\")<\/code><\/pre>\n

Later, in the init<\/code> block of the class, these variables are configured further. The buttons gets their disabledProperty<\/code> bound, and the tblItems<\/code> is references from these bindings. This is the major issue with this code IMO.<\/p>\n

The items are declared in one place, instantiated another and configured a third place in the code. That's three different places to look for how each of these elements are treated. We can actually change all this so everything is done in one place - inside the builder expression that created them. We can get rid of those singleAssign<\/code> variable declarations, and make the binding expression much more concise in the process.<\/p>\n

The reason this is needed in the original sample, is that the binding expressions work on the selectedItem of the TableView. We want to avoid that alltogether, so before we can clean up these variables, let's create a ViewModel.<\/p>\n

ViewModel<\/h2>\n

An ItemViewModel<\/code> can wrap an instance of your domain object and gives you properties you can bind against which will stay valid even when the item it represents is changed. We want the view model to solve two issues for us:<\/p>\n