diff --git a/_data/docs.yml b/_data/docs.yml index b7ec3455..24bd6c4a 100644 --- a/_data/docs.yml +++ b/_data/docs.yml @@ -71,6 +71,8 @@ docs: - list - table + - table-styling + - table-headers - tree - title: Data Binding diff --git a/api/size.md b/api/size.md new file mode 100644 index 00000000..6576f7d0 --- /dev/null +++ b/api/size.md @@ -0,0 +1,4 @@ +--- +permalink: /api/size/ +redirect_to: /api/v2.6/size/ +--- diff --git a/api/widget.html b/api/widget.html deleted file mode 100644 index d97c4c44..00000000 --- a/api/widget.html +++ /dev/null @@ -1,4 +0,0 @@ ---- -permalink: /api/widget/ -redirect_to: /api/v1.4/widget/ ---- diff --git a/api/widget/table.md b/api/widget/table.md new file mode 100644 index 00000000..72ee9705 --- /dev/null +++ b/api/widget/table.md @@ -0,0 +1,4 @@ +--- +permalink: /api/widget/table/ +redirect_to: /api/v2.6/widget/table/ +--- diff --git a/collection/fixed-headers.png b/collection/fixed-headers.png new file mode 100644 index 00000000..73d929ee Binary files /dev/null and b/collection/fixed-headers.png differ diff --git a/collection/table-headers.md b/collection/table-headers.md new file mode 100644 index 00000000..10a5df62 --- /dev/null +++ b/collection/table-headers.md @@ -0,0 +1,95 @@ +--- +title: Table Headers + +--- + +The `Table` collection widget optionally supports +"sticky" row and/or column headers, which +remain at the top or left of the container while the data scrolls. + +This support uses additional callbacks, `CreateHeader` and `UpdateHeader` +to manage a separate template widget which can be +styled separately from the data cell widget. + +```go +package main + +import ( + "fmt" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/app" + "fyne.io/fyne/v2/widget" +) + +var dataTemplateText = "0123456789012345" +var sampData = []string{ + "Lorem ipsum dolo", + "consectetur adip", + "sed do eiusmod t", + "Ut enim ad minim", + "quis nostrud exe", +} +var tableCols = 3 + +func makeTableComponents() *widget.Table { + table := widget.NewTable( + // length: return #rows, #cols + func() (int, int) { + return 15, tableCols + }, + + // create cell template + func() fyne.CanvasObject { + tpl := widget.NewLabel(dataTemplateText) + return tpl + }, + + // update cell + func(id widget.TableCellID, cellTpl fyne.CanvasObject) { + cell := cellTpl.(*widget.Label) + cell.SetText(sampData[(tableCols*id.Row+id.Col)%len(sampData)]) + // no explicit .Refresh() needed because .SetText() did it. + }, + ) + + // Enable sticky row and/or column headers + table.ShowHeaderRow = true + table.ShowHeaderColumn = true + + table.CreateHeader = func() fyne.CanvasObject { + return widget.NewLabelWithStyle("row xx header", + fyne.TextAlignCenter, + fyne.TextStyle{Bold: true, Underline: true}) + } + + table.UpdateHeader = func(id widget.TableCellID, template fyne.CanvasObject) { + cell := template.(*widget.Label) + + if id.Row < 0 && id.Col < 0 { + // the top left header cell {-1, -1} is never populated + panic(fmt.Sprintf("didn't expect update with id %v", id)) + } else if id.Row < 0 { // {row: -1, col: x} Set column header for col x + cell.SetText(fmt.Sprintf("-- Col %d Header --", id.Col)) + } else if id.Col < 0 { // {row: x, col: -1} Set row header for row x + cell.SetText(fmt.Sprintf("Row %d Header", id.Row)) + } + } + return table +} + +func main() { + a := app.NewWithID("com.example.sample.table_headers") + w := a.NewWindow("table_headers") + table := makeTableComponents() + w.SetContent(table) + w.Resize(fyne.NewSize(430, 300)) + w.ShowAndRun() +} +``` + +Which renders as: +![Sample program for sticky headers](fixed-headers.png) + +Refer to [`widget.Table`](/api/widget/table) API for additional details on how to implement headers. + diff --git a/collection/table-styling.md b/collection/table-styling.md new file mode 100644 index 00000000..72e6a6ad --- /dev/null +++ b/collection/table-styling.md @@ -0,0 +1,109 @@ +--- +title: Table Styling + +--- + +The default styling and size of each cell in the table is determined by +the template object returned by the `CreateCell` callback of the [`Table`](/api/widget/table) collection widget. + +These can be overridden for a given cell by styling the cell widget in the `UpdateCell` callback. + +The size of a particular row or column can be overridden with +`Table` methods `.SetRowHeight()` or `.SetColumnWidth()`. + +```go +package main + +import ( + "fmt" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/app" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" +) + +func makeTableComponents() *widget.Table { + wide_text := "row %2d, wider, left aligned" + + table := widget.NewTable( + // length: return #rows, #cols + func() (int, int) { + return 15, 3 + }, + + // create cell template + func() fyne.CanvasObject { + return widget.NewLabel("template text") + }, + + // update cell + func(id widget.TableCellID, cellTpl fyne.CanvasObject) { + cell := cellTpl.(*widget.Label) + + // style each column differently + switch id.Col { + case 0: + cell.SetText("right aligned") + cell.Alignment = fyne.TextAlignTrailing + case 1: + cell.SetText("centered") + cell.Alignment = fyne.TextAlignCenter + case 2: + cell.SetText(fmt.Sprintf( + wide_text, id.Row)) + cell.Alignment = fyne.TextAlignLeading + } + // style alternating rows differently + if id.Row%2 == 1 { + cell.TextStyle = fyne.TextStyle{Bold: true} + cell.Importance = widget.DangerImportance + } else { + cell.TextStyle = fyne.TextStyle{} + cell.Importance = widget.MediumImportance + } + cell.Refresh() // refresh needed after styling changes + }, + ) + + // set an individual column width to fit a given string and styling + stdTextWidth := fyne.CurrentApp().Settings().Theme().Size(theme.SizeNameText) + strSize := fyne.MeasureText(wide_text, stdTextWidth, fyne.TextStyle{Bold: true}) + table.SetColumnWidth(2, strSize.Width) + + return table +} +func main() { + a := app.NewWithID("com.example.sample.table_style") + w := a.NewWindow("table_style") + table := makeTableComponents() + w.SetContent(table) + w.Resize(fyne.NewSize(430, 300)) + w.ShowAndRun() +} +``` + +Which renders as: +![Sample program showing table styling](./table-styling.png) + +Several things to note about the `UpdateCell` callback: +1. Invoke the cell's `.Refresh()` method. +Most widget "setter" methods do this automatically (e.g `cell.SetText()` +in the example), but if the setter is not the last operation in the +callback, the callback must do it explicitly. +2. Update all the styling attributes in each invocation. +The `fyne.CanvasObject` provided on entry to the callback +may be a newly-instantiated template with initial styling +*or* a cell retrieved from cache which might have had other styling. +The `UpdateCell` callback should update *all* the styling attributes +appropriate to the cell index and value. + +Note that the sample overrides the width of one column +to fit the longest string in that column. +It uses [`fyne.MeasureText()`](/api/size) +to calculate the size of the string in screen units, +which is useful calculation in many contexts. + +--- + +Next, we'll discuss setting [table headers](table-headers). diff --git a/collection/table-styling.png b/collection/table-styling.png new file mode 100644 index 00000000..de55dee9 Binary files /dev/null and b/collection/table-styling.png differ diff --git a/collection/table.md b/collection/table.md index cb98dc14..63a02c8c 100644 --- a/collection/table.md +++ b/collection/table.md @@ -6,50 +6,56 @@ redirect_from: - /widget/table --- -The `Table` collection widget is like the [List](/collection/list) widget (another of the toolkit's collection widgets) with a two-dimensional index. -Like `List` this is designed to help build really performant -interfaces when lots of data is being presented. -Because of this the widget is not created with all the data embedded, but instead calls out to the data source when needed. +The `Table` collection widget is like the [List](/collection/list) widget (another of the toolkit's collection widgets), but with a two-dimensional data structure. +It is designed to help build really performant +interfaces when lots of data is being presented. The `Table` uses callback functions to ask for data when it is required. -There are 3 main callbacks, `Length`, `CreateCell` and `UpdateCell`. The Length callback (passed first) is the simplest, -it returns how many items are in the data to be presented, the two ints it returns represent the row and column count. -The other two relate to the content templates. -The `CreateCell` callback returns a new template object, just like list. -The difference being that `MinSize` will define the standard size of each cell, and the minimum size of the table (it shows at least one cell). -As previously the `UpdateCell` is called to apply data to a cell template. The index passed in is the same `(row, col)` int pair. +* The `Length` callback returns the number of rows and columns of data from the data source. +The `Table` widget will display scrollbars if the layout doesn't have room to display all the data at once. + +* The `CreateCell` callback returns an object which will be the template for all cells in the table. +`MinSize` of the template object defines the standard size of each cell +and also the minimum size of the table (since it shows at least one cell). + +* The `UpdateCell` callback applies data, content and styling for each cell of the table. It is invoked for a particular cell only when that cell needs to be displayed, with a `TableCellID` specifying the row and column of the cell. ```go package main import ( - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/app" - "fyne.io/fyne/v2/widget" + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/app" + "fyne.io/fyne/v2/widget" ) var data = [][]string{[]string{"top left", "top right"}, - []string{"bottom left", "bottom right"}} + []string{"bottom left", "bottom right"}} func main() { - myApp := app.New() - myWindow := myApp.NewWindow("Table Widget") - - list := widget.NewTable( - func() (int, int) { - return len(data), len(data[0]) - }, - func() fyne.CanvasObject { - return widget.NewLabel("wide content") - }, - func(i widget.TableCellID, o fyne.CanvasObject) { - o.(*widget.Label).SetText(data[i.Row][i.Col]) - }) - - myWindow.SetContent(list) - myWindow.ShowAndRun() + myApp := app.New() + myWindow := myApp.NewWindow("Table Widget") + + list := widget.NewTable( + // Length callback + func() (int, int) { + return len(data), len(data[0]) + }, + // CreateCell callback + func() fyne.CanvasObject { + return widget.NewLabel("wide content") + }, + // UpdateCell callback + func(i widget.TableCellID, o fyne.CanvasObject) { + o.(*widget.Label).SetText(data[i.Row][i.Col]) + }) + + myWindow.SetContent(list) + myWindow.ShowAndRun() } ``` -For more info, for example on how to add headers to the table, see the [widget.Table API documentation](/api/v2.5/widget/table.html). +--- +Next, we'll see how to [style](/collection/table-styling) the table, +and how to display [headers](/collection/table-headers).