CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-apache-groovy--groovy-swing

SwingBuilder for creating Swing GUIs in Groovy - provides DSL for declarative UI construction

Pending
Overview
Eval results
Files

data-binding.mddocs/

Data Binding

Groovy Swing provides a comprehensive data binding system that connects UI components to data models automatically, enabling declarative data synchronization.

Core Binding Concepts

The binding system is built around ValueModel interfaces and binding factories.

// Binding factories
def bind(Map attributes = [:])
def bindGroup(Map attributes = [:])
def bindProxy(Map attributes = [:])

ValueModel Interface

The foundation of the binding system for representing bindable values.

interface ValueModel {
    Object getValue()
    void setValue(Object value)
    Class getType()
    boolean isEditable()
    void addValueChangeListener(PropertyChangeListener listener)
    void removeValueChangeListener(PropertyChangeListener listener)
}

ValueModel Implementations

Concrete implementations of ValueModel for different data scenarios.

// Basic value holder
class ValueHolder implements ValueModel {
    ValueHolder()
    ValueHolder(Object value)
    ValueHolder(Object value, Class type)
}

// Property-based model
class PropertyModel implements ValueModel {
    PropertyModel(Object bean, String propertyName)
}

// Nested property model
class NestedValueModel implements ValueModel {
    NestedValueModel(ValueModel parentModel, String childPropertyName)
}

// Closure-based model
class ClosureModel implements ValueModel {
    ClosureModel(Closure readClosure)
    ClosureModel(Closure readClosure, Closure writeClosure)
}

// Form data model
class FormModel {
    FormModel()
    FormModel(Map initialValues)
    
    ValueModel getValueModel(String propertyName)
    void setProperty(String name, Object value)
    Object getProperty(String name)
}

ValueModel Examples

// Basic value holder
def nameModel = new ValueHolder('John Doe', String)
nameModel.setValue('Jane Smith')
println nameModel.getValue()  // 'Jane Smith'

// Property model for bean binding
class Person {
    String name
    int age
}

def person = new Person(name: 'Alice', age: 30)
def nameModel = new PropertyModel(person, 'name')
def ageModel = new PropertyModel(person, 'age')

// Nested property model
def addressModel = new PropertyModel(person, 'address')
def cityModel = new NestedValueModel(addressModel, 'city')

// Closure-based model
def dynamicModel = new ClosureModel(
    { -> person.name.toUpperCase() },  // Read closure
    { value -> person.name = value.toLowerCase() }  // Write closure
)

// Form model
def formModel = new FormModel([
    firstName: 'John',
    lastName: 'Doe',
    email: 'john@example.com'
])

Binding Factories

Factory methods for creating different types of data bindings.

Basic Binding

// Simple property binding
def bind(Map attributes)

// Common attributes:
// source: Object - source object
// sourceProperty: String - property name on source
// target: Object - target object (optional, defaults to component)
// targetProperty: String - property name on target (optional)
// converter: Closure - value converter (optional)
// reverseConverter: Closure - reverse converter (optional)
// validator: Closure - validation closure (optional)

Binding Examples

def swing = new SwingBuilder()
def person = new Person(name: 'John', age: 25, email: 'john@example.com')

swing.frame(title: 'Data Binding Demo') {
    panel {
        gridBagLayout()
        
        // Simple text field binding
        label(text: 'Name:', constraints: gbc(gridx: 0, gridy: 0))
        textField(
            columns: 20,
            text: bind(source: person, sourceProperty: 'name'),
            constraints: gbc(gridx: 1, gridy: 0)
        )
        
        // Binding with converter
        label(text: 'Age:', constraints: gbc(gridx: 0, gridy: 1))
        textField(
            columns: 20,
            text: bind(
                source: person, 
                sourceProperty: 'age',
                converter: { it.toString() },
                reverseConverter: { Integer.parseInt(it) }
            ),
            constraints: gbc(gridx: 1, gridy: 1)
        )
        
        // Binding with validation
        label(text: 'Email:', constraints: gbc(gridx: 0, gridy: 2))
        textField(
            columns: 20,
            text: bind(
                source: person, 
                sourceProperty: 'email',
                validator: { it?.contains('@') }
            ),
            constraints: gbc(gridx: 1, gridy: 2)
        )
        
        // Checkbox binding
        checkBox(
            text: 'Is Active',
            selected: bind(source: person, sourceProperty: 'active'),
            constraints: gbc(gridx: 1, gridy: 3)
        )
    }
}

Binding Groups

Binding groups for managing multiple related bindings.

def bindGroup(Map attributes = [:])

// BindGroup methods:
// bind() - Activate all bindings in group
// unbind() - Deactivate all bindings in group  
// update() - Update all bindings in group
// reverseUpdate() - Reverse update all bindings

