Back to Terminal Gui

TableView Deep Dive

docfx/docs/tableview.md

2.0.19.8 KB
Original Source

TableView Deep Dive

TableView displays infinitely-sized tabular data from any ITableSource and supports keyboard/mouse navigation, multi-cell selection, column styling, and checkbox columns.

Table of Contents


Data Sources

TableView does not own data. Assign an ITableSource to the Table property.

ITableSource

The core interface. Implement it to bridge any data model into a TableView:

csharp
public interface ITableSource
{
    int Rows { get; }
    int Columns { get; }
    string [] ColumnNames { get; }
    object this [int row, int col] { get; }
}

Built-in Implementations

ClassUse Case
DataTableSourceWraps a System.Data.DataTable
EnumerableTableSource<T>Projects a collection of objects into columns via lambdas
ListTableSourceWraps an IList into a multi-column layout
TreeTableSource<T>Adds expand/collapse tree behavior to rows

DataTable Example

csharp
DataTable dt = new ();
dt.Columns.Add ("Name");
dt.Columns.Add ("Age", typeof (int));
dt.Rows.Add ("Alice", 30);
dt.Rows.Add ("Bob", 25);

TableView tv = new () { Table = new DataTableSource (dt) };

Object Collection Example

csharp
TableView tv = new ()
{
    Table = new EnumerableTableSource<Process> (
        Process.GetProcesses (),
        new Dictionary<string, Func<Process, object>> ()
        {
            { "ID", p => p.Id },
            { "Name", p => p.ProcessName },
            { "Threads", p => p.Threads.Count },
        })
};

CSV Example

csharp
DataTable dt = new ();
string [] lines = File.ReadAllLines (filename);

foreach (string h in lines [0].Split (','))
{
    dt.Columns.Add (h);
}

foreach (string line in lines.Skip (1))
{
    dt.Rows.Add (line.Split (','));
}

TableView tv = new () { Table = new DataTableSource (dt) };

Selection Model

TableView implements IValue<TableSelection?> to expose the complete selection state as a single value.

Key Types

TypeDescription
TableSelectionImmutable snapshot: Cursor (a Point) + Regions (an IReadOnlyList<TableSelectionRegion>)
TableSelectionRegionA contiguous rectangular selection. Has Origin, Rectangle, and IsExtended
Value propertyThe current TableSelection?. null means no table is set or selection was cleared

Cursor

The cursor is the active cell — the anchor for navigation. Access it via Value.Cursor (Point where X = column index, Y = row index).

Move the cursor programmatically with SetSelection (col, row, extend).

Multi-Selection

When MultiSelect is true (the default), users can create rectangular selection regions:

  • Shift+Arrow — extends a region from the cursor to the new position
  • Ctrl+Click — unions the clicked cell as an independent extended selection
  • Space (Command.ToggleExtend) — toggles the current cell's IsExtended state
  • Ctrl+A — selects all cells

Extended regions (IsExtended = true) persist through keyboard navigation. Non-extended regions are cleared on the next cursor move.

FullRowSelect

When FullRowSelect is true, entire rows are selected instead of individual cells. All cells in the cursor's row are reported as selected by GetAllSelectedCells () and IsSelected ().

Reading the Selection

csharp
// Cursor position
Point cursor = tv.Value!.Cursor; // (col, row)

// All selected cell coordinates
IEnumerable<Point> cells = tv.GetAllSelectedCells ();

// Check if a specific cell is selected
bool sel = tv.IsSelected (col, row);

Key & Mouse Bindings

Default Key Bindings

KeyCommand
Arrow keysMove cursor one cell
Shift+ArrowExtend selection
PageUp / PageDownMove one page
Home / EndMove to start/end of row
Ctrl+Home / Ctrl+EndMove to first/last row
Shift+Home/End/Ctrl+Home/Ctrl+EndExtend selection to row/table boundary
Ctrl+ASelect all
SpaceCommand.ToggleExtend — toggle current cell's extended selection

Default Mouse Bindings

Mouse EventCommand
ClickCommand.Activate — moves cursor to clicked cell
Ctrl+ClickCommand.ToggleExtend — unions clicked cell into selection
Alt+ClickCommand.ToggleExtend — extends rectangular region to clicked cell
Double-clickCommand.Accept
Scroll wheelScroll up/down/left/right

Customizing Bindings

TableView uses the standard KeyBindings and MouseBindings infrastructure. Override DefaultKeyBindings (static) or instance-level bindings.


Rendering & Scrolling

TableView renders only the visible portion of the table. Horizontal and vertical scrolling is handled via ColumnOffset and RowOffset (backed by Viewport).

