Codetown ::: a software developer's community
Michael Levin and I grabbed coffee a few weeks ago discussing what we might do with Orlando JUG (thanks to gmail for the offer for a TornadoFX workshop being shoved in my spam email for weeks). We started up a KotlinTown group in CodeTown for anyone interested in meeting other JUG members of all levels — introductory and experienced Java users alike!
One of the things I wanted to start doing with KotlinTown is Kotlin Tuesdays: we’re still figuring out a format — I’d like to get a chat successfully set up for Kotlin use only, but in the meantime, I’m posting both here and there and if you’re interested in joining the community, comment below or message us!
TornadoFX was created by Edvin Syse, who leveraged Kotlin to create a framework for JavaFX. TornadoFX was my first love with Kotlin, and I highly recommend checking out my other posts or even Github for other really cool projects people have done playing with TornadoFX. As a developer with extensive experience in large-scale web development, I definitely have JS fatigue with the amount of new frameworks that seem to come out every year. I’m not the only one either, Mike Hearn’s post on It’s Time to Kill the Web sounds more plausible by the day.
THAT ASIDES I am not here to argue web v. native development. TornadoFX is really fun and is a small framework (hence, short learning curve), and it’s a great way to start learning Kotlin without being bogged down by servers or mobile or web development. Ya’ll ready? Good. You’re not required to know JavaFX to get started with native application development, and I’ll be providing Kotlin and TornadoFX documentation below. Documentation for TornadoFX also has extensive examples with a convenient search function for you to browse around.
1. On IntelliJ, go to Preferences > Plugins and make sure Kotlin and TornadoFX is installed.
2. General consensus among TorandoFXers is that Maven is a lot easier to use — if you’re not familiar with Gradle v. Maven, don’t worry about it. It is not necessary to understand how to use either for this project, but don’t hesitate to ask for questions on setup if you run into a blocker. Once you have your plugins downloaded, open a new project and select the tornadofx-maven-plugin, which will mostly set up your project immediately.
3. It seems like the latest update for Kotlin didn’t pick up the latest running update for your Kotlin version (if you’re not sure what version you’re running, check your plugins for the latest i.e. 1.2.41). Go into your pom.xml file to edit the version and click “Import Changes” below. If it’s builds successfully, you’re good to go!
4. You’ll need to set up minimal configuration to run an instance of your application: luckily, the TornadoFX Maven plugin already did most of the painful setup for you. Go to Run > Edit Configurations, and add a new TornadoFX Configuration. Just name your config something meaningful and grab the MyApp class address, since that is already set up to launch the app. $MODULE_DIR$ detects the source of your project in your computer so you don’t have to dig for an address. Add the 1.8 JDK to the JRE and the name of your project to the classpath of the module, then hit Apply and OK. If you don’t have 1.8 JDK, you’ll need to download it from Oracle.
5. Press “Run” (or the green arrow) and you’ll see an instance of the skeleton application!Not too bad, right? The setup is always the worst part of a project.
While TornadoFX has a lot of great components made specifically for the framework like Datagrid or Datepicker, already pre-made for your ease of use, I want to create my own neighborhood with specific streets and houses and I want full control of what goes in each block, so I will use the JavaFX GridPane written with TornadoFX DSLs. You’ll notice that you already have an app and view folder in your project — you’ll want to add a controller and model folder as well for easy organization. If you’re not familiar with MVC, the structure is a general architectural pattern developers have come up with to work with large frameworks, all split to handle different aspects of the application.
What I had was house icons I downloaded from a free icon site — you’re welcome to use the ones in my project or find your own. Downloading free icons is great especially since I’m not a graphic designer myself. TornadoFX has all the features JavaFX has, if you want to know how to do something, look up Oracle JavaFX documentation/examples and transfer it to Kotlin! Here’s why I love Kotlin — in JavaFX, a gridpane is typically created like so:
class GridPaneExample {
/**
* @param args the command line arguments
*/
public static void main (String[ ] args) {
Application.launch(args);
}
@Override
public void start (Stage primaryStage) {
int numCol = 8;
GridPane pane = new GridPane( );
pane.setVgap(15);
pane.setPadding(new Insets(15, 15, 15, 15) );
// one row
for (int i = 0; i < numCol; i++) {
pane.add(horizontalStreetPane( ));
}
// show scene
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show( );
}
public StackPane horizontalStreetPane( ) {
Rectangle grass = new Rectangle(100.0, 100.0);
grass.setFill(Color.GREEN);
StackPane stack = new StackPane(grass);
Rectangle street = new Rectangle(100.0, 40.0);
street.setFill(Color.GRAY);
// adding additional elements overlays elements
stack.getChildren( ).add(street);
return stack;
}
A JavaFX rendering of GridPane with a single row
Given that I want to have crossing streets and houses placed in different parts of the grid, this code can grow redundant quickly. With TornadoFX and Kotlin, you can easily use DSLs to create an equivalent layout and easily add properties with apply to the parent. In Kotlin, you’ll notice that the code is a lot easier to read, and there are a couple syntactic things you see right away:
// TornadoFX has View, Controller, and Model components, allowing you to separate your code.
class MyApp: View( ) {
// global variables
val numCol : Int = 8;
// every view has a root component
override val root = gridpane {
vgap = 15.0
padding = insets( 15)
for (var i in 1..numCol) {
add(horitzontalStreetPane( ))
}
fun horizontalStreetPane( ): StackPane {
return stackpane {
rectangle {
fill = c("4E9830")
width = 100.0
height = 100.0
}
rectangle {
fill = c("919191")
width = 100.0
height = 40.0
}
}
}
A TornadoFX rendering of the above example.
That being said, I want to separate my individual components in the controller for function logic and create a quick and dirty version of my GridPane. You can test your results with the run every time. Below, are some more notible features for TornadoFX:
class NeighborhoodView: View( ) {
// dependency injection
val controller: NeighborhoodController by inject( )
// set up neighborhood
override val root = gridpane {
row {
add(controller.grassPane( ))
add(controller.grassPane( ))
add(controller.housePane(Pos.BASELINE_LEFT))
add(controller.verticalStreetPane( ))
add(controller.housePane(Pos.BASELINE_CENTER))
add(controller.verticalStreetPane( ))
add(controller.housePane(Pos.BASELINE_CENTER))
add(controller.grassPane( ))
}
// you can add a lot more rows below as you like.
// use this to check placement:
// isGridLinesVisible = true
}
}
class Styles: Stylesheet( ) {
companion object {
val main by cssclass( )
val neighborhood by cssclass( )
}
init {
main {
backgroundColor += c("222222")
}
neighborhood {
backgroundColor += c("4E9830")
prefWidth = 470.px
prefHeight = 590.px
}
}
}
}
// located in the controller package
class NeighborhoodController: Controller( ) {
fun housePane(position: Pos) : StackPane {
val houseNum = (1..6).random( )
return StackPane.apply {
rectangle {
fill = c("4E9830")
width = 100.0
height = 100.0
}
imageview("house$houseNum.png").apply {
alignment = position
}
}
fun grassPane( ) : StackPane {
return StackPane.apply {
rectangle {
fill = c("4E9830")
width = 100.0
height = 40.0
}
}
}
fun verticalStreetPane( ) : StackPane {
return StackPane.apply {
rectangle {
fill = c("4E9830")
width = 100.0
height = 100.0
}
rectangle {
fill = c("919191")
width = 100.0
height = 100.0
}
}
}
private fun ClosedRange.random( ) =Random( ).nextInt(endInclusive - start) + start
}
A TornadoFX rendering of the above example.
Build your neighborhood however you like:
One of the neat things about TornadoFX streamlines complex JavaFX layouts, including tabpane and tableview. The following is seriously crash course on some of these concepts, so I’m attaching documentation for the model creation and binding for the following result:
class MainView: View("Neighborhood Cat Scheduler" ) {
override val root = stackpane {
borderpane {
prefHeight = 700.0
prefWidth = 800.0
top(NeighborhoodView::class)
left(BottomView::class)
}
addClass(Styles.main)
}
}
// Kotlin data classes simplifies the way we hold data
data class CatSchedule(val ownerName: String? = null, val catName: String? = null,
val address: String? = null, val time: String? = null,val catImage: String? = null)
// allows for easy data binding for display and changes
class CatScheduleModel: ItemViewModel/span>CatSchedule>( ) {
val ownerName = bind { item?.ownerName?.toProperty( ) }
val catName = bind { item?.catName?.toProperty( ) }
val address = bind { item?.address?.toProperty( ) }
val time = bind { item?.time?.toProperty( ) }
val catImage = bind { item?.catImage?.toProperty( ) }
}
// modelview in displayed view
class BottomView: View( ) {
private val model: CatScheduleModel by inject( )
private val mondays = listOf(
CatSchedule("Tom Mariano", "Meowsers", "1110 6th Street", "3:00PM", "/kitty/kitty1.png"),
CatSchedule("John Hill", "Tom", "1115 6th Street", "4:00PM", "/kitty/kitty2.png"),
CatSchedule("Louise Vargas", "Mr. Whiskers", "1120 6th Street", "4:30PM", "/kitty/kitty3.png"),
CatSchedule("Dale Benton", "Pepper", "2111 8th Street", "7:00PM", "/kitty/kitty4.png"),
CatSchedule("Tucker Harrison", "Princess", "2267 8th Street", "8:00PM", "/kitty/kitty5.png")
).observable()
override val root = hbox {
tabpane {
tab("Monday") {
tableview(mondays) {
column("Owner", CatSchedule:ownerName)
column("Cat", CatSchedule:catName)
column("Address", CatSchedule:address).remainingWidth( )
column("Time", CatSchedule:time)
bindSelected(model)
smartResize( )
}
}
addClass(Styles.schedule)
}
stackpane {
rectangle {
width = 200.0
height = 200.0
fill = Color.TRANSPARENT
}
imageview(cat, true)
}
}
End result:
We have a lot of neat things going, but there’s a lot more we could do from here: for example, the cats avis might change per selection, and maybe we want to be able to edit our schedule. A friend of my recommended I make this canine-compatible as well: I’m inclined to agree since I’m more of a dog person myself. I’d like to be really ambitious and connect those actions with the houses as well — what are your ideas with the project?
I intend on setting up a chat on here - tried earlier last week but I'm hoping the endeavor will be successful later this week. Stay tuned!
Tags:
Sorry it took me a while - I promise I'll deliver the next part ON TIME y'all. Any questions?
Codetown is a social network. It's got blogs, forums, groups, personal pages and more! You might think of Codetown as a funky camper van with lots of compartments for your stuff and a great multimedia system, too! Best of all, Codetown has room for all of your friends.
Created by Michael Levin Dec 18, 2008 at 6:56pm. Last updated by Michael Levin May 4, 2018.
Check out the Codetown Jobs group.
AWS has launched the open-source Model Context Protocol (MCP) Servers, revolutionizing AI-powered code assistants. These servers enhance development speed and security, ensuring adherence to AWS best practices. With features like automated Infrastructure as Code and cost insights, MCP democratizes AWS expertise and empowers developers to optimize cloud solutions effortlessly.
By Steef-Jan WiggersAWS has recently announced the general availability of Amazon VPC Route Server. This new option simplifies dynamic routing in a VPC, allowing developers to advertise routing information via Border Gateway Protocol (BGP) from virtual appliances and dynamically update the VPC route tables associated with subnets and internet gateways.
By Renato LosioIan Hoffman discusses Slack's architectural evolution from workspace-centric to Unified Grid. He explains scaling challenges & Enterprise Grid complexities, and shares lessons learned during this significant architectural shift, drawing insightful parallels to the history of astronomy and emphasizing the importance of questioning foundational assumptions in software development.
By Ian HoffmanIn this podcast, Shane Hastie, Lead Editor for Culture & Methods, spoke with Trisha Gee about the challenges and importance of addressing flaky tests, their impact on developer productivity and morale, best practices for testing, and broader concepts of measuring and improving developer productivity.
By Trisha GeeStefania Chaplin and Azhir Mahmood explain how to navigate the complexities of AI in highly regulated industries. They discuss MLOps pipelines, data security, evolving legislation (GDPR, EU AI Act), and the critical frameworks for responsible, secure, and explainable AI. Learn practical prevention techniques, XAI methods, and future trends in AI for cybersecurity and beyond.
By Stefania Chaplin, Azhir Mahmood
© 2025 Created by Michael Levin.
Powered by