0
# Data Binding System
1
2
Comprehensive data binding framework for connecting GUI components to data models with validation, conversion, and automatic synchronization.
3
4
## Capabilities
5
6
### Core Binding Interfaces
7
8
The foundation of the binding system providing contracts for data synchronization.
9
10
```groovy { .api }
11
/**
12
* Complete bidirectional binding interface with converter/validator support
13
*/
14
interface FullBinding extends BindingUpdatable {
15
SourceBinding getSourceBinding()
16
TargetBinding getTargetBinding()
17
void setSourceBinding(SourceBinding source)
18
void setTargetBinding(TargetBinding target)
19
void setValidator(Closure validator)
20
Closure getValidator()
21
void setConverter(Closure converter)
22
Closure getConverter()
23
void setReverseConverter(Closure reverseConverter)
24
Closure getReverseConverter()
25
}
26
27
/**
28
* Base interface for all binding update operations
29
*/
30
interface BindingUpdatable {
31
void bind()
32
void unbind()
33
void rebind()
34
void update()
35
void reverseUpdate()
36
}
37
38
/**
39
* Source endpoint of a binding relationship
40
*/
41
interface SourceBinding extends BindingUpdatable {
42
Object getSourceValue()
43
void setSourceBinding(PropertyChangeListener listener)
44
}
45
46
/**
47
* Target endpoint of a binding relationship
48
*/
49
interface TargetBinding extends BindingUpdatable {
50
void setTargetValue(Object value)
51
}
52
53
/**
54
* Event-triggered binding interface
55
*/
56
interface TriggerBinding {
57
FullBinding getFullBinding()
58
void setFullBinding(FullBinding fullBinding)
59
void bind()
60
void unbind()
61
}
62
```
63
64
### Binding Factory
65
66
The primary factory for creating and configuring data bindings through the builder DSL.
67
68
```groovy { .api }
69
/**
70
* Primary factory for creating data bindings
71
*/
72
class BindFactory {
73
/** Create binding between source and target */
74
Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attributes)
75
76
/** Handle binding attributes in component definitions */
77
boolean bindingAttributeDelegate(FactoryBuilderSupport builder, Object node, Map attributes)
78
}
79
80
// Binding DSL usage
81
bind(source: sourceObject, sourceProperty: 'propertyName',
82
target: targetComponent, targetProperty: 'text',
83
converter: { value -> value.toString() },
84
validator: { value -> value != null })
85
```
86
87
**Usage Examples:**
88
89
```groovy
90
import groovy.swing.SwingBuilder
91
import groovy.model.ValueHolder
92
93
def swing = new SwingBuilder()
94
def model = new ValueHolder('Initial Text')
95
96
swing.frame(title: 'Binding Example') {
97
panel {
98
// Bind text field to model
99
textField(id: 'textInput', columns: 20)
100
bind(source: model, sourceProperty: 'value',
101
target: textInput, targetProperty: 'text')
102
103
// Two-way binding with validation
104
textField(id: 'numberInput', columns: 10)
105
bind(source: model, sourceProperty: 'value',
106
target: numberInput, targetProperty: 'text',
107
converter: { it.toString() },
108
reverseConverter: {
109
try { Integer.parseInt(it) }
110
catch (NumberFormatException e) { 0 }
111
},
112
validator: { it instanceof Number && it >= 0 })
113
}
114
}
115
```
116
117
### Value Models
118
119
Observable data containers that support property change notifications.
120
121
```groovy { .api }
122
/**
123
* Interface for observable values with change notification
124
*/
125
interface ValueModel {
126
Object getValue()
127
void setValue(Object value)
128
Class getType()
129
boolean isEditable()
130
}
131
132
/**
133
* Basic implementation of ValueModel
134
*/
135
class ValueHolder implements ValueModel {
136
ValueHolder(Object value = null, Class type = Object, boolean editable = true)
137
Object getValue()
138
void setValue(Object value)
139
Class getType()
140
boolean isEditable()
141
void addPropertyChangeListener(PropertyChangeListener listener)
142
void removePropertyChangeListener(PropertyChangeListener listener)
143
}
144
145
/**
146
* Property-based value model with reflection support
147
*/
148
class PropertyModel implements ValueModel {
149
PropertyModel(Object bean, String propertyName)
150
Object getValue()
151
void setValue(Object value)
152
Class getType()
153
boolean isEditable()
154
void addPropertyChangeListener(PropertyChangeListener listener)
155
void removePropertyChangeListener(PropertyChangeListener listener)
156
}
157
158
/**
159
* Nested property access model for complex object graphs
160
*/
161
class NestedValueModel implements ValueModel {
162
NestedValueModel(Object root, String propertyPath)
163
Object getValue()
164
void setValue(Object value)
165
Class getType()
166
boolean isEditable()
167
void addPropertyChangeListener(PropertyChangeListener listener)
168
void removePropertyChangeListener(PropertyChangeListener listener)
169
}
170
171
/**
172
* Closure-based computed model
173
*/
174
class ClosureModel implements ValueModel {
175
ClosureModel(Closure closure)
176
Object getValue()
177
void setValue(Object value)
178
Class getType()
179
boolean isEditable()
180
void addPropertyChangeListener(PropertyChangeListener listener)
181
void removePropertyChangeListener(PropertyChangeListener listener)
182
}
183
```
184
185
**Usage Examples:**
186
187
```groovy
188
import groovy.model.*
189
190
// Basic value holder
191
def nameModel = new ValueHolder('John Doe', String)
192
nameModel.addValueChangeListener { evt ->
193
println "Name changed from ${evt.oldValue} to ${evt.newValue}"
194
}
195
196
// Property model for existing bean
197
class Person {
198
String name
199
int age
200
}
201
def person = new Person(name: 'Alice', age: 30)
202
def nameProperty = new PropertyModel(person, 'name')
203
def ageProperty = new PropertyModel(person, 'age')
204
205
// Nested property model
206
def addressProperty = new NestedValueModel(person, 'address.street')
207
208
// Computed model
209
def fullNameModel = new ClosureModel {
210
"${person.firstName} ${person.lastName}"
211
}
212
```
213
214
### Component Property Bindings
215
216
Specialized binding classes for different Swing components.
217
218
```groovy { .api }
219
/**
220
* Text component binding with document listeners
221
*/
222
class JTextComponentProperties {
223
static Object getText(JTextComponent self)
224
static void setText(JTextComponent self, Object value)
225
}
226
227
/**
228
* Button selection state binding
229
*/
230
class AbstractButtonProperties {
231
static Object getSelected(AbstractButton self)
232
static void setSelected(AbstractButton self, Object value)
233
}
234
235
/**
236
* Slider value binding
237
*/
238
class JSliderProperties {
239
static Object getValue(JSlider self)
240
static void setValue(JSlider self, Object value)
241
}
242
243
/**
244
* Combo box selection and item binding
245
*/
246
class JComboBoxProperties {
247
static Object getSelectedItem(JComboBox self)
248
static void setSelectedItem(JComboBox self, Object value)
249
static Object getSelectedIndex(JComboBox self)
250
static void setSelectedIndex(JComboBox self, Object value)
251
}
252
253
/**
254
* List selection binding
255
*/
256
class JListProperties {
257
static Object getSelectedElement(JList self)
258
static Object getSelectedElements(JList self)
259
static Object getSelectedIndex(JList self)
260
static Object getSelectedIndices(JList self)
261
static Object getSelectedValue(JList self)
262
static Object getSelectedValues(JList self)
263
}
264
265
/**
266
* Table selection and data binding
267
*/
268
class JTableProperties {
269
static Object getSelectedElement(JTable self)
270
static Object getSelectedElements(JTable self)
271
static Object getSelectedRow(JTable self)
272
static Object getSelectedRows(JTable self)
273
static Object getSelectedColumn(JTable self)
274
static Object getSelectedColumns(JTable self)
275
}
276
277
/**
278
* Spinner value binding
279
*/
280
class JSpinnerProperties {
281
static Object getValue(JSpinner self)
282
static void setValue(JSpinner self, Object value)
283
}
284
```
285
286
### Binding Implementations
287
288
Concrete implementations of the binding interfaces for various scenarios.
289
290
```groovy { .api }
291
/**
292
* Base implementation of FullBinding
293
*/
294
abstract class AbstractFullBinding implements FullBinding {
295
SourceBinding sourceBinding
296
TargetBinding targetBinding
297
Closure validator
298
Closure converter
299
Closure reverseConverter
300
301
void bind()
302
void unbind()
303
void rebind()
304
void update()
305
}
306
307
/**
308
* Property-based binding implementation
309
*/
310
class PropertyBinding extends AbstractFullBinding {
311
PropertyBinding(Object source, String sourceProperty,
312
Object target, String targetProperty)
313
}
314
315
/**
316
* Property path binding for nested properties
317
*/
318
class PropertyPathFullBinding extends AbstractFullBinding {
319
PropertyPathFullBinding(Object source, String sourcePath,
320
Object target, String targetPath)
321
}
322
323
/**
324
* Closure-based source binding
325
*/
326
class ClosureSourceBinding implements SourceBinding {
327
ClosureSourceBinding(Closure closure)
328
Object getSourceValue()
329
void setSourceBinding(PropertyChangeListener listener)
330
void bind()
331
void unbind()
332
void rebind()
333
void update()
334
}
335
336
/**
337
* Event-triggered binding
338
*/
339
class EventTriggerBinding implements TriggerBinding {
340
EventTriggerBinding(Object source, String eventName,
341
FullBinding fullBinding = null, Closure closure = null)
342
}
343
344
/**
345
* Timer-based binding updates
346
*/
347
class SwingTimerTriggerBinding implements TriggerBinding {
348
SwingTimerTriggerBinding(int delay, FullBinding fullBinding = null)
349
}
350
351
/**
352
* Property-based binding with configurable update strategies
353
*/
354
class PropertyBinding extends AbstractFullBinding {
355
PropertyBinding(Object sourceBean, String sourceProperty,
356
Object targetBean, String targetProperty,
357
UpdateStrategy updateStrategy = UpdateStrategy.MIXED)
358
359
static enum UpdateStrategy {
360
/** EDT if on EDT, otherwise invokeLater */
361
MIXED,
362
/** Always use invokeLater */
363
ASYNC,
364
/** invokeAndWait if not on EDT, direct if on EDT */
365
SYNC,
366
/** Execute in same thread */
367
SAME,
368
/** Execute outside EDT */
369
OUTSIDE,
370
/** Always execute in background thread */
371
DEFER
372
}
373
}
374
375
/**
376
* Bidirectional property binding between two properties
377
*/
378
class MutualPropertyBinding extends PropertyBinding {
379
MutualPropertyBinding(Object sourceBean, String sourceProperty,
380
Object targetBean, String targetProperty,
381
UpdateStrategy updateStrategy = UpdateStrategy.MIXED)
382
}
383
384
/**
385
* Aggregate multiple bindings for group operations
386
*/
387
class AggregateBinding implements FullBinding {
388
void addBinding(FullBinding binding)
389
void removeBinding(FullBinding binding)
390
List<FullBinding> getBindings()
391
}
392
```
393
394
### Binding Groups and Proxies
395
396
Advanced binding constructs for complex scenarios.
397
398
```groovy { .api }
399
/**
400
* Grouped bindings for batch operations
401
*/
402
class BindGroupFactory {
403
Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attributes)
404
}
405
406
/**
407
* Binding proxy for complex binding scenarios
408
*/
409
class BindingProxy {
410
BindingProxy(Object target, String property, Map args = [:])
411
void bind(Object source, String sourceProperty)
412
void unbind()
413
}
414
415
/**
416
* Multiple binding aggregation
417
*/
418
class AggregateBinding extends AbstractFullBinding {
419
AggregateBinding(FullBinding... bindings)
420
void addBinding(FullBinding binding)
421
void removeBinding(FullBinding binding)
422
}
423
```
424
425
**Usage Examples:**
426
427
```groovy
428
// Binding group for form validation
429
bindGroup {
430
bind(source: model.name, target: nameField, targetProperty: 'text')
431
bind(source: model.email, target: emailField, targetProperty: 'text')
432
bind(source: model.age, target: ageSpinner, targetProperty: 'value')
433
}
434
435
// Binding proxy for complex property chains
436
def proxy = new BindingProxy(someComplexObject, 'deeply.nested.property')
437
proxy.bind(textField, 'text')
438
439
// Timer-based binding for real-time updates
440
def timerBinding = new SwingTimerTriggerBinding(1000) // Update every second
441
timerBinding.fullBinding = bind(source: statusModel, target: statusLabel)
442
```
443
444
### Validation and Conversion
445
446
Built-in support for data validation and type conversion in bindings.
447
448
```groovy { .api }
449
// Binding with validation
450
bind(source: model, sourceProperty: 'value',
451
target: textField, targetProperty: 'text',
452
validator: { value ->
453
if (value == null || value.toString().trim().isEmpty()) {
454
throw new IllegalArgumentException('Value cannot be empty')
455
}
456
return true
457
})
458
459
// Binding with bi-directional conversion
460
bind(source: model, sourceProperty: 'temperature',
461
target: textField, targetProperty: 'text',
462
converter: { celsius ->
463
String.format('%.1f°C', celsius)
464
},
465
reverseConverter: { text ->
466
def match = text =~ /(\d+\.?\d*)°?C?/
467
match ? Double.parseDouble(match[0][1]) : 0.0
468
})
469
```
470
471
### Table Model Integration
472
473
Enhanced table models with binding support for complex data display.
474
475
```groovy { .api }
476
/**
477
* Enhanced table model with PropertyModel columns
478
*/
479
class DefaultTableModel extends AbstractTableModel {
480
DefaultTableModel(List rowData = [], List columnNames = [])
481
void addColumn(String name, Closure valueGetter)
482
void addPropertyColumn(String name, String property)
483
void fireTableDataChanged()
484
}
485
486
/**
487
* Table column with value model support
488
*/
489
class DefaultTableColumn extends TableColumn {
490
DefaultTableColumn(ValueModel valueModel, String header)
491
ValueModel getValueModel()
492
void setValueModel(ValueModel model)
493
}
494
```
495
496
**Usage Examples:**
497
498
```groovy
499
// Table with bound data model
500
table {
501
tableModel(list: peopleList) {
502
propertyColumn(header: 'Name', propertyName: 'name', editable: true)
503
propertyColumn(header: 'Age', propertyName: 'age', type: Integer)
504
closureColumn(header: 'Status') { person ->
505
person.age >= 18 ? 'Adult' : 'Minor'
506
}
507
}
508
}
509
510
// Bind table selection to detail view
511
bind(source: table, sourceProperty: 'selectedElement',
512
target: detailPanel, targetProperty: 'person')
513
```