17

我编写了一个代码来使用 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它应该可以工作。但它没有设置自动大小。

4

6 回答 6

25

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

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

 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;
    }
于 2014-10-03T14:00:26.843 回答
7

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

于 2013-08-19T02:14:21.170 回答
1

我还没有时间研究它,但我想我会分享一个似乎对此进行过一些研究的人的评论,而不是仅仅留下评论和链接。

我个人在让官方公式与现实相适应时遇到了问题。即短字符串的单元格太小,长字符串的单元格太大,最重要的是,Excel 中显示的值比我插入到DocumentFormat.OpenXml.Spreadsheet.Column's Width-property 中的值成比例地小。我的快速解决方案就是设置最小宽度。

无论如何,这是评论:

最后我不得不这样做,因为我感兴趣的 xlsx 文件是自动生成的,并且一旦打开它们就应该看起来不错,所以我进一步研究了这一点,发现在准确调整列大小时存在一些问题Excel。

  1. 需要使用准确的字符大小,这意味着您需要使用 MeasureCharacterRanges 而不是使用 MeasureString,请参阅http://groups.google.com/group/microsoft.public.office.developer.com.add_ins/browse_thread/thread/2fc33557feb72ab4 /adddc50480b8cff?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 处四舍五入到的字符宽度最接近的整数。请注意,每个字符都是四舍五入的,而不是总和

于 2017-01-11T07:32:48.387 回答
1

我终于找到了一个使用图形引擎 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

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

于 2020-12-22T15:38:19.923 回答
0

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

于 2014-02-13T06:09:05.233 回答
0

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

于 2020-01-29T11:26:41.257 回答