[GH-ISSUE #1040] Clarification on setting table column widths #752

Closed
opened 2026-03-04 01:07:29 +03:00 by kerem · 2 comments
Owner

Originally created by @jsumners-nr on GitHub (Oct 17, 2024).
Original GitHub issue: https://github.com/rivo/tview/issues/1040

I have a case where I want columns to have specific widths, e.g.:

  1. width 2
  2. width 23
  3. width 6
  4. width 14
  5. width 3
  6. width all remaining space

Based on https://github.com/rivo/tview/issues/227 and https://github.com/rivo/tview/issues/242, I think what I need to be doing is:

Simple example app
package main

import (
	"github.com/gdamore/tcell/v2"
	"github.com/rivo/tview"
)

type TUI struct {
	app *tview.Application

	bottomBar *tview.Grid
	grid      *tview.Grid
}

func main() {
	tui := TUI{}
	tui.app = tview.NewApplication()

	// Presuming a width of 100 cells:
	tui.bottomBar = tview.NewGrid().SetRows(1).SetColumns(0, 0)
	tui.bottomBar.SetBackgroundColor(tcell.ColorGhostWhite)

	leftStatus := tview.NewTextView().SetTextAlign(tview.AlignLeft).SetText("status bar").SetTextColor(tcell.ColorBlack)
	leftStatus.SetBackgroundColor(tcell.ColorWhite)
	rightStatus := tview.NewTextView().SetTextAlign(tview.AlignRight).SetText("(s)earch | (h)elp").SetTextColor(tcell.ColorBlack)
	rightStatus.SetBackgroundColor(tcell.ColorWhite)
	tui.bottomBar.AddItem(leftStatus, 0, 0, 1, 1, 0, 0, false)
	tui.bottomBar.AddItem(rightStatus, 0, 1, 1, 1, 0, 0, false)

	tui.grid = tview.NewGrid().
		SetRows(0, 1).
		SetColumns(0).
		AddItem(tui.bottomBar, 1, 0, 1, 1, 0, 0, false)

	_, _, width, _ := tui.grid.GetRect()
	content := &MyTable{
		width: width,
		rows: [][]string{
			[]string{"1", "two", "three"},
			[]string{"4", "five", "six"},
		},
	}
	table := tview.NewTable().SetContent(content)
	table.Box.SetBorder(true) // Just to see the layout for testing.
	tui.grid.AddItem(table, 0, 0, 1, 1, 0, 0, true)

	tui.app.SetRoot(tui.grid, true).SetFocus(table)
	tui.app.Run()
}

type MyTable struct {
	width int
	rows  [][]string
}

func (t *MyTable) GetCell(row int, col int) *tview.TableCell {
	if row >= len(t.rows) {
		return nil
	}

	r := t.rows[row]
	if col >= len(r) {
		return nil
	}

	cell := tview.NewTableCell(r[col])
	switch col {
	case 0:
		cell.SetMaxWidth(2)
	case 1:
		cell.SetMaxWidth(5)
	case 2:
		cell.SetMaxWidth(t.width - 7)
		cell.SetExpansion(1)
		txt := cell.Text + " "
		for range 100 {
			txt += "!"
		}
		txt += "~~~~~~"
		cell.Text = txt
	}

	return cell
}

func (t *MyTable) GetRowCount() int {
	return len(t.rows)
}

func (t *MyTable) GetColumnCount() int {
	if len(t.rows) == 0 {
		return 0
	}
	return len(t.rows[0])
}

func (t *MyTable) SetCell(row int, col int, cell *tview.TableCell) {

}

func (t *MyTable) RemoveRow(row int) {}

func (t *MyTable) RemoveColumn(col int) {}

func (t *MyTable) InsertRow(row int) {}

func (t *MyTable) InsertColumn(col int) {}

func (t *MyTable) Clear() {}

Is this correct? It's rather weird to set the "maximum width" in order to get a fixed width. And it's surprising to need toggle expansion of columns on.

Originally created by @jsumners-nr on GitHub (Oct 17, 2024). Original GitHub issue: https://github.com/rivo/tview/issues/1040 I have a case where I want columns to have specific widths, e.g.: 1. width 2 2. width 23 3. width 6 4. width 14 5. width 3 6. width _all remaining space_ Based on https://github.com/rivo/tview/issues/227 and https://github.com/rivo/tview/issues/242, I _think_ what I need to be doing is: <details> <summary>Simple example app</summary> ```go package main import ( "github.com/gdamore/tcell/v2" "github.com/rivo/tview" ) type TUI struct { app *tview.Application bottomBar *tview.Grid grid *tview.Grid } func main() { tui := TUI{} tui.app = tview.NewApplication() // Presuming a width of 100 cells: tui.bottomBar = tview.NewGrid().SetRows(1).SetColumns(0, 0) tui.bottomBar.SetBackgroundColor(tcell.ColorGhostWhite) leftStatus := tview.NewTextView().SetTextAlign(tview.AlignLeft).SetText("status bar").SetTextColor(tcell.ColorBlack) leftStatus.SetBackgroundColor(tcell.ColorWhite) rightStatus := tview.NewTextView().SetTextAlign(tview.AlignRight).SetText("(s)earch | (h)elp").SetTextColor(tcell.ColorBlack) rightStatus.SetBackgroundColor(tcell.ColorWhite) tui.bottomBar.AddItem(leftStatus, 0, 0, 1, 1, 0, 0, false) tui.bottomBar.AddItem(rightStatus, 0, 1, 1, 1, 0, 0, false) tui.grid = tview.NewGrid(). SetRows(0, 1). SetColumns(0). AddItem(tui.bottomBar, 1, 0, 1, 1, 0, 0, false) _, _, width, _ := tui.grid.GetRect() content := &MyTable{ width: width, rows: [][]string{ []string{"1", "two", "three"}, []string{"4", "five", "six"}, }, } table := tview.NewTable().SetContent(content) table.Box.SetBorder(true) // Just to see the layout for testing. tui.grid.AddItem(table, 0, 0, 1, 1, 0, 0, true) tui.app.SetRoot(tui.grid, true).SetFocus(table) tui.app.Run() } type MyTable struct { width int rows [][]string } func (t *MyTable) GetCell(row int, col int) *tview.TableCell { if row >= len(t.rows) { return nil } r := t.rows[row] if col >= len(r) { return nil } cell := tview.NewTableCell(r[col]) switch col { case 0: cell.SetMaxWidth(2) case 1: cell.SetMaxWidth(5) case 2: cell.SetMaxWidth(t.width - 7) cell.SetExpansion(1) txt := cell.Text + " " for range 100 { txt += "!" } txt += "~~~~~~" cell.Text = txt } return cell } func (t *MyTable) GetRowCount() int { return len(t.rows) } func (t *MyTable) GetColumnCount() int { if len(t.rows) == 0 { return 0 } return len(t.rows[0]) } func (t *MyTable) SetCell(row int, col int, cell *tview.TableCell) { } func (t *MyTable) RemoveRow(row int) {} func (t *MyTable) RemoveColumn(col int) {} func (t *MyTable) InsertRow(row int) {} func (t *MyTable) InsertColumn(col int) {} func (t *MyTable) Clear() {} ``` </details> Is this correct? It's rather weird to set the "maximum width" in order to get a fixed width. And it's surprising to need toggle expansion of columns on.
kerem closed this issue 2026-03-04 01:07:30 +03:00
Author
Owner

@rivo commented on GitHub (Nov 2, 2024):

I'm not sure that you need to use SetContent here. That function (and the associated interface) is meant for more complex table designs like virtual tables (see the virtual table demo). Also, all that Grid code kind of had me confused at first whether you're asking about grid layout or table layout.

  1. width 2
  2. width 23
  3. width 6
  4. width 14
  5. width 3
  6. width all remaining space

I think something like the following should be sufficient:

func makeCell(row, col int) *tview.TableCell {
	var width, exp int
	if col < 5 {
		width = []int{2, 23, 6, 14, 3}[col]
	} else {
		width = 1
		exp = 1
	}
	text := tableContent[row][col]
	if width > 0 && len(text) < width {
		text += strings.Repeat(" ", width-len(text))
	}
	return tview.NewTableCell(text).
		SetMaxWidth(width).
		SetExpansion(exp)
}

The Table class primarily renders each cell as is, allocating space as needed. It was only later that people requested a maximum width (which would truncate the cell text if needed). And then, even later, people wanted to add space to columns when there was space left on screen, thus the "expansion" parameter. I'm not sure why one would want a width of exactly 23 for a table column instead of "whatever is needed". I mean, this is possible if required but you need to write a bit of extra code to make it happen.

<!-- gh-comment-id:2453013324 --> @rivo commented on GitHub (Nov 2, 2024): I'm not sure that you need to use `SetContent` here. That function (and the associated interface) is meant for more complex table designs like virtual tables (see the [virtual table demo](https://github.com/rivo/tview/tree/master/demos/table/virtualtable)). Also, all that `Grid` code kind of had me confused at first whether you're asking about grid layout or table layout. > 1. width 2 > 2. width 23 > 3. width 6 > 4. width 14 > 5. width 3 > 6. width all remaining space I think something like the following should be sufficient: ```go func makeCell(row, col int) *tview.TableCell { var width, exp int if col < 5 { width = []int{2, 23, 6, 14, 3}[col] } else { width = 1 exp = 1 } text := tableContent[row][col] if width > 0 && len(text) < width { text += strings.Repeat(" ", width-len(text)) } return tview.NewTableCell(text). SetMaxWidth(width). SetExpansion(exp) } ``` The `Table` class primarily renders each cell as is, allocating space as needed. It was only later that people requested a maximum width (which would truncate the cell text if needed). And then, even later, people wanted to add space to columns when there was space left on screen, thus the "expansion" parameter. I'm not sure why one would want a width of _exactly 23_ for a table column instead of "whatever is needed". I mean, this is possible if required but you need to write a bit of extra code to make it happen.
Author
Owner

@jsumners-nr commented on GitHub (Nov 4, 2024):

I'm not sure why one would want a width of exactly 23 for a table column instead of "whatever is needed".

The design had called for fixed width leading fields, e.g. log level and timestamp fields, in order to allocate the maximum amount of space for a primary field (log message). Ultimately, I ended up with the following to accomplish my goal:

func (t *LinesTableContent) GetCell(row int, col int) *tview.TableCell {
	if row < 0 || col < 0 {
		return nil
	}
	if row >= len(t.lines) {
		return nil
	}

	r := t.lines[row]
	if col >= LINES_TABLE_COLUMN_COUNT {
		return nil
	}

	cell := tview.NewTableCell("")
	switch col {
	case 0: // Timestamp
		cell.SetMaxWidth(23).
			SetText(r.TimeStampString()).
			SetTextColor(tcell.ColorYellow)
	case 1: // LogLevel
		cell.SetMaxWidth(6).
			SetText(r.Level().String()).
			SetTextColor(t.levelColor(r.Level())).
			SetAlign(tview.AlignLeft)
	case 2: // SourceComponent
		cell.SetMaxWidth(0).SetText(r.Component())
	case 3: // Expand indicator
		cell.SetMaxWidth(3).
			SetText(t.expandIndicator(r)).
			SetTextColor(tcell.GetColor("#BB5FB9"))
	case 4: // Log message
		cell.SetExpansion(1).SetText(r.Message())
	}

	return cell
}

And:

table := tview.NewTable()
table.SetEvaluateAllRows(true) // For consistent column widths when scrolling.
table.SetContent(NewLinesTableContent(t.lines))

So, yes, I had to use the maximum width property in combination with the expansion property.

<!-- gh-comment-id:2454630024 --> @jsumners-nr commented on GitHub (Nov 4, 2024): > I'm not sure why one would want a width of _exactly 23_ for a table column instead of "whatever is needed". The design had called for fixed width leading fields, e.g. log level and timestamp fields, in order to allocate the maximum amount of space for a primary field (log message). Ultimately, I ended up with the following to accomplish my goal: ```go func (t *LinesTableContent) GetCell(row int, col int) *tview.TableCell { if row < 0 || col < 0 { return nil } if row >= len(t.lines) { return nil } r := t.lines[row] if col >= LINES_TABLE_COLUMN_COUNT { return nil } cell := tview.NewTableCell("") switch col { case 0: // Timestamp cell.SetMaxWidth(23). SetText(r.TimeStampString()). SetTextColor(tcell.ColorYellow) case 1: // LogLevel cell.SetMaxWidth(6). SetText(r.Level().String()). SetTextColor(t.levelColor(r.Level())). SetAlign(tview.AlignLeft) case 2: // SourceComponent cell.SetMaxWidth(0).SetText(r.Component()) case 3: // Expand indicator cell.SetMaxWidth(3). SetText(t.expandIndicator(r)). SetTextColor(tcell.GetColor("#BB5FB9")) case 4: // Log message cell.SetExpansion(1).SetText(r.Message()) } return cell } ``` And: ```go table := tview.NewTable() table.SetEvaluateAllRows(true) // For consistent column widths when scrolling. table.SetContent(NewLinesTableContent(t.lines)) ``` So, yes, I had to use the maximum width property in combination with the expansion property.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/tview#752
No description provided.