Skip to content

Grid Layout ​

GridNode provides true 2D layout with consistent column widths and row heights across all cells. Unlike nesting HorizontalLayout inside VerticalLayout (where each row calculates widths independently), GridNode ensures uniform cell sizing across the entire grid.

GridNode demo: a dashboard with nested grids, progress bars, and a log table

A dashboard built with GridNode — nested grids and a log table.

When to Use GridNode ​

Use CaseWhy GridNode
Data tablesConsistent column alignment across rows
DashboardsMulti-panel layouts with precise sizing
FormsAligned labels and inputs
Metrics displaysUniform cell sizes for visual consistency

Basic Usage ​

Create a grid with explicit dimensions or build it dynamically:

csharp
// Explicit dimensions
var grid = new GridNode(rows: 3, cols: 2);

// Or build dynamically with AddRow
var grid = new GridNode()
    .WithColumns(SizeConstraint.Exactly(15), SizeConstraint.FillRemaining())
    .AddRow(new TextNode("Name:"), new TextNode("John Doe"))
    .AddRow(new TextNode("Email:"), new TextNode("[email protected]"))
    .AddRow(new TextNode("Role:"), new TextNode("Developer"));

Column and Row Constraints ​

Define column widths and row heights using the same constraint types as other layouts:

csharp
var grid = new GridNode()
    .WithColumns(
        SizeConstraint.Exactly(10),      // Fixed 10 columns
        SizeConstraint.FillRemaining(),  // Take remaining space
        SizeConstraint.Exactly(8))       // Fixed 8 columns
    .WithRows(
        SizeConstraint.Exactly(1),       // Header row: 1 line
        SizeConstraint.FillRemaining()); // Content: remaining space

Available constraints:

ConstraintDescription
Exactly(n)Fixed size in columns/rows
FillRemaining()Take remaining space after fixed/auto
Auto()Size to content
Percentage(n)Percentage of available space

Setting Cell Content ​

Use SetCell() for explicit positioning or AddRow() for sequential building:

csharp
// Explicit positioning (row, col, content)
grid.SetCell(0, 0, new TextNode("Header").Bold());
grid.SetCell(1, 0, new TextNode("Cell 1,0"));
grid.SetCell(1, 1, new TextNode("Cell 1,1"));

// Sequential row building
grid.AddRow(
    new TextNode("Label"),
    new TextNode("Value"));

Cell Spanning ​

Cells can span multiple columns and/or rows:

csharp
// Header spanning 3 columns
grid.SetCell(0, 0,
    new TextNode("Dashboard Title").AlignCenter().Bold(),
    colSpan: 3);

// Tall cell spanning 2 rows
grid.SetCell(1, 0,
    new TextNode("Sidebar\nContent"),
    rowSpan: 2);

Grid Lines ​

Add borders between cells using WithGridLines():

csharp
var grid = new GridNode()
    .WithColumns(SizeConstraint.Exactly(10), SizeConstraint.FillRemaining())
    .AddRow(new TextNode("Name"), new TextNode("Value"))
    .AddRow(new TextNode("CPU"), new TextNode("45%"))
    .WithGridLines(BorderStyle.Single)
    .WithGridLineColor(Color.Blue);

Available border styles:

  • BorderStyle.None - No grid lines (default)
  • BorderStyle.Single - Single line (─│┌┐└┘├┤┬┴┼)
  • BorderStyle.Double - Double line (═║╔╗╚╝╠╣╦╩╬)
  • BorderStyle.Rounded - Rounded corners (─│╭╮╰╯├┤┬┴┼)
  • BorderStyle.Ascii - ASCII characters (-|+)

Nested Grids ​

Grids can contain other grids for complex layouts:

csharp
// Metrics panel with its own grid
var metricsGrid = new GridNode()
    .WithColumns(SizeConstraint.Exactly(10), SizeConstraint.FillRemaining())
    .AddRow(new TextNode("CPU:"), new TextNode("45%"))
    .AddRow(new TextNode("Memory:"), new TextNode("62%"));

// Main dashboard grid
var dashboard = new GridNode()
    .WithColumns(SizeConstraint.Exactly(30), SizeConstraint.FillRemaining())
    .WithRows(SizeConstraint.Exactly(1), SizeConstraint.FillRemaining())
    .WithGridLines(BorderStyle.Double);

dashboard.SetCell(0, 0, new TextNode("System Dashboard").AlignCenter(), colSpan: 2);
dashboard.SetCell(1, 0, new PanelNode().WithTitle("Metrics").WithContent(metricsGrid));
dashboard.SetCell(1, 1, new PanelNode().WithTitle("Logs").WithContent(logsContent));

