dotnet/wpf · error · InvalidOperationException
SR.RowCachePageNotFound
Error message
SR.RowCachePageNotFound
What it means
RowCache.GetRowIndexForPageNumber scans its cached rows for the row containing the requested page number. If no row covers that page, the internal layout model is inconsistent (a page exists in the PageCache but no row claims it), so InvalidOperationException(SR.RowCachePageNotFound) is thrown rather than returning wrong layout data.
Solutions
- Invalidate and rebuild caches together: reset the RowCache whenever the PageCache or document content changes (call the viewer's InvalidateScrollInfo/Recalc paths).
- Verify the paginator's PageCount is consistent with the rows before requesting a page.
- Ensure content changes during paging raise the proper change notifications so the cache recalculates.
- Capture the page number and stack for a framework bug report if reproducible with stock FlowDocument content.
Example fix
// before var row = rowCache.GetRowForPageNumber(pageNumber); // stale cache -> throw // after rowCache.RecalcRowsForFixedPageSizes(); // or otherwise invalidate on content change var row = rowCache.GetRowForPageNumber(Math.Min(pageNumber, paginator.PageCount - 1));
Defensive patterns
Strategy: try-catch
Validate before calling
bool pageInCache = pageNumber >= 0 && pageNumber < paginator.PageCount && rowCache.RowCount > 0;
Type guard
bool RowCacheIsConsistent(RowCache rc, int page) =>
rc != null && rc.PageCache != null && page >= 0; Try / catch
try
{
row = rowCache.GetRowForPageNumber(pageNumber);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("page"))
{
// cache/layout desync: invalidate caches and retry once
rowCache.RecalcRowsForFixedPageSizes();
row = rowCache.GetRowForPageNumber(pageNumber);
} Prevention
- Invalidate RowCache whenever document content or PageCache changes
- Clamp requested page numbers to the paginator's PageCount
- Let change notifications drive recalculation instead of manual caching
When it happens
Trigger: Requesting GetRowForPageNumber (or triggering RecalcRowsForFixedPageSizes) with a page number outside the ranges covered by cached rows — typically after layout/dynamic content changes desynchronized the RowCache from the PageCache.
Common situations: Dynamic content resizing in DocumentViewer/FlowDocumentPageViewer while pages are being fetched; reporting/printing pipelines asking for pages before row recalculation finishes; bugs in custom IDynamicDocumentPaginator page counts.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- 0x80040206
- ArgumentNullException: child
- SR.ArrangeReentrancyInvalid
- SR.ArrangeReentrancyInvalid
- SR.FlowDocumentInvalidContnetChange
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/d84c3f80ad3a80bd.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/documents/RowCache.cs:288
//Search our cache for the row that contains the page.
//NOTE: Future perf item:
//This search can be re-written as a binary search, which will be O(log(N))
//instead of O(N).
for (int i = 0; i < _rowCache.Count; i++)
{
RowInfo rowInfo = _rowCache[i];
if (pageNumber >= rowInfo.FirstPage &&
pageNumber < rowInfo.FirstPage + rowInfo.PageCount)
{
//We found the row, return the index.
return i;
}
}
//We didn't find it. Something is very likely wrong with our layout.
//We'll throw, as this is an indicator that our layout cannot be trusted.
throw new InvalidOperationException(SR.RowCachePageNotFound);
}
/// <summary>
/// Returns the row that lives at the specified vertical offset.
/// </summary>
/// <param name="offset">The vertical offset to find the corresponding row for</param>
/// <returns>The index of the row that lives at the offset.</returns>
public int GetRowIndexForVerticalOffset(double offset)
{
ArgumentOutOfRangeException.ThrowIfNegative(offset);
ArgumentOutOfRangeException.ThrowIfGreaterThan(offset, ExtentHeight);
//If we have no rows we'll return 0 (the top of the non-existent document)
if (_rowCache.Count == 0)
{
return 0;
}
View on GitHub (pinned to 81131a70a4)