A declarative Swing GUI builder for Groovy applications that provides a concise and maintainable way to create complex Swing user interfaces
—
Comprehensive data binding framework for connecting GUI components to data models with validation, conversion, and automatic synchronization.
The foundation of the binding system providing contracts for data synchronization.
/**
* Complete bidirectional binding interface with converter/validator support
*/
interface FullBinding extends BindingUpdatable {
SourceBinding getSourceBinding()
TargetBinding getTargetBinding()
void setSourceBinding(SourceBinding source)
void setTargetBinding(TargetBinding target)
void setValidator(Closure validator)
Closure getValidator()
void setConverter(Closure converter)
Closure getConverter()
void setReverseConverter(Closure reverseConverter)
Closure getReverseConverter()
}
/**
* Base interface for all binding update operations
*/
interface BindingUpdatable {
void bind()
void unbind()
void rebind()
void update()
void reverseUpdate()
}
/**
* Source endpoint of a binding relationship
*/
interface SourceBinding extends BindingUpdatable {
Object getSourceValue()
void setSourceBinding(PropertyChangeListener listener)
}
/**
* Target endpoint of a binding relationship
*/
interface TargetBinding extends BindingUpdatable {
void setTargetValue(Object value)
}
/**
* Event-triggered binding interface
*/
interface TriggerBinding {
FullBinding getFullBinding()
void setFullBinding(FullBinding fullBinding)
void bind()
void unbind()
}The primary factory for creating and configuring data bindings through the builder DSL.
/**
* Primary factory for creating data bindings
*/
class BindFactory {
/** Create binding between source and target */
Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attributes)
/** Handle binding attributes in component definitions */
boolean bindingAttributeDelegate(FactoryBuilderSupport builder, Object node, Map attributes)
}
// Binding DSL usage
bind(source: sourceObject, sourceProperty: 'propertyName',
target: targetComponent, targetProperty: 'text',
converter: { value -> value.toString() },
validator: { value -> value != null })Usage Examples:
import groovy.swing.SwingBuilder
import groovy.model.ValueHolder
def swing = new SwingBuilder()
def model = new ValueHolder('Initial Text')
swing.frame(title: 'Binding Example') {
panel {
// Bind text field to model
textField(id: 'textInput', columns: 20)
bind(source: model, sourceProperty: 'value',
target: textInput, targetProperty: 'text')
// Two-way binding with validation
textField(id: 'numberInput', columns: 10)
bind(source: model, sourceProperty: 'value',
target: numberInput, targetProperty: 'text',
converter: { it.toString() },
reverseConverter: {
try { Integer.parseInt(it) }
catch (NumberFormatException e) { 0 }
},
validator: { it instanceof Number && it >= 0 })
}
}Observable data containers that support property change notifications.
/**
* Interface for observable values with change notification
*/
interface ValueModel {
Object getValue()
void setValue(Object value)
Class getType()
boolean isEditable()
}
/**
* Basic implementation of ValueModel
*/
class ValueHolder implements ValueModel {
ValueHolder(Object value = null, Class type = Object, boolean editable = true)
Object getValue()
void setValue(Object value)
Class getType()
boolean isEditable()
void addPropertyChangeListener(PropertyChangeListener listener)
void removePropertyChangeListener(PropertyChangeListener listener)
}
/**
* Property-based value model with reflection support
*/
class PropertyModel implements ValueModel {
PropertyModel(Object bean, String propertyName)
Object getValue()
void setValue(Object value)
Class getType()
boolean isEditable()
void addPropertyChangeListener(PropertyChangeListener listener)
void removePropertyChangeListener(PropertyChangeListener listener)
}
/**
* Nested property access model for complex object graphs
*/
class NestedValueModel implements ValueModel {
NestedValueModel(Object root, String propertyPath)
Object getValue()
void setValue(Object value)
Class getType()
boolean isEditable()
void addPropertyChangeListener(PropertyChangeListener listener)
void removePropertyChangeListener(PropertyChangeListener listener)
}
/**
* Closure-based computed model
*/
class ClosureModel implements ValueModel {
ClosureModel(Closure closure)
Object getValue()
void setValue(Object value)
Class getType()
boolean isEditable()
void addPropertyChangeListener(PropertyChangeListener listener)
void removePropertyChangeListener(PropertyChangeListener listener)
}Usage Examples:
import groovy.model.*
// Basic value holder
def nameModel = new ValueHolder('John Doe', String)
nameModel.addValueChangeListener { evt ->
println "Name changed from ${evt.oldValue} to ${evt.newValue}"
}
// Property model for existing bean
class Person {
String name
int age
}
def person = new Person(name: 'Alice', age: 30)
def nameProperty = new PropertyModel(person, 'name')
def ageProperty = new PropertyModel(person, 'age')
// Nested property model
def addressProperty = new NestedValueModel(person, 'address.street')
// Computed model
def fullNameModel = new ClosureModel {
"${person.firstName} ${person.lastName}"
}Specialized binding classes for different Swing components.
/**
* Text component binding with document listeners
*/
class JTextComponentProperties {
static Object getText(JTextComponent self)
static void setText(JTextComponent self, Object value)
}
/**
* Button selection state binding
*/
class AbstractButtonProperties {
static Object getSelected(AbstractButton self)
static void setSelected(AbstractButton self, Object value)
}
/**
* Slider value binding
*/
class JSliderProperties {
static Object getValue(JSlider self)
static void setValue(JSlider self, Object value)
}
/**
* Combo box selection and item binding
*/
class JComboBoxProperties {
static Object getSelectedItem(JComboBox self)
static void setSelectedItem(JComboBox self, Object value)
static Object getSelectedIndex(JComboBox self)
static void setSelectedIndex(JComboBox self, Object value)
}
/**
* List selection binding
*/
class JListProperties {
static Object getSelectedElement(JList self)
static Object getSelectedElements(JList self)
static Object getSelectedIndex(JList self)
static Object getSelectedIndices(JList self)
static Object getSelectedValue(JList self)
static Object getSelectedValues(JList self)
}
/**
* Table selection and data binding
*/
class JTableProperties {
static Object getSelectedElement(JTable self)
static Object getSelectedElements(JTable self)
static Object getSelectedRow(JTable self)
static Object getSelectedRows(JTable self)
static Object getSelectedColumn(JTable self)
static Object getSelectedColumns(JTable self)
}
/**
* Spinner value binding
*/
class JSpinnerProperties {
static Object getValue(JSpinner self)
static void setValue(JSpinner self, Object value)
}Concrete implementations of the binding interfaces for various scenarios.
/**
* Base implementation of FullBinding
*/
abstract class AbstractFullBinding implements FullBinding {
SourceBinding sourceBinding
TargetBinding targetBinding
Closure validator
Closure converter
Closure reverseConverter
void bind()
void unbind()
void rebind()
void update()
}
/**
* Property-based binding implementation
*/
class PropertyBinding extends AbstractFullBinding {
PropertyBinding(Object source, String sourceProperty,
Object target, String targetProperty)
}
/**
* Property path binding for nested properties
*/
class PropertyPathFullBinding extends AbstractFullBinding {
PropertyPathFullBinding(Object source, String sourcePath,
Object target, String targetPath)
}
/**
* Closure-based source binding
*/
class ClosureSourceBinding implements SourceBinding {
ClosureSourceBinding(Closure closure)
Object getSourceValue()
void setSourceBinding(PropertyChangeListener listener)
void bind()
void unbind()
void rebind()
void update()
}
/**
* Event-triggered binding
*/
class EventTriggerBinding implements TriggerBinding {
EventTriggerBinding(Object source, String eventName,
FullBinding fullBinding = null, Closure closure = null)
}
/**
* Timer-based binding updates
*/
class SwingTimerTriggerBinding implements TriggerBinding {
SwingTimerTriggerBinding(int delay, FullBinding fullBinding = null)
}
/**
* Property-based binding with configurable update strategies
*/
class PropertyBinding extends AbstractFullBinding {
PropertyBinding(Object sourceBean, String sourceProperty,
Object targetBean, String targetProperty,
UpdateStrategy updateStrategy = UpdateStrategy.MIXED)
static enum UpdateStrategy {
/** EDT if on EDT, otherwise invokeLater */
MIXED,
/** Always use invokeLater */
ASYNC,
/** invokeAndWait if not on EDT, direct if on EDT */
SYNC,
/** Execute in same thread */
SAME,
/** Execute outside EDT */
OUTSIDE,
/** Always execute in background thread */
DEFER
}
}
/**
* Bidirectional property binding between two properties
*/
class MutualPropertyBinding extends PropertyBinding {
MutualPropertyBinding(Object sourceBean, String sourceProperty,
Object targetBean, String targetProperty,
UpdateStrategy updateStrategy = UpdateStrategy.MIXED)
}
/**
* Aggregate multiple bindings for group operations
*/
class AggregateBinding implements FullBinding {
void addBinding(FullBinding binding)
void removeBinding(FullBinding binding)
List<FullBinding> getBindings()
}Advanced binding constructs for complex scenarios.
/**
* Grouped bindings for batch operations
*/
class BindGroupFactory {
Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attributes)
}
/**
* Binding proxy for complex binding scenarios
*/
class BindingProxy {
BindingProxy(Object target, String property, Map args = [:])
void bind(Object source, String sourceProperty)
void unbind()
}
/**
* Multiple binding aggregation
*/
class AggregateBinding extends AbstractFullBinding {
AggregateBinding(FullBinding... bindings)
void addBinding(FullBinding binding)
void removeBinding(FullBinding binding)
}Usage Examples:
// Binding group for form validation
bindGroup {
bind(source: model.name, target: nameField, targetProperty: 'text')
bind(source: model.email, target: emailField, targetProperty: 'text')
bind(source: model.age, target: ageSpinner, targetProperty: 'value')
}
// Binding proxy for complex property chains
def proxy = new BindingProxy(someComplexObject, 'deeply.nested.property')
proxy.bind(textField, 'text')
// Timer-based binding for real-time updates
def timerBinding = new SwingTimerTriggerBinding(1000) // Update every second
timerBinding.fullBinding = bind(source: statusModel, target: statusLabel)Built-in support for data validation and type conversion in bindings.
// Binding with validation
bind(source: model, sourceProperty: 'value',
target: textField, targetProperty: 'text',
validator: { value ->
if (value == null || value.toString().trim().isEmpty()) {
throw new IllegalArgumentException('Value cannot be empty')
}
return true
})
// Binding with bi-directional conversion
bind(source: model, sourceProperty: 'temperature',
target: textField, targetProperty: 'text',
converter: { celsius ->
String.format('%.1f°C', celsius)
},
reverseConverter: { text ->
def match = text =~ /(\d+\.?\d*)°?C?/
match ? Double.parseDouble(match[0][1]) : 0.0
})Enhanced table models with binding support for complex data display.
/**
* Enhanced table model with PropertyModel columns
*/
class DefaultTableModel extends AbstractTableModel {
DefaultTableModel(List rowData = [], List columnNames = [])
void addColumn(String name, Closure valueGetter)
void addPropertyColumn(String name, String property)
void fireTableDataChanged()
}
/**
* Table column with value model support
*/
class DefaultTableColumn extends TableColumn {
DefaultTableColumn(ValueModel valueModel, String header)
ValueModel getValueModel()
void setValueModel(ValueModel model)
}Usage Examples:
// Table with bound data model
table {
tableModel(list: peopleList) {
propertyColumn(header: 'Name', propertyName: 'name', editable: true)
propertyColumn(header: 'Age', propertyName: 'age', type: Integer)
closureColumn(header: 'Status') { person ->
person.age >= 18 ? 'Adult' : 'Minor'
}
}
}
// Bind table selection to detail view
bind(source: table, sourceProperty: 'selectedElement',
target: detailPanel, targetProperty: 'person')Install with Tessl CLI
npx tessl i tessl/maven-org-codehaus-groovy--groovy-swing