Table Rendering Model

  1. Header — column names with optional overline, underline, and vertical separators (controlled by TableStyle)
  2. Data rows — rendered from RowOffset until viewport is filled
  3. Columns — rendered from ColumnOffset right, each column sized by content width (clamped by MinCellWidth / MaxCellWidth and per-column ColumnStyle)

TableStyle

TableStyle controls the visual appearance:

PropertyDefaultDescription
ShowHeaderstrueShow column header row
ShowHorizontalHeaderOverlinetrueLine above headers
ShowHorizontalHeaderUnderlinetrueLine below headers
ShowVerticalCellLinestrueVertical separators between cells
ShowVerticalHeaderLinestrueVertical separators between headers
ShowHorizontalBottomLinefalseLine below last row
AlwaysShowHeadersfalseLock headers when scrolling
ExpandLastColumntrueFill remaining space with last column
SmoothHorizontalScrollingtrueMinimal horizontal scroll increments
InvertSelectedCellFirstCharacterfalseShow cursor character inversion
RowColorGetternullCustom row coloring delegate

EnsureCursorIsVisible

After programmatic cursor changes, call EnsureCursorIsVisible () to scroll the viewport so the cursor cell is on screen. Update () does this automatically.


Column Styling

Use TableStyle.ColumnStyles to customize individual columns:

csharp
tv.Style.ColumnStyles [2] = new ColumnStyle
{
    Alignment = Alignment.End,
    MaxWidth = 20,
    MinWidth = 5,
    Format = "C2",        // currency format
    ColorGetter = args => args.CellValue is int v && v < 0
        ? new Scheme () { Normal = new (Color.Red, Color.Black) }
        : null
};

ColumnStyle Properties

PropertyDescription
AlignmentDefault text alignment for the column
AlignmentGetterPer-cell alignment delegate (overrides Alignment)
ColorGetterPer-cell Scheme delegate
RepresentationGetterCustom objectstring conversion
FormatIFormattable.ToString format string
MaxWidthMaximum column width in characters
MinWidthMinimum column width in characters
MinAcceptableWidthFlexible lower bound for column width
VisibleHide the column entirely

Checkbox Columns

Wrap any ITableSource with a checkbox column using CheckBoxTableSourceWrapperByIndex or CheckBoxTableSourceWrapperByObject<T>:

csharp
// By row index
CheckBoxTableSourceWrapperByIndex checkSrc = new (tv, tv.Table!);
tv.Table = checkSrc;

// Read checked rows
HashSet<int> checked = checkSrc.CheckedRows;
csharp
// By object property
CheckBoxTableSourceWrapperByObject<MyObj> checkSrc = new (
    tv,
    enumSource,
    obj => obj.IsSelected,
    (obj, val) => obj.IsSelected = val
);
tv.Table = checkSrc;

Space toggles checkboxes on the selected row(s). Clicking the checkbox column header toggles all rows. Set UseRadioButtons = true for single-select radio behavior.


Tree Tables

TreeTableSource<T> combines TreeView<T> expand/collapse with TableView column rendering:

csharp
TreeView<FileSystemInfo> tree = new ()
{
    TreeBuilder = new DelegateTreeBuilder<FileSystemInfo> (
        d => d is DirectoryInfo dir ? dir.GetFileSystemInfos () : [],
        d => d is DirectoryInfo),
    AspectGetter = f => f.Name
};

tree.AddObject (new DirectoryInfo ("/"));

TreeTableSource<FileSystemInfo> src = new (
    tv,
    "Name",
    tree,
    new Dictionary<string, Func<FileSystemInfo, object>> ()
    {
        { "Size", f => f is FileInfo fi ? fi.Length : 0 },
        { "Modified", f => f.LastWriteTime }
    });

tv.Table = src;

Arrow Left/Right collapse/expand nodes when the tree column has focus.


Events

TableView uses the standard IValue<T> and View event patterns:

EventWhen
ValueChangingBefore Value changes. Set Handled = true to cancel.
ValueChangedAfter Value changed. Use this to react to cursor/selection changes.
AcceptedUser double-clicks or presses the Accept key on a cell.
ActivatingUser clicks a cell (Command.Activate).

Example: Reacting to Cursor Movement

csharp
tv.ValueChanged += (sender, e) =>
{
    if (e.NewValue is { } sel)
    {
        statusBar.Text = $"Row {sel.Cursor.Y}, Col {sel.Cursor.X}";
    }
};

Example: Handling Cell Activation

csharp
tv.Accepted += (sender, e) =>
{
    Point cursor = tv.Value!.Cursor;
    object cellValue = tv.Table! [cursor.Y, cursor.X];
    MessageBox.Query ("Cell", $"Value: {cellValue}", "OK");
};