V4 V5

Styles inherit across four levels. The lower level wins where it sets something; a property it leaves unset (null) comes from above.

Cell < Row < Column < Sheet (RootStyle)

Sheet, column, row and cell levels combining

The four levels

using unvell.ReoGrid.Core;
using unvell.ReoGrid.Core.Style;

ws.RootStyle = ws.RootStyle with { FontFamily = "Segoe UI", FontSize = 11f };
ws.SetColumnStyle(1, new StyleRecord { TextAlign = HAlign.Right });
ws.SetRowStyle(0, new StyleRecord { Bold = true });
ws.SetCellStyle(0, 1, new StyleRecord { BackgroundColor = 0xFFFFF3CD });

// the effective style of B1:
//   FontFamily = "Segoe UI"      <- sheet
//   TextAlign  = Right            <- column
//   Bold       = true             <- row
//   Background = 0xFFFFF3CD       <- cell
StyleRecord effective = ws.GetEffectiveStyle(0, 1);
LevelSet withRead with
Cellws.SetCellStyle(r, c, style)ws.GetCellStyle(r, c)
Rowws.SetRowStyle(row, style)ws.GetRowStyle(row)
Columnws.SetColumnStyle(col, style)ws.GetColumnStyle(col)
Sheetws.RootStylesame

Rows beat columns. When a row and a column both set the same property, the row’s value wins — unless the range API applied the column later, which follows Excel’s “last format wins” (see Where a row style and a column style cross).

Overriding to cancel inheritance

ws.SetRowStyle(0, new StyleRecord { Bold = true });

// the row is bold, but not this one cell
ws.SetCellStyle(0, 3, new StyleRecord { Bold = false });

false is not “do not inherit” — it is “explicitly off”. To inherit, use null.

Getting the effective style

GetEffectiveStyle(r, c) returns the result of resolving all four levels. Rendering, printing and export all go through it.

Note how it differs from GetCellStyle(r, c).

MethodReturns
GetCellStyle(r, c)Only what was set directly on that cell (null when nothing was)
GetEffectiveStyle(r, c)The resolved result (never null)

How one level combines with the next

A single step of inheritance is MergeOver.

var baseStyle = new StyleRecord { FontSize = 11f, Bold = true };
var over = new StyleRecord { Bold = false, Italic = true };

// whatever `over` sets wins; where it is null, baseStyle survives
StyleRecord result = baseStyle.MergeOver(over);
// → FontSize = 11, Bold = false, Italic = true

Why this matters for performance

Inheritance is what lets whole-row and whole-column formatting cost nothing per cell.

// the good way - one entry
ws.SetColumnStyle(2, new StyleRecord { TextAlign = HAlign.Right });

// the bad way - a million entries get materialized
for (int r = 0; r < ws.RowCount; r++)
	ws.SetCellStyle(r, 2, new StyleRecord { TextAlign = HAlign.Right });

The result looks the same, but memory use and running time do not. Always use the row and column APIs for whole-row and whole-column formatting.

Applying a style to a range

SetRangeStyle / MutateRangeStyle decide which level to write from the shape of the range, so the caller does not have to.

// A sub-range -> one write per cell
ws.SetRangeStyle("B2:D5", new StyleRecord { BackgroundColor = 0xFFFFF3CD });

// Whole columns -> the column default style (one write per column)
ws.SetRangeStyle("A:C", new StyleRecord { TextAlign = HAlign.Right });

// The whole sheet -> a single RootStyle write
ws.SetRangeStyle(new RangePosition(0, 0, ws.RowCount, ws.ColumnCount),
	new StyleRecord { FontFamily = "Meiryo UI" });

// To change one property and keep the rest, use MutateRangeStyle
ws.MutateRangeStyle("A:C", s => s with { Bold = true });
Shape of the rangeWritten toWritesRangeStyleScope
A sub-rangethe cellsone per cellCells
Whole columns (A:C)the column default styleone per columnColumns
Whole rows (3:5)the row default styleone per rowRows
The whole sheetRootStyleoneSheet

SetRangeStyle replaces what the target level held; MutateRangeStyle hands you the current style and updates it with what you return. The whole sheet is the one exception: RootStyle is always fully populated, so even SetRangeStyle merges over it.

Asking where a write will land

GetRangeStyleScope reports which level a range would be written to, before you write it — useful when the UI needs to know how many cells an operation would materialize.

// ask where a write would land, before making it
RangeStyleScope scope = ws.GetRangeStyleScope(RangePosition.Parse("A:C"));
// -> RangeStyleScope.Columns (three writes, to the column defaults)

if (ws.GetRangeStyleScope(range) == RangeStyleScope.Cells)
{
	// this path materializes cells, so confirm first when the range is large
}

// the shape tests are available on their own too
bool wholeCols = ws.IsWholeColumns(range);
bool wholeRows = ws.IsWholeRows(range);
bool wholeSheet = ws.IsWholeSheet(range);

_ = (scope, wholeCols, wholeRows, wholeSheet);

Whole-sheet wins over whole-columns, which wins over whole-rows; anything left is Cells. The shape tests are also available individually as IsWholeColumns / IsWholeRows / IsWholeSheet. All of them test against the sheet’s declared dimensions, so a range like A:C counts as whole columns whatever RowCount happens to be.

Writing to a row / column / sheet default means a cell’s own settings stay put: a cell with a background colour of its own keeps it when its column is filled. Crossings are the one exception.

Where a row style and a column style cross

Rows outrank columns, so “make row 5 red, then make column C blue” would leave C5 red. Excel’s rule is that the last format applied wins, so SetRangeStyle / MutateRangeStyle materialize only the cells where a row style and a column style cross, and line those up with the later write.

  • Only the properties the other line actually sets are pinned to the cell; everything else keeps inheriting, so a crossing never freezes a whole style onto the cell.
  • The cost is the number of styled lines on the other axis, not the number of cells. A sheet with no row or column styles pays nothing at all.
  • A whole-sheet write folds into the styled rows and columns instead — one write per styled line, and no new cells.

Writing SetRowStyle / SetColumnStyle / RootStyle directly does none of this: those resolve purely by the fixed order Root ◁ Column ◁ Row ◁ Cell.

When formatting a selection from the UI, the control’s helpers (SetSelectionBackColor and friends) go through the same API, so whole-row, whole-column and whole-sheet selections are routed the same way.

Relationship to conditional formatting

Conditional formatting sits outside the inheritance chain and is layered on top of the effective style. Only cells whose condition matches are overridden, and the model’s styles are not changed.

See Conditional Formatting.

Was this article helpful?