65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Open XML 创建基本的 Excel 工作簿

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (47投票s)

2012年4月23日

CPOL

10分钟阅读

viewsIcon

286900

downloadIcon

20549

本文介绍了如何使用 Open XML 创建一个基本的 Excel 工作簿。

引言

本文的目的是描述如何仅仅使用 DocumentFormat.OpenXml.dll(命名空间为 DocumentFormat.OpenXml)来创建一个 Excel 工作簿。

为了测试示例,您必须从下载中心下载并安装 Open XML SDK 2.0。

此演示适用于 C# 和 Visual Basic。

使用 Open XML SDK 创建 Excel 文件时,无需安装 Microsoft Office,因此此库无需安装 Excel 即可使用。但是,演示项目在创建 xlsx 文档后会启动它,因此为了查看创建的文件,应安装一个可以打开 xlsx 文件的程序来查看该文件。

关于 Open XML 的一点解释

Open XML 是一种用于 Office 文件的开放式标准化格式。使用的标准是

这些标准定义了 Office 文件的结构和元素。Office 文件(如 Excel 的 xlsx)本身是包含特定目录和文件结构的压缩文件。包含电子表格内容的文件的 xml 文件,与其他 xml 文件一样。

就 Excel 文件而言,一个基本的 xlsx 文件例如包含以下文件

  • /[Content_Types].xml:定义电子表格的部件和扩展名
  • /xl/workbook.xml:例如工作簿中包含的工作表
  • /xl/styles.xml:工作表中使用的样式
  • /xl/sharedStrings.xml:单元格之间共享的字符串
  • /xl/worksheets/sheet1.xml...:实际的工作表

实际的包包含更多文件,但在此文章的范围内,这些是最有趣的。所包含的演示项目展示了为生成和修改这些文件而进行的一些操作。

关于项目

项目本身非常简单。它由两个类组成:MainWindow 类和一个静态 Excel 类。Excel 类负责对 Excel 电子表格进行的所有操作。它有点像一个工具类,但请注意它远未完成。它旨在用作学习工具或实际实现的种子。

在编写此演示时,我发现 Excel 对 XML 文件非常挑剔。一个令人惊讶的是,XML 文件中元素的顺序非常重要。例如,样式表中的元素,如字体、填充、边框、CellStyleXfs、CellXfs 等必须按特定顺序排列。否则,文档将被解释为已损坏。

另一个观察是,元素索引经常被使用(例如共享字符串的索引)。但是,库中没有提供获取索引的支持,因此必须循环集合以计算所需元素的索引。

因此,在构建此工具时,最好的工具之一是从 xlsx (=zip) 文件中提取数据以查看实际内容的实用程序。

