OpenXML:Excel中的自动调整列宽

时间:2013-08-16 08:03:23

标签: c# openxml openxml-sdk

我编写了一个使用OpenXML生成Excel文件的代码。 下面是在Excel中生成列的代码。

Worksheet worksheet = new Worksheet();
Columns columns = new Columns();
int numCols = dt1.Columns.Count;
for (int col = 0; col < numCols; col++)
{
    Column c = CreateColumnData((UInt32)col + 1, (UInt32)numCols + 1, 20.42578125D);

    columns.Append(c);
}
worksheet.Append(columns);

另外,我尝试在line下创建列。

Column c = new Column
{
    Min = (UInt32Value)1U,
    Max = (UInt32Value)1U,
    Width = 25.42578125D,
    BestFit = true,
    CustomWidth = true
};

我认为使用BestFit它应该有效。但它没有设置自动尺寸。

6 个答案:

答案 0 :(得分:14)

不幸的是,你必须自己计算

这就是我所拥有的。它适用于我的数据表格,其中包含一些额外的代码来处理我设置的一些样式。它无论如何都不完美,但却能满足我的需要。

 private WorksheetPart mySheetPart;
 private void WriteToTable()
 {
      //Get your sheet data - write Rows and Cells
      SheetData sheetData = GetSheetData();

      //get your columns (where your width is set)
      Columns columns = AutoSize(sheetData);

      //add to a WorksheetPart.WorkSheet
      mySheetPart.Worksheet = new Worksheet();
      mySheetPart.Worksheet.Append(columns);
      mySheetPart.Worksheet.Append(sheetData);
 }

 private Columns AutoSize(SheetData sheetData)
 {
        var maxColWidth = GetMaxCharacterWidth(sheetData);

        Columns columns = new Columns();
        //this is the width of my font - yours may be different
        double maxWidth = 7;
        foreach (var item in maxColWidth)
        {
            //width = Truncate([{Number of Characters} * {Maximum Digit Width} + {5 pixel padding}]/{Maximum Digit Width}*256)/256
            double width = Math.Truncate((item.Value * maxWidth + 5) / maxWidth * 256) / 256;

            //pixels=Truncate(((256 * {width} + Truncate(128/{Maximum Digit Width}))/256)*{Maximum Digit Width})
            double pixels = Math.Truncate(((256 * width + Math.Truncate(128 / maxWidth)) / 256) * maxWidth);

            //character width=Truncate(({pixels}-5)/{Maximum Digit Width} * 100+0.5)/100
            double charWidth = Math.Truncate((pixels - 5) / maxWidth * 100 + 0.5) / 100;

            Column col = new Column() { BestFit = true, Min = (UInt32)(item.Key + 1), Max = (UInt32)(item.Key + 1), CustomWidth = true, Width = (DoubleValue)width };
            columns.Append(col);
        }

        return columns;
  }


  private Dictionary<int, int> GetMaxCharacterWidth(SheetData sheetData)
    {
        //iterate over all cells getting a max char value for each column
        Dictionary<int, int> maxColWidth = new Dictionary<int, int>();
        var rows = sheetData.Elements<Row>();
        UInt32[] numberStyles = new UInt32[] { 5, 6, 7, 8 }; //styles that will add extra chars
        UInt32[] boldStyles = new UInt32[] { 1, 2, 3, 4, 6, 7, 8 }; //styles that will bold
        foreach (var r in rows)
        {
            var cells = r.Elements<Cell>().ToArray();

            //using cell index as my column
            for (int i = 0; i < cells.Length; i++)
            {
                var cell = cells[i];
                var cellValue = cell.CellValue == null ? string.Empty : cell.CellValue.InnerText;
                var cellTextLength = cellValue.Length;

                if (cell.StyleIndex != null && numberStyles.Contains(cell.StyleIndex))
                {
                    int thousandCount = (int)Math.Truncate((double)cellTextLength / 4);

                    //add 3 for '.00' 
                    cellTextLength += (3 + thousandCount);
                }

                if (cell.StyleIndex != null && boldStyles.Contains(cell.StyleIndex))
                {
                    //add an extra char for bold - not 100% acurate but good enough for what i need.
                    cellTextLength += 1;
                }

                if (maxColWidth.ContainsKey(i))
                {
                    var current = maxColWidth[i];
                    if (cellTextLength > current)
                    {
                        maxColWidth[i] = cellTextLength;
                    }
                }
                else
                {
                    maxColWidth.Add(i, cellTextLength);
                }
            }
        }

        return maxColWidth;
    }

答案 1 :(得分:7)

