如何使用 C# 附加现有 excel 文件

How to append existing excel file using C#

下面是我的代码,我无法在 excel 中附加内容, 第一次加载程序时,excel可以正常输出 但是在第二次加载程序时,excel 会严重崩溃(无法打开)。 我正在使用 FileMode.Append、FileAccess.Write,但仍然无法正常工作 有没有我错过的步骤?请帮我解决这个问题,非常感谢!

            XSSFWorkbook XSSFworkbook = new XSSFWorkbook(); //建立活頁簿
            ISheet sheet = XSSFworkbook.CreateSheet(tbx_Build.Text); //建立sheet
            //設定樣式           
            ICellStyle headerStyle = XSSFworkbook.CreateCellStyle();
            IFont headerfont = XSSFworkbook.CreateFont();
            headerStyle.Alignment = HorizontalAlignment.Center; //水平置中
            headerStyle.VerticalAlignment = VerticalAlignment.Center; //垂直置中
            headerfont.FontName = "Segoe UI";
            headerfont.FontHeightInPoints = 12;
            headerfont.Boldweight = (short)FontBoldWeight.Bold;
            headerStyle.SetFont(headerfont);
            XSSFCellStyle cs = (XSSFCellStyle)XSSFworkbook.CreateCellStyle();
            cs.WrapText = true; // 設定換行
            cs.VerticalAlignment = NPOI.SS.UserModel.VerticalAlignment.Top;
            //新增標題列
            IRow headerrow = sheet.CreateRow(0);//建立行
            headerrow.HeightInPoints = 20;
            headerrow.CreateCell(0).SetCellValue("System_name");
            headerrow.CreateCell(1).SetCellValue("Fixture_name");
            headerrow.CreateCell(2).SetCellValue("build_ID");
            headerrow.CreateCell(3).SetCellValue("start_time");
            headerrow.CreateCell(4).SetCellValue("end_time");
            headerrow.CreateCell(5).SetCellValue("serial_number");
            headerrow.CreateCell(6).SetCellValue("Status");
            headerrow.CreateCell(7).SetCellValue("Symptom_label");
            headerrow.CreateCell(8).SetCellValue("Repair");
            headerrow.CreateCell(9).SetCellValue("Measurement");
            headerrow.CreateCell(10).SetCellValue("Board_slot");
            headerrow.CreateCell(11).SetCellValue("Error");
            headerrow.CreateCell(12).SetCellValue("Version");
            for (int i = 0; i < 13; i++)
            {
                headerrow.GetCell(i).CellStyle = headerStyle; //套用樣式
            }
            //填入資料
            int rowIndex = 1;
            for (int i = 0; i < dt.Rows.Count; i++)
            {
                IRow row = sheet.CreateRow(rowIndex);//建立行
                row.HeightInPoints = 18;
                row.CreateCell(0).SetCellValue(Convert.ToString(dt.Rows[i]["System_name"]));
                row.CreateCell(1).SetCellValue(Convert.ToString(dt.Rows[i]["Fixture_name"]));
                row.CreateCell(2).SetCellValue(Convert.ToString(dt.Rows[i]["build_ID"]));
                row.CreateCell(3).SetCellValue(Convert.ToString(dt.Rows[i]["start_time"]));
                row.CreateCell(4).SetCellValue(Convert.ToString(dt.Rows[i]["end_time"]));
                row.CreateCell(5).SetCellValue(Convert.ToString(dt.Rows[i]["serial_number"]));
                row.CreateCell(6).SetCellValue(Convert.ToString(dt.Rows[i]["Status"]));
                row.CreateCell(7).SetCellValue(Convert.ToString(dt.Rows[i]["Symptom_label"]));
                row.CreateCell(8).SetCellValue(Convert.ToString(dt.Rows[i]["Repair"]));
                row.CreateCell(9).SetCellValue(Convert.ToString(dt.Rows[i]["Measurement"]));
                row.CreateCell(10).SetCellValue(Convert.ToString(dt.Rows[i]["Board_slot"]));
                row.CreateCell(11).SetCellValue(Convert.ToString(dt.Rows[i]["Error"]));
                row.CreateCell(12).SetCellValue(Convert.ToString(dt.Rows[i]["Version"]));
                if (dt.Rows[i]["Error"].ToString().Contains("\n"))
                {
                    row.GetCell(12).CellStyle = cs;
                    row.HeightInPoints = 45;
                }
                else if (dt.Rows[i]["Repair"].ToString().Contains("\n"))
                {
                    row.GetCell(12).CellStyle = cs;
                    row.HeightInPoints = 45;
                }
                sheet.AutoSizeColumn(i); //欄位自動調整大小
                rowIndex++;
            }

            string newName2 = "Yield.xlsx";
            string exportpath2 = server_backup_failed + "\output";

            if (!System.IO.Directory.Exists(exportpath2))
            {
                System.IO.Directory.CreateDirectory(exportpath2);//不存在就建立目錄 
            }
                var file2 = new FileStream(exportpath2 + "\" + newName2, FileMode.Append, FileAccess.Write);
                XSSFworkbook.Write(file2, true);
                file2.Close();
                XSSFworkbook.Close();
        }

