Table

@Composable
fun <R> Table(rows: List<R>, modifier: SwingModifier = SwingModifier, selectedRowIndices: Set<Int>? = null, onSelectionChange: (Set<Int>) -> Unit = {}, selectionMode: Int = ListSelectionModel.MULTIPLE_INTERVAL_SELECTION, sortable: Boolean = false, sortKeys: List<RowSorter.SortKey>? = null, onSortChange: (List<RowSorter.SortKey>) -> Unit = {}, rowFilter: RowFilter<in TableModel, in Int>? = null, rowHeight: Int? = null, autoResizeMode: Int = JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS, fillsViewportHeight: Boolean = false, columnLayout: TableColumnLayout? = null, onColumnLayoutChange: (TableColumnLayout) -> Unit = {}, block: TableScope<R>.() -> Unit)

A grid the user reads, sorts, and selects in: a JTable reporting the selection, the sort order, and the column layout the user leaves it in.

rows are the row data; declare each column in block with its header, a value extractor, and (optionally) in-place editing. Rows and columns are data: changing rows or the declared columns rebuilds the table's model on recomposition. Selection is declared with selectedRowIndices and reported through onSelectionChange, expressed as the general multi-select shape so one component covers all of SelectionMode's modes. Place it in a org.jetbrains.compose.swing.components.layout.ScrollPane to scroll and to show the column header.

ScrollPane {
content {
Table(
rows = people,
selectedRowIndices = selection,
onSelectionChange = { selection = it },
) {
column("Name") { it.name }
column("Age", isEditable = true, onCellEdit = { row, _, v -> update(row, v) }) { it.age }
}
}
}

A cell edit commits through the edited column's onCellEdit; the displayed value does not change until the caller updates the backing state and the next composition supplies fresh rows. onSelectionChange reports the user's selection changes only, once per settled change - so dragging across rows produces one callback at the end rather than one per row crossed, and rendering new rows or new columns produces none. A declared selection is the composition's state and is re-applied on every pass: it survives such a change (an index the current rows no longer cover is dropped), and a user change the caller does not adopt does not stand. Undeclared, the selection is the user's alone - never imposed, and kept across new rows and new columns all the same; where the new rows are too few to hold it, the rows that fall outside them leave the selection and onSelectionChange reports what is left of it.

A selected row is named by its index into rows - the model's own row space, the space TableColumnLayout.modelIndices names its columns in - and never by the position it is drawn at: sorting and filtering move where a row is shown and leave the row each index names alone.

Sorting is off until sortable turns it on, as it is on a bare JTable. With it on, clicking a column header sorts the rows by that column, sortKeys declares the order they are in and onSortChange reports the order a header click leaves them in, and rowFilter decides which of them are shown at all. A declared order is the composition's state and is re-applied on every pass, so a header click the caller does not adopt does not stand; undeclared, the order is the user's alone and is never imposed. Each column brings its own isSortable and comparator to that sorting.

The order and the widths of the columns are the same kind of state, declared with columnLayout and reported through onColumnLayoutChange: dragging a column header sideways reorders the columns and dragging the divider between two headers resizes them, and each reaches onColumnLayoutChange with the layout the columns are then in. New columns rebuild the layout from the declarations, so a declared layout is put back over them and survives, and a change the caller does not adopt is reported once and does not stand; an undeclared layout - the user's own - is carried across the rebuild as far as the new columns can hold it, with onColumnLayoutChange reporting what is left of it where they cannot. A column's own minWidth and maxWidth bound every width it can be left at, a drag's as much as a declaration's, and autoResizeMode decides how the columns share a width change between them.

A column renders its cells through the renderer the table picks by the column's class until its cellContent gives it a composable cell of its own. A table gives every one of its rows the same height and never measures one by what its cells ask for, so a cell taller than the text a table sizes its rows for is what rowHeight is for.

Parameters

rows

the row data to display

modifier

the SwingModifier applied to the underlying component

selectedRowIndices

the indices into rows of the selected rows the caller declares; null - the default - leaves the selection to the user

onSelectionChange

callback invoked with the indices into rows the user settles on

selectionMode