Binding Group Examples

swing.frame(title: 'Binding Group Demo') {
    def person = new Person()
    
    // Create binding group
    def personBindings = bindGroup()
    
    panel {
        gridLayout(rows: 4, columns: 2)
        
        label(text: 'First Name:')
        textField(text: bind(group: personBindings, source: person, sourceProperty: 'firstName'))
        
        label(text: 'Last Name:')
        textField(text: bind(group: personBindings, source: person, sourceProperty: 'lastName'))
        
        label(text: 'Age:')
        spinner(
            value: bind(group: personBindings, source: person, sourceProperty: 'age'),
            model: spinnerNumberModel(minimum: 0, maximum: 120)
        )
        
        button(text: 'Update All') {
            actionPerformed {
                personBindings.update()
            }
        }
        
        button(text: 'Reset Form') {
            actionPerformed {
                personBindings.reverseUpdate()
            }
        }
    }
}

Advanced Binding Features

Binding Proxies

Proxy objects for advanced binding scenarios.

def bindProxy(Map attributes = [:])

// BindingProxy provides:
// - Delayed binding resolution
// - Dynamic target switching
// - Conditional binding activation

Complex Binding Scenarios

// List binding with selection
swing.frame(title: 'List Binding') {
    def items = ['Item 1', 'Item 2', 'Item 3'] as ObservableList
    def selectedItem = new ValueHolder()
    
    splitPane {
        // List with items binding
        scrollPane(constraints: 'left') {
            list(
                listData: bind(source: items),
                selectedValue: bind(source: selectedItem, sourceProperty: 'value')
            )
        }
        
        // Detail panel bound to selection
        panel(constraints: 'right') {
            label(text: bind(
                source: selectedItem,
                sourceProperty: 'value',
                converter: { "Selected: $it" }
            ))
        }
    }
}

// Table binding with row selection
swing.frame(title: 'Table Binding') {
    def tableData = [
        [name: 'John', age: 30],
        [name: 'Jane', age: 25]
    ] as ObservableList
    
    def selectedRow = new ValueHolder()
    
    table(
        model: bind(source: tableData),
        selectedElement: bind(source: selectedRow, sourceProperty: 'value')
    ) {
        propertyColumn(header: 'Name', propertyName: 'name')
        propertyColumn(header: 'Age', propertyName: 'age')
    }
}

Component-Specific Binding Support

Different components support different binding properties through specialized property classes.

// AbstractButton binding properties
// - selected, text, icon, enabled, visible

// JTextField binding properties  
// - text, enabled, editable, visible

// JList binding properties
// - listData, selectedIndex, selectedValue, selectedValues

// JTable binding properties
// - model, selectedRow, selectedColumn, selectedElement

// JComboBox binding properties
// - items, selectedItem, selectedIndex

// JSpinner binding properties
// - value, enabled

// JSlider binding properties
// - value, minimum, maximum, enabled

// JProgressBar binding properties
// - value, minimum, maximum, string, stringPainted

Component Binding Examples

def model = new FormModel([
    items: ['A', 'B', 'C'],
    selectedItem: 'B',
    percentage: 50,
    description: 'Sample text'
])

swing.panel {
    vbox {
        // Combo box binding
        comboBox(
            items: bind(source: model, sourceProperty: 'items'),
            selectedItem: bind(source: model, sourceProperty: 'selectedItem')
        )
        
        // Slider with progress bar
        slider(
            minimum: 0, maximum: 100,
            value: bind(source: model, sourceProperty: 'percentage')
        )
        progressBar(
            minimum: 0, maximum: 100,
            value: bind(source: model, sourceProperty: 'percentage'),
            stringPainted: true
        )
        
        // Text area binding
        scrollPane {
            textArea(
                rows: 5, columns: 30,
                text: bind(source: model, sourceProperty: 'description')
            )
        }
    }
}

Binding Lifecycle

Understanding binding activation and cleanup.

// Binding lifecycle methods
// bind() - Activate binding and start synchronization
// unbind() - Deactivate binding and stop synchronization
// update() - Force immediate source-to-target update
// reverseUpdate() - Force immediate target-to-source update

Manual Binding Management

// Creating and managing bindings manually
def nameBinding = swing.bind(
    source: person,
    sourceProperty: 'name',
    target: textField,
    targetProperty: 'text'
)

// Activate binding
nameBinding.bind()

// Update from source
nameBinding.update()

// Update from target  
nameBinding.reverseUpdate()

// Deactivate when done
nameBinding.unbind()

Install with Tessl CLI

npx tessl i tessl/maven-org-apache-groovy--groovy-swing

docs

borders.md

components.md

core-builder.md

data-binding.md

index.md

layouts.md

menus.md

tables.md

tile.json