如前所述……我可以从一些测试中确认……

FileMode.Append

不会像您预期的那样工作。这似乎有些明显,因为一个工作簿可以有多个工作表,而您所描述的是将数据附加到现有工作表中……FileStream 将不知道如何执行此操作。

因此,如前所述,您需要打开文件,进行更改(追加行),然后保存并关闭文件。在我的测试中,这可以使用您的大部分代码进行一些小的更改。在您当前的代码中,它总是创建一个“新的”Excel 工作簿和工作表。因此,如果您想将某些内容“附加”到工作表,则这“意味着”工作簿和工作表可能已经存在。所以,如果工作簿已经存在,那么我们要打开它而不是创建一个新的。

因此,我建议采用一个小方法,该方法采用 string 文件路径和文件名以及 return 工作簿。如果工作簿存在,那么我们只需 return 工作簿。如果工作簿不存在,那么我们 return 一个“新”工作簿。如果路径错误或任何其他故障......我们将 return 一个 null 值。像……

private XSSFWorkbook GetExcelWorkbook(string filePath) {
  if (!File.Exists(filePath)) {
    return new XSSFWorkbook();
  }
  try {
    using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
      return new XSSFWorkbook(fs);
    }
  }
  catch (Exception ex) {
    Debug.WriteLine("Excel Error getting workbook: " + ex.Message);
    return null;
  }
}

这应该有助于获取工作簿。接下来,代码似乎正在寻找“特定”工作表。与上面的代码类似,一个简单的方法需要一个工作簿和一个工作表名称以及 returns 一个具有给定名称的工作表可能会派上用场。同样,如果工作表已经存在,那么我们只需 return 该工作表。如果工作表不存在,那么我们将创建一个具有给定名称的新工作表。此外,如果工作表是新的,我们将继续添加 headers 行。这也是另一种方法,因此您也可以在每次附加新数据时添加 header 行。但是,在此示例中,仅在创建新工作表时才添加 header,并假定 header 行已存在于现有文件中。

这个方法可能看起来像……

private ISheet GetWorksheet(XSSFWorkbook wb, string targetSheetName) {
  try {
    for (int i = 0; i < wb.NumberOfSheets; i++) {
      if (wb.GetSheetAt(i).SheetName.Equals(targetSheetName)) {
        return wb.GetSheetAt(i);
      }
    }
    ISheet ws = wb.CreateSheet(targetSheetName);
    AddHeaders(wb, ws);
    return ws;
  }
  catch (Exception e) {
    Debug.WriteLine("Excel Error getting worksheet: " + e.Message);
    return null;
  }
}

