DataGrid
Tabular data display with columns, sorting, selection, and scrolling
Namespace: Beep.Skia.Controls — Inherits: MaterialControl → SkiaComponent
Table of Contents
Overview
The DataGrid control renders tabular data with a Material Design 3.0 aesthetic using SkiaSharp. It supports column headers, sortable columns by clicking headers, row selection with single and multi-select modes, and both vertical and horizontal scrolling. The grid is rendered entirely through SkiaSharp draw calls with no underlying data-bound grid framework — rows and columns are virtualized for performance with large data sets.
Cells are rendered using the DrawCell virtual method, which subclasses can override for custom cell painting (progress bars, icons, color swatches, etc.). Alternating row colors, grid lines, and header styling all follow the active Material theme.
Properties
| Property | Type | Description |
|---|---|---|
Columns |
List<DataGridColumn> |
The column definitions. Each column specifies a header label, width, data binding key, and optional sort configuration. |
Rows |
IList<object> |
The data rows. Each element is typically a dictionary, anonymous object, or typed model. The grid uses the DataField property of each column to extract cell values via reflection or indexer lookup. |
HeaderHeight |
float |
Height of the column header row. Default is 56f per MD3 dense table spec. |
RowHeight |
float |
Height of each data row. Default is 48f per MD3 standard table row spec. |
ShowHeader |
bool |
When false, the column header row is hidden and the data rows begin at Y=0. Default is true. |
SelectionMode |
SelectionMode |
Enum: None, Single (default), or Multi. In multi-select mode, rows can be toggled with Ctrl+Click or selected as a range with Shift+Click. |
SelectedIndex |
int |
Zero-based index of the selected row in single-select mode. -1 when nothing is selected. |
SelectedIndices |
HashSet<int> |
Set of selected row indices in multi-select mode. |
ShowGridLines |
bool |
When true, horizontal and vertical grid lines are drawn between rows and columns. Default is false (clean MD3 style). |
AlternateRowColor |
bool |
When true, even rows receive a subtle background tint (SurfaceVariant at low opacity). Default is false. |
SortColumnIndex |
int |
Index of the currently sorted column. -1 when no sort is applied. Set automatically when a column header is clicked. |
SortDirection |
SortDirection |
Ascending or Descending. The sort indicator icon is drawn in the corresponding column header. |
DataGridColumn Model
| Property | Type | Description |
|---|---|---|
HeaderText |
string |
The label displayed in the column header. Rendered using LabelMedium typography. |
DataField |
string |
The property name or dictionary key used to extract the cell value from each row object. Supports nested paths via dot notation (e.g., "Address.City"). |
Width |
float |
Fixed column width in logical pixels. Default is 120f. Set to 0 for auto-size (fills remaining space). Only one column should have auto-width. |
MinWidth |
float |
Minimum column width when resizing. Default is 60f. |
IsSortable |
bool |
When true, clicking the column header toggles sort direction. Default is true. |
Alignment |
SKTextAlign |
Horizontal alignment for cell text. Left (default), Center, or Right. Numeric columns typically use Right alignment. |
FormatString |
string |
Optional .NET format string applied to cell values (e.g., "N2" for two-decimal numbers, "d" for short dates). If null, ToString() is used. |
CellRenderer |
Action<SKCanvas, SKRect, object> |
Optional custom render delegate. When set, the delegate is called instead of the default text rendering. Use for custom cell content like swatches, progress bars, or icons. |
Cell Rendering
By default, cell values are rendered as text using BodyMedium typography. The cell background alternates between Surface (selected/odd) and SurfaceVariant (even) when AlternateRowColor is enabled. Selected rows are highlighted with a translucent Primary state layer at 8% opacity.
For custom cell rendering, assign a CellRenderer delegate to a column. The delegate receives the canvas, the cell's bounding rectangle (already clipped), and the raw cell value. Use canvas transforms and save/restore within the delegate.
Custom Cell Renderer for a Progress Bar
var progressColumn = new DataGridColumn
{
HeaderText = "Progress",
DataField = "PercentComplete",
Width = 150,
CellRenderer = (canvas, rect, value) =>
{
float percent = Convert.ToSingle(value) / 100f;
float barWidth = rect.Width * percent;
using var bgPaint = new SKPaint { Color = SKColors.LightGray };
canvas.DrawRect(rect.Left, rect.Top + 8, rect.Width, rect.Height - 16, bgPaint);
using var fillPaint = new SKPaint { Color = SKColors.Green };
canvas.DrawRect(rect.Left, rect.Top + 8, barWidth, rect.Height - 16, fillPaint);
}
};
Scroll Support
The DataGrid supports both vertical and horizontal scrolling when the content exceeds the control's visible area. Scroll behavior is controlled by these properties:
| Property | Type | Description |
|---|---|---|
ScrollOffsetY | float | Current vertical scroll offset. Rows above this offset are clipped from rendering (virtualization). Default is 0. |
MaxScrollY | float | Read-only. Maximum vertical scroll value. Computed as Rows.Count * RowHeight - VisibleHeight. |
ScrollOffsetX | float | Current horizontal scroll offset. Columns to the left are clipped. Default is 0. |
ShowScrollbar | bool | When true, a Material-themed scroll bar thumb is rendered on the right edge. Default is true. |
Scroll is handled by the InputManager. The grid responds to MouseWheel events for vertical scroll and Shift+MouseWheel for horizontal scroll. Touch scrolling with inertia is supported when the hosting application provides touch input.
Code Example
DataGrid with Sortable Columns and Row Selection
var data = new List<Dictionary<string, object>>
{
new() { ["Id"] = 1, ["Name"] = "Alice", ["Dept"] = "Engineering", ["Salary"] = 95000 },
new() { ["Id"] = 2, ["Name"] = "Bob", ["Dept"] = "Design", ["Salary"] = 82000 },
new() { ["Id"] = 3, ["Name"] = "Carol", ["Dept"] = "Engineering", ["Salary"] = 105000 },
new() { ["Id"] = 4, ["Name"] = "Dave", ["Dept"] = "Marketing", ["Salary"] = 78000 },
new() { ["Id"] = 5, ["Name"] = "Eve", ["Dept"] = "Design", ["Salary"] = 91000 }
};
var grid = new DataGrid
{
X = 20,
Y = 60,
Width = 700,
Height = 400,
SelectionMode = SelectionMode.Single,
AlternateRowColor = true,
Columns = new List<DataGridColumn>
{
new() { HeaderText = "ID", DataField = "Id", Width = 60, Alignment = SKTextAlign.Center },
new() { HeaderText = "Name", DataField = "Name", Width = 180 },
new() { HeaderText = "Department", DataField = "Dept", Width = 200 },
new() { HeaderText = "Salary", DataField = "Salary", Width = 120, Alignment = SKTextAlign.Right, FormatString = "C0" }
},
Rows = data.Cast<object>().ToList()
};
grid.Initialize();
grid.SelectedIndexChanged += (s, idx) =>
{
if (idx >= 0)
{
var row = (Dictionary<string, object>)grid.Rows[idx];
Console.WriteLine($"Selected: {row["Name"]}");
}
};
drawingManager.AddComponent(grid);
drawingManager.RequestRedraw();
DataGrid with Object Data Source
public record Employee(int Id, string Name, string Department, decimal Salary);
var employees = new List<Employee>
{
new(1, "Alice", "Engineering", 95000m),
new(2, "Bob", "Design", 82000m),
new(3, "Carol", "Engineering", 105000m)
};
var grid = new DataGrid
{
X = 20, Y = 60,
Width = 700, Height = 250,
Columns = new List<DataGridColumn>
{
new() { HeaderText = "ID", DataField = "Id", Width = 60 },
new() { HeaderText = "Name", DataField = "Name", Width = 200 },
new() { HeaderText = "Dept", DataField = "Department", Width = 200 },
new() { HeaderText = "Salary", DataField = "Salary",
Width = 120, Alignment = SKTextAlign.Right, FormatString = "C0" }
},
Rows = employees.Cast<object>().ToList()
};
grid.Initialize();
drawingManager.AddComponent(grid);
Note
The grid performs no data binding in the .NET WinForms/WPF sense. Rows are a snapshot list. To update the grid after data changes, assign a new list to Rows and call grid.Invalidate(). For large datasets (10k+ rows), virtualized rendering ensures only visible rows are drawn.