Focus Navigation ​

Enable keyboard navigation for interactive grids:

csharp
var grid = new GridNode()
    .WithNavigationMode(GridNavigationMode.CellNavigation)
    .WithFocusHighlight(Color.Blue, Color.White);

Navigation modes:

ModeBehavior
NoneDisplay only, no keyboard navigation
CellNavigationArrow keys move between cells, Enter activates
ChildFocusRoutingTab routes to focusable children in cells

Observables ​

csharp
// React to focus changes
grid.FocusedCellChanged.Subscribe(pos =>
    Console.WriteLine($"Focused: row {pos.Row}, col {pos.Col}"));

// React to cell activation (Enter key)
grid.CellActivated.Subscribe(info =>
    HandleCellActivated(info.Row, info.Col, info.Cell));

Complete Example: Dashboard ​

csharp
public class DashboardPage : ReactivePage<DashboardViewModel>
{
    public override ILayoutNode BuildLayout()
    {
        return BuildDashboard().Fill();
    }

    private GridNode BuildDashboard()
    {
        var dashboard = new GridNode()
            .WithColumns(
                SizeConstraint.Exactly(35),
                SizeConstraint.FillRemaining())
            .WithRows(
                SizeConstraint.Exactly(3),
                SizeConstraint.FillRemaining())
            .WithGridLines(BorderStyle.Double)
            .WithGridLineColor(Color.Blue);

        // Header spanning both columns
        dashboard.SetCell(0, 0,
            new TextNode("System Dashboard")
                .AlignCenter()
                .Bold()
                .WithForeground(Color.Cyan),
            colSpan: 2);

        // Left column: Metrics
        dashboard.SetCell(1, 0, BuildMetricsPanel());

        // Right column: Event log
        dashboard.SetCell(1, 1, BuildLogsPanel());

        return dashboard;
    }

    private ILayoutNode BuildMetricsPanel()
    {
        var grid = new GridNode()
            .WithColumns(
                SizeConstraint.Exactly(10),
                SizeConstraint.FillRemaining());

        grid.AddRow(
            new TextNode("CPU:"),
            ViewModel.CpuUsageChanged
                .Select(cpu => BuildProgressBar(cpu))
                .AsLayout());

        grid.AddRow(
            new TextNode("Memory:"),
            ViewModel.MemoryUsageChanged
                .Select(mem => BuildProgressBar(mem))
                .AsLayout());

        return new PanelNode()
            .WithTitle("Metrics")
            .WithContent(grid);
    }

    private static TextNode BuildProgressBar(int percentage)
    {
        var filled = (int)(percentage / 100.0 * 12);
        var bar = new string('█', filled) + new string('░', 12 - filled);
        return new TextNode($"{bar} {percentage,3}%")
            .WithForeground(percentage >= 80 ? Color.Red : Color.Green);
    }
}

GridNode vs. Nested Layouts ​

FeatureGridNodeNested Horizontal/Vertical
Column width consistencyAll rows share same widthsEach row independent
Grid linesBuilt-in border renderingManual with PanelNode
Cell spanningNative colspan/rowspanComplex workarounds
2D navigationBuilt-in arrow key navNot available
Best forTables, dashboards, formsSimple stacking

API Reference ​

Properties ​

PropertyTypeDescription
RowCountintNumber of rows
ColumnCountintNumber of columns
GridLinesBorderStyleBorder style for grid lines
GridLineColorColor?Color for grid lines
CellPaddingintPadding inside cells
NavigationModeGridNavigationModeKeyboard navigation mode
FocusedRowintCurrently focused row
FocusedColumnintCurrently focused column

Methods ​

MethodDescription
WithColumns(...)Set column constraints
WithRows(...)Set row constraints
SetCell(row, col, content, colSpan?, rowSpan?)Set cell content
AddRow(...)Add a row with cells
WithGridLines(style)Set grid line style
WithGridLineColor(color)Set grid line color
WithCellPadding(padding)Set cell padding
WithNavigationMode(mode)Set navigation mode
WithFocusHighlight(bg, fg)Set focus highlight colors

Observables ​

ObservableTypeDescription
InvalidatedIObservable<Unit>Grid needs re-render
FocusedCellChangedIObservable<(int Row, int Col)>Focus moved
CellActivatedIObservable<(int Row, int Col, ILayoutNode? Cell)>Cell activated

Released under the Apache 2.0 License.