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
- Selection Model
- Key & Mouse Bindings
- Rendering & Scrolling
- Column Styling
- Checkbox Columns
- Tree Tables
- Events
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:
public interface ITableSource
{
int Rows { get; }
int Columns { get; }
string [] ColumnNames { get; }
object this [int row, int col] { get; }
}
Built-in Implementations
| 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 Example
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
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
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
| Type | Description |
|---|---|
TableSelection |
Immutable snapshot: SelectedCell (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 |
SelectedCell
The selected cell is the active cell — the anchor for navigation. Access it via Value.SelectedCell (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'sIsExtendedstate - 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 ().
Tip — Home/End with FullRowSelect: By default,
HomeandEndnavigate to the start/end of the current row (i.e. first/last column). To makeHome/Endjump to the first/last row instead (which is often more useful in full-row mode), rebind them toCommand.StartandCommand.End:tableView.KeyBindings.Remove (Key.Home); tableView.KeyBindings.Add (Key.Home, Command.Start); tableView.KeyBindings.Remove (Key.End); tableView.KeyBindings.Add (Key.End, Command.End);This is the pattern used by
UICatalogRunnablefor its scenario list.
Reading the Selection
// Selected cell position
Point selectedCell = tv.Value!.SelectedCell; // (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
| 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 |
Default Mouse Bindings
| 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 |
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
- Header — column names with optional overline, underline, and vertical separators (controlled by
TableStyle) - Data rows — rendered from
RowOffsetuntil viewport is filled - Columns — rendered from
ColumnOffsetright, each column sized by content width (clamped byMinCellWidth/MaxCellWidthand per-columnColumnStyle)
TableStyle
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 |
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:
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
| 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 |
Checkbox Columns
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.
Tree Tables
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.
Events
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 selection changes. |
Accepted |
User double-clicks or presses the Accept key on a cell. |
Activating |
User clicks a cell (Command.Activate). |
Example: Reacting to Selection Changes
tv.ValueChanged += (sender, e) =>
{
if (e.NewValue is { } sel)
{
statusBar.Text = $"Row {sel.SelectedCell.Y}, Col {sel.SelectedCell.X}";
}
};
Example: Handling Cell Activation
tv.Accepted += (sender, e) =>
{
Point selectedCell = tv.Value!.SelectedCell;
object cellValue = tv.Table! [selectedCell.Y, selectedCell.X];
MessageBox.Query ("Cell", $"Value: {cellValue}", "OK");
};