ClosedXML
ClosedXML copied to clipboard
DataValidation ranges broken after row insert/delete
Under certain conditions data validation ranges are broken when rows are inserted or deleted. I have narrowed down the issue to two test cases that clearly show the problem:
Here a range is selected that is in use by a validation after insert. (Tanken from here https://github.com/ClosedXML/ClosedXML/blob/e911cf49b47ccc0f354de292696c6e3d3a0a2500/ClosedXML.Tests/Excel/DataValidations/DataValidationShiftTests.cs#L36)
[Test]
public void DataValidationShiftedOnRowInsert()
{
using (var wb = new XLWorkbook())
{
var ws = wb.AddWorksheet("DataValidationShift");
ws.SelectedRanges.Add(ws.Range("G6:G7")); // <---- A range is selected that is used in a validation after insert
ws.Range("A1:A1").CreateDataValidation().WholeNumber.Between(0, 1);
ws.Range("B1:B2").CreateDataValidation().WholeNumber.Between(0, 1);
ws.Range("C1:C3").CreateDataValidation().WholeNumber.Between(0, 1);
ws.Range("D2:F2").CreateDataValidation().WholeNumber.Between(0, 1);
ws.Range("G4:G5").CreateDataValidation().WholeNumber.Between(0, 1);
ws.Cells("A1:G5").Value = 1;
ws.Row(2).InsertRowsBelow(2);
var dv = ws.DataValidations.ToArray();
Assert.AreEqual(5, dv.Length);
Assert.AreEqual("A1:A1", dv[0].Ranges.Single().RangeAddress.ToString());
Assert.AreEqual("B1:B4", dv[1].Ranges.Single().RangeAddress.ToString());
Assert.AreEqual("C1:C5", dv[2].Ranges.Single().RangeAddress.ToString());
Assert.AreEqual("D2:F4", dv[3].Ranges.Single().RangeAddress.ToString());
Assert.AreEqual("G6:G7", dv[4].Ranges.Single().RangeAddress.ToString());
}
}
The same can be done in the respective test for row deletion DataValidationShiftedOnRowDelete.
I believe the issue lies somewhere in the NotifyRangeShiftedRows function. Here the _rangeRepository is enumerated and the ranges are modified. Could it be if a range is already in the repository, e.g., selected as above, this range is shifted twice?
public void NotifyRangeShiftedRows(XLRange range, Int32 rowsShifted)
{
var rangesToShift = _rangeRepository
.Where(r => r.RangeAddress.IsValid)
.OrderBy(r => r.RangeAddress.FirstAddress.RowNumber * -Math.Sign(rowsShifted))
.ToList();
WorksheetRangeShiftedRows(range, rowsShifted);
foreach (var storedRange in rangesToShift)
{
if (storedRange.IsEntireColumn())
continue;
if (ReferenceEquals(range, storedRange))
continue;
storedRange.WorksheetRangeShiftedRows(range, rowsShifted);
}
range.WorksheetRangeShiftedRows(range, rowsShifted);
}
See last commit on this branch
https://github.com/sopherio/ClosedXML/tree/2198-data-validation-broken-after-row-insert-delete