上面代码的演练很简单……循环遍历给定的工作簿工作表以查找目标工作表名称。如果找到工作表,它将被 returned。如果未找到工作表,则使用目标名称创建一个新工作表,将 header 添加到新工作表,然后将 return 添加到新工作表。如果出现错误,null 值是 returned。

接下来是将 header 添加到工作表的方法,几乎​​直接从您的代码中获取。

private void AddHeaders(XSSFWorkbook wb, ISheet sheet) {
  IRow headerrow = sheet.CreateRow(0);
  headerrow.HeightInPoints = 20;
  headerrow.CreateCell(0).SetCellValue("System_name");
  headerrow.CreateCell(1).SetCellValue("Fixture_name");
  headerrow.CreateCell(2).SetCellValue("build_ID");
  headerrow.CreateCell(3).SetCellValue("start_time");
  headerrow.CreateCell(4).SetCellValue("end_time");
  headerrow.CreateCell(5).SetCellValue("serial_number");
  headerrow.CreateCell(6).SetCellValue("Status");
  headerrow.CreateCell(7).SetCellValue("Symptom_label");
  headerrow.CreateCell(8).SetCellValue("Repair");
  headerrow.CreateCell(9).SetCellValue("Measurement");
  headerrow.CreateCell(10).SetCellValue("Board_slot");
  headerrow.CreateCell(11).SetCellValue("Error");
  headerrow.CreateCell(12).SetCellValue("Version");
  ICellStyle headerStyle = wb.CreateCellStyle();
  IFont headerfont = wb.CreateFont();
  headerStyle.Alignment = HorizontalAlignment.Center;
  headerStyle.VerticalAlignment = VerticalAlignment.Center;
  headerfont.FontName = "Segoe UI";
  headerfont.FontHeightInPoints = 12;
  headerfont.Boldweight = (short)FontBoldWeight.Bold;
  headerStyle.SetFont(headerfont);
  for (int i = 0; i < 13; i++) {
    headerrow.GetCell(i).CellStyle = headerStyle;
  }
}

这些方法应该可以简化将数据“附加”到现有工作表或创建新工作表的过程。因此,使用上述方法,下面的代码将按此处所述工作……此代码被添加到按钮点击事件中。最初,为将附加到工作表的 DataTable dt 创建了一些测试数据。接下来,我们检查给定的文件夹路径是否存在,如果不存在,则创建它。

接下来我们使用上面的方法获取工作簿和工作表。工作表名称与您在当前代码中的名称相同,来自表单上的 TextBox ...tbx_Build

接下来,我们不将 rowIndex 设置为 1,而是将其设置为工作表中现有数据的最后一行,以便我们可以“追加”数据。具体来说……

int rowIndex = ws.LastRowNum + 1;

接下来创建单元格样式并循环遍历 DataTables dt 行,其中每个单元格都添加到工作表中。请注意,我删除了不必要的“转换”代码并使用了单元格 ToString() 方法。最后出于某种原因将样式添加到某些单元格。

最后,创建 FilesStream 以保存文件并可能在文件不是新文件时覆盖它。

您可能会注意到,工作簿 wbtry/catch/finally 语句的 finally 部分关闭,原因是如果代码在工作簿之后的某个时间失败是打开的,那么它可能不会被关闭。这确保工作簿正确关闭。

