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

  1. Invalidate and rebuild caches together: reset the RowCache whenever the PageCache or document content changes (call the viewer's InvalidateScrollInfo/Recalc paths).
  2. Verify the paginator's PageCount is consistent with the rows before requesting a page.
  3. Ensure content changes during paging raise the proper change notifications so the cache recalculates.
  4. 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

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


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)