如何通过映射 Excel sheet 列 header 将数据 table 中的数据插入现有 Excel 传播 sheet?
How do I insert data into an existing Excel spreadsheet from data table by mapping Excel sheet column header?
我有包含列 header 的宏 Excel 电子表格,其中一些列是计算列。
我需要做的是从数据库中获取数据并填充此文件。
我在查询后检索到的数据表没有 Excel 文件中的所有列,因为 Excel 文件有计算列。
所以我需要将列从数据 table 映射到 Excel 文件并加载数据。
我需要确保在加载新数据之前从文件中删除现有数据,因为每周都需要创建该文件。
我从未在 OpenXML Document 和 EPPlus 工作过。
尝试 #1:使用 EPPlus
private static void OtehrMethod(DataTable dataTable, string filePath)
{
// using EPPlus
var package = new ExcelPackage(new FileInfo(filePath));
ExcelWorksheet workSheet = package.Workbook.Worksheets["MySheet"];
foreach (DataRow row in dataTable.Rows)
{
int i = 1;
object cellValue = workSheet.Cells[2, i].Value;
workSheet.Cells[1, 1].Value = Conver.ToInt(row["Id"]);
// break;
//workSheet.Cells[2, i].Value =row["First_Name"].ToString();
//workSheet.Cells[3, i].Value = row["Last_Name"].ToString();
//workSheet.Cells[4, i].Value = row["Job_Title"].ToString();
//workSheet.Cells[5, i].Value = row["Skills"].ToString();
i++;
}
package.Save();
}
尝试 #2:使用打开 XML
private static void SomeMethod()
{
string filePath = ConfigurationManager.AppSettings["ExcelFilePath"];
string workingSheetName = ConfigurationManager.AppSettings["WorkingSheetName"];
using (SpreadsheetDocument document = SpreadsheetDocument.Open(filePath, true))
{
// WorkbookPart workbook = document.WorkbookPart;
WorkbookPart workbookPart = document.WorkbookPart;
Workbook workbook = document.WorkbookPart.Workbook;
int sheetIndex = 0;
foreach (WorksheetPart worksheetpart in workbook.WorkbookPart.WorksheetParts)
{
Worksheet worksheet = worksheetpart.Worksheet;
string sheetName = workbookPart.Workbook.Descendants<Sheet>().ElementAt(sheetIndex).Name;
if (sheetName.ToUpper() == workingSheetName.ToUpper())
{
IEnumerable<Row> rows = worksheet.GetFirstChild<SheetData>().Descendants<Row>();
foreach (Row row in rows)
{
// How do I map Excel sheet column with data table column and insert the values into Excel ?
// Column["FirstName"] = DTRow["FirstName"]
//Column["LastName"] = DTRow["LastName"]
}
}
sheetIndex++;
}
}
// throw new NotImplementedException();
}
首先,EPPlus 是 OpenXML 的包装器。因此,以下概念适用于两者。学习曲线可能很陡峭,所以我将从 Vincent Tan 程序开始供您检查:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Drawing;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml.Drawing.Spreadsheet;
namespace ExcelOpenXmlSetCellValue
{
class Program
{
static void Main(string[] args)
{
string sFile = "ExcelOpenXmlSetCellValue.xlsx";
if (File.Exists(sFile))
{
File.Delete(sFile);
}
try
{
BuildWorkbook(sFile);
Console.WriteLine("Program end");
Console.ReadLine();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
Console.ReadLine();
}
}
private static void BuildWorkbook(string filename)
{
using (SpreadsheetDocument xl = SpreadsheetDocument.Create(filename, SpreadsheetDocumentType.Workbook))
{
WorkbookPart wbp = xl.AddWorkbookPart();
WorksheetPart wsp = wbp.AddNewPart<WorksheetPart>();
Workbook wb = new Workbook();
FileVersion fv = new FileVersion();
fv.ApplicationName = "Microsoft Office Excel";
Worksheet ws = new Worksheet();
SheetData sd = new SheetData();
Row r;
Cell c;
r = new Row();
// For the 2nd row. I know, it's obvious, but it's recommended
// that you assign a value. While Excel might be able to handle
// a Row class without the RowIndex assigned (in a certain case),
// other applications might find that difficult, in particular,
// your own application.
r.RowIndex = 2;
c = new Cell();
// "D" for 4th column. The "2" is equal to the RowIndex of the Row
// that the Cell class is in. Note that the number part has to be
// equal to the RowIndex or disaster will happen. Or worse, Excel
// spits out the "file corrupted" error and your user sees it.
c.CellReference = "D2";
// by default, an unassigned DataType means CellValues.Number
//c.DataType = CellValues.Number;
c.CellValue = new CellValue("9.81");
r.Append(c);
sd.Append(r);
// What if you need more Cell's in a Row? Blank lines added for readability.
r = new Row();
r.RowIndex = 5;
c = new Cell();
// this way, the number part is always right, but you need to
// assign the RowIndex first. This will make sense when you have
// to append many Cell classes. This is just an alternative to
// assigning the cell reference.
c.CellReference = "F" + r.RowIndex;
// There are 2 other string types, the SharedString and InlineString.
// They are discussed in another chapter. The CellValues.String type
// provides the most straightforward way of including text strings.
c.DataType = CellValues.String;
c.CellValue = new CellValue("Is");
r.Append(c);
c = new Cell();
c.CellReference = "G" + r.RowIndex;
c.DataType = CellValues.String;
c.CellValue = new CellValue("that in");
r.Append(c);
c = new Cell();
c.CellReference = "H" + r.RowIndex;
c.DataType = CellValues.String;
c.CellValue = new CellValue("metres per second squared?");
r.Append(c);
// This is for row 5
sd.Append(r);
// And now, to show you sometimes the code can be rearranged a little.
// But because you need to append the Cell class to the Row class,
// it makes sense (logically and conceptually) to initialise a new Row first.
c = new Cell();
c.CellReference = "F7";
c.DataType = CellValues.String;
c.CellValue = new CellValue("It'd better be. Just got used to the metric system.");
r = new Row();
r.RowIndex = 7;
r.Append(c);
sd.Append(r);
ws.Append(sd);
wsp.Worksheet = ws;
wsp.Worksheet.Save();
Sheets sheets = new Sheets();
Sheet sheet = new Sheet();
sheet.Name = "Sheet1";
sheet.SheetId = 1;
sheet.Id = wbp.GetIdOfPart(wsp);
sheets.Append(sheet);
wb.Append(fv);
wb.Append(sheets);
xl.WorkbookPart.Workbook = wb;
xl.WorkbookPart.Workbook.Save();
xl.Close();
}
}
}
}
那么他书中的摘录可能在概念上有所帮助:
So the SheetData class contains Row classes as children. Each Row class contains Cell classes as children. A Row class represents a row
in the spreadsheet. A Cell class represents a cell in the spreadsheet.
Sometimes, obvious things still need to be said.
It is recommended that you assign a value for the RowIndex property of
the Row class. This is basically the row number for that Row class. If
it's the 4th row, assign 4. The RowIndex property is optional under
Open XML specifications, but spreadsheet applications might not have a
way to deal with this. Even Excel handles a blank RowIndex property
correctly under special conditions (which we'll talk about later in
this chapter).
As for the Cell class, you need to take note of these properties:
- DataType - The data type of the cell value. You will most probably deal with CellValues.Number and CellValues.String.
- CellReference - The cell reference in "A1 format". That means the column letter followed by the row index. For example, "C5" means the
3rd column, 5th row. There's the "R1C1 format" ("R" then row index,
then "C" then column index, giving us "R5C3" for the previous
example), but we're not covering that. "A1 format" is the default in
Excel.
- CellValue - Where the action happens. You might notice that CellValue is actually a class, and its main value is a string. This
means you store number values in string form too (the DataType
property determines the, well, data type).
- CellFormula - Where you have a cell formula. But you can read about it in another chapter. The above 3 properties are the usual ones to
handle.
One more thing. The Row classes must be appended to the SheetData
class in ascending RowIndex order. The Cell classes must be appended
to its Row class in ascending column order. This is not negotiable.
Excel will choke on its error vomit otherwise.
希望这足以让您走上正轨。如果没有,我会回来查看更多问题。
在回答您的其他问题时,对于计算列,在右侧为您为计算列构建的单元格构建计算。
清晰多了。你所在的区域我没有亲自编码,但每个 sheet 的 SheetData 将是清除数据的地方。下面是 Vincent Tan 的另一个程序,展示了如何在现有 sheet 中添加和更改数据:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Drawing;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml.Drawing.Spreadsheet;
namespace ExcelOpenXmlFromTemplate
{
class Program
{
static void Main(string[] args)
{
// Switch between an Excel file with the styles and stuff
// and an actual Excel template file.
// I suggest you check out the accompanying template files first
// to compare how the original and final files will look like.
//string sFileTemplate = "FinancialYearReport.xlsx";
string sFileTemplate = "FinancialYearReportTemplate.xltx";
string sFile = "FinancialYearReportFinal.xlsx";
if (File.Exists(sFile))
{
File.Delete(sFile);
}
if (sFileTemplate.ToLower().EndsWith(".xlsx"))
{
File.Copy(sFileTemplate, sFile);
}
else
{
// ends with .xltx
// Excel template files work differently. You can't work on it
// directly and then save the result to a different file.
// More details on this in the accompanying PDF.
byte[] ba = File.ReadAllBytes(sFileTemplate);
using (MemoryStream ms = new MemoryStream())
{
ms.Write(ba, 0, ba.Length);
using (SpreadsheetDocument xl = SpreadsheetDocument.Open(ms, true))
{
xl.ChangeDocumentType(SpreadsheetDocumentType.Workbook);
xl.Close();
}
File.WriteAllBytes(sFile, ms.ToArray());
}
}
try
{
BuildWorkbookFromTemplate(sFile);
Console.WriteLine("Program end");
Console.ReadLine();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
Console.ReadLine();
}
}
private static void BuildWorkbookFromTemplate(string filename)
{
string sSheetname = "FYReport";
// take note that we're opening a file, and not creating a file as before
using (SpreadsheetDocument xl = SpreadsheetDocument.Open(filename, true))
{
WorkbookPart wbp = xl.WorkbookPart;
// Get the worksheet with the required name.
// To be used to match the ID for the required sheet data
// because the Sheet class and the SheetData class aren't
// linked to each other directly.
Sheet s = null;
if (wbp.Workbook.Sheets.Elements<Sheet>().Count(nm => nm.Name == sSheetname) == 0)
{
// no such sheet with that name
xl.Close();
return;
}
else
{
s = (Sheet)wbp.Workbook.Sheets.Elements<Sheet>().Where(nm => nm.Name == sSheetname).First();
}
WorksheetPart wsp = (WorksheetPart)xl.WorkbookPart.GetPartById(s.Id.Value);
SheetData sd = (SheetData)wsp.Worksheet.GetFirstChild<SheetData>();
// We will update in 2 ways.
// 1) Get the required cell, do the updates on it, then save the worksheet.
// 2) Create a Cell with our data, then throw it to the SheetData, then save the worksheet.
// If the required cell already exists, we overwrite it. If not, we create it.
// The difference between the 2 is that, if your worksheet doesn't have the required cell,
// it becomes more difficult, because you'll have to also append it to the row.
// More details explained in the accompanying PDF.
// I left the saving of the worksheet out of the UpdateCell() function
// because I want to be consistent with the GetCell() version of updating.
// So wsp.Worksheet.Save(); is done after every cell modification.
// Otherwise I'd put it at the end of the update function.
// The code is written for you to understand what's going on, not for optimisation.
int iPreviousYear = DateTime.Now.Year - 1;
int iTheYearBeforeThat = iPreviousYear - 1;
Cell c = null;
// main title
c = GetCell(sd, "A", 1);
if (c != null)
{
if (c.DataType == CellValues.SharedString)
{
c.DataType = CellValues.String;
}
c.CellValue = new CellValue(string.Format("Financial Year Report For {0}/{1}", iTheYearBeforeThat, iPreviousYear));
// I was going to show you how to work with an existing value like so:
// c.CellValue = new CellValue(string.Format("{0} {1}/{2}", c.CellValue.Text.Trim(), iTheYearBeforeThat, iPreviousYear));
// But the existing value is a string, and Excel had kindly saved it into the SharedString table
// when I created the template file.
// The shared string concept makes things a little more interesting if you really want to work with it.
// For now, it's what-you-see-is-what-you-get, which is much simpler.
wsp.Worksheet.Save();
}
// description of the year before the last
c = GetCell(sd, "A", 4);
if (c != null)
{
if (c.DataType == CellValues.SharedString)
{
c.DataType = CellValues.String;
}
c.CellValue = new CellValue(string.Format("Year {0}", iTheYearBeforeThat));
wsp.Worksheet.Save();
}
// description of last year
// I'll show you the difference in code for the 2nd method of updating
c = new Cell();
c.DataType = CellValues.String;
c.CellReference = "A13";
c.CellValue = new CellValue(string.Format("Year {0}", iPreviousYear));
UpdateCell(sd, c, 13);
wsp.Worksheet.Save();
// We will update the revenue and operational costs of "the year before the last" FY.
// You would probably retrieve the values from a database and fill it here.
// I don't have any meaningful data anyway, so I'm gonna cheat with a randomiser...
// I'll use the 2nd method of updating for convenience.
Random rd = new Random();
int i;
for (i = 5; i <= 8; ++i)
{
// revenue values
c = new Cell();
c.DataType = CellValues.Number;
c.CellReference = "B" + i.ToString();
// range from 8.0 to 12.0
c.CellValue = new CellValue((rd.NextDouble() * 4.0 + 8.0).ToString("f2"));
UpdateCell(sd, c, (uint)i);
wsp.Worksheet.Save();
// operational cost values
c = new Cell();
c.DataType = CellValues.Number;
c.CellReference = "C" + i.ToString();
// range from 0.5 to 1.5
c.CellValue = new CellValue((rd.NextDouble() + 0.5).ToString("f2"));
UpdateCell(sd, c, (uint)i);
wsp.Worksheet.Save();
}
// Previous financial year's revenue and operational cost values
for (i = 14; i <= 17; ++i)
{
// revenue values
c = new Cell();
c.DataType = CellValues.Number;
c.CellReference = "B" + i.ToString();
// range from 9.0 to 14.0
// We've got to have increased revenue, right? :)
c.CellValue = new CellValue((rd.NextDouble() * 5.0 + 9.0).ToString("f2"));
UpdateCell(sd, c, (uint)i);
wsp.Worksheet.Save();
// operational cost values
c = new Cell();
c.DataType = CellValues.Number;
c.CellReference = "C" + i.ToString();
// range from 0.4 to 1.4
// We've got to have decreased costs, right? :)
c.CellValue = new CellValue((rd.NextDouble() + 0.4).ToString("f2"));
UpdateCell(sd, c, (uint)i);
wsp.Worksheet.Save();
}
// this section is to show you that UpdateCell() works for
// out of order cells in the same row. Go ahead and mix up
// the order of the 5 cells (A25, B25, C25, D25 and E25) in
// the code and check that they are still ordered correctly in the file.
c = new Cell();
c.DataType = CellValues.String;
c.CellReference = "B25";
c.CellValue = new CellValue("2nd");
UpdateCell(sd, c, 25u);
wsp.Worksheet.Save();
c = new Cell();
c.DataType = CellValues.String;
c.CellReference = "E25";
c.CellValue = new CellValue("5th");
UpdateCell(sd, c, 25u);
wsp.Worksheet.Save();
c = new Cell();
c.DataType = CellValues.String;
c.CellReference = "C25";
c.CellValue = new CellValue("3rd");
UpdateCell(sd, c, 25u);
wsp.Worksheet.Save();
c = new Cell();
c.DataType = CellValues.String;
c.CellReference = "A25";
c.CellValue = new CellValue("1st");
UpdateCell(sd, c, 25u);
wsp.Worksheet.Save();
c = new Cell();
c.DataType = CellValues.String;
c.CellReference = "D25";
c.CellValue = new CellValue("4th");
UpdateCell(sd, c, 25u);
wsp.Worksheet.Save();
// Credits!
c = new Cell();
c.DataType = CellValues.String;
c.CellReference = "A27";
// well, we wouldn't want upper management to think this
// financial report was *easy* to generate, right? Claim credit!
c.CellValue = new CellValue("Generated by Vincent");
UpdateCell(sd, c, 27u);
wsp.Worksheet.Save();
c = new Cell();
c.DataType = CellValues.String;
c.CellReference = "E27";
// you could show upper management that you're working hard,
// and print a time that's in the wee hours of the morning...
// But shame on you...
c.CellValue = new CellValue(string.Format("Generated at {0}", DateTime.Now.ToString("dd MMM yyyy HH:mm:ss")));
UpdateCell(sd, c, 27u);
wsp.Worksheet.Save();
// we have formulae and charts to update in the template
xl.WorkbookPart.Workbook.CalculationProperties.ForceFullCalculation = true;
xl.WorkbookPart.Workbook.CalculationProperties.FullCalculationOnLoad = true;
xl.WorkbookPart.Workbook.Save();
xl.Close();
}
}
private static Cell GetCell(SheetData sd, string ColumnName, UInt32 RowIndex)
{
// There's a small chance that the row has no RowIndex assigned.
// You should know that the property RowIndex is optional.
// This means the following will fail.
// But we can't do much about a missing RowIndex in the template.
Row r = null;
Cell c = null;
if (sd.Elements<Row>().Count(ri => ri.RowIndex == RowIndex) > 0)
{
r = (Row)sd.Elements<Row>().Where(ri => ri.RowIndex == RowIndex).First();
}
if (r != null)
{
string sCellReference = ColumnName.ToUpper() + RowIndex.ToString();
if (r.Elements<Cell>().Count(cr => cr.CellReference.Value == sCellReference) > 0)
{
c = (Cell)r.Elements<Cell>().Where(cr => cr.CellReference.Value == sCellReference).First();
}
}
return c;
}
private static void UpdateCell(SheetData sd, Cell CellData, UInt32 RowIndex)
{
// There's a small chance that the row has no RowIndex assigned.
// You should know that the property RowIndex is optional.
// This means the following will fail.
// But we can't do much about a missing RowIndex in the template.
Row r = null;
if (sd.Elements<Row>().Count(ri => ri.RowIndex == RowIndex) == 0)
{
// There's no row at this index, so it also means there's no cell
// with the data required. So just create the row with the cell.
r = new Row();
r.RowIndex = RowIndex;
r.Append(CellData);
sd.Append(r);
}
else
{
r = (Row)sd.Elements<Row>().Where(ri => ri.RowIndex == RowIndex).First();
// we will assume the cell's CellReference is consistent with the given RowIndex
if (r.Elements<Cell>().Count(cr => cr.CellReference.Value == CellData.CellReference.Value) > 0)
{
// there's an existing cell
Cell c = (Cell)r.Elements<Cell>().Where(cr => cr.CellReference.Value == CellData.CellReference.Value).First();
c.DataType = CellData.DataType;
// reset to "normal" string value if it's a shared string.
// Otherwise, we'll have to go change the value in the SharedStringTable class,
// and that's out of scope for now. Let's focus on updating cells, shall we?
// We are also assuming we're not stupid enough to set the given cell's
// data type to be SharedString...
if (c.DataType == CellValues.SharedString)
{
c.DataType = CellValues.String;
}
c.CellValue = new CellValue(CellData.CellValue.Text);
// set any other additional properties you want
}
else
{
// no such cell
IEnumerable<Cell> iec = r.Elements<Cell>();
// in case there are no cells in the row
if (iec.Count() == 0)
{
r.Append(CellData);
}
else
{
bool bFound = false;
// cells need to be in order of their CellReference values
foreach (Cell c in iec)
{
// We assume the existing Cells are already in order.
// This means the moment our CellData's CellReference is before
// a particular existing Cell's CellReference, we can insert before
// that particular existing Cell.
if (string.Compare(CellData.CellReference.Value, c.CellReference.Value) < 0)
{
bFound = true;
r.InsertBefore(CellData, c);
break;
}
}
// well, we have to insert our CellData *somewhere*.
// If not found in the loop above, then it must have the highest CellReference.
// So we just append it at the end of the row.
if (!bFound)
{
r.Append(CellData);
}
}
// we don't append the row to the SheetData variable because
// the row already exists. We just need to append the cell.
}
}
}
}
}
Vincent 的示例使用设置为模板的工作簿,不涉及清除单元格,但他确实展示了如何覆盖单元格。在我看来,他似乎涵盖了很多可能出错的地方。我会先尝试覆盖任何内容的单元格,看看是否可行。
我有包含列 header 的宏 Excel 电子表格,其中一些列是计算列。
我需要做的是从数据库中获取数据并填充此文件。
我在查询后检索到的数据表没有 Excel 文件中的所有列,因为 Excel 文件有计算列。
所以我需要将列从数据 table 映射到 Excel 文件并加载数据。 我需要确保在加载新数据之前从文件中删除现有数据,因为每周都需要创建该文件。
我从未在 OpenXML Document 和 EPPlus 工作过。
尝试 #1:使用 EPPlus
private static void OtehrMethod(DataTable dataTable, string filePath)
{
// using EPPlus
var package = new ExcelPackage(new FileInfo(filePath));
ExcelWorksheet workSheet = package.Workbook.Worksheets["MySheet"];
foreach (DataRow row in dataTable.Rows)
{
int i = 1;
object cellValue = workSheet.Cells[2, i].Value;
workSheet.Cells[1, 1].Value = Conver.ToInt(row["Id"]);
// break;
//workSheet.Cells[2, i].Value =row["First_Name"].ToString();
//workSheet.Cells[3, i].Value = row["Last_Name"].ToString();
//workSheet.Cells[4, i].Value = row["Job_Title"].ToString();
//workSheet.Cells[5, i].Value = row["Skills"].ToString();
i++;
}
package.Save();
}
尝试 #2:使用打开 XML
private static void SomeMethod()
{
string filePath = ConfigurationManager.AppSettings["ExcelFilePath"];
string workingSheetName = ConfigurationManager.AppSettings["WorkingSheetName"];
using (SpreadsheetDocument document = SpreadsheetDocument.Open(filePath, true))
{
// WorkbookPart workbook = document.WorkbookPart;
WorkbookPart workbookPart = document.WorkbookPart;
Workbook workbook = document.WorkbookPart.Workbook;
int sheetIndex = 0;
foreach (WorksheetPart worksheetpart in workbook.WorkbookPart.WorksheetParts)
{
Worksheet worksheet = worksheetpart.Worksheet;
string sheetName = workbookPart.Workbook.Descendants<Sheet>().ElementAt(sheetIndex).Name;
if (sheetName.ToUpper() == workingSheetName.ToUpper())
{
IEnumerable<Row> rows = worksheet.GetFirstChild<SheetData>().Descendants<Row>();
foreach (Row row in rows)
{
// How do I map Excel sheet column with data table column and insert the values into Excel ?
// Column["FirstName"] = DTRow["FirstName"]
//Column["LastName"] = DTRow["LastName"]
}
}
sheetIndex++;
}
}
// throw new NotImplementedException();
}
首先,EPPlus 是 OpenXML 的包装器。因此,以下概念适用于两者。学习曲线可能很陡峭,所以我将从 Vincent Tan 程序开始供您检查:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Drawing;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml.Drawing.Spreadsheet;
namespace ExcelOpenXmlSetCellValue
{
class Program
{
static void Main(string[] args)
{
string sFile = "ExcelOpenXmlSetCellValue.xlsx";
if (File.Exists(sFile))
{
File.Delete(sFile);
}
try
{
BuildWorkbook(sFile);
Console.WriteLine("Program end");
Console.ReadLine();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
Console.ReadLine();
}
}
private static void BuildWorkbook(string filename)
{
using (SpreadsheetDocument xl = SpreadsheetDocument.Create(filename, SpreadsheetDocumentType.Workbook))
{
WorkbookPart wbp = xl.AddWorkbookPart();
WorksheetPart wsp = wbp.AddNewPart<WorksheetPart>();
Workbook wb = new Workbook();
FileVersion fv = new FileVersion();
fv.ApplicationName = "Microsoft Office Excel";
Worksheet ws = new Worksheet();
SheetData sd = new SheetData();
Row r;
Cell c;
r = new Row();
// For the 2nd row. I know, it's obvious, but it's recommended
// that you assign a value. While Excel might be able to handle
// a Row class without the RowIndex assigned (in a certain case),
// other applications might find that difficult, in particular,
// your own application.
r.RowIndex = 2;
c = new Cell();
// "D" for 4th column. The "2" is equal to the RowIndex of the Row
// that the Cell class is in. Note that the number part has to be
// equal to the RowIndex or disaster will happen. Or worse, Excel
// spits out the "file corrupted" error and your user sees it.
c.CellReference = "D2";
// by default, an unassigned DataType means CellValues.Number
//c.DataType = CellValues.Number;
c.CellValue = new CellValue("9.81");
r.Append(c);
sd.Append(r);
// What if you need more Cell's in a Row? Blank lines added for readability.
r = new Row();
r.RowIndex = 5;
c = new Cell();
// this way, the number part is always right, but you need to
// assign the RowIndex first. This will make sense when you have
// to append many Cell classes. This is just an alternative to
// assigning the cell reference.
c.CellReference = "F" + r.RowIndex;
// There are 2 other string types, the SharedString and InlineString.
// They are discussed in another chapter. The CellValues.String type
// provides the most straightforward way of including text strings.
c.DataType = CellValues.String;
c.CellValue = new CellValue("Is");
r.Append(c);
c = new Cell();
c.CellReference = "G" + r.RowIndex;
c.DataType = CellValues.String;
c.CellValue = new CellValue("that in");
r.Append(c);
c = new Cell();
c.CellReference = "H" + r.RowIndex;
c.DataType = CellValues.String;
c.CellValue = new CellValue("metres per second squared?");
r.Append(c);
// This is for row 5
sd.Append(r);
// And now, to show you sometimes the code can be rearranged a little.
// But because you need to append the Cell class to the Row class,
// it makes sense (logically and conceptually) to initialise a new Row first.
c = new Cell();
c.CellReference = "F7";
c.DataType = CellValues.String;
c.CellValue = new CellValue("It'd better be. Just got used to the metric system.");
r = new Row();
r.RowIndex = 7;
r.Append(c);
sd.Append(r);
ws.Append(sd);
wsp.Worksheet = ws;
wsp.Worksheet.Save();
Sheets sheets = new Sheets();
Sheet sheet = new Sheet();
sheet.Name = "Sheet1";
sheet.SheetId = 1;
sheet.Id = wbp.GetIdOfPart(wsp);
sheets.Append(sheet);
wb.Append(fv);
wb.Append(sheets);
xl.WorkbookPart.Workbook = wb;
xl.WorkbookPart.Workbook.Save();
xl.Close();
}
}
}
}
那么他书中的摘录可能在概念上有所帮助:
So the SheetData class contains Row classes as children. Each Row class contains Cell classes as children. A Row class represents a row in the spreadsheet. A Cell class represents a cell in the spreadsheet. Sometimes, obvious things still need to be said.
It is recommended that you assign a value for the RowIndex property of the Row class. This is basically the row number for that Row class. If it's the 4th row, assign 4. The RowIndex property is optional under Open XML specifications, but spreadsheet applications might not have a way to deal with this. Even Excel handles a blank RowIndex property correctly under special conditions (which we'll talk about later in this chapter).
As for the Cell class, you need to take note of these properties:
- DataType - The data type of the cell value. You will most probably deal with CellValues.Number and CellValues.String.
- CellReference - The cell reference in "A1 format". That means the column letter followed by the row index. For example, "C5" means the 3rd column, 5th row. There's the "R1C1 format" ("R" then row index, then "C" then column index, giving us "R5C3" for the previous example), but we're not covering that. "A1 format" is the default in Excel.
- CellValue - Where the action happens. You might notice that CellValue is actually a class, and its main value is a string. This means you store number values in string form too (the DataType property determines the, well, data type).
- CellFormula - Where you have a cell formula. But you can read about it in another chapter. The above 3 properties are the usual ones to handle.
One more thing. The Row classes must be appended to the SheetData class in ascending RowIndex order. The Cell classes must be appended to its Row class in ascending column order. This is not negotiable. Excel will choke on its error vomit otherwise.
希望这足以让您走上正轨。如果没有,我会回来查看更多问题。
在回答您的其他问题时,对于计算列,在右侧为您为计算列构建的单元格构建计算。
清晰多了。你所在的区域我没有亲自编码,但每个 sheet 的 SheetData 将是清除数据的地方。下面是 Vincent Tan 的另一个程序,展示了如何在现有 sheet 中添加和更改数据:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Drawing;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml.Drawing.Spreadsheet;
namespace ExcelOpenXmlFromTemplate
{
class Program
{
static void Main(string[] args)
{
// Switch between an Excel file with the styles and stuff
// and an actual Excel template file.
// I suggest you check out the accompanying template files first
// to compare how the original and final files will look like.
//string sFileTemplate = "FinancialYearReport.xlsx";
string sFileTemplate = "FinancialYearReportTemplate.xltx";
string sFile = "FinancialYearReportFinal.xlsx";
if (File.Exists(sFile))
{
File.Delete(sFile);
}
if (sFileTemplate.ToLower().EndsWith(".xlsx"))
{
File.Copy(sFileTemplate, sFile);
}
else
{
// ends with .xltx
// Excel template files work differently. You can't work on it
// directly and then save the result to a different file.
// More details on this in the accompanying PDF.
byte[] ba = File.ReadAllBytes(sFileTemplate);
using (MemoryStream ms = new MemoryStream())
{
ms.Write(ba, 0, ba.Length);
using (SpreadsheetDocument xl = SpreadsheetDocument.Open(ms, true))
{
xl.ChangeDocumentType(SpreadsheetDocumentType.Workbook);
xl.Close();
}
File.WriteAllBytes(sFile, ms.ToArray());
}
}
try
{
BuildWorkbookFromTemplate(sFile);
Console.WriteLine("Program end");
Console.ReadLine();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
Console.ReadLine();
}
}
private static void BuildWorkbookFromTemplate(string filename)
{
string sSheetname = "FYReport";
// take note that we're opening a file, and not creating a file as before
using (SpreadsheetDocument xl = SpreadsheetDocument.Open(filename, true))
{
WorkbookPart wbp = xl.WorkbookPart;
// Get the worksheet with the required name.
// To be used to match the ID for the required sheet data
// because the Sheet class and the SheetData class aren't
// linked to each other directly.
Sheet s = null;
if (wbp.Workbook.Sheets.Elements<Sheet>().Count(nm => nm.Name == sSheetname) == 0)
{
// no such sheet with that name
xl.Close();
return;
}
else
{
s = (Sheet)wbp.Workbook.Sheets.Elements<Sheet>().Where(nm => nm.Name == sSheetname).First();
}
WorksheetPart wsp = (WorksheetPart)xl.WorkbookPart.GetPartById(s.Id.Value);
SheetData sd = (SheetData)wsp.Worksheet.GetFirstChild<SheetData>();
// We will update in 2 ways.
// 1) Get the required cell, do the updates on it, then save the worksheet.
// 2) Create a Cell with our data, then throw it to the SheetData, then save the worksheet.
// If the required cell already exists, we overwrite it. If not, we create it.
// The difference between the 2 is that, if your worksheet doesn't have the required cell,
// it becomes more difficult, because you'll have to also append it to the row.
// More details explained in the accompanying PDF.
// I left the saving of the worksheet out of the UpdateCell() function
// because I want to be consistent with the GetCell() version of updating.
// So wsp.Worksheet.Save(); is done after every cell modification.
// Otherwise I'd put it at the end of the update function.
// The code is written for you to understand what's going on, not for optimisation.
int iPreviousYear = DateTime.Now.Year - 1;
int iTheYearBeforeThat = iPreviousYear - 1;
Cell c = null;
// main title
c = GetCell(sd, "A", 1);
if (c != null)
{
if (c.DataType == CellValues.SharedString)
{
c.DataType = CellValues.String;
}
c.CellValue = new CellValue(string.Format("Financial Year Report For {0}/{1}", iTheYearBeforeThat, iPreviousYear));
// I was going to show you how to work with an existing value like so:
// c.CellValue = new CellValue(string.Format("{0} {1}/{2}", c.CellValue.Text.Trim(), iTheYearBeforeThat, iPreviousYear));
// But the existing value is a string, and Excel had kindly saved it into the SharedString table
// when I created the template file.
// The shared string concept makes things a little more interesting if you really want to work with it.
// For now, it's what-you-see-is-what-you-get, which is much simpler.
wsp.Worksheet.Save();
}
// description of the year before the last
c = GetCell(sd, "A", 4);
if (c != null)
{
if (c.DataType == CellValues.SharedString)
{
c.DataType = CellValues.String;
}
c.CellValue = new CellValue(string.Format("Year {0}", iTheYearBeforeThat));
wsp.Worksheet.Save();
}
// description of last year
// I'll show you the difference in code for the 2nd method of updating
c = new Cell();
c.DataType = CellValues.String;
c.CellReference = "A13";
c.CellValue = new CellValue(string.Format("Year {0}", iPreviousYear));
UpdateCell(sd, c, 13);
wsp.Worksheet.Save();
// We will update the revenue and operational costs of "the year before the last" FY.
// You would probably retrieve the values from a database and fill it here.
// I don't have any meaningful data anyway, so I'm gonna cheat with a randomiser...
// I'll use the 2nd method of updating for convenience.
Random rd = new Random();
int i;
for (i = 5; i <= 8; ++i)
{
// revenue values
c = new Cell();
c.DataType = CellValues.Number;
c.CellReference = "B" + i.ToString();
// range from 8.0 to 12.0
c.CellValue = new CellValue((rd.NextDouble() * 4.0 + 8.0).ToString("f2"));
UpdateCell(sd, c, (uint)i);
wsp.Worksheet.Save();
// operational cost values
c = new Cell();
c.DataType = CellValues.Number;
c.CellReference = "C" + i.ToString();
// range from 0.5 to 1.5
c.CellValue = new CellValue((rd.NextDouble() + 0.5).ToString("f2"));
UpdateCell(sd, c, (uint)i);
wsp.Worksheet.Save();
}
// Previous financial year's revenue and operational cost values
for (i = 14; i <= 17; ++i)
{
// revenue values
c = new Cell();
c.DataType = CellValues.Number;
c.CellReference = "B" + i.ToString();
// range from 9.0 to 14.0
// We've got to have increased revenue, right? :)
c.CellValue = new CellValue((rd.NextDouble() * 5.0 + 9.0).ToString("f2"));
UpdateCell(sd, c, (uint)i);
wsp.Worksheet.Save();
// operational cost values
c = new Cell();
c.DataType = CellValues.Number;
c.CellReference = "C" + i.ToString();
// range from 0.4 to 1.4
// We've got to have decreased costs, right? :)
c.CellValue = new CellValue((rd.NextDouble() + 0.4).ToString("f2"));
UpdateCell(sd, c, (uint)i);
wsp.Worksheet.Save();
}
// this section is to show you that UpdateCell() works for
// out of order cells in the same row. Go ahead and mix up
// the order of the 5 cells (A25, B25, C25, D25 and E25) in
// the code and check that they are still ordered correctly in the file.
c = new Cell();
c.DataType = CellValues.String;
c.CellReference = "B25";
c.CellValue = new CellValue("2nd");
UpdateCell(sd, c, 25u);
wsp.Worksheet.Save();
c = new Cell();
c.DataType = CellValues.String;
c.CellReference = "E25";
c.CellValue = new CellValue("5th");
UpdateCell(sd, c, 25u);
wsp.Worksheet.Save();
c = new Cell();
c.DataType = CellValues.String;
c.CellReference = "C25";
c.CellValue = new CellValue("3rd");
UpdateCell(sd, c, 25u);
wsp.Worksheet.Save();
c = new Cell();
c.DataType = CellValues.String;
c.CellReference = "A25";
c.CellValue = new CellValue("1st");
UpdateCell(sd, c, 25u);
wsp.Worksheet.Save();
c = new Cell();
c.DataType = CellValues.String;
c.CellReference = "D25";
c.CellValue = new CellValue("4th");
UpdateCell(sd, c, 25u);
wsp.Worksheet.Save();
// Credits!
c = new Cell();
c.DataType = CellValues.String;
c.CellReference = "A27";
// well, we wouldn't want upper management to think this
// financial report was *easy* to generate, right? Claim credit!
c.CellValue = new CellValue("Generated by Vincent");
UpdateCell(sd, c, 27u);
wsp.Worksheet.Save();
c = new Cell();
c.DataType = CellValues.String;
c.CellReference = "E27";
// you could show upper management that you're working hard,
// and print a time that's in the wee hours of the morning...
// But shame on you...
c.CellValue = new CellValue(string.Format("Generated at {0}", DateTime.Now.ToString("dd MMM yyyy HH:mm:ss")));
UpdateCell(sd, c, 27u);
wsp.Worksheet.Save();
// we have formulae and charts to update in the template
xl.WorkbookPart.Workbook.CalculationProperties.ForceFullCalculation = true;
xl.WorkbookPart.Workbook.CalculationProperties.FullCalculationOnLoad = true;
xl.WorkbookPart.Workbook.Save();
xl.Close();
}
}
private static Cell GetCell(SheetData sd, string ColumnName, UInt32 RowIndex)
{
// There's a small chance that the row has no RowIndex assigned.
// You should know that the property RowIndex is optional.
// This means the following will fail.
// But we can't do much about a missing RowIndex in the template.
Row r = null;
Cell c = null;
if (sd.Elements<Row>().Count(ri => ri.RowIndex == RowIndex) > 0)
{
r = (Row)sd.Elements<Row>().Where(ri => ri.RowIndex == RowIndex).First();
}
if (r != null)
{
string sCellReference = ColumnName.ToUpper() + RowIndex.ToString();
if (r.Elements<Cell>().Count(cr => cr.CellReference.Value == sCellReference) > 0)
{
c = (Cell)r.Elements<Cell>().Where(cr => cr.CellReference.Value == sCellReference).First();
}
}
return c;
}
private static void UpdateCell(SheetData sd, Cell CellData, UInt32 RowIndex)
{
// There's a small chance that the row has no RowIndex assigned.
// You should know that the property RowIndex is optional.
// This means the following will fail.
// But we can't do much about a missing RowIndex in the template.
Row r = null;
if (sd.Elements<Row>().Count(ri => ri.RowIndex == RowIndex) == 0)
{
// There's no row at this index, so it also means there's no cell
// with the data required. So just create the row with the cell.
r = new Row();
r.RowIndex = RowIndex;
r.Append(CellData);
sd.Append(r);
}
else
{
r = (Row)sd.Elements<Row>().Where(ri => ri.RowIndex == RowIndex).First();
// we will assume the cell's CellReference is consistent with the given RowIndex
if (r.Elements<Cell>().Count(cr => cr.CellReference.Value == CellData.CellReference.Value) > 0)
{
// there's an existing cell
Cell c = (Cell)r.Elements<Cell>().Where(cr => cr.CellReference.Value == CellData.CellReference.Value).First();
c.DataType = CellData.DataType;
// reset to "normal" string value if it's a shared string.
// Otherwise, we'll have to go change the value in the SharedStringTable class,
// and that's out of scope for now. Let's focus on updating cells, shall we?
// We are also assuming we're not stupid enough to set the given cell's
// data type to be SharedString...
if (c.DataType == CellValues.SharedString)
{
c.DataType = CellValues.String;
}
c.CellValue = new CellValue(CellData.CellValue.Text);
// set any other additional properties you want
}
else
{
// no such cell
IEnumerable<Cell> iec = r.Elements<Cell>();
// in case there are no cells in the row
if (iec.Count() == 0)
{
r.Append(CellData);
}
else
{
bool bFound = false;
// cells need to be in order of their CellReference values
foreach (Cell c in iec)
{
// We assume the existing Cells are already in order.
// This means the moment our CellData's CellReference is before
// a particular existing Cell's CellReference, we can insert before
// that particular existing Cell.
if (string.Compare(CellData.CellReference.Value, c.CellReference.Value) < 0)
{
bFound = true;
r.InsertBefore(CellData, c);
break;
}
}
// well, we have to insert our CellData *somewhere*.
// If not found in the loop above, then it must have the highest CellReference.
// So we just append it at the end of the row.
if (!bFound)
{
r.Append(CellData);
}
}
// we don't append the row to the SheetData variable because
// the row already exists. We just need to append the cell.
}
}
}
}
}
Vincent 的示例使用设置为模板的工作簿,不涉及清除单元格,但他确实展示了如何覆盖单元格。在我看来,他似乎涵盖了很多可能出错的地方。我会先尝试覆盖任何内容的单元格,看看是否可行。