Reading and writing XLSX (OOXML) is handled by unvell.ReoGrid.IO.Excel.
It depends on no external packages (neither the Open XML SDK nor EPPlus is used).
Everything is bundled with the One packages.
Reading and writing
using unvell.ReoGrid.Core;
using unvell.ReoGrid.IO.Excel;
Workbook loaded = XlsxReader.Read("book.xlsx");
XlsxWriter.Write(wb, "out.xlsx");
// a stream works too
using var stream = File.OpenRead("book.xlsx");
Workbook fromStream = XlsxReader.Read(stream);
XlsxWriter.Write has a stream overload as well.
Streaming reads
Reading is streamed through XmlReader directly into the sparse store.
Memory usage is proportional to the number of cells that actually exist
(not the sheet’s declared dimensions).
Lazy loading for huge files
// open a huge file with per-sheet lazy loading
using var virtualBook = XlsxReader.OpenVirtual("large.xlsx", SheetLoadMode.OnDemand);
| Mode | Behavior |
|---|---|
OnDemand | Builds each sheet the first time it is displayed |
Background | Builds the active sheet first, then prefetches the rest in the background |
VirtualWorkbook is IDisposable. It holds on to the underlying stream,
so dispose it when you are done.
Column widths
Excel stores a column width as a number of digits of the Normal font, and measures that digit on the screen showing the sheet. The width depends on the font and the DPI, and does not scale with the DPI: Arial 10’s digit is 7 px at 100 %, but 11 px at 150 % (7.33 logical px), not 10.5. Yu Gothic 11’s is 8 px at every scale.
Set the measurement once in a UI host and opened xlsx columns come out as wide as Excel draws them on the same screen. Saving converts back with the same measurement, so a width nobody touched is written back as the number that was read.
// open column widths as wide as Excel draws them on this screen (set once in a UI host)
XlsxColumnWidths.MaxDigitWidthMeasurer =
(family, sizePt) => GdiTextMetrics.MaxDigitWidth(family, sizePt, grid.DeviceDpi);
WPF and Avalonia do not draw with GDI, so pass XlsxColumnWidths.ExcelMaxDigitWidth, which looks
Excel’s digit width up in a built-in table at the screen’s DPI. The table holds GDI measurements —
the way Excel measures — for each pixel size of the font, so columns match Excel at 150 % and 200 %
too. On Avalonia, use 96 × TopLevel.RenderScaling as the DPI.
// WPF / Avalonia: Excel's digit widths from the built-in table, at this screen's DPI
double dpi = VisualTreeHelper.GetDpi(grid).PixelsPerInchX;
XlsxColumnWidths.MaxDigitWidthMeasurer =
(family, sizePt) => XlsxColumnWidths.ExcelMaxDigitWidth(family, sizePt, dpi);
Left unset (on a server, for instance), the digit width Excel measures at 100 % comes from the same table: 7 px for Calibri 11, Aptos Narrow 11 and Arial 10, 8 px for Yu Gothic 11 and MS PGothic 11. Japanese workbooks open as wide as in Excel at 100 % too. A font not in the table counts as Calibri, Excel’s default.
How formulas are handled
Formulas are not re-evaluated on load. The cached values saved by Excel are used as-is.
var wb = XlsxReader.Read("book.xlsx");
// The cached values Excel saved are read as-is, so nothing is re-evaluated on load.
// Call this only when you want to compute them yourself.
foreach (var sheet in wb.Worksheets)
sheet.Recalculate();
There is a reason for this design: even for files containing functions that V5 does not
implement, the values Excel calculated are displayed as-is. Re-evaluating them would
turn them into #NAME?.
What is supported
| Feature | Read | Write |
|---|---|---|
| Values (numbers, strings, booleans, dates, errors) | Yes | Yes |
| Shared strings, inline strings, rich strings | Yes | Yes |
| Styles (font, color, alignment, wrapping) | Yes | Yes |
| Number formats | Yes | Yes |
| Borders | Yes | Yes |
| Merged cells | Yes | Yes |
| Row heights, column widths, hidden state | Yes | Yes |
| 1900 / 1904 date systems | Yes | Yes |
Conditional formatting (<conditionalFormatting> / <dxfs>) | Yes | Yes |
Defined names (<definedNames>) | Yes | Yes |
| Outlines (grouping) | Yes | Yes |
| Images (drawing) | Yes | Yes |
Page setup (<pageSetup>) | Yes | Yes |
Header/footer (<headerFooter>) and print titles | Yes | Yes |
| View state (frozen panes, zoom, gridlines), hidden sheets, tab colors | Yes | Yes |
Auto filter (<autoFilter>) | Yes | Yes |
Cell notes (comments{n}.xml plus VML) | Yes | Yes |
Data validation (<dataValidations>) | Yes | Yes |
| The validation x14 extension (a list sourced from another sheet) | Yes | No (written as the classic element) |
Sheet protection (<sheetProtection>) | Yes | Yes |
| Formulas | Yes (cached values preserved) | Yes |
Not supported
| Feature | Notes |
|---|---|
| Charts and shapes | Images are supported |
| Cell types | XLSX has no corresponding concept, so they become plain cells |
| Threaded comments | Only the classic note is supported |
| Alternating row fills (banded rows) | Excel keeps them inside a table style |
Built-in names (_xlnm.*) | Only Print_Area and Print_Titles round-trip, as PrintSettings |
| Data bar negative colors, zero axis, and borders | Requires the OOXML x14 extensions |
| Split (unfrozen) panes | Frozen panes do round-trip |
cellStyleXfs (xfId) style inheritance | Not expanded on read |