Two types describe a position. Both are structs and both are zero-based — A1 is
(0, 0).
The namespace is unvell.ReoGrid.Core.
CellPosition
using unvell.ReoGrid.Core;
var p = new CellPosition(2, 1); // zero-based = B3
string a = p.ToAddress(); // "B3"
var parsed = CellPosition.Parse("B3"); // "$B$3"; lowercase works too
if (CellPosition.TryParse("ZZ99", out var q))
Console.WriteLine(q.ToAddress());
Parse accepts $-prefixed absolute notation and lowercase letters. An address beyond
Excel’s dimensions is invalid: Parse throws and TryParse returns false.
Row and Col are read-only fields.
RangePosition
var r = new RangePosition(row: 3, col: 1, rows: 3, cols: 4); // B4:E6
var bounds = RangePosition.FromBounds(3, 1, 5, 4); // the same range, given by its corners
var parsed = RangePosition.Parse("B4:E6");
var single = RangePosition.Parse("A1"); // a single cell is a range too
var cols = RangePosition.Parse("A:C"); // whole column
var rows = RangePosition.Parse("3:5"); // whole row
string text = r.ToAddress(); // "B4:E6" - round-trips through Parse
The constructor takes a start position and a size (rows / cols). To build one from
its corners, use FromBounds; corners given in either order are normalized.
ToAddress() returns a form that round-trips through Parse — "A1" for a single cell,
"A:C" for whole columns, "3:5" for whole rows, "A1:C3" otherwise.
Testing a range
int lastRow = r.EndRow;
int lastCol = r.EndCol;
int cells = r.CellCount;
bool one = r.IsSingleCell;
CellPosition anchor = r.TopLeft;
bool inside = r.Contains(4, 2);
bool covers = r.Contains(other);
bool overlaps = r.Intersects(other);
RangePosition merged = r.Union(other);
Union returns the smallest rectangle containing both ranges (not a set union).
Resolving in a sheet’s context
RangePosition carries no sheet information. A sheet-qualified reference like
Sheet1!A1:B2 is not something RangePosition.Parse can handle, so use the sheet’s API.
// whole columns and rows clamp to the sheet's dimensions, and names resolve here
RangePosition cols = ws.ResolveRange("A:C");
RangePosition named = ws.ResolveRange("MyRange");
if (ws.TryResolveRange("a name that does not exist", out var found))
Console.WriteLine(found.ToAddress());
ResolveRange differs from RangePosition.Parse in two ways.
- It clamps whole columns and rows to that sheet’s dimensions — resolving
"A:C"on a 5,000-row sheet gives you 5,000 rows - It resolves defined names — sheet scope wins over workbook scope
Given something it cannot resolve, ResolveRange throws and TryResolveRange returns false.
How Excel notation maps
| Notation | Example | Handling |
|---|---|---|
| Single cell | A1 | Parses as a range with Rows = Cols = 1 |
| Ordinary range | B4:E6 | As written |
| Absolute | $A$1:$C$3 | The $ is ignored; same range |
| Whole columns | A:C | Spans 1,048,576 rows (clamp with ResolveRange) |
| Whole rows | 3:5 | Spans 16,384 columns (likewise) |
| Sheet-qualified | Sheet1!A1 | Not parseable — use ResolveRange or the defined-name API |