how many rows/ranges may be selected; MULTIPLE_INTERVAL_SELECTION - the default - lets the user select any number of ranges

sortable

whether the table sorts and filters its rows; false - the default - leaves them in the order rows declares and its column headers inert

sortKeys

the sort order the caller declares; null - the default - leaves the order to the user

onSortChange

callback invoked with the order the user's header click leaves the rows in

rowFilter

which of the rows the table shows, or null - the default - to show all of them; a filter is adopted by identity, so pass a stable one (e.g. remember {}) to avoid churn

rowHeight

the height in pixels of every row; null - the default - leaves it to the look and feel

autoResizeMode

how the columns share out a change to the table's width; AUTO_RESIZE_SUBSEQUENT_COLUMNS - the default - takes the change out of the columns right of the one resized, while AUTO_RESIZE_OFF leaves every column its width and scrolls instead

fillsViewportHeight

whether the table stretches to the full height of the viewport showing it, rather than to the height of the rows it holds; false - the default - leaves it as tall as its rows

columnLayout

the column order and widths the caller declares; null - the default - leaves the column layout to the user

onColumnLayoutChange

callback invoked when the user reorders or resizes the columns

block

declares the columns; see TableScope

See also


@Composable
fun <R> Table(rows: List<R>, listSelectionListener: ListSelectionListener, modifier: SwingModifier = SwingModifier, selectedRowIndices: Set<Int>? = null, selectionMode: Int = ListSelectionModel.MULTIPLE_INTERVAL_SELECTION, sortable: Boolean = false, sortKeys: List<RowSorter.SortKey>? = null, rowSorterListener: RowSorterListener? = null, rowFilter: RowFilter<in TableModel, in Int>? = null, rowHeight: Int? = null, autoResizeMode: Int = JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS, fillsViewportHeight: Boolean = false, columnLayout: TableColumnLayout? = null, tableColumnModelListener: TableColumnModelListener? = null, block: TableScope<R>.() -> Unit)

A Table driven by raw listeners instead of the onSelectionChange/onSortChange/onColumnLayoutChange lambdas. The selection listener observes the table's selectionModel, so it sees the adjusting events of a drag as well as the settled one. A listener is notified of the user's own changes only, and of what a rebuild of the rows or the columns took away from the user; each is removed on the same instance, so pass a stable one (e.g. remember {}) to avoid churn.

A selection event is handed on with the table as its source, so the selection is read back from it the way a list's is read back from the list. Its firstIndex and lastIndex stay the numbers the selection model published - screen rows, which a sort order or a filter parts from the model rows selectedRowIndices names - because renumbering a widget's own event would say something the widget never said. A loss is the one event the widget did not number: the rows it names are the ones the user lost, so it carries them as model rows, the only space that still describes a row the table may no longer show.

Parameters

rows

the row data to display

listSelectionListener

the listener notified of the user's selection-model changes

modifier

the SwingModifier applied to the underlying component

selectedRowIndices

the indices into rows of the selected rows the caller declares; null - the default - leaves the selection to the user

selectionMode

how many rows/ranges may be selected; MULTIPLE_INTERVAL_SELECTION - the default - lets the user select any number of ranges

sortable

whether the table sorts and filters its rows; false - the default - leaves them in the order rows declares and its column headers inert

sortKeys

the sort order the caller declares; null - the default - leaves the order to the user

rowSorterListener

the listener notified of the user's sort-order changes; null - the default - reports none of them

rowFilter

which of the rows the table shows, or null - the default - to show all of them; a filter is adopted by identity, so pass a stable one (e.g. remember {}) to avoid churn

rowHeight

the height in pixels of every row; null - the default - leaves it to the look and feel

autoResizeMode

how the columns share out a change to the table's width; AUTO_RESIZE_SUBSEQUENT_COLUMNS - the default - takes the change out of the columns right of the one resized, while AUTO_RESIZE_OFF leaves every column its width and scrolls instead

fillsViewportHeight

whether the table stretches to the full height of the viewport showing it, rather than to the height of the rows it holds; false - the default - leaves it as tall as its rows

columnLayout

the column order and widths the caller declares; null - the default - leaves the column layout to the user

