0
# Data Binding
1
2
Groovy Swing provides a comprehensive data binding system that connects UI components to data models automatically, enabling declarative data synchronization.
3
4
## Core Binding Concepts
5
6
The binding system is built around ValueModel interfaces and binding factories.
7
8
```groovy { .api }
9
// Binding factories
10
def bind(Map attributes = [:])
11
def bindGroup(Map attributes = [:])
12
def bindProxy(Map attributes = [:])
13
```
14
15
## ValueModel Interface
16
17
The foundation of the binding system for representing bindable values.
18
19
```groovy { .api }
20
interface ValueModel {
21
Object getValue()
22
void setValue(Object value)
23
Class getType()
24
boolean isEditable()
25
void addValueChangeListener(PropertyChangeListener listener)
26
void removeValueChangeListener(PropertyChangeListener listener)
27
}
28
```
29
30
## ValueModel Implementations
31
32
Concrete implementations of ValueModel for different data scenarios.
33
34
```groovy { .api }
35
// Basic value holder
36
class ValueHolder implements ValueModel {
37
ValueHolder()
38
ValueHolder(Object value)
39
ValueHolder(Object value, Class type)
40
}
41
42
// Property-based model
43
class PropertyModel implements ValueModel {
44
PropertyModel(Object bean, String propertyName)
45
}
46
47
// Nested property model
48
class NestedValueModel implements ValueModel {
49
NestedValueModel(ValueModel parentModel, String childPropertyName)
50
}
51
52
// Closure-based model
53
class ClosureModel implements ValueModel {
54
ClosureModel(Closure readClosure)
55
ClosureModel(Closure readClosure, Closure writeClosure)
56
}
57
58
// Form data model
59
class FormModel {
60
FormModel()
61
FormModel(Map initialValues)
62
63
ValueModel getValueModel(String propertyName)
64
void setProperty(String name, Object value)
65
Object getProperty(String name)
66
}
67
```
68
69
### ValueModel Examples
70
71
```groovy
72
// Basic value holder
73
def nameModel = new ValueHolder('John Doe', String)
74
nameModel.setValue('Jane Smith')
75
println nameModel.getValue() // 'Jane Smith'
76
77
// Property model for bean binding
78
class Person {
79
String name
80
int age
81
}
82
83
def person = new Person(name: 'Alice', age: 30)
84
def nameModel = new PropertyModel(person, 'name')
85
def ageModel = new PropertyModel(person, 'age')
86
87
// Nested property model
88
def addressModel = new PropertyModel(person, 'address')
89
def cityModel = new NestedValueModel(addressModel, 'city')
90
91
// Closure-based model
92
def dynamicModel = new ClosureModel(
93
{ -> person.name.toUpperCase() }, // Read closure
94
{ value -> person.name = value.toLowerCase() } // Write closure
95
)
96
97
// Form model
98
def formModel = new FormModel([
99
firstName: 'John',
100
lastName: 'Doe',
101
email: 'john@example.com'
102
])
103
```
104
105
## Binding Factories
106
107
Factory methods for creating different types of data bindings.
108
109
### Basic Binding
110
111
```groovy { .api }
112
// Simple property binding
113
def bind(Map attributes)
114
115
// Common attributes:
116
// source: Object - source object
117
// sourceProperty: String - property name on source
118
// target: Object - target object (optional, defaults to component)
119
// targetProperty: String - property name on target (optional)
120
// converter: Closure - value converter (optional)
121
// reverseConverter: Closure - reverse converter (optional)
122
// validator: Closure - validation closure (optional)
123
```
124
125
### Binding Examples
126
127
```groovy
128
def swing = new SwingBuilder()
129
def person = new Person(name: 'John', age: 25, email: 'john@example.com')
130
131
swing.frame(title: 'Data Binding Demo') {
132
panel {
133
gridBagLayout()
134
135
// Simple text field binding
136
label(text: 'Name:', constraints: gbc(gridx: 0, gridy: 0))
137
textField(
138
columns: 20,
139
text: bind(source: person, sourceProperty: 'name'),
140
constraints: gbc(gridx: 1, gridy: 0)
141
)
142
143
// Binding with converter
144
label(text: 'Age:', constraints: gbc(gridx: 0, gridy: 1))
145
textField(
146
columns: 20,
147
text: bind(
148
source: person,
149
sourceProperty: 'age',
150
converter: { it.toString() },
151
reverseConverter: { Integer.parseInt(it) }
152
),
153
constraints: gbc(gridx: 1, gridy: 1)
154
)
155
156
// Binding with validation
157
label(text: 'Email:', constraints: gbc(gridx: 0, gridy: 2))
158
textField(
159
columns: 20,
160
text: bind(
161
source: person,
162
sourceProperty: 'email',
163
validator: { it?.contains('@') }
164
),
165
constraints: gbc(gridx: 1, gridy: 2)
166
)
167
168
// Checkbox binding
169
checkBox(
170
text: 'Is Active',
171
selected: bind(source: person, sourceProperty: 'active'),
172
constraints: gbc(gridx: 1, gridy: 3)
173
)
174
}
175
}
176
```
177
178
## Binding Groups
179
180
Binding groups for managing multiple related bindings.
181
182
```groovy { .api }
183
def bindGroup(Map attributes = [:])
184
185
// BindGroup methods:
186
// bind() - Activate all bindings in group
187
// unbind() - Deactivate all bindings in group
188
// update() - Update all bindings in group
189
// reverseUpdate() - Reverse update all bindings
190
```
191
192
### Binding Group Examples
193
194
```groovy
195
swing.frame(title: 'Binding Group Demo') {
196
def person = new Person()
197
198
// Create binding group
199
def personBindings = bindGroup()
200
201
panel {
202
gridLayout(rows: 4, columns: 2)
203
204
label(text: 'First Name:')
205
textField(text: bind(group: personBindings, source: person, sourceProperty: 'firstName'))
206
207
label(text: 'Last Name:')
208
textField(text: bind(group: personBindings, source: person, sourceProperty: 'lastName'))
209
210
label(text: 'Age:')
211
spinner(
212
value: bind(group: personBindings, source: person, sourceProperty: 'age'),
213
model: spinnerNumberModel(minimum: 0, maximum: 120)
214
)
215
216
button(text: 'Update All') {
217
actionPerformed {
218
personBindings.update()
219
}
220
}
221
222
button(text: 'Reset Form') {
223
actionPerformed {
224
personBindings.reverseUpdate()
225
}
226
}
227
}
228
}
229
```
230
231
## Advanced Binding Features
232
233
### Binding Proxies
234
235
Proxy objects for advanced binding scenarios.
236
237
```groovy { .api }
238
def bindProxy(Map attributes = [:])
239
240
// BindingProxy provides:
241
// - Delayed binding resolution
242
// - Dynamic target switching
243
// - Conditional binding activation
244
```
245
246
### Complex Binding Scenarios
247
248
```groovy
249
// List binding with selection
250
swing.frame(title: 'List Binding') {
251
def items = ['Item 1', 'Item 2', 'Item 3'] as ObservableList
252
def selectedItem = new ValueHolder()
253
254
splitPane {
255
// List with items binding
256
scrollPane(constraints: 'left') {
257
list(
258
listData: bind(source: items),
259
selectedValue: bind(source: selectedItem, sourceProperty: 'value')
260
)
261
}
262
263
// Detail panel bound to selection
264
panel(constraints: 'right') {
265
label(text: bind(
266
source: selectedItem,
267
sourceProperty: 'value',
268
converter: { "Selected: $it" }
269
))
270
}
271
}
272
}
273
274
// Table binding with row selection
275
swing.frame(title: 'Table Binding') {
276
def tableData = [
277
[name: 'John', age: 30],
278
[name: 'Jane', age: 25]
279
] as ObservableList
280
281
def selectedRow = new ValueHolder()
282
283
table(
284
model: bind(source: tableData),
285
selectedElement: bind(source: selectedRow, sourceProperty: 'value')
286
) {
287
propertyColumn(header: 'Name', propertyName: 'name')
288
propertyColumn(header: 'Age', propertyName: 'age')
289
}
290
}
291
```
292
293
## Component-Specific Binding Support
294
295
Different components support different binding properties through specialized property classes.
296
297
```groovy { .api }
298
// AbstractButton binding properties
299
// - selected, text, icon, enabled, visible
300
301
// JTextField binding properties
302
// - text, enabled, editable, visible
303
304
// JList binding properties
305
// - listData, selectedIndex, selectedValue, selectedValues
306
307
// JTable binding properties
308
// - model, selectedRow, selectedColumn, selectedElement
309
310
// JComboBox binding properties
311
// - items, selectedItem, selectedIndex
312
313
// JSpinner binding properties
314
// - value, enabled
315
316
// JSlider binding properties
317
// - value, minimum, maximum, enabled
318
319
// JProgressBar binding properties
320
// - value, minimum, maximum, string, stringPainted
321
```
322
323
### Component Binding Examples
324
325
```groovy
326
def model = new FormModel([
327
items: ['A', 'B', 'C'],
328
selectedItem: 'B',
329
percentage: 50,
330
description: 'Sample text'
331
])
332
333
swing.panel {
334
vbox {
335
// Combo box binding
336
comboBox(
337
items: bind(source: model, sourceProperty: 'items'),
338
selectedItem: bind(source: model, sourceProperty: 'selectedItem')
339
)
340
341
// Slider with progress bar
342
slider(
343
minimum: 0, maximum: 100,
344
value: bind(source: model, sourceProperty: 'percentage')
345
)
346
progressBar(
347
minimum: 0, maximum: 100,
348
value: bind(source: model, sourceProperty: 'percentage'),
349
stringPainted: true
350
)
351
352
// Text area binding
353
scrollPane {
354
textArea(
355
rows: 5, columns: 30,
356
text: bind(source: model, sourceProperty: 'description')
357
)
358
}
359
}
360
}
361
```
362
363
## Binding Lifecycle
364
365
Understanding binding activation and cleanup.
366
367
```groovy { .api }
368
// Binding lifecycle methods
369
// bind() - Activate binding and start synchronization
370
// unbind() - Deactivate binding and stop synchronization
371
// update() - Force immediate source-to-target update
372
// reverseUpdate() - Force immediate target-to-source update
373
```
374
375
### Manual Binding Management
376
377
```groovy
378
// Creating and managing bindings manually
379
def nameBinding = swing.bind(
380
source: person,
381
sourceProperty: 'name',
382
target: textField,
383
targetProperty: 'text'
384
)
385
386
// Activate binding
387
nameBinding.bind()
388
389
// Update from source
390
nameBinding.update()
391
392
// Update from target
393
nameBinding.reverseUpdate()
394
395
// Deactivate when done
396
nameBinding.unbind()
397
```