BeepVerticalTable
Column-oriented data grid with 10 visual styles, header/row sizing, image support, and cell-level click events.
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.
Data model:
BindingList<SimpleItem> (columns); each column's Children list holds row cellsNamespace: 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
Columnsproperty holds aBindingList<SimpleItem>. EachSimpleItemrepresents one column with aTextheader and optionalImagePath. - Rows (Cells) — Each column's
Childrenproperty is a list ofSimpleItemobjects, 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();
}
Visual Preview
| Feature | Basic | Pro |
|---|---|---|
| Storage | 5 GB | 100 GB |
| Users | 1 | Unlimited |
| 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.
| Style | Character |
|---|---|
Style1 | Flat, alternating light-grey rows, accent header |
Style2 | Compact cells, minimal borders |
Style3 | Bold header with rounded corners |
Style4 | Material-style with shadow header |
Style5 | Dark-background header, light data rows |
Style6 | Striped with full column borders |
Style7 | Gradient header fill |
Style8 | Card-style with elevated selected row |
Style9 | Minimal — header only, no row dividers |
Style10 | High-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
Columnsoutside of initial setup, calltable.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
HeaderHeightrequirements. Test at your target DPI settings to avoid cropped images.