Skip to content

Fix column width distortion in AutoFit with active AutoFilter#2387

Closed
lievendf wants to merge 1 commit into
EPPlusSoftware:develop8from
lievendf:fix/autofit-autofilter-cells
Closed

Fix column width distortion in AutoFit with active AutoFilter#2387
lievendf wants to merge 1 commit into
EPPlusSoftware:develop8from
lievendf:fix/autofit-autofilter-cells

Conversation

@lievendf

Copy link
Copy Markdown
Contributor

Description

This PR addresses a bug where performing an AutoFit (or AutoFitColumns()) on a worksheet with an active AutoFilter results in distorted and bloated column widths (clamping column widths to at least ~16-17 points regardless of actual cell content).

Root Cause

During the AutoFit calculation, EPPlus iterates over active autofilter addresses (afAddr) to ensure the column headers are sized correctly.
However, in src/EPPlus/Core/AutofitHelper.cs (line 129), the code was fetching the entire row-range representing the AutoFilter's headers:

var cell = worksheet.Cells[af.Address];

For an AutoFilter range such as A1:U1, af.Address represents the multi-cell range "A1:U1". Consequently, cell becomes a multi-cell ExcelRange containing a 2D array of cells.

When EPPlus attempts to measure the text width of this cell via GetTextLength(), cell.TextForWidth evaluates the underlying value of the entire range. Since a multi-cell range evaluates to a 2D object array (object[,]), formatting it falls back to calling .ToString(), which returns the string "System.Object[,]" (exactly 16 characters).

This 16-character string was then measured for every column covered by the AutoFilter, artificially inflating the column widths to a minimum of ~16.07 points (clamped to the length of the string "System.Object[]"), even if the column's header was as short as "hour" (4 characters) or "minute" (6 characters).

Solution

The fix surgically targets the specific column's header cell instead of the entire AutoFilter header range.
In src/EPPlus/Core/AutofitHelper.cs (line 129), we replace:

var cell = worksheet.Cells[af.Address];

with:

var cell = worksheet.Cells[af._fromRow, col];

This correctly resolves to the individual column cell for the column col currently being processed, allowing the column to auto-fit to its true text length.


Code Changes

src/EPPlus/Core/AutofitHelper.cs

@@ -126,7 +126,7 @@
                 {
                     if (af.Collide(fromRow, col, toRow, col) != eAddressCollition.No)
                     {
-                        var cell = worksheet.Cells[af.Address];
+                        var cell = worksheet.Cells[af._fromRow, col];
                         var cellStyleId = styles.CellXfs[cell.StyleID];
                         currentMaxWidth = GetTextLength(cell, textLengthCache, styles, cellStyleId, normalSize, MaximumWidth, currentMaxWidth);
                     }

Unit Test

We have added the following unit test to the EPPlusTest suite in src/EPPlusTest/WorkSheetTests.cs:

        [TestMethod]
        public void AutoFitColumnsWithAutoFilter()
        {
            var ws = _pck.Workbook.Worksheets.Add("AutofitAutoFilter");
            ws.Cells["A1"].Value = "hour";
            ws.Cells["B1"].Value = "minute";
            ws.Cells["A2"].Value = 12;
            ws.Cells["B2"].Value = 30;

            ws.Cells["A1:B2"].AutoFilter = true;

            ws.Cells["A1:B2"].AutoFitColumns();

            // Without the fix, the AutoFilter header row range (A1:B1) is measured as a whole.
            // Under the hood, worksheet.Cells["A1:B1"].TextForWidth evaluated to "System.Object[,]" (16 chars),
            // which forced a minimum width of ~16.07 points.
            // With the fix, the specific cell for each column in the AutoFilter is measured, 
            // resulting in a narrow width matching "hour" / "minute".
            Assert.IsTrue(ws.Column(1).Width < 12d, $"Column 1 width should be small but was {ws.Column(1).Width}");
            Assert.IsTrue(ws.Column(2).Width < 12d, $"Column 2 width should be small but was {ws.Column(2).Width}");
        }

Verification

  • Compilation: Successfully compiled the EPPlus solution (EPPlus.sln) with 0 errors.
  • Unit Test Execution: Executed dotnet test targeting the new unit test under both .NET 8.0 and .NET Framework 4.8.1, passing successfully:
    • Passed! - Failed: 0, Passed: 1, Skipped: 0, Total: 1, Duration: 574 ms - EPPlusTest.dll (net8.0)
    • Passed! - Failed: 0, Passed: 1, Skipped: 0, Total: 1, Duration: 1 s - EPPlusTest.dll (net481)

@swmal

swmal commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Hello @lievendf,

Thanks for this — the fix is correct and we've merged it. While reviewing it I noticed that it also surfaced a separate, pre-existing issue: with the header now measured correctly, the autofilter dropdown arrow is no longer accounted for. I've opened a follow-up PR to handle that.

@swmal swmal closed this Jun 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants