Overview

BeepVerticalTable is a custom-painted data grid organized as a vertical list of named columns. Each column (SimpleItem) owns a collection of row children, making it suitable for displaying category-grouped data, product comparison tables, spec sheets, and schedules.

Built on BaseControl, the table uses a LayoutHelper for geometry calculations and a pluggable VerticalTablePainter that exposes 10 pre-built visual styles (Style1–Style10). Cells are individually selectable and fire CellClicked / SelectionChanged events carrying column and row indices.

Inheritance: Control → BaseControl → BeepVerticalTable
Data model: BindingList<SimpleItem> (columns); each column's Children list holds row cells
Namespace: TheTechIdea.Beep.Winform.Controls

Key Features

🎨 10 Visual Styles

Switch between Style1–Style10 at design time or runtime without data changes.

📐 Flexible Sizing

Independent HeaderHeight, RowHeight, and ColumnWidth properties for precise layout control.

🖼️ Image Support

Per-column header images when ShowImage = true and SimpleItem.ImagePath is set.

🖱️ Cell Interaction

CellClicked and SelectionChanged events deliver (Item, ColumnIndex, RowIndex) for precise handling.

🔗 Live Data Binding

Backed by BindingList — adding, removing, or updating SimpleItem entries automatically repaints.

🎯 Programmatic Selection

SetSelection(col, row) for keyboard navigation, automated demos, or external event-driven highlighting.

Data Model

BeepVerticalTable uses a two-level hierarchy built from SimpleItem objects:

  • Columns — The Columns property holds a BindingList<SimpleItem>. Each SimpleItem represents one column with a Text header and optional ImagePath.
  • Rows (Cells) — Each column's Children property is a list of SimpleItem objects, one per row in that column.
// SimpleItem structure (simplified)
class SimpleItem
{
    public string Text      { get; set; }
    public string ImagePath { get; set; }
    public string Value     { get; set; }
    public List<SimpleItem> Children { get; set; } = new();
}
Layout note: All columns must have the same number of rows for the table to render uniformly. BeepVerticalTable does not enforce this but will paint empty cells for missing rows.

Visual Preview

Feature Basic Pro
Storage5 GB100 GB
Users1Unlimited
Priority Support✗✓

Rendered appearance depends on TableStyle — this preview shows Style1 (flat alternating rows).

Basic Usage

Populating Columns and Rows

var table = new BeepVerticalTable
{
    HeaderHeight = 80,
    RowHeight    = 40,
    ColumnWidth  = 150,
    TableStyle   = VerticalTablePainterStyle.Style1,
    ShowImage    = false
};

// Define columns
var colFeature = new SimpleItem { Text = "Feature" };
var colBasic   = new SimpleItem { Text = "Basic" };
var colPro     = new SimpleItem { Text = "Pro" };

// Add rows to each column (same count required)
var rows = new[]
{
    ("Storage",          "5 GB",       "100 GB"),
    ("Users",            "1",          "Unlimited"),
    ("Priority Support", "No",         "Yes"),
    ("API Access",       "Restricted", "Full")
};

foreach (var (feature, basic, pro) in rows)
{
    colFeature.Children.Add(new SimpleItem { Text = feature });
    colBasic.Children.Add(new SimpleItem   { Text = basic   });
    colPro.Children.Add(new SimpleItem     { Text = pro     });
}

table.Columns.Add(colFeature);
table.Columns.Add(colBasic);
table.Columns.Add(colPro);

this.Controls.Add(table);

Handling Cell Clicks

table.CellClicked += (s, e) =>
{
    // e is (SimpleItem item, int columnIndex, int rowIndex)
    var (item, col, row) = e;
    statusBar.Text = $"Clicked: [{col},{row}] = {item.Text}";
};

table.SelectionChanged += (s, e) =>
{
    var (item, col, row) = e;
    UpdateDetailPanel(item);
};

Programmatic Selection

// Select the first data cell in column 1
table.SetSelection(columnIndex: 1, rowIndex: 0);

// Read selection info
int col  = table.SelectedColumnIndex;
int row  = table.SelectedRowIndex;
var item = table.SelectedItem; // null if nothing selected

Properties

Property Type Default Description
Columns BindingList<SimpleItem> (empty) The column definitions. Each SimpleItem.Children list holds the row cells for that column.
SelectedItem SimpleItem? null Read-only. The currently selected cell's SimpleItem, or null if no cell is selected.
SelectedColumnIndex int -1 Read-only. Zero-based index of the selected column, or -1 if nothing is selected.
SelectedRowIndex int -1 Read-only. Zero-based index of the selected row, or -1 if nothing is selected.
HeaderHeight int 80 Height in pixels of the column header row. Increase when adding images.
RowHeight int 40 Height in pixels of each data row.
ColumnWidth int 150 Width in pixels of each column. All columns share the same width.
TableStyle VerticalTablePainterStyle Style1 Visual style for the grid painter. Values: Style1 through Style10.
ShowImage bool false When true, header cells render any image specified in SimpleItem.ImagePath.
LayoutHelper VerticalTableLayoutHelper (auto) Read-only. Provides geometry for cell hit-testing; rarely accessed directly.

