Skip to content

表格 ​

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 时,块中的单元格会报告其被选中。

分列的行组成的树,请见大纲。