docfx/docs/tableview.md
TableView displays infinitely-sized tabular data from any ITableSource and supports keyboard/mouse navigation, multi-cell selection, column styling, and checkbox columns.
TableView does not own data. Assign an ITableSource to the Table property.
The core interface. Implement it to bridge any data model into a TableView:
public interface ITableSource
{
int Rows { get; }
int Columns { get; }
string [] ColumnNames { get; }
object this [int row, int col] { get; }
}
| Class | Use Case |
|---|---|
DataTableSource | Wraps a System.Data.DataTable |
EnumerableTableSource<T> | Projects a collection of objects into columns via lambdas |
ListTableSource | Wraps an IList into a multi-column layout |
TreeTableSource<T> | Adds expand/collapse tree behavior to rows |
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) };
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 },
})
};
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) };
TableView implements IValue<TableSelection?> to expose the complete selection state as a single value.
| Type | Description |
|---|---|
TableSelection | Immutable snapshot: Cursor (a Point) + Regions (an IReadOnlyList<TableSelectionRegion>) |
TableSelectionRegion | A contiguous rectangular selection. Has Origin, Rectangle, and IsExtended |
Value property | The current TableSelection?. null means no table is set or selection was cleared |
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).
When MultiSelect is true (the default), users can create rectangular selection regions:
Command.ToggleExtend) — toggles the current cell's IsExtended stateExtended regions (IsExtended = true) persist through keyboard navigation. Non-extended regions are cleared on the next cursor move.
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 ().
// 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 | Command |
|---|---|
| Arrow keys | Move cursor one cell |
| Shift+Arrow | Extend selection |
| PageUp / PageDown | Move one page |
| Home / End | Move to start/end of row |
| Ctrl+Home / Ctrl+End | Move to first/last row |
| Shift+Home/End/Ctrl+Home/Ctrl+End | Extend selection to row/table boundary |
| Ctrl+A | Select all |
| Space | Command.ToggleExtend — toggle current cell's extended selection |
| Mouse Event | Command |
|---|---|
| Click | Command.Activate — moves cursor to clicked cell |
| Ctrl+Click | Command.ToggleExtend — unions clicked cell into selection |
| Alt+Click | Command.ToggleExtend — extends rectangular region to clicked cell |
| Double-click | Command.Accept |
| Scroll wheel | Scroll up/down/left/right |
TableView uses the standard KeyBindings and MouseBindings infrastructure. Override DefaultKeyBindings (static) or instance-level bindings.
TableView renders only the visible portion of the table. Horizontal and vertical scrolling is handled via ColumnOffset and RowOffset (backed by Viewport).
TableStyle)RowOffset until viewport is filledColumnOffset right, each column sized by content width (clamped by MinCellWidth / MaxCellWidth and per-column ColumnStyle)TableStyle controls the visual appearance:
| Property | Default | Description |
|---|---|---|
ShowHeaders | true | Show column header row |
ShowHorizontalHeaderOverline | true | Line above headers |
ShowHorizontalHeaderUnderline | true | Line below headers |
ShowVerticalCellLines | true | Vertical separators between cells |
ShowVerticalHeaderLines | true | Vertical separators between headers |
ShowHorizontalBottomLine | false | Line below last row |
AlwaysShowHeaders | false | Lock headers when scrolling |
ExpandLastColumn | true | Fill remaining space with last column |
SmoothHorizontalScrolling | true | Minimal horizontal scroll increments |
InvertSelectedCellFirstCharacter | false | Show cursor character inversion |
RowColorGetter | null | Custom row coloring delegate |
After programmatic cursor changes, call EnsureCursorIsVisible () to scroll the viewport so the cursor cell is on screen. Update () does this automatically.
Use TableStyle.ColumnStyles to customize individual columns:
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
};
| Property | Description |
|---|---|
Alignment | Default text alignment for the column |
AlignmentGetter | Per-cell alignment delegate (overrides Alignment) |
ColorGetter | Per-cell Scheme delegate |
RepresentationGetter | Custom object → string conversion |
Format | IFormattable.ToString format string |
MaxWidth | Maximum column width in characters |
MinWidth | Minimum column width in characters |
MinAcceptableWidth | Flexible lower bound for column width |
Visible | Hide the column entirely |
Wrap any ITableSource with a checkbox column using CheckBoxTableSourceWrapperByIndex or CheckBoxTableSourceWrapperByObject<T>:
// By row index
CheckBoxTableSourceWrapperByIndex checkSrc = new (tv, tv.Table!);
tv.Table = checkSrc;
// Read checked rows
HashSet<int> checked = checkSrc.CheckedRows;
// 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.
TreeTableSource<T> combines TreeView<T> expand/collapse with TableView column rendering:
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.
TableView uses the standard IValue<T> and View event patterns:
| Event | When |
|---|---|
ValueChanging | Before Value changes. Set Handled = true to cancel. |
ValueChanged | After Value changed. Use this to react to cursor/selection changes. |
Accepted | User double-clicks or presses the Accept key on a cell. |
Activating | User clicks a cell (Command.Activate). |
tv.ValueChanged += (sender, e) =>
{
if (e.NewValue is { } sel)
{
statusBar.Text = $"Row {sel.Cursor.Y}, Col {sel.Cursor.X}";
}
};
tv.Accepted += (sender, e) =>
{
Point cursor = tv.Value!.Cursor;
object cellValue = tv.Table! [cursor.Y, cursor.X];
MessageBox.Query ("Cell", $"Value: {cellValue}", "OK");
};