0
# Swing UI Building
1
2
Groovy provides powerful Swing integration through SwingBuilder, enabling declarative GUI construction with a fluent, builder-pattern API that simplifies desktop application development.
3
4
## SwingBuilder
5
6
### Core Builder Class
7
8
```groovy { .api }
9
class SwingBuilder extends FactoryBuilderSupport {
10
SwingBuilder()
11
SwingBuilder(boolean init)
12
13
Object invokeMethod(String name, Object args)
14
15
// Container methods
16
JFrame frame(Map attributes, Closure content)
17
JFrame frame(Closure content)
18
JDialog dialog(Map attributes, Closure content)
19
JPanel panel(Map attributes, Closure content)
20
JScrollPane scrollPane(Map attributes, Closure content)
21
JSplitPane splitPane(Map attributes, Closure content)
22
JTabbedPane tabbedPane(Map attributes, Closure content)
23
24
// Control methods
25
JButton button(Map attributes, Closure content)
26
JLabel label(Map attributes, Closure content)
27
JTextField textField(Map attributes)
28
JTextArea textArea(Map attributes)
29
JCheckBox checkBox(Map attributes)
30
JRadioButton radioButton(Map attributes)
31
JComboBox comboBox(Map attributes)
32
JList list(Map attributes)
33
JTable table(Map attributes)
34
JTree tree(Map attributes)
35
36
// Menu methods
37
JMenuBar menuBar(Map attributes, Closure content)
38
JMenu menu(Map attributes, Closure content)
39
JMenuItem menuItem(Map attributes, Closure content)
40
JPopupMenu popupMenu(Map attributes, Closure content)
41
42
// Layout methods
43
Object borderLayout(Map attributes)
44
Object flowLayout(Map attributes)
45
Object gridLayout(Map attributes)
46
Object gridBagLayout(Map attributes)
47
Object cardLayout(Map attributes)
48
Object boxLayout(Map attributes)
49
50
// Action methods
51
Action action(Map attributes, Closure closure)
52
void bind(Map attributes)
53
54
// Event handling
55
void edt(Closure closure)
56
void doLater(Closure closure)
57
void doOutside(Closure closure)
58
}
59
```
60
61
## Basic GUI Components
62
63
### Simple Window Creation
64
65
```groovy
66
import groovy.swing.SwingBuilder
67
import javax.swing.WindowConstants
68
69
def swing = new SwingBuilder()
70
71
def frame = swing.frame(
72
title: 'My Application',
73
size: [400, 300],
74
locationRelativeTo: null,
75
defaultCloseOperation: WindowConstants.EXIT_ON_CLOSE
76
) {
77
panel {
78
label(text: 'Hello, Swing!')
79
button(text: 'Click Me', actionPerformed: {
80
println 'Button clicked!'
81
})
82
}
83
}
84
85
frame.setVisible(true)
86
```
87
88
### Layout Management
89
90
```groovy
91
import groovy.swing.SwingBuilder
92
import java.awt.BorderLayout
93
import java.awt.FlowLayout
94
import java.awt.GridLayout
95
96
def swing = new SwingBuilder()
97
98
def frame = swing.frame(title: 'Layout Examples', size: [500, 400]) {
99
// BorderLayout example
100
borderLayout()
101
102
panel(constraints: BorderLayout.NORTH) {
103
flowLayout()
104
label(text: 'North Panel')
105
button(text: 'Button 1')
106
button(text: 'Button 2')
107
}
108
109
panel(constraints: BorderLayout.CENTER) {
110
gridLayout(rows: 2, cols: 2, hgap: 5, vgap: 5)
111
button(text: 'Grid 1')
112
button(text: 'Grid 2')
113
button(text: 'Grid 3')
114
button(text: 'Grid 4')
115
}
116
117
panel(constraints: BorderLayout.SOUTH) {
118
flowLayout(alignment: FlowLayout.RIGHT)
119
button(text: 'OK')
120
button(text: 'Cancel')
121
}
122
}
123
124
frame.setVisible(true)
125
```
126
127
### Input Controls
128
129
```groovy
130
import groovy.swing.SwingBuilder
131
import javax.swing.border.TitledBorder
132
133
def swing = new SwingBuilder()
134
def model = [name: '', age: 0, subscribed: false, category: 'Bronze']
135
136
def frame = swing.frame(title: 'Input Form', size: [400, 300]) {
137
borderLayout()
138
139
panel(constraints: BorderLayout.CENTER, border: new TitledBorder('User Information')) {
140
gridBagLayout()
141
142
label(text: 'Name:', constraints: gbc(gridx: 0, gridy: 0, anchor: WEST))
143
textField(
144
columns: 20,
145
constraints: gbc(gridx: 1, gridy: 0, fill: HORIZONTAL),
146
text: bind(source: model, sourceProperty: 'name', mutual: true)
147
)
148
149
label(text: 'Age:', constraints: gbc(gridx: 0, gridy: 1, anchor: WEST))
150
spinner(
151
model: spinnerNumberModel(value: 18, minimum: 0, maximum: 120),
152
constraints: gbc(gridx: 1, gridy: 1),
153
stateChanged: { e -> model.age = e.source.value }
154
)
155
156
checkBox(
157
text: 'Subscribe to newsletter',
158
constraints: gbc(gridx: 0, gridy: 2, gridwidth: 2),
159
selected: bind(source: model, sourceProperty: 'subscribed', mutual: true)
160
)
161
162
label(text: 'Category:', constraints: gbc(gridx: 0, gridy: 3, anchor: WEST))
163
comboBox(
164
items: ['Bronze', 'Silver', 'Gold', 'Platinum'],
165
constraints: gbc(gridx: 1, gridy: 3),
166
selectedItem: bind(source: model, sourceProperty: 'category', mutual: true)
167
)
168
}
169
170
panel(constraints: BorderLayout.SOUTH) {
171
flowLayout()
172
button(text: 'Submit', actionPerformed: {
173
println "Name: ${model.name}"
174
println "Age: ${model.age}"
175
println "Subscribed: ${model.subscribed}"
176
println "Category: ${model.category}"
177
})
178
button(text: 'Reset', actionPerformed: {
179
model.name = ''
180
model.age = 18
181
model.subscribed = false
182
model.category = 'Bronze'
183
})
184
}
185
}
186
187
frame.setVisible(true)
188
```
189
190
## Advanced Components
191
192
### Table with Data
193
194
```groovy
195
import groovy.swing.SwingBuilder
196
import javax.swing.table.DefaultTableModel
197
198
def swing = new SwingBuilder()
199
200
// Sample data
201
def data = [
202
['John Doe', 30, 'Engineering', 75000],
203
['Jane Smith', 25, 'Marketing', 65000],
204
['Bob Johnson', 35, 'Sales', 70000],
205
['Alice Brown', 28, 'Engineering', 80000]
206
]
207
208
def columnNames = ['Name', 'Age', 'Department', 'Salary']
209
def tableModel = new DefaultTableModel(data as Object[][], columnNames as Object[])
210
211
def frame = swing.frame(title: 'Employee Table', size: [600, 400]) {
212
borderLayout()
213
214
scrollPane(constraints: BorderLayout.CENTER) {
215
table(
216
model: tableModel,
217
selectionMode: SINGLE_SELECTION,
218
mouseClicked: { e ->
219
if (e.clickCount == 2) {
220
def table = e.source
221
def row = table.selectedRow
222
if (row >= 0) {
223
def name = tableModel.getValueAt(row, 0)
224
swing.optionPane().showMessageDialog(
225
frame,
226
"Selected employee: $name",
227
'Selection',
228
swing.optionPane.INFORMATION_MESSAGE
229
)
230
}
231
}
232
}
233
)
234
}
235
236
panel(constraints: BorderLayout.SOUTH) {
237
flowLayout()
238
button(text: 'Add Employee', actionPerformed: {
239
def name = swing.optionPane().showInputDialog('Enter name:')
240
if (name) {
241
tableModel.addRow(['New Employee', 25, 'Department', 50000] as Object[])
242
}
243
})
244
button(text: 'Remove Selected', actionPerformed: {
245
def table = frame.contentPane.getComponent(0).viewport.view
246
def selectedRow = table.selectedRow
247
if (selectedRow >= 0) {
248
tableModel.removeRow(selectedRow)
249
}
250
})
251
}
252
}
253
254
frame.setVisible(true)
255
```
256
257
### Menu System
258
259
```groovy
260
import groovy.swing.SwingBuilder
261
import javax.swing.KeyStroke
262
import java.awt.event.KeyEvent
263
264
def swing = new SwingBuilder()
265
266
def frame = swing.frame(title: 'Menu Example', size: [500, 300]) {
267
menuBar {
268
menu(text: 'File', mnemonic: 'F') {
269
menuItem(
270
text: 'New',
271
mnemonic: 'N',
272
accelerator: KeyStroke.getKeyStroke(KeyEvent.VK_N, KeyEvent.CTRL_MASK),
273
actionPerformed: { println 'New file' }
274
)
275
menuItem(
276
text: 'Open',
277
mnemonic: 'O',
278
accelerator: KeyStroke.getKeyStroke(KeyEvent.VK_O, KeyEvent.CTRL_MASK),
279
actionPerformed: { println 'Open file' }
280
)
281
separator()
282
menuItem(
283
text: 'Exit',
284
mnemonic: 'x',
285
actionPerformed: { System.exit(0) }
286
)
287
}
288
289
menu(text: 'Edit', mnemonic: 'E') {
290
menuItem(text: 'Cut', accelerator: KeyStroke.getKeyStroke(KeyEvent.VK_X, KeyEvent.CTRL_MASK))
291
menuItem(text: 'Copy', accelerator: KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.CTRL_MASK))
292
menuItem(text: 'Paste', accelerator: KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.CTRL_MASK))
293
}
294
295
menu(text: 'View', mnemonic: 'V') {
296
checkBoxMenuItem(text: 'Show Toolbar', selected: true)
297
checkBoxMenuItem(text: 'Show Status Bar', selected: false)
298
separator()
299
300
// Radio button group
301
def group = buttonGroup()
302
radioButtonMenuItem(text: 'Small Icons', buttonGroup: group, selected: true)
303
radioButtonMenuItem(text: 'Large Icons', buttonGroup: group)
304
}
305
}
306
307
// Main content area
308
textArea(text: 'Main content area...', editable: false)
309
}
310
311
frame.setVisible(true)
312
```
313
314
## Data Binding
315
316
### Automatic Data Binding
317
318
```groovy
319
import groovy.swing.SwingBuilder
320
import groovy.beans.Bindable
321
322
class Person {
323
@Bindable String firstName = ''
324
@Bindable String lastName = ''
325
@Bindable int age = 0
326
@Bindable boolean employed = false
327
328
String getFullName() {
329
return "$firstName $lastName".trim()
330
}
331
}
332
333
def swing = new SwingBuilder()
334
def person = new Person()
335
336
def frame = swing.frame(title: 'Data Binding Example', size: [400, 250]) {
337
gridBagLayout()
338
339
label(text: 'First Name:', constraints: gbc(gridx: 0, gridy: 0, anchor: WEST))
340
textField(
341
columns: 15,
342
constraints: gbc(gridx: 1, gridy: 0, fill: HORIZONTAL),
343
text: bind(source: person, sourceProperty: 'firstName', mutual: true)
344
)
345
346
label(text: 'Last Name:', constraints: gbc(gridx: 0, gridy: 1, anchor: WEST))
347
textField(
348
columns: 15,
349
constraints: gbc(gridx: 1, gridy: 1, fill: HORIZONTAL),
350
text: bind(source: person, sourceProperty: 'lastName', mutual: true)
351
)
352
353
label(text: 'Age:', constraints: gbc(gridx: 0, gridy: 2, anchor: WEST))
354
spinner(
355
model: spinnerNumberModel(minimum: 0, maximum: 120),
356
constraints: gbc(gridx: 1, gridy: 2),
357
value: bind(source: person, sourceProperty: 'age', mutual: true)
358
)
359
360
checkBox(
361
text: 'Employed',
362
constraints: gbc(gridx: 0, gridy: 3, gridwidth: 2),
363
selected: bind(source: person, sourceProperty: 'employed', mutual: true)
364
)
365
366
separator(constraints: gbc(gridx: 0, gridy: 4, gridwidth: 2, fill: HORIZONTAL))
367
368
label(
369
text: bind(source: person, sourceProperty: 'fullName'),
370
constraints: gbc(gridx: 0, gridy: 5, gridwidth: 2, anchor: CENTER),
371
font: label().font.deriveFont(16f)
372
)
373
374
button(
375
text: 'Print Info',
376
constraints: gbc(gridx: 0, gridy: 6, gridwidth: 2),
377
actionPerformed: {
378
println "Person: ${person.fullName}, Age: ${person.age}, Employed: ${person.employed}"
379
}
380
)
381
}
382
383
frame.setVisible(true)
384
```
385
386
## Custom Components
387
388
### Custom Panel Component
389
390
```groovy
391
import groovy.swing.SwingBuilder
392
import javax.swing.JPanel
393
import java.awt.Graphics
394
import java.awt.Color
395
396
class DrawingPanel extends JPanel {
397
private List<Point> points = []
398
399
DrawingPanel() {
400
background = Color.WHITE
401
addMouseListener([
402
mouseClicked: { e ->
403
points.add(new Point(e.x, e.y))
404
repaint()
405
}
406
] as java.awt.event.MouseAdapter)
407
}
408
409
@Override
410
protected void paintComponent(Graphics g) {
411
super.paintComponent(g)
412
g.color = Color.BLUE
413
points.each { point ->
414
g.fillOval(point.x - 3, point.y - 3, 6, 6)
415
}
416
}
417
418
void clearPoints() {
419
points.clear()
420
repaint()
421
}
422
}
423
424
def swing = new SwingBuilder()
425
426
def drawingPanel = new DrawingPanel()
427
428
def frame = swing.frame(title: 'Custom Drawing Panel', size: [500, 400]) {
429
borderLayout()
430
431
widget(drawingPanel, constraints: BorderLayout.CENTER)
432
433
panel(constraints: BorderLayout.SOUTH) {
434
flowLayout()
435
button(text: 'Clear', actionPerformed: {
436
drawingPanel.clearPoints()
437
})
438
button(text: 'Count Points', actionPerformed: {
439
swing.optionPane().showMessageDialog(
440
frame,
441
"Points drawn: ${drawingPanel.points.size()}",
442
'Point Count',
443
swing.optionPane.INFORMATION_MESSAGE
444
)
445
})
446
}
447
}
448
449
frame.setVisible(true)
450
```
451
452
## Threading and EDT
453
454
### Event Dispatch Thread Management
455
456
```groovy
457
import groovy.swing.SwingBuilder
458
import javax.swing.SwingUtilities
459
import java.util.concurrent.Executors
460
461
def swing = new SwingBuilder()
462
463
def frame = swing.frame(title: 'Threading Example', size: [400, 200]) {
464
borderLayout()
465
466
def progressBar = progressBar(
467
minimum: 0,
468
maximum: 100,
469
value: 0,
470
stringPainted: true,
471
constraints: BorderLayout.CENTER
472
)
473
474
panel(constraints: BorderLayout.SOUTH) {
475
flowLayout()
476
477
button(text: 'Start Task', actionPerformed: {
478
// Run long task in background thread
479
swing.doOutside {
480
(1..100).each { i ->
481
Thread.sleep(50) // Simulate work
482
483
// Update UI on EDT
484
swing.edt {
485
progressBar.value = i
486
progressBar.string = "Processing ${i}%"
487
}
488
}
489
490
// Final update on EDT
491
swing.edt {
492
progressBar.string = "Complete!"
493
swing.optionPane().showMessageDialog(
494
frame,
495
'Task completed successfully!',
496
'Success',
497
swing.optionPane.INFORMATION_MESSAGE
498
)
499
}
500
}
501
})
502
503
button(text: 'Reset', actionPerformed: {
504
progressBar.value = 0
505
progressBar.string = null
506
})
507
}
508
}
509
510
frame.setVisible(true)
511
```
512
513
### Timer-based Updates
514
515
```groovy
516
import groovy.swing.SwingBuilder
517
import javax.swing.Timer
518
import java.text.SimpleDateFormat
519
520
def swing = new SwingBuilder()
521
def dateFormat = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss')
522
523
def frame = swing.frame(title: 'Clock Example', size: [300, 100]) {
524
borderLayout()
525
526
def timeLabel = label(
527
text: dateFormat.format(new Date()),
528
constraints: BorderLayout.CENTER,
529
horizontalAlignment: swing.label.CENTER,
530
font: label().font.deriveFont(18f)
531
)
532
533
// Update every second
534
def timer = new Timer(1000) { e ->
535
timeLabel.text = dateFormat.format(new Date())
536
}
537
timer.start()
538
539
// Stop timer when window closes
540
addWindowListener([
541
windowClosing: { e -> timer.stop() }
542
] as java.awt.event.WindowAdapter)
543
}
544
545
frame.setVisible(true)
546
```
547
548
## Styling and Look & Feel
549
550
### Custom Look and Feel
551
552
```groovy
553
import groovy.swing.SwingBuilder
554
import javax.swing.UIManager
555
import javax.swing.plaf.nimbus.NimbusLookAndFeel
556
557
// Set look and feel
558
try {
559
UIManager.setLookAndFeel(new NimbusLookAndFeel())
560
} catch (Exception e) {
561
println "Could not set Nimbus L&F: ${e.message}"
562
}
563
564
def swing = new SwingBuilder()
565
566
def frame = swing.frame(title: 'Styled Application', size: [400, 300]) {
567
borderLayout()
568
569
panel(constraints: BorderLayout.NORTH, background: java.awt.Color.LIGHT_GRAY) {
570
flowLayout()
571
label(text: 'Styled Header', font: label().font.deriveFont(16f))
572
}
573
574
tabbedPane(constraints: BorderLayout.CENTER) {
575
panel(name: 'Tab 1') {
576
gridLayout(rows: 3, cols: 2, hgap: 10, vgap: 10)
577
label(text: 'Name:')
578
textField(columns: 15)
579
label(text: 'Email:')
580
textField(columns: 15)
581
label(text: 'Notes:')
582
scrollPane {
583
textArea(rows: 3, columns: 15)
584
}
585
}
586
587
panel(name: 'Tab 2') {
588
boxLayout(axis: swing.boxLayout.Y_AXIS)
589
checkBox(text: 'Option 1')
590
checkBox(text: 'Option 2')
591
checkBox(text: 'Option 3')
592
separator()
593
radioButton(text: 'Choice A')
594
radioButton(text: 'Choice B')
595
}
596
}
597
598
panel(constraints: BorderLayout.SOUTH) {
599
flowLayout()
600
button(text: 'OK', preferredSize: [80, 30])
601
button(text: 'Cancel', preferredSize: [80, 30])
602
button(text: 'Apply', preferredSize: [80, 30])
603
}
604
}
605
606
frame.setVisible(true)
607
```
608
609
This comprehensive documentation covers all the major aspects of Groovy's Swing integration, from basic components to advanced features like data binding, custom components, and threading. The SwingBuilder provides a powerful and expressive way to create desktop applications with minimal boilerplate code.