【问题标题】:OpenXML: Auto Size column width in ExcelOpenXML:Excel 中的自动调整列宽
【发布时间】:2013-08-16 08:03:23
【问题描述】:

我编写了一个代码来使用 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);

另外,我尝试在下面的行创建列。

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

我认为使用BestFit 应该可以。但它没有设置自动大小。

【问题讨论】:

    标签: c# openxml openxml-sdk


    【解决方案1】:

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

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

     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;
        }
    

    【讨论】:

    • 你还没有定义你的“GetSheetData()”方法。
    • 等一下,我明白你的意思了,你的意思是自己编写设置所有单元格值的实现,然后继续设置列宽,我的错。
    • AutoSize()中的charWidth变量没有使用?
    • @Macke 变量 widthpixelscharWidth 应该是函数。它们直接反映了 3 个公式 listed on MSDN
    • 如果您使用的是SharedStringTable,则需要在GetMaxCharacterWidth中获取cellValue后获取字符串:if (cell.DataType == CellValues.SharedString) cellValue = sharedStringTable.ChildElements[int.Parse(cellValue)].InnerText;
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      我没有时间研究它,但我想我应该分享一个似乎对此进行过一些研究的人的评论,而不是仅仅发表评论和a link

      我个人在让the official formulas 适应现实时遇到了问题。 IE。短字符串的单元格太小,长字符串的单元格太大,最重要的是,Excel 中显示的值比我插入到DocumentFormat.OpenXml.Spreadsheet.ColumnWidth 属性中的值成比例地小。我的快速解决方案是设置最小宽度。

      无论如何,这是评论:

      最后我不得不这样做,因为我感兴趣的 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. 尽管规范说要添加 5 个像素(1 个用于边框,2 个用于每个边距)Excel 似乎使用 9 - 1 作为边框,5 用于前导空间,3 用于尾随空间 - 我只是通过使用无障碍应用程序。使用 Excel 自动调整列后放大镜和计数像素

      实际上,我的计算基于基础字体指标,因此我实际上并没有使用 MeasureCharacterRanges 或 MeasureString。如果有人有兴趣从字体指标中做到这一点,那么:

      Width = Truncate( {DesiredWidth} + 9 / {MaxDigitWidth} ) / 256

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

      【讨论】:

        【解决方案4】:

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

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

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

        方法如下。它需要一个 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
        

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

        【讨论】:

          【解决方案5】:

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

          【讨论】:

            【解决方案6】:

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2016-01-21
              • 2023-03-29
              • 2013-05-21
              • 2011-02-06
              • 1970-01-01
              • 2011-03-29
              • 2014-04-05
              相关资源
              最近更新 更多