tableColumnModelListener

the listener notified of the user's column reorders and resizes; null

  • the default - reports none of them

block

declares the columns; see TableScope

See also


@Composable
fun Table(model: TableModel, modifier: SwingModifier = SwingModifier, selectedRowIndices: Set<Int>? = null, onSelectionChange: (Set<Int>) -> Unit = {}, selectionMode: Int = ListSelectionModel.MULTIPLE_INTERVAL_SELECTION, sortable: Boolean = false, sortKeys: List<RowSorter.SortKey>? = null, onSortChange: (List<RowSorter.SortKey>) -> Unit = {}, rowFilter: RowFilter<in TableModel, in Int>? = null, rowHeight: Int? = null, autoResizeMode: Int = JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS, fillsViewportHeight: Boolean = false, columnLayout: TableColumnLayout? = null, onColumnLayoutChange: (TableColumnLayout) -> Unit = {})

A grid the user reads, sorts, and selects in, over a TableModel the caller owns: a JTable reporting the selection, the sort order, and the column layout the user leaves it in.

The model is displayed as-is: its own columns, values, and editability drive the table, and the library never mutates it. Supplying a new model instance swaps it into the table on recomposition. Selection is declared with selectedRowIndices and reported through onSelectionChange, expressed as the general multi-select shape so one component covers all of SelectionMode's modes, and survives a model swap whether declared or not. Place it in a org.jetbrains.compose.swing.components.layout.ScrollPane to scroll and to show the column header.

ScrollPane {
content {
Table(
model = myTableModel,
selectedRowIndices = selection,
onSelectionChange = { selection = it },
)
}
}

onSelectionChange reports the user's selection changes only, once per settled change - so dragging across rows produces one callback at the end rather than one per row crossed, and installing a new model produces none. A declared selection is the composition's state and is re-applied on every pass, so a user change the caller does not adopt does not stand; undeclared, the selection is the user's alone and is never imposed - where the new model has too few rows to hold it, the rows that fall outside it leave the selection and onSelectionChange reports what is left of it.

A selected row is named by its index in model - the model's own row space, the space TableColumnLayout.modelIndices names its columns in - and never by the position it is drawn at: sorting and filtering move where a row is shown and leave the row each index names alone.

Sorting is off until sortable turns it on, as it is on a bare JTable. With it on, clicking a column header sorts the rows by that column, sortKeys declares the order they are in and onSortChange reports the order a header click leaves them in, and rowFilter decides which of them are shown at all. A declared order is the composition's state and is re-applied on every pass, so a header click the caller does not adopt does not stand; undeclared, the order is the user's alone and is never imposed.

The order and the widths of the columns are the same kind of state, declared with columnLayout and reported through onColumnLayoutChange: dragging a column header sideways reorders the columns and dragging the divider between two headers resizes them, and each reaches onColumnLayoutChange with the layout the columns are then in. A new model rebuilds the columns from it, so a declared layout is put back over them and survives the swap, and a change the caller does not adopt is reported once and does not stand; an undeclared layout - the user's own - is carried across the swap as far as the new model's columns can hold it, with onColumnLayoutChange reporting what is left of it where they cannot.

Parameters

model

the table model to display; owned by the caller and never mutated by the library

modifier

the SwingModifier applied to the underlying component

selectedRowIndices

the indices in model of the selected rows the caller declares; null - the default - leaves the selection to the user

onSelectionChange

callback invoked with the indices in model the user settles on

selectionMode

how many rows/ranges may be selected; MULTIPLE_INTERVAL_SELECTION - the default - lets the user select any number of ranges

sortable

whether the table sorts and filters its rows; false - the default - leaves them in the order model holds them in and its column headers inert

sortKeys

the sort order the caller declares; null - the default - leaves the order to the user

onSortChange

callback invoked with the order the user's header click leaves the rows in

rowFilter

which of the rows the table shows, or null - the default - to show all of them; a filter is adopted by identity, so pass a stable one (e.g. remember {}) to avoid churn

rowHeight

the height in pixels of every row; null - the default - leaves it to the look and feel

autoResizeMode

