SwingBuilder for creating Swing GUIs in Groovy - provides DSL for declarative UI construction
—
Groovy Swing provides comprehensive table support with enhanced models, custom columns, renderers, and sorters for building data-driven table interfaces.
Core table components for displaying tabular data.
// Table component
def table(Map attributes = [:], Closure closure = null)
// Table structure
def tableModel(Map attributes = [:], Closure closure = null)
def columnModel(Map attributes = [:])
def tableColumn(Map attributes = [:])
// Column types
def column(Map attributes = [:])
def propertyColumn(Map attributes = [:])
def closureColumn(Map attributes = [:])Enhanced table models with ValueModel integration and data binding support.
// Default table model with enhanced features
class DefaultTableModel extends AbstractTableModel {
DefaultTableModel()
DefaultTableModel(List rowData, List columnNames)
// ValueModel integration
ValueModel getValueModel(int row, int column)
void setValueModel(ValueModel model, int row, int column)
}
// Table model factory attributes:
// list: List - row data as list of maps or objects
// columns: List - column definitions
// sortable: boolean - enable sorting supportdef swing = new SwingBuilder()
// Simple table with data
def tableData = [
[name: 'John', age: 30, city: 'New York'],
[name: 'Jane', age: 25, city: 'Los Angeles'],
[name: 'Bob', age: 35, city: 'Chicago']
]
swing.frame(title: 'Simple Table') {
scrollPane {
table(
model: tableModel(list: tableData) {
propertyColumn(header: 'Name', propertyName: 'name', width: 100)
propertyColumn(header: 'Age', propertyName: 'age', width: 50)
propertyColumn(header: 'City', propertyName: 'city', width: 120)
}
)
}
}
// Table with explicit column model
swing.scrollPane {
table {
// Define table model
tableModel(list: tableData)
// Define column model
columnModel {
column(header: 'Name', width: 100)
column(header: 'Age', width: 50)
column(header: 'City', width: 120)
}
}
}Different column types for various data display and editing scenarios.
def propertyColumn(Map attributes = [:])
// PropertyColumn attributes:
// header: String - column header text
// propertyName: String - property name to bind to
// type: Class - property type for editing
// width: int - preferred column width
// editable: boolean - whether column is editable
// renderer: TableCellRenderer - custom renderer
// editor: TableCellEditor - custom editordef closureColumn(Map attributes = [:])
// ClosureColumn attributes:
// header: String - column header text
// read: Closure - closure to read value from row object
// write: Closure - closure to write value to row object (optional)
// type: Class - value type
// width: int - preferred column width
// editable: boolean - whether column is editabledef people = [
new Person(firstName: 'John', lastName: 'Doe', age: 30),
new Person(firstName: 'Jane', lastName: 'Smith', age: 25)
]
swing.scrollPane {
table(model: tableModel(list: people)) {
// Property-based columns
propertyColumn(
header: 'First Name',
propertyName: 'firstName',
width: 100,
editable: true
)
propertyColumn(
header: 'Last Name',
propertyName: 'lastName',
width: 100,
editable: true
)
propertyColumn(
header: 'Age',
propertyName: 'age',
type: Integer,
width: 50,
editable: true
)
// Closure-based computed column
closureColumn(
header: 'Full Name',
read: { row -> "${row.firstName} ${row.lastName}" },
width: 150,
editable: false
)
// Closure column with custom formatting
closureColumn(
header: 'Status',
read: { row -> row.age >= 30 ? 'Senior' : 'Junior' },
width: 80
)
}
}Custom renderers for specialized cell display.
// Renderer factories
def tableCellRenderer(Map attributes = [:])
def listCellRenderer(Map attributes = [:])
def cellRenderer(Map attributes = [:])
def headerRenderer(Map attributes = [:])
// Renderer update factory
def onRender(Closure renderClosure)swing.scrollPane {
table(model: tableModel(list: salesData)) {
propertyColumn(header: 'Product', propertyName: 'product')
propertyColumn(header: 'Revenue', propertyName: 'revenue') {
// Custom currency renderer
tableCellRenderer { renderer, table, value, isSelected, hasFocus, row, column ->
renderer.text = NumberFormat.getCurrencyInstance().format(value)
if (value > 10000) {
renderer.foreground = Color.GREEN
renderer.font = renderer.font.deriveFont(Font.BOLD)
} else {
renderer.foreground = Color.BLACK
renderer.font = renderer.font.deriveFont(Font.PLAIN)
}
return renderer
}
}
propertyColumn(header: 'Status', propertyName: 'status') {
// Icon renderer based on status
tableCellRenderer { renderer, table, value, isSelected, hasFocus, row, column ->
renderer.text = value
renderer.icon = value == 'Active' ? activeIcon : inactiveIcon
return renderer
}
}
// Progress bar renderer for percentage column
propertyColumn(header: 'Progress', propertyName: 'percentage') {
tableCellRenderer { renderer, table, value, isSelected, hasFocus, row, column ->
def progressBar = new JProgressBar(0, 100)
progressBar.value = value
progressBar.stringPainted = true
progressBar.string = "${value}%"
return progressBar
}
}
}
}Custom editors for specialized cell editing.
// Editor factories
def cellEditor(Map attributes = [:])
def editorValue(Closure valueClosure)
def prepareEditor(Closure prepareClosure)swing.scrollPane {
table(model: tableModel(list: employeeData)) {
propertyColumn(header: 'Name', propertyName: 'name')
propertyColumn(header: 'Department', propertyName: 'department') {
// Combo box editor
cellEditor {
comboBox(items: ['Sales', 'Marketing', 'Engineering', 'HR'])
}
}
propertyColumn(header: 'Salary', propertyName: 'salary') {
// Formatted text field editor
cellEditor {
formattedTextField(format: NumberFormat.getCurrencyInstance())
}
}
propertyColumn(header: 'Start Date', propertyName: 'startDate') {
// Custom date picker editor
cellEditor {
bean(class: 'com.toedter.calendar.JDateChooser')
}
// Custom value extraction
editorValue { editor ->
return editor.date
}
// Custom editor preparation
prepareEditor { editor, table, value, isSelected, row, column ->
editor.date = value
return editor
}
}
}
}Built-in sorting support with TableSorter integration.
// TableSorter class for sortable table models
class TableSorter extends TableMap {
TableSorter(TableModel model)
void setTableHeader(JTableHeader tableHeader)
boolean isSorting()
int getSortingStatus(int column)
void setSortingStatus(int column, int status)
}
// Sorting constants
// DESCENDING = -1
// NOT_SORTED = 0
// ASCENDING = 1import groovy.swing.table.TableSorter
def tableData = [
[name: 'Alice', age: 30, salary: 50000],
[name: 'Bob', age: 25, salary: 45000],
[name: 'Charlie', age: 35, salary: 60000]
]
swing.frame(title: 'Sortable Table') {
scrollPane {
def tableModel = tableModel(list: tableData)
def sorter = new TableSorter(tableModel)
table(model: sorter) {
// Connect sorter to table header for click sorting
sorter.setTableHeader(it.tableHeader)
propertyColumn(header: 'Name', propertyName: 'name')
propertyColumn(header: 'Age', propertyName: 'age', type: Integer)
propertyColumn(header: 'Salary', propertyName: 'salary', type: Integer)
}
}
}
// Programmatic sorting
sorter.setSortingStatus(1, TableSorter.ASCENDING) // Sort by age ascendingTable selection handling and event processing.
// Selection binding support
// selectedRow: int - selected row index
// selectedColumn: int - selected column index
// selectedElement: Object - selected row object
// selectedElements: List - multiple selected objectsdef selectedPerson = new ValueHolder()
swing.frame(title: 'Table Selection') {
borderLayout()
// Table with selection binding
scrollPane(constraints: BorderLayout.CENTER) {
table(
model: tableModel(list: people),
selectedElement: bind(source: selectedPerson, sourceProperty: 'value')
) {
propertyColumn(header: 'Name', propertyName: 'name')
propertyColumn(header: 'Age', propertyName: 'age')
}
}
// Detail panel showing selected person
panel(constraints: BorderLayout.SOUTH) {
label(text: bind(
source: selectedPerson,
sourceProperty: 'value',
converter: { person ->
person ? "Selected: ${person.name} (${person.age})" : "No selection"
}
))
}
}
// Multiple selection
swing.table(
selectionMode: ListSelectionModel.MULTIPLE_INTERVAL_SELECTION,
selectedElements: bind(source: selectedItems, sourceProperty: 'value')
) {
// Table columns...
}// Custom table model with live data
class LiveDataTableModel extends DefaultTableModel {
void startUpdating() {
timer = new Timer(1000) {
// Update data periodically
fireTableDataChanged()
}
timer.start()
}
}
swing.table(model: new LiveDataTableModel()) {
// Column definitions...
}def originalData = [/* data */]
def filteredData = new FilteredListModel(originalData)
def filterText = new ValueHolder('')
swing.frame(title: 'Filtered Table') {
borderLayout()
// Filter input
panel(constraints: BorderLayout.NORTH) {
label(text: 'Filter:')
textField(
columns: 20,
text: bind(source: filterText, sourceProperty: 'value')
) {
// Apply filter on text change
bind(source: filterText, sourceProperty: 'value') { newValue ->
filteredData.filter = { item ->
item.name.toLowerCase().contains(newValue.toLowerCase())
}
}
}
}
// Filtered table
scrollPane(constraints: BorderLayout.CENTER) {
table(model: tableModel(list: filteredData)) {
propertyColumn(header: 'Name', propertyName: 'name')
propertyColumn(header: 'Description', propertyName: 'description')
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-apache-groovy--groovy-swing