I am trying to create an Excel file using Open XML C#, where there are multiple sheets in my excel file that needs to be created. Please find my code below and help me with a solution.
When I open the excel only 1 sheet is present, sheet 2 is getting overwritten in sheet 1.
public void CreateExcelFile(string filePath , List<SheetData> excelSheet)
{
var sheetCount = GetSheetCount(filePath);
using (SpreadsheetDocument spreedDoc = SpreadsheetDocument.Create(filePath,
DocumentFormat.OpenXml.SpreadsheetDocumentType.Workbook))
{
Workbook workbook1 = new Workbook();
workbook1.AddNamespaceDeclaration("r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships");
FileVersion fileVersion1 = new FileVersion() { ApplicationName = "xl", LastEdited = "5", LowestEdited = "4", BuildVersion = "9302" };
WorkbookProperties workbookProperties1 = new WorkbookProperties() { FilterPrivacy = true, DefaultThemeVersion = (UInt32Value)124226U };
BookViews bookViews1 = new BookViews();
WorkbookView workbookView1 = new WorkbookView() { XWindow = 240, YWindow = 105, WindowWidth = (UInt32Value)14805U, WindowHeight = (UInt32Value)8010U, ActiveTab = (UInt32Value)2U };
bookViews1.Append(workbookView1);
Sheets sheetCol = new Sheets();
int ctr = 0;
foreach (var sheetToCreate in excelSheet)
{
ctr++;
//WorksheetPart worksheetPart = null;
//worksheetPart = wbPart.AddNewPart<WorksheetPart>();
//var sheetData = new SheetData();
//worksheetPart.Worksheet = new Worksheet(sheetData);
//worksheetPart.Worksheet = new Worksheet(sheetToCreate);
string sheetName = "Sheet";
sheetName += ctr.ToString();
var sheet = new Sheet()
{
Id = "rId" + ctr,
SheetId = (UInt32)ctr,
Name = sheetName
};
sheet.Append(sheetToCreate);
sheetCol.Append(sheet);
//wbPart.Workbook.Sheets.AppendChild(sheet);
//wbPart.Workbook.Append(sheetCol);
workbook1.Sheets.Append(sheet);
//var workingSheet = ((WorksheetPart)wbPart.GetPartById(sheet.Id)).Worksheet;
}
workbook1.Append(fileVersion1);
workbook1.Append(workbookProperties1);
workbook1.Append(bookViews1);
workbook1.Append(sheetCol);
//Set Border
//wbPark
workbook1.Save();
}
}
When dealing with OpenXML, the "OpenXML Productivity Tool" (downloadable from Microsoft's site) is your friend. When I work with creating documents with OpenXML, I do the following:
Create a document similar to what I want using Excel or Word (in this case, an Excel spreadsheet document with two sheets (I labeled them "First" and "Second")).
Open the document up in the productivity tool
Reflect the code (a button on the Tool's UI)
Copy/paste the code I want.
If I do that, I end up with all the code I need. I've boiled it down to the essentials to answer your question. Excel will complain that the document produced below is corrupt (since all it includes is the code to create the two sheets - and leaves lots of other required code out - all of which is stealable from the tool). However, if you tell Excel "Repair this for me", you will get a document with two sheets, labeled "First" and "Second"
WorkbookPart workbookPart1 = document.AddWorkbookPart();
Workbook workbook1 = new Workbook() { MCAttributes = new MarkupCompatibilityAttributes() { Ignorable = "x15 xr xr6 xr10 xr2" } };
//lots of namespace declarations - copy them from the tool (they are needed!!)
//lots of code for WorkbookProperties, etc (you can get this from Tool)
Sheets sheets1 = new Sheets();
Sheet sheet1 = new Sheet() { Name = "First", SheetId = (UInt32Value)1U, Id = "rId1" };
Sheet sheet2 = new Sheet() { Name = "Second", SheetId = (UInt32Value)2U, Id = "rId2" };
sheets1.Append(sheet1);
sheets1.Append(sheet2);
//More elided code
workbook1.Append(sheets1);
workbookPart1.Workbook = workbook1;
I strongly recommend that you keep the pluralization of things like Sheet and Sheets clear. Your code has Sheets sheetCol = new Sheets();, it's not clear after that that sheetCol is a Sheets and not a Sheet.
Then you have something called sheetToCreate which is a SheetData object. Again, that's confusing - call it something clear like thisSheetData or something. I really strongly recommend that you follow the OpenXML naming as much as you can, otherwise it is extremely hard to follow what's going on.
The Productivity Tool is also handy once you've written out your file. You can see what you created, often spotting what the error is. In addition, there's a validate button that usually (not always) points you to problems in the structure of your documents.
Good luck. Prepare to get better at cussing at your code!
Related
I have generated a spreadsheet with data and a chart based on the data using C# OpenXML.
I am now wanting to now embed this chart into a word document, so that when these reports are sent out, the charts can be edited if need be (Not an external link).
I have looked around for hours and can't find any consistent documentation for articles which achieve this. Most leads seem to talk about 'EmbeddedPackageParts'.
If anyone has any helpful articles or can provide some clarity it would be appreciated.
Cheers,
Duncan.
I ended up figuring this out!
Steps:
Generate xlsx file with my data in it.
Save the xlsx file locally
In my word document create a new chart part and generate the graph contents
ChartPart wordChartPart = document.MainDocumentPart.AddNewPart<ChartPart>();
string wordChartId = document.MainDocumentPart.GetIdOfPart(wordChartPart);
WordDocumentBuilder.Workflows.SpreadsheetUtils.GenerateBarChartPart(wordChartPart, categories, dataRows);
Embed the spreadsheet in the ChartPart
EmbeddedPackagePart embeddedObjectPart = wordChartPart.AddEmbeddedPackagePart(#"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
using (FileStream stream = new FileStream(file, FileMode.Open))
{
byte[] documentBytes = new byte[stream.Length];
stream.Read(documentBytes, 0, documentBytes.Length);
using (BinaryWriter writer = new BinaryWriter(embeddedObjectPart.GetStream()))
{
writer.Write(documentBytes);
writer.Flush();
}
}
I could then insert an Inline Drawing into my word document which referenced the chart part.
you can open both the new word document and the excel sheet that your charts are included currently.
You can add some rich text content controls to the word document and insert the Chartparts of the excel sheet in those places.
Make sure that your charts have titles. You can read the charts from the titles
To append them to the word document you need to create a paragraph object and append it to a Run object and also you need to define an Inline also in order to append the charts.
I hope you can make the things I said clear by looking at the following example.
private string WordReportGeneration(string docPath, string excelPath)
{
string[] chartTitles = new string[] {"","","","","","",.... };//Chart titles
string[] bookMark = new string[] { "C1", "C2", "C3",..... };//rich text controls of the word doc
for (int i = 0; i < chartTitles.Length; i++) //going through the chart title array
{
using (WordprocessingDocument myWordDoc = WordprocessingDocument.Open(docPath, true))
{
MainDocumentPart mainPart = myWordDoc.MainDocumentPart;
SdtBlock sdt = null;
mainPart.Document.Descendants<SdtBlock>().ToList().ForEach(b => {
var child = b.SdtProperties.GetFirstChild<Tag>();
if (child != null && child.Val.Equals(bookMark[i]))
sdt = b;
});
Paragraph p = sdt.SdtContentBlock.GetFirstChild<Paragraph>();
p.RemoveAllChildren();
Run r = new Run();
p.Append(r);
Drawing drawing = new Drawing();
r.Append(drawing);
Inline inline = new Inline(
new Extent()
{ Cx = 5486400, Cy = 3200400 });
using (SpreadsheetDocument mySpreadsheet = SpreadsheetDocument.Open(excelPath, true))
{
WorkbookPart workbookPart = mySpreadsheet.WorkbookPart;
Sheet theSheet = workbookPart.Workbook.Descendants<Sheet>().FirstOrDefault(s => s.Name == "Report");
WorksheetPart worksheetPart = (WorksheetPart)workbookPart.GetPartById(theSheet.Id);
DrawingsPart drawingPart = worksheetPart.DrawingsPart;
ChartPart chartPart = (ChartPart)drawingPart.ChartParts.FirstOrDefault(x =>
x.ChartSpace.ChildElements[4].FirstChild.InnerText.Trim() == chartTitles[i]);
ChartPart importedChartPart = mainPart.AddPart<ChartPart>(chartPart);
string relId = mainPart.GetIdOfPart(importedChartPart);
DocumentFormat.OpenXml.Drawing.Spreadsheet.GraphicFrame frame = drawingPart.WorksheetDrawing.Descendants<DocumentFormat.OpenXml.Drawing.Spreadsheet.GraphicFrame>().First();
Graphic clonedGraphic = (Graphic)frame.Graphic.CloneNode(true);
ChartReference c = clonedGraphic.GraphicData.GetFirstChild<ChartReference>();
c.Id = relId;
DocProperties docPr = new DocProperties();
docPr.Name = "XXX";
docPr.Id = GetMaxDocPrId(mainPart) + 1;
inline.Append(docPr, clonedGraphic);
drawing.Append(inline);
}
myWordDoc.Save();
myWordDoc.Close();
}
}
return docPath;
}
enter code here
enter code here
I am using OpenXML to manipulate Excel files.
I am sending the Excel files as memory stream, editing them and then send them back to browser so they open in client office program. I create new spread sheet by using this code:
public static void InsertWorksheet(string docName)
{
using (SpreadsheetDocument spreadSheet = SpreadsheetDocument.Open(docName, true))
{
// Add a blank WorksheetPart.
WorksheetPart newWorksheetPart = spreadSheet.WorkbookPart.AddNewPart<WorksheetPart>();
newWorksheetPart.Worksheet = new Worksheet(new SheetData());
Sheets sheets = spreadSheet.WorkbookPart.Workbook.GetFirstChild<Sheets>();
string relationshipId = spreadSheet.WorkbookPart.GetIdOfPart(newWorksheetPart);
// Get a unique ID for the new worksheet.
uint sheetId = 1;
if (sheets.Elements<Sheet>().Count() > 0)
{
sheetId = sheets.Elements<Sheet>().Select(s => s.SheetId.Value).Max() + 1;
}
// Give the new worksheet a name.
string sheetName = "Sheet" + sheetId;
// Append the new worksheet and associate it with the workbook.
Sheet sheet = new Sheet() { Id = relationshipId, SheetId = sheetId, Name = sheetName };
sheets.Append(sheet);
}
}
My problem is that this sheet is added last among the sheets in the workbook. I want it to be sheet nr 1. I am looking for tips on how I can set my newly created sheet to be sheet nr. 1.
Maybe instead of sheets.Append you call something that inserts it at the beginning? is there a Insert or Prepend method?
I use the following code, and I'm wondering if I can somehow make this a sheet separated excel file with each row in its own sheet.
DataTable dt = new DataTable();
SqlConnection objcon = new SqlConnection(connectionString);
string sql = "SELECT * FROM table";
SqlDataAdapter objda = new SqlDataAdapter(sql, objcon);
objda.Fill(dt);
GridView gvreport = new GridView();
gvreport.DataSource = dt;
gvreport.DataBind();
string date = DateTime.Now.ToString("yyyy_MM_dd_HHmmss");
string fileName = string.Format("TheFilename_{0}.xls", date);
Response.ClearContent();
Response.AddHeader("content-disposition", "attachment; filename=" + fileName);
Response.ContentType = "application/excel";
System.IO.StringWriter sw = new System.IO.StringWriter();
HtmlTextWriter htw = new HtmlTextWriter(sw);
gvreport.RenderControl(htw);
Response.Write(sw.ToString());
Response.End();
return null;
I know that this doesn't exactly generate a real excel file but rather tricks the browser to create one. Which is probably why its a bit more complicated to make advanced excel files.
I've already tried with things like setting a max amount of rows per gridview page and so on.
Are there any way of doing this, or in general create an excel file in another way that allows me to do this. Without needing Windows office or similar installed.
Thank you for your time.
I would use something like OpenXML. I'm guess the reason your creating the excel file on the fly is because you don't want to use interop objects because they are too heavy and bulky. However you still need the flexibility to manipulate objects. I would look at the OpenXML library as it contains many of the manipulations that your looking for the code would look something like below to get you started. In the above link you'll notice there is an example for mvc. Which really doesn't change the underlying code much.
using (SpreadsheetDocument spreadSheetDocument = SpreadsheetDocument.Create(filename, SpreadsheetDocumentType.Workbook))
{
WorkbookPart workBookPart = spreadSheetDocument.AddWorkbookPart();
WorksheetPart worksheetPart = workBookPart.AddNewPart<WorksheetPart>();
WorkbookStylesPart workBookStylesPart = workBookPart.AddNewPart<WorkbookStylesPart>();
Stylesheet stylesheet = new CustomStyleSheet();
stylesheet.Save(workBookStylesPart);
string relId = workBookPart.GetIdOfPart(worksheetPart);
Workbook workbook = new Workbook();
Worksheet worksheet = new Worksheet();
var columns = CreateColumns();
Sheets sheets = new Sheets();
Sheet sheet = new Sheet { Name = "mySheet", SheetId = 1, Id = relId };
sheets.Append(sheet);
worksheet.Append(columns);
SheetData sheetData = CreateSheetData();
worksheet.Append(sheetData);
workbook.Append(sheets);
worksheetPart.Worksheet = worksheet;
worksheetPart.Worksheet.Save();
spreadSheetDocument.WorkbookPart.Workbook = workbook;
spreadSheetDocument.WorkbookPart.Workbook.Save();
//Now I want to add an empty sheet
sheet = new Sheet { Name = "mySheet2", SheetId = 2, Id = relId };
sheets.Append(sheet);
spreadSheetDocument.WorkbookPart.Workbook.Save();
spreadSheetDocument.Close();
}
I've been using OPEN XML to create a spread sheet which can then be downloaded. I want to set the column widths and also change the header to bold. I've done this using Office.Interop.Excel but I'm struggling with this format. My code is below, you can see I've set up a font but have been unable to assign it to my spread sheet, I have got messages saying it's not possible to assign to a tree.
SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Create(fileName, SpreadsheetDocumentType.Workbook); //Open(fileName, true);
try
{
WorkbookPart workbookpart = spreadsheetDocument.AddWorkbookPart();
workbookpart.Workbook = new DocumentFormat.OpenXml.Spreadsheet.Workbook();
WorksheetPart worksheetPart = workbookpart.AddNewPart<WorksheetPart>();
worksheetPart.Worksheet = new DocumentFormat.OpenXml.Spreadsheet.Worksheet(new SheetData());
DocumentFormat.OpenXml.Spreadsheet.Sheets sheets = spreadsheetDocument.WorkbookPart.Workbook.AppendChild<DocumentFormat.OpenXml.Spreadsheet.Sheets>(new DocumentFormat.OpenXml.Spreadsheet.Sheets());
Sheet sheet = new Sheet() { Id = spreadsheetDocument.WorkbookPart.GetIdOfPart(worksheetPart), SheetId = 1, Name = "Models" };
sheets.Append(sheet);
DocumentFormat.OpenXml.Spreadsheet.Font boldFont = new DocumentFormat.OpenXml.Spreadsheet.Font();
Bold bFontBold = new Bold();
boldFont.Append(bFontBold);
DocumentFormat.OpenXml.Spreadsheet.Worksheet worksheet = new DocumentFormat.OpenXml.Spreadsheet.Worksheet();
SheetData sheetData = new SheetData();
........
Row row = new Row();
Cell cell = new Cell()
{
CellReference = "A" + (intI + 1),
DataType = CellValues.String,
CellValue = new CellValue(ModelBookrowDetail[0])
};
row.Append(cell);
Cell cell2 = new Cell()
{
CellReference = "B" + (intI + 1),
DataType = CellValues.String,
CellValue = new CellValue(ModelBookrowDetail[1])
};
row.Append(cell2);
.........
sheetData.Append(row);
}
worksheet.Append(sheetData);
worksheetPart.Worksheet = worksheet;
spreadsheetDocument.Close();
}
catch
{
spreadsheetDocument.Close();
}
I don't know how set you are on using OpenXml. I might suggest looking into ClosedXml. Up until now it has saved me a lot of manhours. It only has one downside to it, and that is that it doesn't manage Word documents like OpenXml does.
ClosedXml is a more object oriented Open Source Library, and is far more well documented in contrast to what i have experienced with OpenXml.
You can find ClosedXml here:
https://github.com/ClosedXML/ClosedXML
Best of luck
Olliver
When I follow this tutorial:
http://msdn.microsoft.com/en-us/library/cc881781.aspx
to open an Excel document and insert an empty worksheet the final result is a message telling "Excel found unreadable content in ... Do you want to recover the contents of this workbook...". If I recover all the inserted sheets are blank (even if I add content programatically)
After renaming the xlsx to .zip and examining it shows that the worksheets have been created, and content added.
Anyone with any similar problems? It may be something with not creating relationships between newly created parts...
That error message means that the XML that makes up your excel document is not conforming to the XML Schema and is invalid. You can use the Open XML SDK 2.0 Productivity Tool to see where the issue is located.
I also copied the code from the bottom of your link and got it to work like Chris said in his comment. Does your code look like this below?
// Open the document for editing.
using (SpreadsheetDocument spreadSheet = SpreadsheetDocument.Open(docName, true))
{
// Add a blank WorksheetPart.
WorksheetPart newWorksheetPart =
spreadSheet.WorkbookPart.AddNewPart<WorksheetPart>();
newWorksheetPart.Worksheet = new Worksheet(new SheetData());
Sheets sheets = spreadSheet.WorkbookPart.Workbook.GetFirstChild<Sheets>();
string relationshipId =
spreadSheet.WorkbookPart.GetIdOfPart(newWorksheetPart);
// Get a unique ID for the new worksheet.
uint sheetId = 1;
if (sheets.Elements<Sheet>().Count() > 0)
{
sheetId =
sheets.Elements<Sheet>().Select(s => s.SheetId.Value).Max() + 1;
}
// Give the new worksheet a name.
string sheetName = "Sheet" + sheetId;
// Append the new worksheet and associate it with the workbook.
Sheet sheet = new Sheet()
{ Id = relationshipId, SheetId = sheetId, Name = sheetName };
sheets.Append(sheet);
string docName = #"C:\Users\Public\Documents\Sheet7.xlsx";
InsertWorksheet(docName);
}
// Given a document name, inserts a new worksheet.
public static void InsertWorksheet(string docName)
{
// Open the document for editing.
using (SpreadsheetDocument spreadSheet = SpreadsheetDocument.Open(docName, true))
{
// Add a blank WorksheetPart.
WorksheetPart newWorksheetPart = spreadSheet.WorkbookPart.AddNewPart<WorksheetPart>();
newWorksheetPart.Worksheet = new Worksheet(new SheetData());
Sheets sheets = spreadSheet.WorkbookPart.Workbook.GetFirstChild<Sheets>();
string relationshipId = spreadSheet.WorkbookPart.GetIdOfPart(newWorksheetPart);
// Get a unique ID for the new worksheet.
uint sheetId = 1;
if (sheets.Elements<Sheet>().Count() > 0)
{
sheetId = sheets.Elements<Sheet>().Select(s => s.SheetId.Value).Max() + 1;
}
// Give the new worksheet a name.
string sheetName = "Sheet" + sheetId;
// Append the new worksheet and associate it with the workbook.
Sheet sheet = new Sheet() { Id = relationshipId, SheetId = sheetId, Name = sheetName };
sheets.Append(sheet);
}
}