Namespace: Beep.Skia.Controls — Inherits: MaterialControl → SkiaComponent

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

PropertyTypeDescription
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

PropertyTypeDescription
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:

PropertyTypeDescription
ScrollOffsetYfloatCurrent vertical scroll offset. Rows above this offset are clipped from rendering (virtualization). Default is 0.
MaxScrollYfloatRead-only. Maximum vertical scroll value. Computed as Rows.Count * RowHeight - VisibleHeight.
ScrollOffsetXfloatCurrent horizontal scroll offset. Columns to the left are clipped. Default is 0.
ShowScrollbarboolWhen 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.