BestFit属性是一个信息属性(可能由Excel优化)。您仍需要提供列的宽度。这意味着您必须根据单元格内容实际计算列宽。 Open XML SDK不会自动为您执行此操作,因此最好为此使用第三方库。

答案 2 :(得分:1)

我没有时间去研究它,但我没有发表评论而是a link,而是认为我会分享一些似乎对此做过一些研究的人的评论。

我个人遇到了让the official formulas符合现实的问题。即短字符串的单元格太小,字符串越长,单元格越大,最重要的是,Excel中显示的值与我在DocumentFormat.OpenXml.Spreadsheet.Column的{​​{1}} - 属性中插入的值成比例地小。我的快速解决方案就是拥有最小宽度。

无论如何,这是评论:

  

我最终必须这样做,因为我感兴趣的xlsx文件是自动生成的,一旦打开它们应该看起来很好所以我进一步研究了这一点,发现有一些问题需要准确在Excel中调整列的大小。

     
      
  1. 需要使用准确的字符大小调整,这意味着您需要使用MeasureCharacterRanges而不是使用MeasureString,请参阅http://groups.google.com/group/microsoft.public.office.developer.com.add_ins/browse_thread/thread/2fc33557feb72ab4/adaddc50480b8cff?lnk=raot

  2.   
  3. 尽管规格说要添加5个像素(1个用于边框,2个用于每个边距),Excel似乎使用9 - 1作为边框,5个作为前导空格,3个作为尾部空格 - 我只通过使用辅助功能应用程序找到了这个。放大镜和使用Excel自动调整列后计算像素

  4.         

    实际上我的计算基于底层字体指标,所以我实际上并没有使用MeasureCharacterRanges或MeasureString。如果有人对字体指标感兴趣,那么:

         

    Width

         

    {MaxDigitWidth}是一个四舍五入到96 dpi的0..9位数的最近像素的整数   {DesiredWidth}是将所有字符宽度加在一起的总和,其中每个字符宽度是96 dpi处字符宽度四舍五入到最接近的整数。请注意,每个字符都是四舍五入而不是总和

答案 3 :(得分:1)

我终于找到了一个解决方案,它使用图形引擎 GDI+ 来准确地做到这一点。 avg. char width * number of chars 永远不会削减它。

这里有一个警告,如果您的目标平台是 linux,您将需要几个库 - 一个用于 GDI+ 互操作,另一个用于添加开放的 microsoft web 字体。

它使用像素的基本单位,考虑到 excel 字体是 pt 和列宽 INCHES! (笑)

这是方法。它需要一个 JSchema 来映射到列,但你明白了要点。

private static Columns CreateColumnDefs(JSchema jsonSchema)
{
    const double cellPadding = .4;
    const double minCellWidth = 10;

    var columnDefs = new Columns();
    var columnIndex = 0U;

    // set up graphics for calculating column width based on heading text width
    var bmp = new Bitmap(1, 1);

    // todo: ensure linux host has gdi and "Microsoft core fonts" libs installed
    using var graphics = Graphics.FromImage(bmp);
    graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
    graphics.PageUnit = GraphicsUnit.Pixel;

    // excel fonts are in points - Arial 10pt matches the excel default of Calibri 11pt pretty well...
    using var font = new Font("Arial", 10F, FontStyle.Bold, GraphicsUnit.Point);

    // currently only handles 1 level (no nested objects)
    foreach (var (_, value) in jsonSchema.Properties)
    {
        var pixelWidth = graphics.MeasureString(value.Title, font).Width;

        // see: https://stackoverflow.com/questions/7716078/formula-to-convert-net-pixels-to-excel-width-in-openxml-format/7902415
        var openXmlWidth = (pixelWidth - 12 + 5) / 7d + 1; // from pixels to inches.

        columnIndex++;
        var column = new Column
        {
            BestFit = true,
            CustomWidth = true,
            Width = Math.Max(openXmlWidth + cellPadding, minCellWidth),
            Min = columnIndex,
            Max = columnIndex
        };
        columnDefs.Append(column);
    }

    return columnDefs;
}

编辑

要在 linux 上运行安装,例如:

RUN apt update && apt-get install -y libgdiplus

(我不需要任何额外的字体库,所以也许系统字体可以工作)

答案 4 :(得分:0)

这里是可能的公式 width =截断([{字符数}} * {最大数字宽度} + {5像素填充}] / {最大数字宽度} * 256)/ 256

答案 5 :(得分:0)

@Mittal Patel看看我的answer。发布的代码使用DOM方法,但是我也使用SAX方法创建了一个解决方案,并且结果非常出色-没有内存消耗(直接从DataReader写入),并且比Interop库快得多。自动调整功能是唯一的缺点-您必须两次读取相同的数据。