Events

Event Signature Description
CellClicked EventHandler<(SimpleItem, int, int)> Fires on mouse-down over a cell. Tuple carries (clickedItem, columnIndex, rowIndex).
SelectionChanged EventHandler<(SimpleItem, int, int)> Fires when the selected cell changes. Tuple carries (newItem, columnIndex, rowIndex).

Key Methods

Method Signature Description
SetSelection void SetSelection(int columnIndex, int rowIndex) Programmatically selects a cell and fires SelectionChanged. Pass -1 to clear selection.
CalculateLayout void CalculateLayout() Recalculates the internal layout geometry. Called automatically on resize or data change; call manually after bulk Columns updates.
UpdatePainter void UpdatePainter() Switches the active painter when TableStyle is changed at runtime.
ApplyTheme void ApplyTheme() Re-applies the current Beep theme to all column and row colors.

TableStyle Reference

BeepVerticalTable includes 10 plug-in painter styles. Each style changes colors, borders, header background, and alternating row fill. Styles are set via TableStyle = VerticalTablePainterStyle.StyleN.

StyleCharacter
Style1Flat, alternating light-grey rows, accent header
Style2Compact cells, minimal borders
Style3Bold header with rounded corners
Style4Material-style with shadow header
Style5Dark-background header, light data rows
Style6Striped with full column borders
Style7Gradient header fill
Style8Card-style with elevated selected row
Style9Minimal — header only, no row dividers
Style10High-contrast for accessibility

Code Examples

Product Comparison Table

var plans = new[] { "Feature", "Starter", "Professional", "Enterprise" };
var columns = plans.Select(p => new SimpleItem { Text = p }).ToArray();

var features = new (string feature, string[] values)[]
{
    ("Price/mo",     new[] { "",       "$9",    "$29",    "Custom" }),
    ("Storage",      new[] { "",       "10 GB", "100 GB", "1 TB"   }),
    ("Users",        new[] { "",       "3",     "25",     "∞"      }),
    ("API Calls/day",new[] { "",       "1,000", "50,000", "∞"      }),
    ("SLA",          new[] { "",       "—",     "99.9%",  "99.99%" })
};

foreach (var (feature, values) in features)
{
    for (int i = 0; i < columns.Length; i++)
        columns[i].Children.Add(new SimpleItem { Text = values[i] == "" ? feature : values[i] });
}

var cmpTable = new BeepVerticalTable
{
    Columns      = new BindingList<SimpleItem>(columns),
    HeaderHeight = 60,
    RowHeight    = 36,
    ColumnWidth  = 130,
    TableStyle   = VerticalTablePainterStyle.Style4
};
this.Controls.Add(cmpTable);

Spec Sheet with Images

var colSpec  = new SimpleItem { Text = "Spec" };
var colModelA = new SimpleItem { Text = "Model A", ImagePath = "images/model-a.png" };
var colModelB = new SimpleItem { Text = "Model B", ImagePath = "images/model-b.png" };

foreach (var (spec, a, b) in specs)
{
    colSpec.Children.Add(new SimpleItem  { Text = spec });
    colModelA.Children.Add(new SimpleItem { Text = a   });
    colModelB.Children.Add(new SimpleItem { Text = b   });
}

specTable.ShowImage    = true;
specTable.HeaderHeight = 100;  // room for images
specTable.Columns.Add(colSpec);
specTable.Columns.Add(colModelA);
specTable.Columns.Add(colModelB);

Switching TableStyle at Runtime

styleCombo.SelectedIndexChanged += (s, e) =>
{
    var styleName = $"Style{styleCombo.SelectedIndex + 1}";
    table.TableStyle = (VerticalTablePainterStyle)Enum.Parse(
        typeof(VerticalTablePainterStyle), styleName);
    table.UpdatePainter();
    table.Invalidate();
};

Theming

BeepVerticalTable inherits full Beep theme support. Calling BeepThemesManager.ApplyTheme(table, "Ocean") (or applying a theme to the parent form) updates header background, text colors, alternating row fills, and selection highlight to match the chosen theme palette.

// Apply theme to table specifically
BeepThemesManager.ApplyTheme(table, "Dark");

// Re-apply after changing style
table.TableStyle = VerticalTablePainterStyle.Style5;
table.UpdatePainter();
table.ApplyTheme();

Best Practices

  • Keep column counts small — BeepVerticalTable is optimized for 2–8 columns. For wide datasets with many columns prefer BeepSimpleGrid.
  • Match row counts across columns — While not strictly enforced, inconsistent row counts will produce misaligned cells. Always add row items to every column in the same loop.
  • Suspend repaints during bulk loads — Call SuspendLayout() / ResumeLayout() when adding dozens of rows to avoid per-item repaints.
  • Use CalculateLayout after bulk changes — When programmatically modifying Columns outside of initial setup, call table.CalculateLayout() to refresh geometry before showing the control.
  • Size the container appropriately — The control will not automatically scroll. Ensure the host panel is large enough, or add it inside a scrollable panel (AutoScroll = true).
  • Use ShowImage thoughtfully — Images in headers increase HeaderHeight requirements. Test at your target DPI settings to avoid cropped images.