如果您下载该项目,您会注意到使用了完全限定的类名。在本文中,为了提高可读性,我删除了类前面的命名空间。因此,如果您从代码片段中复制代码,请不要忘记为这两个命名空间定义 using(在 C# 中)或 Imports(在 VB 中)

  • DocumentFormat.OpenXml.Packaging
  • DocumentFormat.OpenXml.Spreadsheet

创建电子表格

现在来看代码。第一件事是创建电子表格。这实际上是 xlsx 文件。电子表格在 C# 中创建如下:

/// <summary>
/// Creates the workbook
/// </summary>
/// <returns>Spreadsheet created</returns>
public static SpreadsheetDocument CreateWorkbook(string fileName) {
   SpreadsheetDocument spreadSheet = null;
   SharedStringTablePart sharedStringTablePart;
   WorkbookStylesPart workbookStylesPart;

   try {
      // Create the Excel workbook
      spreadSheet = SpreadsheetDocument.Create(fileName, SpreadsheetDocumentType.Workbook, false);

      // Create the parts and the corresponding objects
      // Workbook
      spreadSheet.AddWorkbookPart();
      spreadSheet.WorkbookPart.Workbook = new Workbook();
      spreadSheet.WorkbookPart.Workbook.Save();

      // Shared string table
      sharedStringTablePart = spreadSheet.WorkbookPart.AddNewPart<SharedStringTablePart>();
      sharedStringTablePart.SharedStringTable = new SharedStringTable();
      sharedStringTablePart.SharedStringTable.Save();

      // Sheets collection
      spreadSheet.WorkbookPart.Workbook.Sheets = new Sheets();
      spreadSheet.WorkbookPart.Workbook.Save();

      // Stylesheet
      workbookStylesPart = spreadSheet.WorkbookPart.AddNewPart<WorkbookStylesPart>();
      workbookStylesPart.Stylesheet = new Stylesheet();
      workbookStylesPart.Stylesheet.Save();
   } catch (System.Exception exception) {
      System.Windows.MessageBox.Show(exception.Message, "Excel OpenXML basics", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Hand);
   }

   return spreadSheet;
} 

而在 Visual Basic 中:

''' <summary>
''' Creates the workbook
''' </summary>
''' <returns>Spreadsheet created</returns>
Public Shared Function CreateWorkbook(fileName As String) As SpreadsheetDocument
    Dim spreadSheet As SpreadsheetDocument = Nothing
    Dim sharedStringTablePart As SharedStringTablePart
    Dim workbookStylesPart As WorkbookStylesPart

    Try
        ' Create the Excel workbook
        spreadSheet = SpreadsheetDocument.Create(fileName, SpreadsheetDocumentType.Workbook, False)

        ' Create the parts and the corresponding objects
        ' Workbook
        spreadSheet.AddWorkbookPart()
        spreadSheet.WorkbookPart.Workbook = New Workbook()
        spreadSheet.WorkbookPart.Workbook.Save()

        ' Shared string table
        sharedStringTablePart = spreadSheet.WorkbookPart.AddNewPart(Of SharedStringTablePart)()
        sharedStringTablePart.SharedStringTable = New SharedStringTable()
        sharedStringTablePart.SharedStringTable.Save()

        ' Sheets collection
        spreadSheet.WorkbookPart.Workbook.Sheets = New DocumentFormat.OpenXml.Spreadsheet.Sheets()
        spreadSheet.WorkbookPart.Workbook.Save()

        ' Stylesheet
        workbookStylesPart = spreadSheet.WorkbookPart.AddNewPart(Of WorkbookStylesPart)()
        workbookStylesPart.Stylesheet = New Stylesheet()
        workbookStylesPart.Stylesheet.Save()
    Catch exception As System.Exception
        System.Windows.MessageBox.Show(exception.Message, "Excel OpenXML basics", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Hand)
    End Try

    Return spreadSheet
End Function   

创建电子表格文档后,添加所有工作表共有的四个基本部件:

  • 工作簿:包含工作簿本身的定义
  • 共享字符串表:单元格之间共享的字符串表
  • 工作表集合:工作簿中的工作表集合
  • 样式表:单元格的样式定义。例如边框、字体等。
创建这些每个部件时,首先创建部件,然后创建对象本身。请注意,部件在 DocumentFormat.OpenXml.Packaging 命名空间中找到,而 Excel 对象在 DocumentFormat.OpenXml.Spreadsheet 命名空间中。因此,部件描述“存储”,而 Spreadsheet 命名空间中的对象描述 xml 文件中的内容元素。

创建样式

使用代码创建

创建样式定义的一种方法是从代码中创建它们。在演示中,创建了以下基本样式:

  • 数字格式(在 x:numFmts 中):用于正确格式化货币值
  • 字体(在 x:fonts 中):使用 Calibri,字号为 11
  • 填充(在 x:fills 中):定义一个无图案的填充
  • 边框(在 x:borders 中):定义一个无边框的边框定义
  • 单元格样式格式(在 x:CellStyleXfs 中):使用通用的单元格样式格式定义
  • 单元格格式(在 x:CellXfs 中)
    • 通用文本
    • 日期:NumberFormatId 22
    • 货币:NumberFormatId 164,引用数字格式
    • 百分比:NumberFormatId 10
有趣的是,许多数字格式都是预定义的。因此,为了使用正确的格式,必须找出 ID。一种方法是创建包含所需格式的 Excel 文件并检查 xlsx 文件的内容来查找信息。

另一件事是,如前所述,样式表元素的顺序至关重要。如果顺序不正确,Excel 会很容易将文档解释为损坏。

样式创建的一个片段是创建 CellStyleFormats 元素和格式内的 CellFormat 元素

Stylesheet stylesheet = spreadsheet.WorkbookPart.WorkbookStylesPart.Stylesheet;

// Cell formats (x:CellXfs)
stylesheet.InsertAt<CellFormats>(new CellFormats(), 5);

// General text
stylesheet.GetFirstChild<CellFormats>().InsertAt<CellFormat>(
   new CellFormat() {
      FormatId = 0,
      NumberFormatId = 0
   }, 0);
以及在 Visual Basic 中
Dim stylesheet As Stylesheet = spreadsheet.WorkbookPart.WorkbookStylesPart.Stylesheet

' Cell formats (x:CellXfs)
stylesheet.InsertAt(Of CellFormats)(New CellFormats(), 5)

' General text
stylesheet.GetFirstChild(Of CellFormats)().InsertAt(Of CellFormat)(
   New CellFormat() With {
      .FormatId = 0,
      .NumberFormatId = 0
   }, 0) 

使用预定义的样式表

定义样式的另一种方法是使用包含样式定义的现有 XML 文件。在项目中,有一个 PredefinedStyles.xml 文件包含在构建输出中。此文件的内容被读取到字符串中,然后添加到之前创建的空样式表中。

样式定义如下(它实际上与使用代码创建的相同):

<x:numFmts>
  <x:numFmt numFmtId="164" formatCode="#,##0.00\ &quot;€&quot;" />
</x:numFmts>
<x:fonts>
  <x:font>
    <x:sz val="11" />
    <x:name val="Calibri" />
  </x:font>
</x:fonts>
<x:fills>
  <x:fill>
    <x:patternFill patternType="none" />
  </x:fill>
</x:fills>
<x:borders>
  <x:border>
    <x:left />
    <x:right />
    <x:top />
    <x:bottom />
    <x:diagonal />
  </x:border>
</x:borders>
<x:cellStyleXfs>
  <x:xf numFmtId="0" fontId="0" fillId="0" borderId="0" />
</x:cellStyleXfs>
<x:cellXfs>
  <x:xf numFmtId="0" xfId="0" />
  <x:xf numFmtId="22" fontId="0" fillId="0" borderId="0" xfId="0" applyNumberFormat="1" />
  <x:xf numFmtId="164" fontId="0" fillId="0" borderId="0" xfId="0" applyNumberFormat="1" />
  <x:xf numFmtId="10" fontId="0" fillId="0" borderId="0" xfId="0" applyNumberFormat="1" />
</x:cellXfs> 

它在 C# 中使用以下方法添加:

      /// <summary>
      /// Adds a predefined style from the given xml
      /// </summary>
      /// <param name="spreadsheet">Spreadsheet to use</param>
      /// <param name="xml">Style definition as xml</param>
      /// <returns>True if succesful</returns>
      public static bool AddPredefinedStyles(SpreadsheetDocument spreadsheet, string xml) {
         spreadsheet.WorkbookPart.WorkbookStylesPart.Stylesheet.InnerXml = xml;
         spreadsheet.WorkbookPart.WorkbookStylesPart.Stylesheet.Save();

         return true;
      } 

以及 Visual Basic 中的函数

    ''' <summary>
    ''' Adds a predefined style from the given xml
    ''' </summary>
    ''' <param name="spreadsheet">Spreadsheet to use</param>
    ''' <param name="xml">Style definition as xml</param>
    ''' <returns>True if succesful</returns>
    Public Shared Function AddPredefinedStyles(spreadsheet As SpreadsheetDocument, xml As String) As Boolean
        spreadsheet.WorkbookPart.WorkbookStylesPart.Stylesheet.InnerXml = xml
        spreadsheet.WorkbookPart.WorkbookStylesPart.Stylesheet.Save()

        Return True
    End Function 

事实上,基本上任何元素都可以通过简单地添加 XML 来填充。例如,工作表可以通过添加适当的 XML 作为工作表的内部文本来填充。

添加工作表

下一步是添加工作表。添加工作表很简单。但是,需要记住的一个陷阱是定义与工作簿的关系。否则,当工作簿打开时,工作表将不包含在内。

工作簿的创建在 C# 中如下所示:

/// <summary>
/// Adds a new worksheet to the workbook
/// </summary>
/// <param name="spreadsheet">Spreadsheet to use</param>
/// <param name="name">Name of the worksheet</param>
/// <returns>True if succesful</returns>
public static bool AddWorksheet(SpreadsheetDocument spreadsheet, string name) {
  Sheets sheets = spreadsheet.WorkbookPart.Workbook.GetFirstChild<Sheets>();
  Sheet sheet;
  WorksheetPart worksheetPart;

  // Add the worksheetpart
  worksheetPart = spreadsheet.WorkbookPart.AddNewPart<WorksheetPart>();
  worksheetPart.Worksheet = new Worksheet(new SheetData());
  worksheetPart.Worksheet.Save();

  // Add the sheet and make relation to workbook
  sheet = new DocumentFormat.OpenXml.Spreadsheet.Sheet() {
     Id = spreadsheet.WorkbookPart.GetIdOfPart(worksheetPart),
     SheetId = (uint)(spreadsheet.WorkbookPart.Workbook.Sheets.Count() + 1),
     Name = name
  };
  sheets.Append(sheet);
  spreadsheet.WorkbookPart.Workbook.Save();

  return true;
}   

以及 Visual Basic 中的等效函数

''' <summary>
''' Adds a new worksheet to the workbook
''' </summary>
''' <param name="spreadsheet">Spreadsheet to use</param>
''' <param name="name">Name of the worksheet</param>
''' <returns>True if succesful</returns>
Public Shared Function AddWorksheet(spreadsheet As SpreadsheetDocument, name As String) As Boolean
    Dim sheets As Sheets = spreadsheet.WorkbookPart.Workbook.GetFirstChild(Of Sheets)()
    Dim sheet As Sheet
    Dim worksheetPart As WorksheetPart

    ' Add the worksheetpart
    worksheetPart = spreadsheet.WorkbookPart.AddNewPart(Of WorksheetPart)()
    worksheetPart.Worksheet = New Worksheet(New SheetData())
    worksheetPart.Worksheet.Save()

    ' Add the sheet and make relation to workbook
    sheet = New Sheet With {
       .Id = spreadsheet.WorkbookPart.GetIdOfPart(worksheetPart),
       .SheetId = (spreadsheet.WorkbookPart.Workbook.Sheets.Count() + 1),
       .Name = name}
    sheets.Append(sheet)
    spreadsheet.WorkbookPart.Workbook.Save()

    Return True
End Function

另一件事是记住正确地给工作表编号。因此,在定义 SheetId 时会查询工作表的数量。

另一个常见的事物:共享字符串

工作簿包含一个共享字符串表。其思想是工作表可以引用共享字符串,而无需在工作表中实际存储字符串值。

尽管这听起来是个好主意,并且是建议的方法,但对文件大小的影响并不是很大。当我用单个(短)字符串重复 10,000 次进行测试时,使用共享字符串与在工作表中重复字符串之间的差异只有几百字节。这很大程度上是因为即使字符串重复,压缩算法也能很好地压缩字符串。当然,工作簿的结构和字符串的位置对此影响很大。

为了将共享字符串添加到表中,我在 C# 中使用以下方法:

/// <summary>
/// Add a single string to shared strings table.
/// Shared string table is created if it doesn't exist.
/// </summary>
/// <param name="spreadsheet">Spreadsheet to use</param>
/// <param name="stringItem">string to add</param>
/// <param name="save">Save the shared string table</param>
/// <returns></returns>
public static bool AddSharedString(SpreadsheetDocument spreadsheet, string stringItem, bool save = true) {
   SharedStringTable sharedStringTable = spreadsheet.WorkbookPart.SharedStringTablePart.SharedStringTable;

   if (0 == sharedStringTable.Where(item => item.InnerText == stringItem).Count()) {
      sharedStringTable.AppendChild(
         new DocumentFormat.OpenXml.Spreadsheet.SharedStringItem(
            new DocumentFormat.OpenXml.Spreadsheet.Text(stringItem)));

      // Save the changes
      if (save) {
         sharedStringTable.Save();
      }
   }

   return true;
}  

在 Visual Basic 中

''' <summary>
''' Add a single string to shared strings table.
''' Shared string table is created if it doesn't exist.
''' </summary>
''' <param name="spreadsheet">Spreadsheet to use</param>
''' <param name="stringItem">string to add</param>
''' <param name="save">Save the shared string table</param>
''' <returns></returns>
Public Shared Function AddSharedString(spreadsheet As SpreadsheetDocument, stringItem As String, Optional save As Boolean = True) As Boolean
    Dim sharedStringTable As SharedStringTable = spreadsheet.WorkbookPart.SharedStringTablePart.SharedStringTable

    Dim stringQuery = (From item In sharedStringTable
                      Where item.InnerText = stringItem
                      Select item).Count()

    If 0 = stringQuery Then
        sharedStringTable.AppendChild(
           New DocumentFormat.OpenXml.Spreadsheet.SharedStringItem(
              New DocumentFormat.OpenXml.Spreadsheet.Text(stringItem)))

        ' Save the changes
        If save Then
            sharedStringTable.Save()
        End If
    End If

    Return True
End Function  

首先,检查字符串是否存在,如果不存在,则将其添加到表中。

在许多方法中,我有一个可选的 save 参数。这是因为保存更改相当慢,所以当数据例如在循环中添加时,保存会延迟。

现在谈谈数据

此演示将以下数据添加到工作表:

  • 一个共享字符串,3 次
  • 一个小数
  • 一个整数
  • 一个货币值
  • 一个日期
  • 一个百分比
  • 以及一个布尔值

所有数据都通过特定方法添加,但它们都使用一个通用方法来实际将值存储在工作表中。

我不会列出添加单元格值的过程,因为它是一个稍大的方法,但它包含以下操作:

  1. 检查已定义行是否存在。如果未找到,则创建一行
  2. 检查已定义单元格(基于列索引)是否存在。如果未找到,则创建一个单元格
  3. 检查列是否在列集合中定义。如有必要,则添加。实际上这并非强制性,但通过定义列可以设置其宽度。
  4. 最后将值添加到单元格中
几件重要的事情

  • 再次注意顺序。行和单元格在 XML 中必须按正确的顺序排列
  • 不使用日期数据类型。这是因为 Office 2007 不支持日期类型。
  • 单元格中的引用定义为 A1、B3 等。从代码角度来看,这是一种相当繁琐的方式。这就是我同时使用列和行的索引的原因。

列字符串的构造如下

/// <summary>
/// Converts a column number to column name (i.e. A, B, C..., AA, AB...)
/// </summary>
/// <param name="columnIndex">Index of the column</param>
/// <returns>Column name</returns>
public static string ColumnNameFromIndex(uint columnIndex) {
   uint remainder;
   string columnName = "";

   while (columnIndex > 0) {
      remainder = (columnIndex - 1) % 26;
      columnName = System.Convert.ToChar(65 + remainder).ToString() + columnName;
      columnIndex = (uint)((columnIndex - remainder) / 26);
   }

   return columnName;
}  

以及在 VB.Net 中

''' <summary>
''' Converts a column number to column name (i.e. A, B, C..., AA, AB...)
''' </summary>
''' <param name="columnIndex">Index of the column</param>
''' <returns>Column name</returns>
Public Shared Function ColumnNameFromIndex(columnIndex As UInt32) As String
    Dim remainder As UInt32
    Dim columnName As String = ""

    While (columnIndex > 0)
        remainder = (columnIndex - 1) Mod 26
        columnName = System.Convert.ToChar(65 + remainder).ToString() + columnName
        columnIndex = ((columnIndex - remainder) / 26)
    End While

    Return columnName
End Function 

添加字符串或共享字符串

如前所述,共享字符串在工作表中仅使用一个索引来指向共享字符串表中的字符串。令人惊讶的是,我没有找到直接从表中获取字符串索引的机制。相反,我必须构建一个循环来计算索引。

/// <summary>
/// Returns the index of a shared string.
/// </summary>
/// <param name="spreadsheet">Spreadsheet to use</param>
/// <param name="stringItem">String to search for</param>
/// <returns>Index of a shared string. -1 if not found</returns>
public static int IndexOfSharedString(SpreadsheetDocument spreadsheet, string stringItem) {
   SharedStringTable sharedStringTable = spreadsheet.WorkbookPart.SharedStringTablePart.SharedStringTable;
   bool found = false;
   int index = 0;

   foreach (SharedStringItem sharedString in sharedStringTable.Elements<SharedStringItem>()) {
      if (sharedString.InnerText == stringItem) {
         found = true;
         break; ;
      }
      index++;
   }

   return found ? index : -1;
} 

以及在 Visual Basic 中

''' <summary>
''' Returns the index of a shared string.
''' </summary>
''' <param name="spreadsheet">Spreadsheet to use</param>
''' <param name="stringItem">String to search for</param>
''' <returns>Index of a shared string. -1 if not found</returns>
Public Shared Function IndexOfSharedString(spreadsheet As SpreadsheetDocument, stringItem As String) As Int32
    Dim sharedStringTable As SharedStringTable = spreadsheet.WorkbookPart.SharedStringTablePart.SharedStringTable
    Dim found As Boolean = False
    Dim index As Int32 = 0

    For Each sharedString As SharedStringItem In sharedStringTable.Elements(Of SharedStringItem)()
        If sharedString.InnerText = stringItem Then
            found = True
            Exit For
        End If
        index = index + 1
    Next

    If found Then
        Return index
    Else
        Return -1
    End If
End Function 

添加字符串的方法非常简单。它提供了添加共享字符串或普通字符串的选项。

/// <summary>
/// Sets a string value to a cell
/// </summary>
/// <param name="spreadsheet">Spreadsheet to use</param>
/// <param name="worksheet">Worksheet to use</param>
/// <param name="columnIndex">Index of the column</param>
/// <param name="rowIndex">Index of the row</param>
/// <param name="stringValue">String value to set</param>
/// <param name="useSharedString">Use shared strings? If true and the string isn't found in shared strings, it will be added</param>
/// <param name="save">Save the worksheet</param>
/// <returns>True if succesful</returns>
public static bool SetCellValue(SpreadsheetDocument spreadsheet, Worksheet worksheet, uint columnIndex, uint rowIndex, string stringValue, bool useSharedString, bool save = true) {
   string columnValue = stringValue;
   CellValues cellValueType;

   // Add the shared string if necessary
   if (useSharedString) {
      if (Excel.IndexOfSharedString(spreadsheet, stringValue) == -1) {
         Excel.AddSharedString(spreadsheet, stringValue, true);
      }
      columnValue = Excel.IndexOfSharedString(spreadsheet, stringValue).ToString();
      cellValueType = CellValues.SharedString;
   } else {
      cellValueType = CellValues.String;
   }

   return SetCellValue(spreadsheet, worksheet, columnIndex, rowIndex, cellValueType, columnValue, null, save);
} 

以及在 VB.Net 中

''' <summary>
''' Sets a string value to a cell
''' </summary>
''' <param name="spreadsheet">Spreadsheet to use</param>
''' <param name="worksheet">Worksheet to use</param>
''' <param name="columnIndex">Index of the column</param>
''' <param name="rowIndex">Index of the row</param>
''' <param name="stringValue">String value to set</param>
''' <param name="useSharedString">Use shared strings? If true and the string isn't found in shared strings, it will be added</param>
''' <param name="save">Save the worksheet</param>
''' <returns>True if succesful</returns>
Public Shared Function SetStringCellValue(spreadsheet As SpreadsheetDocument, worksheet As Worksheet, columnIndex As UInt32, rowIndex As UInt32, stringValue As String, useSharedString As Boolean, Optional save As Boolean = True) As Boolean
    Dim columnValue As String = stringValue
    Dim cellValueType As CellValues

    ' Add the shared string if necessary
    If (useSharedString) Then
        If (Excel.IndexOfSharedString(spreadsheet, stringValue) = -1) Then
            Excel.AddSharedString(spreadsheet, stringValue, True)
        End If
        columnValue = Excel.IndexOfSharedString(spreadsheet, stringValue).ToString()
        cellValueType = CellValues.SharedString
    Else
        cellValueType = CellValues.String
    End If

    Return SetCellValue(spreadsheet, worksheet, columnIndex, rowIndex, cellValueType, columnValue, Nothing, save)
End Function 

添加数值

添加数值与添加非共享字符串值非常相似。唯一需要注意的是在 xml 内部使用正确的十进制分隔符(.),因此可能需要替换十进制分隔符。另一种方法是将当前线程的区域性设置为 en-US。如果您使用 EN_US_CULTURE 编译符号(项目中默认)编译项目,则线程的 UI 区域性将设置为本地区域性,但线程的 CurrentCulture 将替换为 en-US。这消除了重新格式化小数的需要。但是,请记住,如果您要获取例如 CurrencySymbol 等区域设置,则必须从 CurrentUICulture 中获取。

代码如下所示:

/// <summary>
/// Sets a cell value with double number
/// </summary>
/// <param name="spreadsheet">Spreadsheet to use</param>
/// <param name="worksheet">Worksheet to use</param>
/// <param name="columnIndex">Index of the column</param>
/// <param name="rowIndex">Index of the row</param>
/// <param name="doubleValue">Double value</param>
/// <param name="styleIndex">Style to use</param>
/// <param name="save">Save the worksheet</param>
/// <returns>True if succesful</returns>
public static bool SetCellValue(SpreadsheetDocument spreadsheet, Worksheet worksheet, uint columnIndex, uint rowIndex, double doubleValue, uint? styleIndex, bool save = true) {
#if EN_US_CULTURE
   string columnValue = doubleValue.ToString();
#else
   string columnValue = doubleValue.ToString().Replace(System.Globalization.CultureInfo.CurrentUICulture.NumberFormat.NumberDecimalSeparator, ".");
#endif

   return SetCellValue(spreadsheet, worksheet, columnIndex, rowIndex, CellValues.Number, columnValue, styleIndex, save);
}

Visual Basic 版本是:

''' <summary>
''' Sets a cell value with double number
''' </summary>
''' <param name="spreadsheet">Spreadsheet to use</param>
''' <param name="worksheet">Worksheet to use</param>
''' <param name="columnIndex">Index of the column</param>
''' <param name="rowIndex">Index of the row</param>
''' <param name="doubleValue">Double value</param>
''' <param name="styleIndex">Style to use</param>
''' <param name="save">Save the worksheet</param>
''' <returns>True if succesful</returns>
Public Shared Function SetDoubleCellValue(spreadsheet As SpreadsheetDocument, worksheet As Worksheet, columnIndex As UInt32, rowIndex As UInt32, doubleValue As Double, styleIndex As UInt32?, Optional save As Boolean = True) As Boolean
#If EN_US_CULTURE Then
    Dim columnValue As String = doubleValue.ToString()
#Else
    Dim columnValue As String = doubleValue.ToString().Replace(System.Globalization.CultureInfo.CurrentUICulture.NumberFormat.NumberDecimalSeparator, ".")
#End If

    Return SetCellValue(spreadsheet, worksheet, columnIndex, rowIndex, CellValues.Number, columnValue, styleIndex, save)
End Function

添加日期值

日期值有点棘手。关于日期需要记住的事项:

  • 需要指定样式。我使用了预定义的格式 ID 22 来正确格式化日期
  • 在样式定义中,ApplyNumberFormat 必须为 true。
  • 日期表示为双精度值
  • Excel 从 1900 年 1 月 1 日开始计算日期。这与 .Net 不同,因此必须使用 OLE 自动化日期
  • 可能需要更正小数分隔符(取决于区域设置)。例如,我的小数分隔符是逗号,所以必须用点号替换。或者如前面数字所述,可以使用 en-US 区域性。
添加日期值如下所示:

/// <summary>
/// Sets a cell value with a date
/// </summary>
/// <param name="spreadsheet">Spreadsheet to use</param>
/// <param name="worksheet">Worksheet to use</param>
/// <param name="columnIndex">Index of the column</param>
/// <param name="rowIndex">Index of the row</param>
/// <param name="datetimeValue">DateTime value</param>
/// <param name="styleIndex">Style to use</param>
/// <param name="save">Save the worksheet</param>
/// <returns>True if succesful</returns>
public static bool SetCellValue(SpreadsheetDocument spreadsheet, Worksheet worksheet, uint columnIndex, uint rowIndex, System.DateTime datetimeValue, uint? styleIndex, bool save = true) {
#if EN_US_CULTURE
   string columnValue = datetimeValue.ToOADate().ToString();
#else
   string columnValue = datetimeValue.ToOADate().ToString().Replace(System.Globalization.CultureInfo.CurrentUICulture.NumberFormat.NumberDecimalSeparator, ".");
#endif

   return SetCellValue(spreadsheet, worksheet, columnIndex, rowIndex, CellValues.Date, columnValue, styleIndex, save);
} 
以及 Visual Basic 的等效代码

''' <summary>
''' Sets a cell value with a date
''' </summary>
''' <param name="spreadsheet">Spreadsheet to use</param>
''' <param name="worksheet">Worksheet to use</param>
''' <param name="columnIndex">Index of the column</param>
''' <param name="rowIndex">Index of the row</param>
''' <param name="datetimeValue">DateTime value</param>
''' <param name="styleIndex">Style to use</param>
''' <param name="save">Save the worksheet</param>
''' <returns>True if succesful</returns>
Public Shared Function SetDateCellValue(spreadsheet As SpreadsheetDocument, worksheet As Worksheet, columnIndex As UInt32, rowIndex As UInt32, datetimeValue As System.DateTime, styleIndex As UInt32?, Optional save As Boolean = True) As Boolean
#If EN_US_CULTURE Then
    Dim columnValue As String = datetimeValue.ToOADate().ToString()
#Else
    Dim columnValue As String = datetimeValue.ToOADate().ToString().Replace(System.Globalization.CultureInfo.CurrentUICulture.NumberFormat.NumberDecimalSeparator, ".")
#End If

    Return SetCellValue(spreadsheet, worksheet, columnIndex, rowIndex, CellValues.Date, columnValue, styleIndex, save)
End Function 

货币、百分比和布尔值

接下来添加不同值类型的方法与前面非常相似,因此我不会将它们包含在文章中。但是,有几点需要记住:

  • 百分比类似于小数,但格式不同。我使用了格式 ID 10。
  • 货币与百分比非常相似,但这次也需要定义 numFmt
  • Excel 使用 0 和 1 表示布尔值。如果使用 TrueFalse,它们将被解释为文本。在 Visual Basic 中,true 的值 (-1) 需要更改为 1。

最后的话

希望本文能帮助您使用 Open XML Excel 文件。了解代码的最佳方法是调试它并进行小的更改以查看会发生什么 :) 玩得开心!

历史

  • 2012 年 4 月 22 日:创建
  • 2012 年 4 月 25 日:可读性修改,添加了将数字写入 xlsx 时的另一种格式化方式

© . All rights reserved.