how the columns share out a change to the table's width; AUTO_RESIZE_SUBSEQUENT_COLUMNS - the default - takes the change out of the columns right of the one resized, while AUTO_RESIZE_OFF leaves every column its width and scrolls instead

fillsViewportHeight

whether the table stretches to the full height of the viewport showing it, rather than to the height of the rows it holds; false - the default - leaves it as tall as its rows

columnLayout

the column order and widths the caller declares; null - the default - leaves the column layout to the user

onColumnLayoutChange

callback invoked when the user reorders or resizes the columns

See also


@Composable
fun Table(model: TableModel, listSelectionListener: ListSelectionListener, modifier: SwingModifier = SwingModifier, selectedRowIndices: Set<Int>? = null, selectionMode: Int = ListSelectionModel.MULTIPLE_INTERVAL_SELECTION, sortable: Boolean = false, sortKeys: List<RowSorter.SortKey>? = null, rowSorterListener: RowSorterListener? = null, rowFilter: RowFilter<in TableModel, in Int>? = null, rowHeight: Int? = null, autoResizeMode: Int = JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS, fillsViewportHeight: Boolean = false, columnLayout: TableColumnLayout? = null, tableColumnModelListener: TableColumnModelListener? = null)

A model-driven Table driven by raw listeners instead of the onSelectionChange/onSortChange/onColumnLayoutChange lambdas. The selection listener observes the table's selectionModel, so it sees the adjusting events of a drag as well as the settled one. A listener is notified of the user's own changes only, and of what a model swap took away from the user; each is removed on the same instance, so pass a stable one (e.g. remember {}) to avoid churn.

The model is displayed as-is and never mutated by the library, and the selection survives a model swap whether declared or not.

A selection event is handed on with the table as its source, numbered the same way as in the row-based Table overload: firstIndex and lastIndex stay screen rows, and a loss is reported as a model row.

Parameters

model

the table model to display; owned by the caller and never mutated by the library

listSelectionListener

the listener notified of the user's selection-model changes

modifier

the SwingModifier applied to the underlying component

selectedRowIndices

the indices in model of the selected rows the caller declares; null - the default - leaves the selection to the user

selectionMode

how many rows/ranges may be selected; MULTIPLE_INTERVAL_SELECTION - the default - lets the user select any number of ranges

sortable

whether the table sorts and filters its rows; false - the default - leaves them in the order model holds them in and its column headers inert

sortKeys

the sort order the caller declares; null - the default - leaves the order to the user

rowSorterListener

the listener notified of the user's sort-order changes; null - the default - reports none of them

rowFilter

which of the rows the table shows, or null - the default - to show all of them; a filter is adopted by identity, so pass a stable one (e.g. remember {}) to avoid churn

rowHeight

the height in pixels of every row; null - the default - leaves it to the look and feel

autoResizeMode

how the columns share out a change to the table's width; AUTO_RESIZE_SUBSEQUENT_COLUMNS - the default - takes the change out of the columns right of the one resized, while AUTO_RESIZE_OFF leaves every column its width and scrolls instead

fillsViewportHeight

whether the table stretches to the full height of the viewport showing it, rather than to the height of the rows it holds; false - the default - leaves it as tall as its rows

columnLayout

the column order and widths the caller declares; null - the default - leaves the column layout to the user

tableColumnModelListener

the listener notified of the user's column reorders and resizes; null

  • the default - reports none of them

See also


@Composable
fun <R> Table(rows: List<R>, state: TableState, modifier: SwingModifier = SwingModifier, selectionMode: Int = ListSelectionModel.MULTIPLE_INTERVAL_SELECTION, sortable: Boolean = false, sortKeys: List<RowSorter.SortKey>? = null, onSortChange: (List<RowSorter.SortKey>) -> Unit = {}, rowFilter: RowFilter<in TableModel, in Int>? = null, rowHeight: Int? = null, autoResizeMode: Int = JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS, fillsViewportHeight: Boolean = false, columnLayout: TableColumnLayout? = null, onColumnLayoutChange: (TableColumnLayout) -> Unit = {}, block: TableScope<R>.() -> Unit)