private void button1_Click(object sender, EventArgs e) {
  string workbookName = "__NewBook_1.xlsx";
  string saveFilePath = @"D:\Test\Excel_Test";
  DataTable dt = GetTable();
  FillTable(dt);
  XSSFWorkbook wb = null;
  try {
    if (!Directory.Exists(saveFilePath)) {
      Directory.CreateDirectory(saveFilePath);
    }
    wb = GetExcelWorkbook(saveFilePath + @"\" + workbookName);
    if (wb != null) {
      ISheet ws = GetWorksheet(wb, tbx_Build.Text);
      if (ws != null) {
        int rowIndex = ws.LastRowNum + 1;
        ICellStyle cs = wb.CreateCellStyle();
        cs.WrapText = true;
        cs.VerticalAlignment = VerticalAlignment.Top;
        for (int i = 0; i < dt.Rows.Count; i++) {
          IRow row = ws.CreateRow(rowIndex);
          row.HeightInPoints = 18;
          row.CreateCell(0).SetCellValue(dt.Rows[i]["System_name"].ToString());
          row.CreateCell(1).SetCellValue(dt.Rows[i]["Fixture_name"].ToString());
          row.CreateCell(2).SetCellValue(dt.Rows[i]["build_ID"].ToString());
          row.CreateCell(3).SetCellValue(dt.Rows[i]["start_time"].ToString());
          row.CreateCell(4).SetCellValue(dt.Rows[i]["end_time"].ToString());
          row.CreateCell(5).SetCellValue(dt.Rows[i]["serial_number"].ToString());
          row.CreateCell(6).SetCellValue(dt.Rows[i]["Status"].ToString());
          row.CreateCell(7).SetCellValue(dt.Rows[i]["Symptom_label"].ToString());
          row.CreateCell(8).SetCellValue(dt.Rows[i]["Repair"].ToString());
          row.CreateCell(9).SetCellValue(dt.Rows[i]["Measurement"].ToString());
          row.CreateCell(10).SetCellValue(dt.Rows[i]["Board_slot"].ToString());
          row.CreateCell(11).SetCellValue(dt.Rows[i]["Error"].ToString());
          row.CreateCell(12).SetCellValue(dt.Rows[i]["Version"].ToString());
          if (dt.Rows[i]["Error"].ToString().Contains("\n")) {
            row.GetCell(12).CellStyle = cs;
            row.HeightInPoints = 45;
          }
          else if (dt.Rows[i]["Repair"].ToString().Contains("\n")) {
            row.GetCell(12).CellStyle = cs;
            row.HeightInPoints = 45;
          }
          ws.AutoSizeColumn(i);
          rowIndex++;
        }
        using (FileStream fs = new FileStream(saveFilePath + @"\" + workbookName, FileMode.Create, FileAccess.Write)) {
          wb.Write(fs, true);
        }
      }
    }
  }
  catch (Exception ex) {
    Debug.WriteLine("Excel Error: " + ex.Message);
  }
  finally {
    if (wb != null) {
      wb.Close();
    }
  }
}

最后是创建一些测试数据以测试上述方法并完成示例的代码。

using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using HorizontalAlignment = NPOI.SS.UserModel.HorizontalAlignment;


private DataTable GetTable() {
  DataTable dt = new DataTable();
  dt.Columns.Add("System_name");
  dt.Columns.Add("Fixture_name");
  dt.Columns.Add("build_ID");
  dt.Columns.Add("start_time");
  dt.Columns.Add("end_time");
  dt.Columns.Add("serial_number");
  dt.Columns.Add("Status");
  dt.Columns.Add("Symptom_label");
  dt.Columns.Add("Repair");
  dt.Columns.Add("Measurement");
  dt.Columns.Add("Board_slot");
  dt.Columns.Add("Error");
  dt.Columns.Add("Version");
  return dt;
}

private void FillTable(DataTable dt) {
  for (int i = 0; i < 20; i++) {
    dt.Rows.Add("C0R" + (i + 1), "C1R" + (i + 1), "C2R" + (i + 1), "C3R" + (i + 1), "C4R" + (i + 1),
                "C5R" + (i + 1), "C6R" + (i + 1), "C7R" + (i + 1), "C8R" + (i + 1), "C9R" + (i + 1),
                "C10R" + (i + 1), "C11R" + (i + 1), "C12R" + (i + 1));
  }
}

我希望这是有道理的。我对此进行了多次测试,它似乎按预期工作,但是可能仍然存在一些必要的异常检查,但为简洁起见未添加。如果有什么不正确的地方请告诉我,我会尽可能改正。