Searching is FindEngine (unvell.ReoGrid.Core.Search). It is UI-independent, so headless
code with no control in sight can call it directly.
Finding
using unvell.ReoGrid.Core;
using unvell.ReoGrid.Core.Search;
var options = new FindOptions { Text = "Not started" };
// search forward from just after A1; null when there is no hit (the scan wraps)
FindMatch? hit = FindEngine.FindNext(ws, new CellPosition(0, 0), options);
if (hit is FindMatch m)
{
_ = $"{m.Position.ToAddress()}: {m.Text}";
// to continue, hand back the position you just landed on
FindMatch? next = FindEngine.FindNext(ws, m.Position, options);
_ = next;
}
// and backwards
FindMatch? back = FindEngine.FindPrevious(ws, new CellPosition(100, 0), options);
_ = back;
FindNext starts just after the position you hand it and wraps at the end of the sheet —
across sheets too, when WholeWorkbook is set. It returns null when there is no hit.
FindMatch is a read-only record struct carrying Sheet / Row / Col / Text, with
Position giving you a CellPosition. Text is the string the match was made against, so
what it holds depends on LookIn.
Every hit
var options = new FindOptions { Text = "Not started" };
// every hit (the scan visits only cells that exist)
foreach (FindMatch m in FindEngine.Enumerate(ws, options))
{
_ = $"{m.Sheet.Name}!{m.Position.ToAddress()}";
}
// just the count
int count = FindEngine.Count(ws, options);
_ = count;
Changing how it searches
// whole-cell matches only, case-sensitive
var exact = new FindOptions
{
Text = "TODO",
MatchCase = true,
WholeCell = true,
};
// a regular expression; a broken pattern leaves IsValid false rather than throwing
var regex = new FindOptions { Text = @"^\d{4}-\d{2}$", UseRegex = true };
if (!regex.IsValid)
{
// flag the input box, for instance
}
// pick what to read out of each cell; the default is Values (the displayed text)
var inFormulas = new FindOptions { Text = "SUM", LookIn = SearchIn.Formulas };
var inNotes = new FindOptions { Text = "check", LookIn = SearchIn.Comments };
// restrict the range, walk by columns, or widen to the whole workbook
var scoped = new FindOptions
{
Text = "Not started",
Within = RangePosition.Parse("B2:D500"),
Order = SearchOrder.ByColumns,
WholeWorkbook = true,
};
_ = (exact, inFormulas, inNotes, scoped);
FindOptions is an immutable record. A dialog can hold one and pass it to every call, so
find, find-again and replace never drift apart.
| Member | Default | What it does |
|---|---|---|
Text | "" | What to look for; a regular expression when UseRegex is set |
MatchCase | false | Case-sensitive comparison |
WholeCell | false | The cell must equal Text in full (Excel’s “Match entire cell contents”) |
UseRegex | false | Treat Text as a .NET regular expression |
LookIn | Values | Values / Formulas / Comments |
Order | ByRows | Walk by rows or by columns |
Within | null | Restrict to a range; null searches the whole sheet |
WholeWorkbook | false | Continue into the workbook’s other sheets |
IncludeHidden | false | Include rows and columns that are hidden |
IsValid reports whether the pattern compiles when UseRegex is set. A broken pattern does
not throw — flagging the input box beats dying halfway through a scan.
The three LookIn modes
Values(default) — the cell’s displayed text. Because it reads what the number format produced,1234under#,##0matches as1,234.Formulas— the cell’s input string: a formula with its leading=, otherwise the raw value.Comments— the body of a cell note.
Replacing
var options = new FindOptions { Text = "Not started", LookIn = SearchIn.Formulas };
// one cell; false when that cell does not match
bool replaced = FindEngine.ReplaceAt(ws, 1, 2, options, "In progress");
_ = replaced;
// the whole sheet: one undo step and a single recalculation
int n = FindEngine.ReplaceAll(ws, options, "In progress");
_ = n;
// the whole workbook (one undo step per sheet)
int total = FindEngine.ReplaceAll(wb, options, "In progress");
_ = total;
Replace works on the cell’s input string. That is the only text a cell can be written back from, and it is the same reason Excel restricts replacement to formulas.
The consequence: a match that exists only in the displayed text can be found but not
replaced. Searching Values for 1234 under #,##0 hits the comma in 1,234, but that
comma is not stored anywhere in the cell, so ReplaceAt returns false and changes nothing.
Change the number format instead (Number Formats).
ReplaceAll collapses into one undo step per sheet and a single recalculation. The
workbook overload takes one step per sheet, because undo history is per worksheet.
What a scan costs
A search reads only cells that exist, in the store’s 256-row bands. On a sheet of 1,048,576 rows the cost still tracks the content, not the dimensions.
Formula cells are merged in from a pre-sorted key list (a formula whose result is empty leaves
no value entry, so the value scan alone would miss it). Hidden rows and columns are skipped
unless IncludeHidden is set.
In the controls
- WinForms and WPF carry a modeless find dialog: Ctrl+F to find, Ctrl+H to replace, F3 / Shift+F3 to repeat forwards and backwards.
control.FindNext(options)andcontrol.ReplaceAll(options, replacement)do the same without showing a dialog. The selection moves to each hit.control.ShowFindDialog(replace: true)opens it on the replace tab.
Try it in Studio
Edit ▸ Find… (Ctrl+F), Edit ▸ Replace… (Ctrl+H), Edit ▸ Find Next (F3).