A Table driven by a TableState instead of a declared selectedRowIndices and an onSelectionChange lambda. The state owns the selection: the rows it holds are what the table shows selected, the user's own selecting is written back into it, and it is where a row is revealed from.

val state = rememberTableState()

ScrollPane {
Table(rows = people, state = state, modifier = SwingModifier.viewport()) {
column("Name") { it.name }
}
}
Label("Selected: ${state.selectedRowIndices.size}")

Parameters

rows

the row data to display

state

the hoistable selection state the table applies and reports into; see TableState

modifier

the SwingModifier applied to the underlying component

selectionMode

how many rows/ranges may be selected; MULTIPLE_INTERVAL_SELECTION - the default - lets the user select any number of ranges

sortable

whether the table sorts and filters its rows; false - the default - leaves them in the order rows declares and its column headers inert

sortKeys

the sort order the caller declares; null - the default - leaves the order to the user

onSortChange

callback invoked with the order the user's header click leaves the rows in

rowFilter

which of the rows the table shows, or null - the default - to show all of them; a filter is adopted by identity, so pass a stable one (e.g. remember {}) to avoid churn

rowHeight

the height in pixels of every row; null - the default - leaves it to the look and feel

autoResizeMode

how the columns share out a change to the table's width; AUTO_RESIZE_SUBSEQUENT_COLUMNS - the default - takes the change out of the columns right of the one resized, while AUTO_RESIZE_OFF leaves every column its width and scrolls instead

fillsViewportHeight

whether the table stretches to the full height of the viewport showing it, rather than to the height of the rows it holds; false - the default - leaves it as tall as its rows

columnLayout

the column order and widths the caller declares; null - the default - leaves the column layout to the user

onColumnLayoutChange

callback invoked when the user reorders or resizes the columns

block

declares the columns; see TableScope

See also


@Composable
fun Table(model: TableModel, state: TableState, modifier: SwingModifier = SwingModifier, selectionMode: Int = ListSelectionModel.MULTIPLE_INTERVAL_SELECTION, sortable: Boolean = false, sortKeys: List<RowSorter.SortKey>? = null, onSortChange: (List<RowSorter.SortKey>) -> Unit = {}, rowFilter: RowFilter<in TableModel, in Int>? = null, rowHeight: Int? = null, autoResizeMode: Int = JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS, fillsViewportHeight: Boolean = false, columnLayout: TableColumnLayout? = null, onColumnLayoutChange: (TableColumnLayout) -> Unit = {})

A model-driven Table driven by a TableState instead of a declared selectedRowIndices and an onSelectionChange lambda. The state owns the selection: the rows it holds are what the table shows selected, the user's own selecting is written back into it, and it is where a row is revealed from.

The model is displayed as-is and never mutated by the library; the selection survives a model swap.

Parameters

model

the table model to display; owned by the caller and never mutated by the library

state

the hoistable selection state the table applies and reports into; see TableState

modifier

the SwingModifier applied to the underlying component

selectionMode

how many rows/ranges may be selected; MULTIPLE_INTERVAL_SELECTION - the default - lets the user select any number of ranges

sortable

whether the table sorts and filters its rows; false - the default - leaves them in the order model holds them in and its column headers inert

sortKeys

the sort order the caller declares; null - the default - leaves the order to the user

onSortChange

callback invoked with the order the user's header click leaves the rows in

rowFilter

which of the rows the table shows, or null - the default - to show all of them; a filter is adopted by identity, so pass a stable one (e.g. remember {}) to avoid churn

rowHeight

the height in pixels of every row; null - the default - leaves it to the look and feel

autoResizeMode

how the columns share out a change to the table's width; AUTO_RESIZE_SUBSEQUENT_COLUMNS - the default - takes the change out of the columns right of the one resized, while AUTO_RESIZE_OFF leaves every column its width and scrolls instead

fillsViewportHeight

whether the table stretches to the full height of the viewport showing it, rather than to the height of the rows it holds; false - the default - leaves it as tall as its rows

columnLayout

the column order and widths the caller declares; null - the default - leaves the column layout to the user

onColumnLayoutChange

callback invoked when the user reorders or resizes the columns

See also