/// <summary>
/// Creates a <see cref="CellAccessor"/> instance that represents all effective cells on a <see cref="Worksheet"/>.
/// </summary>
/// <param name="worksheet">The worksheet.</param>
/// <remarks>
/// <para>
/// The created instance maintains several child objects of <paramref name="worksheet"/>.
/// Don't dispose <paramref name="worksheet"/> until you've done with it.
/// </para>
/// <para>
/// This class <i>modifies</i> some formatting of the given worksheet for its own purposes.
/// It is not a good idea to save the worksheet after using this class.
/// </para>
/// </remarks>
public CellAccessor(Worksheet worksheet)
{
ExcelApp = worksheet.Application;
Cells = worksheet.Cells;
VisibleCells = Cells.SpecialCells(XlCellType.xlCellTypeVisible);
// To avoid <see cref="Range.Text"/> to return #### for a narrow column.
// This is the only modification we make on the worksheet as mentioned in the remarks above.
Cells.ShrinkToFit = true;
// Extract the meaningful range of cells on the sheet.
var bottom_right = Cells
.SpecialCells(XlCellType.xlCellTypeLastCell)
.Address(true, true, XlReferenceStyle.xlA1);
if (bottom_right != "$A$1")
{
// The normal case.
// I expect that most cells on a glossary worksheet contain text data,
// and their Value2 property usually return a string object.
// We will utilize them at maximum to reduce the COM overhead.
CachedCellValues = Cells.Range("$A$1", bottom_right).Value2 as object[, ];
}
else if (string.IsNullOrWhiteSpace(Cells.Range("$A$1").Text as string))
{
// The worksheet was empty.
CachedCellValues = Array.CreateInstance(typeof(object), new int[] { 0, 0 }, new int[] { 1, 1 }) as object[, ];
}
else
{
// The worksheet contained only one value at $A$1.
CachedCellValues = Array.CreateInstance(typeof(object), new int[] { 1, 1 }, new int[] { 1, 1 }) as object[, ];
CachedCellValues[1, 1] = Cells.Range("$A$1").Value2;
}
var rows = CachedCellValues.GetLength(0);
var columns = CachedCellValues.GetLength(1);
// Trim empty columns at the end if any.
while (columns > 0 &&
Enumerable.Range(1, rows).All(row => string.IsNullOrWhiteSpace(CachedCellValues[row, columns]?.ToString())))
{
--columns;
}
Rows = rows;
Columns = columns;
}