V4 V5

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.

MemberDefaultWhat it does
Text""What to look for; a regular expression when UseRegex is set
MatchCasefalseCase-sensitive comparison
WholeCellfalseThe cell must equal Text in full (Excel’s “Match entire cell contents”)
UseRegexfalseTreat Text as a .NET regular expression
LookInValuesValues / Formulas / Comments
OrderByRowsWalk by rows or by columns
WithinnullRestrict to a range; null searches the whole sheet
WholeWorkbookfalseContinue into the workbook’s other sheets
IncludeHiddenfalseInclude 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, 1234 under #,##0 matches as 1,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) and control.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).

Was this article helpful?