TornadoFX v2

repository·master·Indexed 41 days ago

https://github.com/edvin/tornadofx2

An experimental, Gradle-based JavaFX framework for Kotlin designed for building desktop applications using MVC/MVP patterns. It provides type-safe GUI and CSS builders, built-in dependency injection, an EventBus with thread targeting, and first-class FXML support. Version 2.0.0-SNAPSHOT requires JDK 11+, Kotlin 1.3+, and JavaFX 13+.

Tokens
13.8K
Snippets
60
Records
68
Agent score
100%

What's inside TornadoFX

  1. TornadoFX v2 Core Features

    master

    TornadoFX v2 is a JavaFX framework for Kotlin that provides several developer-focused features:

    • Architectural Patterns: Supports MVC, MVP, and their derivatives.
    • Dependency Injection: Built-in DI support.
    • Type-Safe Builders: Provides type-safe GUI and CSS builders.
    • FXML: First-class support for FXML files.
    • Concurrency: Async task execution.
    • Event System: EventBus with thread targeting.
    • Developer Experience: Hot reload of Views and Stylesheets, and a 'zero config' approach (no XML or annotations required for many tasks).
  2. Install TornadoFX v2 via Gradle

    master

    To use TornadoFX v2 in your Gradle project, you must first add the Sonatype snapshots repository to your repositories block, then add the TornadoFX dependency to your dependencies block. Note that this is currently an experimental branch using 2.0.0-SNAPSHOT.

    repositories {
        maven {
            url = uri("https://oss.sonatype.org/content/repositories/snapshots")
        }
    }
    
    dependencies {
        implementation("no.tornado:tornadofx:2.0.0-SNAPSHOT")
    }
  3. Configure Column Resizing Strategies

    master

    Once smartResize() is enabled, you can define how individual columns behave using several DSL methods. These strategies determine how columns consume available space or respond to manual resizing.

    Available Strategies:

    • Fixed Width: The column maintains a specific width and cannot be resized by the user.
    • Preferred Width: The column starts at a specific width but can be part of the resizing logic.
    • Percentage Width: The column occupies a specific percentage of the total available width.
    • Weighted Width: Columns share remaining space based on a weight (similar to CSS flexbox).
    • Content Width: The column automatically adjusts to fit its content, with optional padding and constraints.
    • Remaining Width: The column expands to fill any leftover space in the table.
    column("ID") { fixedWidth(50.0) }
    column("Name") { weightedWidth(1.0) }
    column("Status") { pctWidth(20.0) }
    column("Description") { remainingWidth() }
    column("Notes") { contentWidth(padding = 5.0) }
  4. Manage form validation with ValidationContext

    master

    The ValidationContext is the central hub for managing validation logic within a form or a group of inputs. It tracks multiple Validator instances, maintains a global valid state, and provides a decorationProvider to define how errors are visually represented on UI nodes.

    Key Capabilities

    • Register Validators: Use addValidator to attach validation logic to properties or UI controls.
    • Global State: Access the valid property to determine if the entire context is currently valid.
    • Manual Validation: Trigger validation for all registered fields or specific ones using validate().
    • Custom Decoration: Override decorationProvider to change how error messages (like tooltips or icons) are displayed.

    Validation Triggers

    Control when validation occurs using ValidationTrigger:

    • ValidationTrigger.OnBlur: Validates when the associated node loses focus.
    • ValidationTrigger.OnChange(delay: Long): Validates when the property changes. You can specify a delay in milliseconds to debounce rapid input.
    • ValidationTrigger.None: No automatic validation occurs.

    Severity Levels

    Validators return a ValidationMessage with one of the following severities:

    • ValidationSeverity.Error (Invalidates the field)
    • ValidationSeverity.Warning
    • ValidationSeverity.Info
    • ValidationSeverity.Success

    Note: A field is considered valid if the validator returns null or a message with a severity other than Error.

    val context = ValidationContext()
    
    // Add a validator to a property
    context.addValidator(textField, myProperty) { value ->
        if (value.isNullOrBlank()) error("Field cannot be empty") else null
    }
    
    // Check if the whole form is valid
    if (context.valid) {
        // Proceed with submission
    }
  5. How the EventBus manages scopes and threading

    master

    The EventBus uses Scope to partition events. When you subscribe to an event with a specific Scope, that subscription is only triggered if the FXEvent being fired has a matching scope property.

    Threading is managed via the runOn property of the FXEvent class:

    • If runOn is ApplicationThread and the event is fired from a background thread, the EventBus uses Platform.runLater to ensure the subscriber's action runs on the JavaFX Application Thread.
    • If runOn is BackgroundThread and the event is fired from the Application Thread, the EventBus spawns a new thread to execute the subscriber's action.
    • If the threading requirements of the event already match the current thread, the action is executed immediately.
  6. Use SortedFilteredList for sortable and filterable data

    master

    A SortedFilteredList is a wrapper for an ObservableList that allows you to easily sort and filter data in JavaFX controls like TableView or ListView. It manages an underlying list, a FilteredList, and a SortedList to provide a seamless interface.

    Key Features:

    • Automatic Sorting/Filtering: The list handles the complexity of applying predicates and sorting logic.
    • Binding: You can bind the list directly to UI controls using bindTo().
    • Dynamic Filtering: Use filterWhen to automatically update the filter predicate whenever an observable value (like a TextField's text) changes.
    • Data Updates: You can update the underlying data at any time using items.setAll() or items.addAll().
    val table = TableView<Person>()
    val data = SortedFilteredList(persons).bindTo(table)
  7. Use BindingAware properties in ViewModels for validation support

    master

    When building ViewModels in TornadoFX, use the BindingAware property implementations instead of standard JavaFX properties if you need validation to work correctly.

    These properties automatically register their relationship with the ViewModel when they are bound to an observable value. This registration allows validators to look up the correct ValidationContext from the ViewModel that the property is bound to.

    To ensure the binding is recorded, you must use the bind() or bindBidirectional() methods provided by these classes. This mechanism relies on the property being part of a ViewModel (the bean).

  8. Enable Smart Resizing for TableView or TreeTableView

    master

    To enable automatic, intelligent column resizing in a TableView or TreeTableView, set the columnResizePolicy using the smartResize() extension function. This allows you to use various resizing strategies (like weight, percentage, or content-based sizing) that react to the available table width and manual user adjustments.

    tableView {
        smartResize()
        column("Name") { 
            // configuration
        }
    }
  9. Create a System Tray icon

    master

    You can add a system tray icon to your application using the trayicon function. This function handles the integration with java.awt.SystemTray and provides a DSL for configuring the icon and its menu.

    Key features:

    • implicitExit: If true, the application will not exit automatically when all windows are closed.
    • autoSize: If true, the tray icon image will be automatically resized.
    • menu DSL: Allows defining a PopupMenu with items and checkboxes.
    • setOnMouseClicked: Allows attaching click listeners, with an option to run the action on the JavaFX thread (fxThread = true).
    trayicon(myImage, "My App Tooltip") {
        menu("Options") {
            item("Settings") { 
                // action
            }
            checkboxItem("Enable Feature", state = true) { isEnabled ->
                // action
            }
        }
        setOnMouseClicked(fxThread = true) { 
            // handle click
        }
    }
  10. Handle user selection in a ListView

    master

    You can easily respond to user interactions in a ListView using extension functions.

    • onUserSelect(clickCount, action): Triggers the provided action when an item is selected via a mouse click (default 2 clicks) or by pressing the ENTER key.
    • onUserDelete(action): Triggers the action when the BACK_SPACE key is pressed while an item is selected.
    • bindSelected(property): Binds the ListView selection to a JavaFX Property<T>.
    • bindSelected(model): Binds the ListView selection to an ItemViewModel<T>'s itemProperty.
    listView.onUserSelect(clickCount = 2) { selectedItem ->
        println("Selected: $selectedItem")
    }
    
    listView.onUserDelete { item ->
        println("Deleting: $item")
    }
    
    // Binding to a property
    val selectedPerson = SimpleObjectProperty<Person>()
    listView.bindSelected(selectedPerson)