外观
表格
ui.Table 把列表的行排成列,上方是 ui.TableColumn 组成的表头,每列有标题、宽度(或 0 表示均分其余列留下的空间)和对齐方式;cell 构建某行某列的内容,通常是一个 Text。它和列表一样只构建可见的行,行的高度等于其最高单元格的高度,因此换行的文本会让该行更高。
go
cols := []ui.TableColumn{{Title: "Name"}, {Title: "Size", Width: 90, Align: ui.End}}
app.files.Selected = &app.file
if ui.Table(c, &app.files, cols, len(files), func(row, col int) {
switch col {
case 0:
ui.Text(c, files[row].Name).SingleLine()
case 1:
ui.Text(c, files[row].Size())
}
}).Grow(1).Submitted() {
app.open(files[app.file])
}选择行
表格接受和列表相同的 ListState(或 nil),并代它获得键盘焦点:设置 Selected 时,点击或按键选择一行;设置 Selection 时选择多行;Submitted 报告双击或 Enter;Key、Label、Reorder 等的作用与列表相同。被 Header 指定的行横跨所有列,由 cell 作为第 0 列构建,并固定在其节中的行从其下方滚过的顶部。
选中和悬停的行是边到边的直角,如同 Finder 列表视图;RoundRows 按主题的 Radius 圆其角,与列表的行一致,适合其他列表都是圆角的应用。
选择单元格
在 Selection 的基础上,CellSelect 让表格像电子表格一样选择一块单元格:选中的行乘以从 Cells.Anchor 到 Cells.Cursor 的列(按显示顺序,Cells.All 表示所有列)。它始终是一块:不连续的多个单元格范围(如 Excel 中 Cmd 拖动追加的那些)无法选中。
- 点击。 点击单元格选中其行和其列;Shift+点击把行和列扩展到它;Cmd+点击(Linux 和 Windows 上为 Ctrl+点击)添加或移除其行,列保持不变。在块之外的单元格上按下时,指针按下即选择;在块之内按下则等待抬起。
- 拖动。 拖过单元格会选中拖过的单元格,并在靠近边缘时向下和横向滚动表格。
- 按键。 Left 和 Right 移动
Cells.Cursor,配合 Shift 扩展列;移动行的按键除非配合 Shift,否则会离开当前列;Cmd+A 选中所有行和所有列。在从右到左(RTL)下,Left 移向屏幕上位于左侧的列。在OutlineTable中,Left 和 Right 仍用于展开和折叠行,列要用指针选择。 - 右键。 在块之外的单元格上,先选中该单元格再打开上下文菜单;在块之内则块保持不变。
MenuKeepsSelection让块原样保留。 - 行头。
RowHeader列(如电子表格的行号列)选择整行:点击或拖动其单元格会选择行并同时选中所有列,点击其表头选择所有行。该列不参与排序。
块中的单元格像选中的行那样显示:强调色背景配强调色的文本色;选中整行时的行头单元格也一样;表格有焦点时,按键移动起点所在行中 Cells.Cursor 处的单元格带有边框。持有焦点的单元格(例如其中的编辑器)按其自身样式显示。选中的行不再整行着色。SelectedColumns 按显示顺序返回块的列 ID,SelectColumns 选中所有行和一段列,供应用自己的命令使用。CellSelected(row, col) 告诉单元格构建器其单元格是否被选中,以便在强调色上用自定义颜色的文本显示。用 RowDoubleClicked 读取所在行双击的单元格会把点击留给行,让块跟随它。
go
cols := []ui.TableColumn{{Title: "#", RowHeader: true, Width: 48, Fixed: true}, {Title: "Name"}, {Title: "Size", Width: 90}}
app.grid.Selection, app.grid.CellSelect = &app.chosen, true
ui.Table(c, &app.grid, cols, len(rows), func(row, col int) {
text, color := rows[row][col], c.Theme().Text
if text == "" {
text, color = "empty", c.Theme().TextMuted
}
if app.grid.CellSelected(row, col) {
color = c.Theme().AccentText // 弱化色在强调色上不显眼
}
ui.Text(c, text).TextColor(color).SingleLine()
}).Grow(1)调整列
用户可以像 Finder 那样调整列:
- 调整宽度。 拖动列头的右边缘可调整列宽,介于其
MinWidth和MaxWidth之间;双击该处让列宽适应其单元格。它前面那些均分剩余空间的列保持宽度不变,使边缘跟随指针。 - 移动。 拖动表头可在其他列之间移动该列。
- 横向滚动。 比表格宽的列会横向滚动,表头随之滚动。
- 固定列。
Fixed列停留在原位,宽度保持不变。
ListState.Columns 按列的 ID(默认为其标题)记录列的顺序和宽度:可以从保存的设置恢复它们,并在变化时保存。
排序
设置 ListState.Sort 后,点击 Sortable 列的表头按该列对行排序(升序),再次点击反转顺序:表头显示箭头,辅助技术读出顺序,表格的 Changed 报告变化。表格按 cell 收到的顺序显示行,所以要按它说的排序,并用 Key 让选择跟随其行:
go
cols := []ui.TableColumn{{Title: "Name", Sortable: true}, {Title: "Size", Width: 90, Align: ui.End, Sortable: true}}
app.files.Sort = &app.sort
sorted := sortFiles(app.files, app.sort) // 按 app.sort.Column,是否降序
app.files.Key = func(i int) any { return sorted[i].ID }要按多列排序,设置 ListState.SortBy。普通点击仍只按一列排序,若该列原本排在第一位则反转;Cmd+点击(Linux 和 Windows 上为 Ctrl+点击)或 Shift+点击(如同网页数据表格)把该列以升序追加到已排序列之后;再点一次变为降序,第三次把它移除。表头显示其方向,多列时还显示其次序。SortBy 变化时会收到新的切片,所以应用自己保存的那份不会被动。若同时设置了 Sort,表格会保持它始终是 SortBy 的第一项,不按任何列排序时为零值 SortOrder,因此先读主排序列的代码可以继续读 Sort:
go
app.files.SortBy = &app.sortBy // []ui.SortOrder,第一位在前
sorted := sortFiles(app.files, app.sortBy...)表头菜单
ListState.HeaderMenu 给表头一个上下文菜单,构建方式同 ContextMenu。col 是被点击列在传给 Table 的列中的索引,无论用户把它们移成了什么顺序;点击到列之后的表头空白处时为 -1,如同 AppKit 的 clickedColumn。构建时没有项的菜单不会打开:
go
app.files.HeaderMenu = func(m *ui.Menu, col int) {
if col >= 0 && m.Item("Hide Column").Chosen() {
app.hide(cols[col].ID)
}
if m.Item("Show All Columns").Chosen() {
app.showAll()
}
}单元格菜单
ListState.CellMenu 给单元格一个上下文菜单,范围覆盖整个单元格(含内边距),构建方式同 ContextMenu。row 和 col 与单元格构建器收到的相同。构建时没有项的菜单不会打开;若单元格内容自带上下文菜单,右键会被它接管:
go
app.grid.CellMenu = func(m *ui.Menu, row, col int) {
if m.Item("Copy").Chosen() {
app.copy(row, col)
}
}就地重命名
单元格中的可编辑文本可以就地重命名,如同 Finder 的文件名:macOS 上按 Return、其他平台上按 F2 编辑选中行的文本;行选中后再点击其文本也可以。
go
ui.Table(c, &app.files, cols, len(files), func(row, col int) {
if col == 0 && ui.EditableText(c, &files[row].Name).Changed() {
app.rename(files[row])
}
})无障碍
辅助技术看到的是一个由 Label 命名的表格,其行以行内文本命名,每行都说明它是所有行中的第几行,列头说明行的排序方式。设置 CellSelect 时,块中的单元格会报告其被选中。
分列的行组成的树,请见大纲。