├── part3 ├── README.md ├── 0_Subsection.md └── Snippets.md ├── .gitignore ├── .idea ├── markdown-navigator │ └── profiles_settings.xml └── markdown-navigator.xml ├── part1 ├── 0_Subsection.md ├── 12_TornadoFX_IDEA_Plugin.md ├── 2_Setting_Up.md ├── 1_Why_TornadoFX.md ├── 8_Charts.md ├── 9_Shapes_and_Animation.md ├── 10_FXML.md ├── 6_CSS.md └── 4_Basic_Controls.md ├── README.md ├── part2 ├── 0_Subsection.md ├── Integration.md ├── Layout_Debugger.md ├── Internationalization.md ├── Dependency_Injection.md ├── Property_Delegates.md ├── Scopes.md ├── Config_Settings_and_State.md ├── OSGi.md ├── EventBus.md ├── JSON_and_REST.md ├── Wizard.md └── Advanced_Data_Controls.md └── SUMMARY.md /part3/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | -------------------------------------------------------------------------------- /.idea/markdown-navigator/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /part1/0_Subsection.md: -------------------------------------------------------------------------------- 1 | # Part 1: TornadoFX Fundamentals 2 | 3 | This section will cover everything you will need to get started with TornadoFX. These sections are somewhat designed to be read sequentially, as concepts may build on top of each other. 4 | -------------------------------------------------------------------------------- /part3/0_Subsection.md: -------------------------------------------------------------------------------- 1 | # Part 3: Cook book 2 | 3 | This part of the guide will contain small recipies and tricks describing how to do different things with the framework. Our hope is that the community will contribute as they discover how to perform certain tasks :\) 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://github.com/edvin/tornadofx/blob/master/graphics/tornado-fx-logo.png?raw=true) 2 | 3 | # TornadoFX Guide 4 | 5 | This is a continual effort to fully document the [TornadoFX](https://github.com/edvin/tornadofx) framework in the format of a book. 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /part2/0_Subsection.md: -------------------------------------------------------------------------------- 1 | # Part 2: Advanced Features 2 | 3 | This section moves beyond the core features of TornadoFX, and showcases advanced features and specific framework capabilities. These chapters are not meant to be read sequentially, but rather cherrypicked for your specific interests and needs. 4 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [Introduction](README.md) 4 | * [Part 1: TornadoFX Fundamentals](part1/0_Subsection.md) 5 | * [1. Why TornadoFX?](part1/1_Why_TornadoFX.md) 6 | * [2. Setting Up](part1/2_Setting_Up.md) 7 | * [3. Components](part1/3_Components.md) 8 | * [4. Basic Controls](part1/4_Basic_Controls.md) 9 | * [5. Data Controls](part1/5_Data_Controls.md) 10 | * [6. Type Safe CSS](part1/6_CSS.md) 11 | * [7. Layouts and Menus](part1/7_Layouts_and_Menus.md) 12 | * [8. Charts](part1/8_Charts.md) 13 | * [9. Shapes and Animation](part1/9_Shapes_and_Animation.md) 14 | * [10. FXML](part1/10_FXML.md) 15 | * [11. Editing Models and Validation](part1/11_Editing_Models_and_Validation.md) 16 | * [12. TornadoFX IDEA Plugin](part1/12_TornadoFX_IDEA_Plugin.md) 17 | * [Part 2: TornadoFX Advanced Features](part2/0_Subsection.md) 18 | * [Property Delegates](part2/Property_Delegates.md) 19 | * [Advanced Data Controls](part2/Advanced_Data_Controls.md) 20 | * [OSGi](part2/OSGi.md) 21 | * [Scopes](part2/Scopes.md) 22 | * [EventBus](part2/EventBus.md) 23 | * [Workspaces](part2/Workspaces.md) 24 | * [Layout Debugger](part2/Layout_Debugger.md) 25 | * [Internationalization](part2/Internationalization.md) 26 | * [Config Settings and State](part2/Config_Settings_and_State.md) 27 | * [JSON and REST](part2/JSON_and_REST.md) 28 | * [Dependency Injection](part2/Dependency_Injection.md) 29 | * [Wizard](part2/Wizard.md) 30 | * [Integrating with other tech](part2/Integration.md) 31 | * [Part 3: Cook Book](part3/0_Subsection.md) 32 | * [Snippets](part3/Snippets.md) 33 | 34 | -------------------------------------------------------------------------------- /part2/Integration.md: -------------------------------------------------------------------------------- 1 | ## Integrate with existing JavaFX Applications 2 | 3 | TornadoFX can happily coexist with an existing Application written in either Kotlin or Java. This enables a gradual migration instead of performing a complete rewrite before you can benefit from TornadoFX in your apps. Feel free to skip this section if it doesn't apply to you. 4 | 5 | **Note**: This feature is available as of version 1.4.3 6 | 7 | To make TornadoFX aware of your application, perform a call to `registerApplication` in your `Application` class `start()` method: 8 | 9 | ```java 10 | public class LegacyApp extends Application { 11 | public void start(Stage primaryStage) throws Exception { 12 | // Register JavaFX app with the TornadoFX runtime 13 | FX.registerApplication(this, primaryStage); 14 | } 15 | } 16 | ``` 17 | > Existing JavaFX Application written in Java 18 | 19 | ### Accessing TornadoFX Views from plain old JavaFX 20 | 21 | Let's say you have created your first TornadoFX View and would like to integrate the root node of that `View` into a plain JavaFX view. You could actually just instantiate the View and put the root Node wherever you like, but since `View` is a singleton, you want to make sure you only ever instantiate a single instance. For this, you can use the `FX.find` function. 22 | 23 | First let's create a simple TornadoFX View, and this time let's write it in plain Java instead of Kotlin. We don't expect that people will write TornadoFX apps in Java, but it is indeed possible :) 24 | 25 | ```java 26 | public class MyView extends View { 27 | public HBox getRoot() { 28 | return new HBox(new Label("I'm a TornadoFX View written in Java")); 29 | } 30 | } 31 | ``` 32 | > TornadoFX View written in Java (!!) 33 | 34 | ```java 35 | // Create a BorderPane for our Scene 36 | BorderPane root = new BorderPane(); 37 | 38 | // Lookup a TornadoFX view and set it's root as the center node 39 | HBox fxView = FX.find(MyView.class).getRoot(); 40 | root.setCenter(fxView); 41 | ``` 42 | 43 | The same mechanics can be used to access TornadoFX Controllers. For Fragments, simply instantiate them and put the root node where you like. 44 | 45 | ### Bootstrapping TornadoFX from Swing 46 | 47 | You can even start a TornadoFX app inside your existing Swing applications! 48 | 49 | ```kotlin 50 | public class SwingApp { 51 | private static void createAndShowGUI() { 52 | // initialize toolkit 53 | JFXPanel wrapper = new JFXPanel(); 54 | 55 | // Init TornadoFX Application 56 | Platform.runLater(() -> { 57 | Stage stage = new Stage(); 58 | MyApp app = new MyApp(); 59 | app.start(stage); 60 | }); 61 | } 62 | 63 | public static void main(String[] args) { 64 | SwingUtilities.invokeLater(SwingApp::createAndShowGUI); 65 | } 66 | } 67 | ``` 68 | 69 | ### Integrate with existing Dependency Injection frameworks 70 | 71 | You can access your existing beans exposed via any dependency injection framework by implementing a SAM class called `DIContainer` and registering it via `FX.setDicontainer()`. More information on the next page. -------------------------------------------------------------------------------- /part2/Layout_Debugger.md: -------------------------------------------------------------------------------- 1 | 2 | ## Layout Debugger 3 | 4 | When you're creating layouts or working on CSS it some times help to be able to visualise the scene graph and make live changes to the node properties of your layout. The absolutely best tool for this job is definitely the [Scenic View](http://fxexperience.com/scenic-view/) tool from [FX Experience](http://fxexperience.com/), but some times you just need to get a quick overview as fast as possible. 5 | 6 | ### Debugging a scene 7 | 8 | Simply hit **Alt-Meta-J** to bring up the built in debugging tool *Layout Debugger*. The debugger attaches to the currently active `Scene` and opens a new window that shows you the current scene graph and properties for the currently selected node. 9 | 10 | ### Usage 11 | 12 | While the debugger is active you can hover over any node in your View and it will be automatically highlighted in the debugger window. Clicking a node will also show you the properties of that node. Some of the properties are editable, like `backgroundColor`, `text`, `padding` etc. 13 | 14 | When you hover over the node tree in the debugger, the corresponding node is also highlighted directly in the View. 15 | 16 | ![](https://i.imgur.com/kKH8ydl.gif) 17 | 18 | ### Stop a debugging session 19 | 20 | Close the debugger window by hitting `Esc` and the debugger session ends. You can debug multiple scenes simultaneously, each debugging session will open a new window corresponding to the scene you debug. 21 | 22 | ### Configurable shortcut 23 | 24 | The default shortcut for the debugger can be changed by setting an instance of `KeyCodeCombination` into `FX.layoutDebuggerShortcut`. You can even change the shortcut while the app is running. A good place to configure the shortcut would be in the `init` block of your `App` class. 25 | 26 | ### Adding features 27 | 28 | While this debugger tool is in no way a replacement for Scenic View, we will add features based on *reasonable* [feature requests](https://github.com/edvin/tornadofx/issues). If the feature adds value for simple debugging purposes and can be implemented in a small amount of code, we will try to add it, or better yet, submit a [pull request](https://github.com/edvin/tornadofx/pulls). Have a look at the [source code](https://github.com/edvin/tornadofx/blob/master/src/main/java/tornadofx/LayoutDebugger.kt) to familiarise yourself with the tool. 29 | 30 | 31 | ### Entering fullscreen 32 | 33 | To enter fullscreen you need to get a hold of the current `stage` and call `stage.isFullScreen = true`. The primary stage is the active stage unless you opened a modal window via `view.openModal()` or manually created a stage. The primary stage is available in the variable `FX.primaryStage`. To open the application in fullscreen on startup you should override `start` in your app class: 34 | 35 | ```kotlin 36 | class MyApp : App(MyView::class) { 37 | override fun start(stage: Stage) { 38 | super.start(stage) 39 | stage.isFullScreen = true 40 | } 41 | } 42 | ``` 43 | 44 | In the following example we toggle fullscreen mode in a modal window via a button: 45 | 46 | ```kotlin 47 | button("Toggle fullscreen") { 48 | setOnAction { 49 | with (modalStage) { isFullScreen = !isFullScreen } 50 | } 51 | } 52 | ``` 53 | 54 | # Logging 55 | 56 | `Component` has a lazy initialized instance of `java.util.Logger` named `log`. Usage: 57 | 58 | ```kotlin 59 | log.info { "Log message here" } 60 | ``` 61 | 62 | TornadoFX makes no changes to the logging capabilities of `java.util.Logger`. See the [javadoc](https://docs.oracle.com/javase/8/docs/api/java/util/logging/Logger.html) for more information. 63 | -------------------------------------------------------------------------------- /part3/Snippets.md: -------------------------------------------------------------------------------- 1 | # Snippets 2 | 3 | ### How to load an image to the imageview{...} in more effective way 4 | 5 | This snippet covers 2 issues: 6 | 7 | * How to get correct view size after image loading 8 | * How to load an image to not affect to (do not to freeze) UI 9 | 10 | The snippet below uses 4 techniques with timing each of them. So one can see that more effective way is to use URL as the constructor parameter along with explicit view resizing. 11 | 12 | ```java 13 | import javafx.application.Application 14 | import javafx.scene.image.Image 15 | import javafx.stage.Stage 16 | import tornadofx.* 17 | 18 | 19 | /** 20 | * This is about how to load an image in more effective way. 21 | */ 22 | class LoadImageView : View() { 23 | 24 | override val root = vbox { 25 | 26 | run { 27 | // 1. Simple synchronous way via property 28 | println("-- load synchronously #1 -- ") 29 | val start = System.currentTimeMillis() 30 | imageview { 31 | image = Image("/big_image.png") 32 | println("loaded for ${System.currentTimeMillis() - start} msecs") 33 | } 34 | println("finished after ${System.currentTimeMillis() - start} msecs") 35 | } 36 | 37 | run { 38 | // 2. Simple synchronous way via constructor 39 | println("-- load synchronously #2 --") 40 | val start = System.currentTimeMillis() 41 | imageview("/big_image.png", lazyload = false) { 42 | println("loaded for ${System.currentTimeMillis() - start} msecs") 43 | } 44 | println("finished after ${System.currentTimeMillis() - start} msecs") 45 | } 46 | 47 | run { 48 | // 3. Asynchronous way through outer background task 49 | println("-- load asynchronously #1 -- ") 50 | val start = System.currentTimeMillis() 51 | imageview { 52 | runAsync { 53 | image = Image("/big_image.png") 54 | println("loaded for ${System.currentTimeMillis() - start} msecs") 55 | } 56 | } 57 | println("finished after ${System.currentTimeMillis() - start} msecs") 58 | } 59 | 60 | // Need between 2 async calls 61 | Thread.sleep(1000) 62 | 63 | run { 64 | // 4. Asynchronous way through lazy loading 65 | println("-- load asynchronously #2 -- ") 66 | val start = System.currentTimeMillis() 67 | imageview("/big_image.png") { 68 | setPrefSize(1920.0, 1080.0) 69 | println("loaded for ${System.currentTimeMillis() - start} msecs") 70 | } 71 | println("finished after ${System.currentTimeMillis() - start} msecs") 72 | } 73 | 74 | // After you run you'll see something like this: 75 | // 76 | // -- load synchronously #1 -- 77 | // loaded for 217 msecs 78 | // finished after 218 msecs 79 | // -- load synchronously #2 -- 80 | // loaded for 150 msecs 81 | // finished after 150 msecs 82 | // -- load asynchronously #1 -- 83 | // finished after 75 msecs 84 | // loaded for 171 msecs 85 | // -- load asynchronously #2 -- 86 | // loaded for 7 msecs 87 | // finished after 7 msecs 88 | // 89 | // So the winner is no.4: Asynchronous way through lazy loading 90 | 91 | } 92 | 93 | } 94 | 95 | class LoadImageApp : App(LoadImageView::class) 96 | 97 | fun main(args: Array) { 98 | launch(args) 99 | } 100 | ``` 101 | -------------------------------------------------------------------------------- /part2/Internationalization.md: -------------------------------------------------------------------------------- 1 | # Internationalization 2 | 3 | TornadoFX makes it very easy to support multiple languages in your app. 4 | 5 | ### Internationalization in Components 6 | 7 | Each `Component` has access to a property called `messages` of type `ResourceBundle`. This can be used to look messages in the current locale and assign them to controls programmatically: 8 | 9 | ```kotlin 10 | class MyView: View() { 11 | init { 12 | val helloLabel = Label(messages["hello"]) 13 | } 14 | } 15 | ``` 16 | > A label is programmatically configured to get it's text from a resource bundle 17 | 18 | As well of the shorthand syntax `messages["key"]`, all other functions of the `ResourceBundle` class is available as well. 19 | 20 | The bundle is automatically loaded by looking up a base name equal to the fully qualified class name of the `Component`. For a Component named `views.CustomerList`, the corresponding resource bundle in `/views/CustomerList.properties` will be used. All normal variants of the resource bundle name is supported, see [ResourceBundle Javadocs](https://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html) for more information. 21 | 22 | ### Internationalization in `FXML` 23 | 24 | When an `FXML` file is loaded via the `fxml` delegate function, the corresponding `messages` property of the component will be used in exactly the same way. 25 | 26 | ```xml 27 | 28 | 30 | ``` 31 | > The message with key `hello` will be injected into the label. 32 | 33 | ### Default Global Messages 34 | 35 | You can add a global set of messages with the base name `Messages` (for example `Messages_en.properties`) at the root of the class path. 36 | 37 | ### Automatic lookup in parent bundle 38 | 39 | When a key is not found in the component bundle, or when there is no bundle for the currrent component, the global resource bundle is consulted. As such, you might use the global bundle for all resources, and place overrides in the per component bundle. 40 | 41 | ### Friendly error messages 42 | 43 | In stead of throwing an exception when a key is not available in your bundle, the value will simply be `[key]`. This makes it easy to spot your errors, and your UI is still fully functional while you add the missing keys. 44 | 45 | ### Configuring the locale 46 | 47 | The default locale is the one retrieved from `Locale.getDefault()`. You can configure a different locale by issuing: 48 | 49 | ```kotlin 50 | FX.locale = Locale("my-locale") 51 | ``` 52 | 53 | The global bundle will automatically be changed to the bundle corresponding to the new locale, and all subsequently loaded components will get their bundle in the new locale as well. 54 | 55 | ### Overriding resource bundles 56 | 57 | If you want to change the bundle for a component after it's been initialized, or if you simply want to load a specific bundle without relying on the conventions, simply assign the new bundle to the `messages` property of the component. 58 | 59 | If you want to use the overriden resource bundle to load `FXML`, make sure you change the bundle before you load the root view: 60 | 61 | ```kotlin 62 | class MyView: View() { 63 | init { messages = ResourceBundle.getBundle("MyCustomBundle") } 64 | override val root = HBox by fxml() 65 | } 66 | ``` 67 | > A manually overriden resource bundle is used by the `FXML` file corresponding to the View 68 | 69 | The same technique can be used to override the global bundle by assigning to `FX.messages`. 70 | 71 | ### Startup locale 72 | 73 | You can override the default locale as early as the `App` class `init` function by assigning to `FX.locale`. 74 | 75 | ### Controllers and Fragments as well 76 | 77 | The same conventions are valid for `Controllers` and `Fragments`, since the functionality is made available to their common super class, `Component`. 78 | 79 | -------------------------------------------------------------------------------- /part2/Dependency_Injection.md: -------------------------------------------------------------------------------- 1 | # Dependency Injection 2 | 3 | `View` and `Controller` are singletons, so you need some way to access the instance of a specific component. Tornado FX supports dependency injection, but you can also lookup components with the `find` function. 4 | 5 | ```kotlin 6 | val myController = find(MyController::class) 7 | ``` 8 | 9 | When you call `find`, the component corresponding to the given class is looked up in a global component registry. If it did not exist prior to the call, it will be created and inserted into the registry before the function returns. 10 | 11 | If you want to declare the controller referance as a field member however, you should use the `inject` delegate instead. This is a lazy mechanism, so the actual instance will only be created the first time you call a function on the injected resource. Using `inject` is always prefered, as it allows your components to have circular dependencies. 12 | 13 | ```kotlin 14 | val myController: MyController by inject() 15 | ``` 16 | 17 | ## Third party injection frameworks 18 | 19 | TornadoFX makes it easy to inject resources from a third party dependency injection framework, like for example Guice or Spring. All you have to do is implement the very simple `DIContainer` interface when you start your application. Let's say you have a Guice module configured with a fictive `HelloService`. Start Guice in the `init` block of your `App` class and register the module with TornadoFX: 20 | 21 | ```kotlin 22 | val guice = Guice.createInjector(MyModule()) 23 | 24 | FX.dicontainer = object : DIContainer { 25 | override fun getInstance(type: KClass) 26 | = guice.getInstance(type.java) 27 | } 28 | ``` 29 | > The DIContainer implementation is configured to delegate lookups to `guice.getInstance` 30 | 31 | To inject the `HelloService` configured in `MyModule`, use the `di` delegate instead of the `inject` delegate: 32 | 33 | ```kotlin 34 | val MyView : View() { 35 | val helloService: HelloService by di() 36 | } 37 | ``` 38 | 39 | The `di` delegate accepts any bean type, while `inject` will only allow beans of type `ScopedInstance`, which includes TornadoFX's `View` and `Controller`. This keeps a clean separation between your UI beans and any beans configured in the external dependency injection framework. 40 | 41 | ## Setting up for Spring 42 | 43 | Above the setup for Guice is shown. Setting up for Spring, in this case using `beans.xml` as `ApplicationContext` is done as follows: 44 | 45 | ### beans.xml 46 | 47 | ```xml 48 | 49 | 50 | 57 | 58 | 59 | 60 | 61 | ``` 62 | This sets Spring up to scan for beans. 63 | 64 | ### Application startup 65 | ```kotlin 66 | class SpringExampleApp : App(SpringExampleView::class) { 67 | init { 68 | val springContext = ClassPathXmlApplicationContext("beans.xml") 69 | FX.dicontainer = object : DIContainer { 70 | override fun getInstance(type: KClass): T = springContext.getBean(type.java) 71 | } 72 | } 73 | } 74 | ``` 75 | This initialized the spring context and hooks it into tornadoFX via the `FX.dicontainer`. Now you can inject Spring beans like this: 76 | ```kotlin 77 | val helloBean : HelloBean by di() 78 | ``` 79 | 80 | It is quite common in the Spring world to name a bean like so: 81 | 82 | ```xml 83 | 84 | 85 | 86 | ``` 87 | The bean is then accessible using the `id`. This can be done in tornadoFX too: 88 | 89 | ```kotlin 90 | class SpringExampleApp : App(SpringExampleView::class) { 91 | init { 92 | val springContext = ClassPathXmlApplicationContext("beans.xml") 93 | FX.dicontainer = object : DIContainer { 94 | override fun getInstance(type: KClass): T = springContext.getBean(type.java) 95 | override fun getInstance(type: KClass, name: String): T = springContext.getBean(type.java,name) 96 | } 97 | } 98 | } 99 | ``` 100 | The second `getInstance` uses both the type of the bean and the id of the bean. Instantiating a bean is down as: 101 | 102 | ```kotlin 103 | val helloBean : HelloBean by di("helloWorld") 104 | ``` 105 | -------------------------------------------------------------------------------- /.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 36 | 37 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /part1/12_TornadoFX_IDEA_Plugin.md: -------------------------------------------------------------------------------- 1 | # 12. TornadoFX IDEA Plugin 2 | 3 | To save time in using TornadoFX, you can install a convenient Intellij IDEA plugin to automatically generate project templates, Views, injections, and other TornadoFX features. Of course, you do not have to use this plugin which was done throughout this book. But it adds some convenience to build TornadoFX applications a little more quickly. 4 | 5 | ## Installing the Plugin 6 | 7 | In the Intellij IDEA workspace, press CONTROL + SHIFT + A and type "Plugins", then press ENTER. You will see a dialog to search and install plugins. Click the *Browse Repositories* button (Figure 13.1). 8 | 9 | **Figure 13.1** After bringing up the *Plugins* dialog, click *Browse Repositories*. 10 | 11 | ![](https://i.imgur.com/s3D1Rso.png) 12 | 13 | You will then see a list of 3rd party plugins available to install. Search for "TornadoFX", select it, and click the green *Install* button (Figure 13.2). 14 | 15 | **Figure 13.2** Search for "TornadoFX" and click *Install* 16 | 17 | ![](https://i.imgur.com/y8xGRTe.png) 18 | 19 | Wait for it to finish installing and the restart Intellij IDEA. 20 | 21 | ### TornadoFX Project Templates 22 | 23 | The TornadoFX plugins has some Maven and Gradle project templates to quickly create a configured TornadoFX application. 24 | 25 | In Intellij IDEA, navigate to *File* -> *New* -> *Project...* (Figure 13.3). 26 | 27 | **Figure 13.3** 28 | 29 | ![](https://i.imgur.com/bwDI8ia.png) 30 | 31 | You will then see a dialog to create a new TornadoFX project. You can create Gradle and Maven flavors, with or without OSGi support. Let's create a Gradle one for demonstration (Figure 13.4). 32 | 33 | **Figure 13.4** 34 | 35 | ![](https://i.imgur.com/TIBOyaP.png) 36 | 37 | In the next dialog, give your project a name, a location folder, and a base package with your domain (Figure 13.5). Then click *Finish*. 38 | 39 | **Figure 13.5** 40 | 41 | ![](https://i.imgur.com/VJ3cx2S.png) 42 | 43 | You may be prompted to import the project as a Gradle project, and click on that prompt if you encounter it. You will then have a TornadoFX application configured and set up, including `App`, `View`, and `Styles` entities set up (Figure 13.6). 44 | 45 | **Figure 13.6** 46 | 47 | A generated TornadoFX project with a Gradle configuration. 48 | ![](https://i.imgur.com/ZbwHJ5K.png) 49 | 50 | These steps apply to the Maven and OSGi wizards as well, and do not forget to put your project on a version tracking system like GIT!. 51 | 52 | 53 | ## Creating Views 54 | 55 | You can create Views, Fragments, and FXML files quickly with the plugin. You can right click a folder in the Project, then navigate the popup menu to *New* -> *TornadoFX View* (Figure 13.7). 56 | 57 | **Figure 13.7** 58 | 59 | ![](https://i.imgur.com/WZIRDXE.png) 60 | 61 | You will then come to a dialog to dictate how the `View` is constructed. You even have the option of specifying it as a `Fragment` instead through the *Type* parameter, as well as an FXML via *Kind*. Finally, you can specify the `Node` type for the *Root*, which should default to a `BorderPane`. 62 | 63 | **Figure 13.8** 64 | 65 | ![](https://i.imgur.com/OjJxrjX.png) 66 | 67 | Click *OK* and a new`View` will generated and added to your project (Figure 13.9). 68 | 69 | **Figure 13.9** A new `View` generated with the TornadoFX plugin 70 | 71 | ![](https://i.imgur.com/s1qXuVx.png) 72 | 73 | ## Injecting Components 74 | 75 | One last minor convenience. You can generate TornadoFX `Component` injections quickly with the plugin. For instance, if you right click the class body of the `MainView`, you can generate the `MyOtherView` as an injected property (Figure 13.10). 76 | 77 | **Figure 13.10** 78 | 79 | ![](https://i.imgur.com/3OH9W5N.png) 80 | 81 | ![](https://i.imgur.com/sBpHhZh.png) 82 | 83 | You can then use a dialog to select the `MyOtherView` as the injected property, then click *OK* (Figure 13.11). 84 | 85 | **Figure 13.11** 86 | 87 | ![](https://i.imgur.com/v7As8aH.png) 88 | 89 | ![](https://i.imgur.com/LpIH2RV.png) 90 | 91 | 92 | ## Generating TornadoFX Properties 93 | 94 | One of the most helpful features in the plugin is the ability to convert plain Kotlin properties into TornadoFX properties. 95 | 96 | Say you have a simple domain class called `Client`. 97 | 98 | ```kotlin 99 | class Client(id: Int, name: String) { 100 | val id: Int = id 101 | val name: String = name 102 | } 103 | ``` 104 | 105 | If you click on a property and then the intent lightbulb, or press ALT+ENTER, you should see a menu popup with an option to convert it to a TornadoFX Property (Figure 13.12). 106 | 107 | **Figure 13.12** 108 | 109 | ![](https://i.imgur.com/CJoffXm.png) 110 | 111 | Do this for each property and your `Client` class should now look like this. 112 | 113 | ```kotlin 114 | class Client(id: Int, name: String) { 115 | var id by property(id) 116 | fun idProperty() = getProperty(Client::id) 117 | 118 | var name by property(name) 119 | fun nameProperty() = getProperty(Client::name) 120 | } 121 | ``` 122 | 123 | Your `Client` now uses JavaFX properties instead of plain properties. Notice the primary constructor will pass the intial values to the `property()` delegates, but you do not have to provide initial values if they are not desired. 124 | 125 | This is a time-saving feature when creating domain types for data controls. Next we will cover how to generate `TableView` columns. 126 | 127 | ## Generating Columns for a TableView 128 | 129 | Another handy feature you can do with the plugin also is generating columns for a `TableView`. If you have a `TableView`, you can put the cursor on its declaration, press ALT + ENTER, and get a prompt to generate the columns (Figure 13.13). 130 | 131 | **Figure 13.13** 132 | 133 | 134 | ![](https://i.imgur.com/CnnYV0A.png) 135 | 136 | You will then see a dialog to confirm which `Person` properties to generate the columns on (Figure 14.14). 137 | 138 | **Figure 13.14** 139 | 140 | ![](https://i.imgur.com/DRgZO9M.png) 141 | 142 | Press "OK" and the columns will then be generated for you (Figure 13.15). 143 | 144 | 145 | **Figure 13.15** 146 | 147 | ![](https://i.imgur.com/kiInhLN.png) 148 | 149 | 150 | Note that at the time of writing this guide, for a given `TableView`, this feature only works if the properties on `T` follow the JavaFX convention using the `Property` delgates. 151 | 152 | 153 | 154 | ## Summary 155 | 156 | The TornadoFX plugin has some time-saving conveniences that you are welcome to take advantage of. Of course, you do not have to use the plugin because it merely provides shortcuts and generates code. In time, there may be more features added to the plugin so be sure to follow the project on GitHub for future developments. 157 | -------------------------------------------------------------------------------- /part1/2_Setting_Up.md: -------------------------------------------------------------------------------- 1 | # Setting Up 2 | 3 | To use TornadoFX, there are several options to set up the dependency for your project. Mainstream build automation tools like [Gradle](http://gradle.org/) and [Maven](https://maven.apache.org/) are supported and should have no issues in getting set up. 4 | 5 | Please note that TornadoFX is a Kotlin library, and therefore your project needs to be configured to use Kotlin. For Gradle and Maven configurations, please refer to the [Kotlin Gradle Setup](https://kotlinlang.org/docs/reference/using-gradle.html) and [Kotlin Maven Setup](https://kotlinlang.org/docs/reference/using-maven.html) guides. Make sure your development environment or IDE is equipped to work with Kotlin and has the proper plugins and compilers. 6 | 7 | This guide will use Intellij IDEA to walk through certain examples. IDEA is the IDE of choice to work with Kotlin, although Eclipse has a plugin as well. 8 | 9 | ## If you are using Oracle JDK 10 | 11 | ### Gradle 12 | 13 | For Gradle, you can set up the dependency directly from Maven Central. Provide the desired version number for the `x.y.z` placeholder. 14 | 15 | ``` 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | // Minimum jvmTarget of 1.8 needed since Kotlin 1.1 21 | compileKotlin { 22 | kotlinOptions.jvmTarget= 1.8 23 | } 24 | 25 | dependencies { 26 | implementation 'no.tornado:tornadofx:x.y.z' 27 | } 28 | ``` 29 | 30 | ### Maven 31 | 32 | To import TornadoFX with Maven, add the following dependency to your POM file. Provide the desired version number for the `x.y.z` placeholder. 33 | 34 | Goes into kotlin-maven-plugin block: 35 | 36 | ``` 37 | 38 | 1.8 39 | 40 | ``` 41 | 42 | Then this goes into `dependencies` block: 43 | 44 | ``` 45 | 46 | no.tornado 47 | tornadofx 48 | x.y.z 49 | 50 | ``` 51 | 52 | ## If you are using OpenJDK 53 | 54 | On Ubuntu 19.10, there is no longer any clean way to run OpenJDK 8 with JFX. OpenJDK in general does not include the JFX module libraries -- JFX support for OpenJDK is commonly provided by OpenJFX, which has a maven distribution as well as packages in various Linux distributions. However, the OpenJFX versions are tied to JDK versions (e.g. OpenJFX 8 is compatible with OpenJDK 8), and unfortunately the OpenJFX 8 version is not available in Ubuntu 19.10's packages, nor does it compile from source using the packaged OpenJDK 8. 55 | 56 | The upshot of this is that if you wish to continue using OpenJDK (on Ubuntu), your options are: 57 | 58 | 1) Stay on OpenJDK 8 but install OpenJFX 8 system wide by adding older dependencies to your system (e.g. https://bugs.launchpad.net/ubuntu/+source/openjfx/+bug/1799946/comments/7) 59 | 60 | 2) Alternately, you can bite the bullet and upgrade to OpenJDK 11, and install OpenJFX via Maven/Gradle. This solution is as follows: 61 | 62 | ### Gradle 63 | 64 | a) Upgrade to OpenJDK 11 via your system's packaging tools. 65 | 66 | b) Add the OpenJFX gradle plugin: 67 | 68 | ``` 69 | plugins { 70 | id 'application' 71 | id 'org.openjfx.javafxplugin' version '0.0.8' 72 | } 73 | ``` 74 | 75 | c) Add gradle dependencies: 76 | 77 | ``` 78 | javafx { 79 | version = "11.0.2" 80 | modules = ['javafx.controls', 'javafx.graphics'] 81 | } 82 | 83 | dependencies { 84 | implementation 'no.tornado:tornadofx:x.y.z' 85 | } 86 | ``` 87 | 88 | d) Change kotlin `jvmTarget` to `11`: 89 | 90 | ``` 91 | compileKotlin { 92 | kotlinOptions.jvmTarget = "11" 93 | } 94 | 95 | compileTestKotlin { 96 | kotlinOptions.jvmTarget = "11" 97 | } 98 | ``` 99 | 100 | e) The gradle doesn't support `JPMS` correctly, so we don't need to add `module-info.java` for project. 101 | 102 | In Intellij Idea, you should change the `Project SDK` to `11` in `File -> Project Structure -> Project Settings -> Project`. 103 | 104 | ### Maven 105 | 106 | a) Upgrade to OpenJDK 11 via your system's packaging tools. 107 | 108 | b) Add maven dependencies: 109 | 110 | ``` 111 | 112 | no.tornado 113 | tornadofx 114 | x.y.z 115 | 116 | 117 | 118 | org.openjfx 119 | javafx 120 | 11.0.2 121 | 122 | 123 | org.openjfx 124 | javafx-base 125 | 11.0.2 126 | 127 | 128 | org.openjfx 129 | javafx-controls 130 | 11.0.2 131 | 132 | ``` 133 | 134 | c) Add the OpenJFX builder to your build/plugins section: 135 | ``` 136 | 137 | 138 | ... 139 | 140 | org.openjfx 141 | javafx-maven-plugin 142 | 0.0.3 143 | 144 | MyMainApp 145 | 146 | 147 | 148 | 149 | ``` 150 | 151 | d) Set the language level in your maven build to 11: 152 | ``` 153 | 154 | 155 | ... 156 | 157 | org.apache.maven.plugins 158 | maven-compiler-plugin 159 | 3.8.0 160 | 161 | 11 162 | 163 | 164 | 165 | 166 | ``` 167 | 168 | e) Finally, add src/main/kotlin/module-info.java, to set up permissioning in Java 11's module system. 169 | 170 | ``` 171 | module yourmodulename { 172 | requires javafx.controls; 173 | requires javafx.graphics; 174 | requires tornadofx; 175 | requires kotlin.stdlib; 176 | opens your.package.to.ui.classes; 177 | } 178 | ``` 179 | If you are using IntelliJ, it will provide helpful prompts to add any additional modules that you may need for the build to succeed with your app. StackOverflow has a plethora of questions from Java programmers switching to the module system for the first time, so if you run into a module permissions related issue, google the error message for a solution. 180 | 181 | (Variations on this section for other platforms are welcomed.) 182 | 183 | ## Other Build Automation Solutions 184 | 185 | For instructions on how to use TornadoFX with other build automation solutions, please refer to the [TornadoFX page at the Central Repository](https://search.maven.org/artifact/no.tornado/tornadofx/) 186 | 187 | ## Manual Import 188 | 189 | To manually download and import the JAR file, go to the [TornadoFX release page](https://github.com/edvin/tornadofx/releases) or the [Central Repository](https://search.maven.org). Download the JAR file and configure it into your project. 190 | -------------------------------------------------------------------------------- /part1/1_Why_TornadoFX.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | User interfaces are becoming increasingly critical to the success of consumer and business applications. With the rise of consumer mobile apps and web applications, business users are increasingly holding enterprise applications to a higher standard of quality. They want rich, feature-packed user interfaces that provide immediate insight and navigate complex screens intuitively. More importantly, they want the application to adapt quickly to business changes on a frequent basis. For the developer, this means the application must not only be maintainable but also evolvable. TornadoFX seeks to assist all these objectives and greatly streamline the development of JavaFX UI's. 4 | 5 | While much of the enterprise IT world is pushing HTML5 and cloud-based applications, many businesses are still using desktop UI frameworks like JavaFX. While it does not distribute to large audiences as easily as web applications, JavaFX works well for "in-house" business applications. Its high-performance with large datasets (and the fact it is native Java) make it a practical choice for applications used behind the corporate firewall. 6 | 7 | JavaFX, like many UI frameworks, can quickly become verbose and difficult to maintain. Fortunately, the rapidly growing Kotlin platform has allowed an opportunity to rethink how JavaFX applications are built. 8 | 9 | 10 | # Why TornadoFX? 11 | 12 | In February 2016, JetBrains released [Kotlin](http://kotlinlang.org), a new JVM language that emphasizes pragmatism over convention. Kotlin works at a higher level of abstraction and provides practical language features not available in Java. One of the more important features of Kotlin is its 100% interoperability with existing Java libraries and codebases, including JavaFX. Even more important is in 2017, Google backed Kotlin as an official language for Android. This gives Kotlin a bright future that has already extended beyond mobile apps. 13 | 14 | While JavaFX can be used with Kotlin in the same manner as Java, some believed Kotlin had language features that could streamline and simplify JavaFX development. Well before Kotlin's beta, Eugen Kiss prototyped JavaFX "builders" with KotlinFX. In January 2016, Edvin Syse rebooted the initiative and released TornadoFX. 15 | 16 | TornadoFX seeks to greatly minimize the amount of code needed to build JavaFX applications. It not only includes type-safe builders to quickly lay out controls and user interfaces, but also features dependency injection, delegated properties, control extension functions, and other practical features enabled by Kotlin. TornadoFX is a fine showcase of how Kotlin can simplify codebases, and it tackles the verbosity of UI code with elegance and simplicity. It can work in conjunction with other popular JavaFX libraries such as [ControlsFX](http://fxexperience.com/controlsfx/) and [JFXtras](http://jfxtras.org/). It works especially well with reactive frameworks such as [ReactFX](https://github.com/TomasMikula/ReactFX) as well as [RxJava](https://github.com/ReactiveX/RxJava) and friends (including [RxJavaFX](https://github.com/ReactiveX/RxJavaFX), [RxKotlin](https://github.com/ReactiveX/RxKotlin), and [RxKotlinFX](https://github.com/thomasnield/RxKotlinFX)). 17 | 18 | # Reader Requirements 19 | 20 | This book expects readers to have some knowledge of Kotlin and have spent some time getting acquainted with it. There will be some coverage of Kotlin language features but only to a certain extent. If you have not done so already, read the [JetBrains Kotlin Reference](https://kotlinlang.org/docs/reference/) and spend a good few hours studying it. 21 | 22 | It helps to be familiar with JavaFX but it is not a requirement. Many Kotlin developers reported using TornadoFX successfully without any JavaFX knowledge. What is particularly important to know about JavaFX is its concepts of `ObservableValue` and `Bindings`, which this guide will cover to a good degree. 23 | 24 | # A Motivational Example 25 | 26 | If you have worked with JavaFX before, you might have created a `TableView` at some point. Say you have a given domain type `Person`. TornadoFX allows you to much more concisely create the JavaBeans-like convention used for the JavaFX binding. 27 | 28 | ```kotlin 29 | class Person(id: Int, name: String, birthday: LocalDate) { 30 | val idProperty = SimpleIntegerProperty(id) 31 | var id by idProperty 32 | 33 | val nameProperty = SimpleStringProperty(name) 34 | var name by nameProperty 35 | 36 | val birthdayProperty = SimpleObjectProperty(birthday) 37 | var birthday by birthdayProperty 38 | 39 | val age: Int get() = Period.between(birthday, LocalDate.now()).years 40 | } 41 | ``` 42 | 43 | You can then build an entire "`View`" containing a `TableView` with a small code footprint. 44 | 45 | ```kotlin 46 | class MyView : View() { 47 | 48 | private val persons = listOf( 49 | Person(1, "Samantha Stuart", LocalDate.of(1981,12,4)), 50 | Person(2, "Tom Marks", LocalDate.of(2001,1,23)), 51 | Person(3, "Stuart Gills", LocalDate.of(1989,5,23)), 52 | Person(4, "Nicole Williams", LocalDate.of(1998,8,11)) 53 | ).observable() 54 | 55 | override val root = tableview(persons) { 56 | column("ID", Person::idProperty) 57 | column("Name", Person::nameProperty) 58 | column("Birthday", Person::birthdayProperty) 59 | readonlyColumn("Age", Person::age) 60 | } 61 | } 62 | ``` 63 | 64 | **RENDERED OUTPUT:** 65 | 66 | ![](https://camo.githubusercontent.com/21d91e000f36556b67bea322ff1152199aee483e/68747470733a2f2f692e696d6775722e636f6d2f41474d435038532e706e67) 67 | 68 | Half of that code was just initializing sample data! If you hone in on just the part declaring the `TableView` with four columns (shown below), you will see it took a simple functional construct to build a `TableView`. It will automatically support edits to the fields as well. 69 | 70 | ```kotlin 71 | tableview(persons) { 72 | column("ID", Person::idProperty) 73 | column("Name", Person::nameProperty) 74 | column("Birthday", Person::birthdayProperty) 75 | readonlyColumn("Age", Person::age) 76 | } 77 | ``` 78 | 79 | As shown below, we can use the `cellFormat()` extension function on a `TableColumn`, and create conditional formatting for "Age" values that are less than `18`. 80 | 81 | ```kotlin 82 | tableview { 83 | items = persons 84 | column("ID", Person::idProperty) 85 | column("Name", Person::nameProperty) 86 | column("Birthday", Person::birthdayProperty) 87 | readonlyColumn("Age", Person::age).cellFormat { 88 | text = it.toString() 89 | style { 90 | if (it < 18) { 91 | backgroundColor += c("#8b0000") 92 | textFill = Color.WHITE 93 | } 94 | } 95 | } 96 | } 97 | ``` 98 | 99 | **RENDERED OUTPUT:** 100 | 101 | ![](https://camo.githubusercontent.com/dbc9a172be484e4ab8eeb0accc3799500e9fe791/68747470733a2f2f692e696d6775722e636f6d2f4d7932475545762e706e67) 102 | 103 | These declarations are pure Kotlin code, and TornadoFX is packed with expressive power for dozens of cases like this. This allows you to focus on creating solutions rather than engineering UI code. Your JavaFX applications will not only be turned around more quickly, but also be maintainable and evolvable. 104 | -------------------------------------------------------------------------------- /part2/Property_Delegates.md: -------------------------------------------------------------------------------- 1 | # Property Delegates 2 | 3 | Kotlin is packed with great language features, and [delegated properties](https://kotlinlang.org/docs/reference/delegated-properties.html) are a powerful way to specify how a property works and create re-usable policies for those properties. On top of the ones that exist in Kotlin's standard library, TornadoFX provides a few more property delegates that are particularly helpful for JavaFX development. 4 | 5 | ### Single Assign 6 | 7 | It is often ideal to initialize properties immediately upon construction. But inevitably there are times when this simply is not feasible. When a property needs to delay its initialization until it is first called, a lazy delegate is typically used. You specify a lambda instructing how the property value is initialized when its getter is called the first time. 8 | 9 | ```kotlin 10 | val fooValue by lazy { buildExpensiveFoo() } 11 | ``` 12 | 13 | But there are situations where the property needs to be assigned later not by a value-supplying lambda, but rather some external entity at a later time. When we leverage type-safe builders we may want to save a `Button` to a class-level property so we can reference it later. If we do not want `myButton` to be nullable, we need to use the [`lateinit` modifier](https://kotlinlang.org/docs/reference/properties.html#late-initialized-properties). 14 | 15 | ```kotlin 16 | class MyView: View() { 17 | 18 | lateinit var myButton: Button 19 | 20 | override val root = vbox { 21 | myButton = button("New Entry") 22 | } 23 | } 24 | ``` 25 | 26 | The problem with `lateinit` is it can be assigned multiple times by accident, and it is not necessarily thread safe. This can lead to classic bugs associated with mutability, and you really should strive for immutability as much as possible ( *Effective Java* by Bloch, Item #13). 27 | 28 | By leveraging the `singleAssign()` delegate, you can guarantee that property is *only* assigned once. Any subsequent assignment attempts will throw a runtime error, and so will accessing it before a value is assigned. This effectively gives us the guarantee of immutability, although it is enforced at runtime rather than compile time. 29 | 30 | ```kotlin 31 | class MyView: View() { 32 | 33 | var myButton: Button by singleAssign() 34 | 35 | override val root = vbox { 36 | myButton = button("New Entry") 37 | } 38 | } 39 | ``` 40 | 41 | Even though this single assignment is not enforced at compile time, infractions can be captured early in the development process. Especially as complex builder designs evolve and variable assignments move around, `singleAssign()` is an effective tool to mitigate mutability problems and allow flexible timing for property assignments. 42 | 43 | By default, `singleAssign()` synchronizes access to its internal value. You should leave it this way especially if your application is multithreaded. If you wish to disable synchronization for whatever reason, you can pass a `SingleAssignThreadSafetyMode.NONE` value for the policy. 44 | 45 | ```kotlin 46 | var myButton: Button by singleAssign(SingleAssignThreadSafetyMode.NONE) 47 | ``` 48 | 49 | ### JavaFX Property Delegate 50 | 51 | Do not confuse the JavaFX `Property` with a standard Java/Kotlin "property". The `Property` is a special type in `JavaFX` that maintains a value internally and notifies listeners of its changes. It is proprietary to JavaFX because it supports binding operations, and will notify the UI when it changes. The `Property` is a core feature of JavaFX and has its own JavaBeans-like pattern. 52 | 53 | This pattern is pretty verbose however, and even with Kotlin's syntax efficiencies it still is pretty verbose. You have to declare the traditional getter/setter as well as the `Property` item itself. 54 | 55 | ```kotlin 56 | class Bar { 57 | private val fooProperty by lazy { SimpleObjectProperty() } 58 | fun fooProperty() = fooProperty 59 | var foo: T 60 | get() = fooProperty.get() 61 | set(value) = fooProperty.set(value) 62 | } 63 | ``` 64 | 65 | Fortunately, TornadoFX can abstract most of this away. By delegating a Kotlin property to a JavaFX `property()`, TornadoFX will get/set that value against a new `Property` instance. To follow JavaFX's convention and provide the `Property` object to UI components, you can create a function that fetches the `Property` from TornadoFX and returns it. 66 | 67 | ```kotlin 68 | class Bar { 69 | var foo by property() 70 | fun fooProperty() = getProperty(Bar::foo) 71 | } 72 | ``` 73 | 74 | Especially as you start working with `TableView` and other complex controls, you will likely find this pattern helpful when creating model classes, and this pattern is used in several places throughout this book. 75 | 76 | Note you do not have to specify the generic type if you have an initial value to provide to the property. In the below example, it will infer the type as `String. 77 | 78 | ```kotlin 79 | class Bar { 80 | var foo by property("baz") 81 | fun fooProperty() = getProperty(Bar::foo) 82 | } 83 | ``` 84 | 85 | #### Alternative Property Syntax 86 | 87 | There is also an alternative syntax which produces almost the same result: 88 | 89 | ```kotlin 90 | import tornadofx.getValue 91 | import tornadofx.setValue 92 | 93 | class Bar { 94 | val fooProperty = SimpleStringProperty() 95 | var foo by fooProperty 96 | } 97 | ``` 98 | 99 | Here you define the JavaFX property manually and delegate the getters and setters directly from the property. This might look cleaner to you, and so you are free to choose whatever syntax you are most comfortable with. However, the first alternative creates a JavaFX compliant property in that it exposes the `Property` via a function called `fooProperty()`, while the latter simply exposes a variable called `fooProperty`. For TornadoFX there is no difference, but if you interact with legacy libraries that require a property function you might need to stick with the first one. 100 | 101 | #### Null safety of Properties 102 | 103 | By default properties will have a [Platform Type](https://kotlinlang.org/docs/reference/java-interop.html#notation-for-platform-types) with uncertain nullability and completely ignore the null safety of Kotlin: 104 | 105 | ```kotlin 106 | class Bar { 107 | var foo by property() 108 | fun fooProperty() = getProperty(Bar::foo) 109 | 110 | val bazProperty = SimpleStringProperty() 111 | var baz by bazProperty 112 | 113 | init { 114 | foo = null 115 | foo.length // Will throw NPE during runtime 116 | 117 | baz = null 118 | baz.length // Will throw NPE during runtime 119 | } 120 | } 121 | ``` 122 | 123 | To remedy this you can set the type of your property on the `var` (not on the Property-Object itself!). But keep in mind to set a default 124 | value on the property object when you set the var to be nullable or you will get an NPE anyways: 125 | 126 | ```kotlin 127 | class Bar { 128 | var foo:String by property("") // Non-nullable String with default value 129 | fun fooProperty() = getProperty(Bar::foo) 130 | 131 | val bazProperty = SimpleStringProperty() // No default needed 132 | var baz: String? by bazProperty // Nullable String 133 | 134 | init { 135 | foo = null // Will no longer compile 136 | foo.length 137 | 138 | baz = null 139 | baz.length // Will no longer compile 140 | } 141 | } 142 | ``` 143 | 144 | ### FXML Delegate 145 | 146 | If you have a given `MyView` View with a neighboring FXML file `MyView.fxml` defining the layout, the `fxid()` property delegate will retrieve the control defined in the FXML file. The control must have an `fx:id` that is the same name as the variable. 147 | 148 | ```xml 149 |