Advanced styling in Excel Open XML

I’ve shown you how to create a stylesheet in Excel Open XML. I’ve even shown you how to insert an image in Excel Open XML too. Today, I’ll show you how to do advanced styling. Warning: confusing indices ahead.

First, let me show you what the resulting Excel sheet should look like:
Styled Excel Open XML

We will insert an image as before. We’ll also put in some kind of header text. This is to illustrate the use of a second font. We’ll also use borders, just because we haven’t done it. And we’ll also put in some cell colour (Andy, I’m looking at you…)

I’ll just dump the code in case you’re the impatient type:

static void Main(string[] args)
{
	string sFile = "ExcelOpenXmlWithImageAndStyles.xlsx";
	if (File.Exists(sFile))
	{
		File.Delete(sFile);
	}
	BuildWorkbook(sFile);
}

private static void BuildWorkbook(string filename)
{
	try
	{
		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();

			WorkbookStylesPart wbsp = wbp.AddNewPart<WorkbookStylesPart>();
			wbsp.Stylesheet = CreateStylesheet();
			wbsp.Stylesheet.Save();

			string sImagePath = "polymathlogo.png";
			DrawingsPart dp = wsp.AddNewPart<DrawingsPart>();
			ImagePart imgp = dp.AddImagePart(ImagePartType.Png, wsp.GetIdOfPart(dp));
			using (FileStream fs = new FileStream(sImagePath, FileMode.Open))
			{
				imgp.FeedData(fs);
			}

			NonVisualDrawingProperties nvdp = new NonVisualDrawingProperties();
			nvdp.Id = 1025;
			nvdp.Name = "Picture 1";
			nvdp.Description = "polymathlogo";
			DocumentFormat.OpenXml.Drawing.PictureLocks picLocks = new DocumentFormat.OpenXml.Drawing.PictureLocks();
			picLocks.NoChangeAspect = true;
			picLocks.NoChangeArrowheads = true;
			NonVisualPictureDrawingProperties nvpdp = new NonVisualPictureDrawingProperties();
			nvpdp.PictureLocks = picLocks;
			NonVisualPictureProperties nvpp = new NonVisualPictureProperties();
			nvpp.NonVisualDrawingProperties = nvdp;
			nvpp.NonVisualPictureDrawingProperties = nvpdp;

			DocumentFormat.OpenXml.Drawing.Stretch stretch = new DocumentFormat.OpenXml.Drawing.Stretch();
			stretch.FillRectangle = new DocumentFormat.OpenXml.Drawing.FillRectangle();

			BlipFill blipFill = new BlipFill();
			DocumentFormat.OpenXml.Drawing.Blip blip = new DocumentFormat.OpenXml.Drawing.Blip();
			blip.Embed = dp.GetIdOfPart(imgp);
			blip.CompressionState = DocumentFormat.OpenXml.Drawing.BlipCompressionValues.Print;
			blipFill.Blip = blip;
			blipFill.SourceRectangle = new DocumentFormat.OpenXml.Drawing.SourceRectangle();
			blipFill.Append(stretch);

			DocumentFormat.OpenXml.Drawing.Transform2D t2d = new DocumentFormat.OpenXml.Drawing.Transform2D();
			DocumentFormat.OpenXml.Drawing.Offset offset = new DocumentFormat.OpenXml.Drawing.Offset();
			offset.X = 0;
			offset.Y = 0;
			t2d.Offset = offset;
			Bitmap bm = new Bitmap(sImagePath);
			//http://en.wikipedia.org/wiki/English_Metric_Unit#DrawingML
			//http://stackoverflow.com/questions/1341930/pixel-to-centimeter
			//http://stackoverflow.com/questions/139655/how-to-convert-pixels-to-points-px-to-pt-in-net-c
			DocumentFormat.OpenXml.Drawing.Extents extents = new DocumentFormat.OpenXml.Drawing.Extents();
			extents.Cx = (long)bm.Width * (long)((float)914400 / bm.HorizontalResolution);
			extents.Cy = (long)bm.Height * (long)((float)914400 / bm.VerticalResolution);
			bm.Dispose();
			t2d.Extents = extents;
			ShapeProperties sp = new ShapeProperties();
			sp.BlackWhiteMode = DocumentFormat.OpenXml.Drawing.BlackWhiteModeValues.Auto;
			sp.Transform2D = t2d;
			DocumentFormat.OpenXml.Drawing.PresetGeometry prstGeom = new DocumentFormat.OpenXml.Drawing.PresetGeometry();
			prstGeom.Preset = DocumentFormat.OpenXml.Drawing.ShapeTypeValues.Rectangle;
			prstGeom.AdjustValueList = new DocumentFormat.OpenXml.Drawing.AdjustValueList();
			sp.Append(prstGeom);
			sp.Append(new DocumentFormat.OpenXml.Drawing.NoFill());

			DocumentFormat.OpenXml.Drawing.Spreadsheet.Picture picture = new DocumentFormat.OpenXml.Drawing.Spreadsheet.Picture();
			picture.NonVisualPictureProperties = nvpp;
			picture.BlipFill = blipFill;
			picture.ShapeProperties = sp;

			Position pos = new Position();
			pos.X = 0;
			pos.Y = 0;
			Extent ext = new Extent();
			ext.Cx = extents.Cx;
			ext.Cy = extents.Cy;
			AbsoluteAnchor anchor = new AbsoluteAnchor();
			anchor.Position = pos;
			anchor.Extent = ext;
			anchor.Append(picture);
			anchor.Append(new ClientData());
			WorksheetDrawing wsd = new WorksheetDrawing();
			wsd.Append(anchor);
			Drawing drawing = new Drawing();
			drawing.Id = dp.GetIdOfPart(imgp);

			wsd.Save(dp);

			UInt32 index;
			Random rand = new Random();

			sd.Append(CreateHeader(10));
			sd.Append(CreateColumnHeader(11));

			for (index = 12; index < 30; ++index)
			{
				sd.Append(CreateContent(index, ref rand));
			}

			ws.Append(sd);
			ws.Append(drawing);
			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();
		}
	}
	catch (Exception e)
	{
		Console.WriteLine(e.ToString());
		Console.ReadLine();
	}
}

private static Stylesheet CreateStylesheet()
{
	Stylesheet ss = new Stylesheet();

	Fonts fts = new Fonts();
	DocumentFormat.OpenXml.Spreadsheet.Font ft = new DocumentFormat.OpenXml.Spreadsheet.Font();
	FontName ftn = new FontName();
	ftn.Val = StringValue.FromString("Calibri");
	FontSize ftsz = new FontSize();
	ftsz.Val = DoubleValue.FromDouble(11);
	ft.FontName = ftn;
	ft.FontSize = ftsz;
	fts.Append(ft);

	ft = new DocumentFormat.OpenXml.Spreadsheet.Font();
	ftn = new FontName();
	ftn.Val = StringValue.FromString("Palatino Linotype");
	ftsz = new FontSize();
	ftsz.Val = DoubleValue.FromDouble(18);
	ft.FontName = ftn;
	ft.FontSize = ftsz;
	fts.Append(ft);

	fts.Count = UInt32Value.FromUInt32((uint)fts.ChildElements.Count);

	Fills fills = new Fills();
	Fill fill;
	PatternFill patternFill;
	fill = new Fill();
	patternFill = new PatternFill();
	patternFill.PatternType = PatternValues.None;
	fill.PatternFill = patternFill;
	fills.Append(fill);

	fill = new Fill();
	patternFill = new PatternFill();
	patternFill.PatternType = PatternValues.Gray125;
	fill.PatternFill = patternFill;
	fills.Append(fill);

	fill = new Fill();
	patternFill = new PatternFill();
	patternFill.PatternType = PatternValues.Solid;
	patternFill.ForegroundColor = new ForegroundColor();
	patternFill.ForegroundColor.Rgb = HexBinaryValue.FromString("00ff9728");
	patternFill.BackgroundColor = new BackgroundColor();
	patternFill.BackgroundColor.Rgb = patternFill.ForegroundColor.Rgb;
	fill.PatternFill = patternFill;
	fills.Append(fill);

	fills.Count = UInt32Value.FromUInt32((uint)fills.ChildElements.Count);

	Borders borders = new Borders();
	Border border = new Border();
	border.LeftBorder = new LeftBorder();
	border.RightBorder = new RightBorder();
	border.TopBorder = new TopBorder();
	border.BottomBorder = new BottomBorder();
	border.DiagonalBorder = new DiagonalBorder();
	borders.Append(border);

	border = new Border();
	border.LeftBorder = new LeftBorder();
	border.LeftBorder.Style = BorderStyleValues.Thin;
	border.RightBorder = new RightBorder();
	border.RightBorder.Style = BorderStyleValues.Thin;
	border.TopBorder = new TopBorder();
	border.TopBorder.Style = BorderStyleValues.Thin;
	border.BottomBorder = new BottomBorder();
	border.BottomBorder.Style = BorderStyleValues.Thin;
	border.DiagonalBorder = new DiagonalBorder();
	borders.Append(border);
	borders.Count = UInt32Value.FromUInt32((uint)borders.ChildElements.Count);

	CellStyleFormats csfs = new CellStyleFormats();
	CellFormat cf = new CellFormat();
	cf.NumberFormatId = 0;
	cf.FontId = 0;
	cf.FillId = 0;
	cf.BorderId = 0;
	csfs.Append(cf);
	csfs.Count = UInt32Value.FromUInt32((uint)csfs.ChildElements.Count);

	uint iExcelIndex = 164;
	NumberFormats nfs = new NumberFormats();
	CellFormats cfs = new CellFormats();

	cf = new CellFormat();
	cf.NumberFormatId = 0;
	cf.FontId = 0;
	cf.FillId = 0;
	cf.BorderId = 0;
	cf.FormatId = 0;
	cfs.Append(cf);

	NumberFormat nfDateTime = new NumberFormat();
	nfDateTime.NumberFormatId = UInt32Value.FromUInt32(iExcelIndex++);
	nfDateTime.FormatCode = StringValue.FromString("dd/mm/yyyy hh:mm:ss");
	nfs.Append(nfDateTime);

	NumberFormat nf4decimal = new NumberFormat();
	nf4decimal.NumberFormatId = UInt32Value.FromUInt32(iExcelIndex++);
	nf4decimal.FormatCode = StringValue.FromString("#,##0.0000");
	nfs.Append(nf4decimal);

	// #,##0.00 is also Excel style index 4
	NumberFormat nf2decimal = new NumberFormat();
	nf2decimal.NumberFormatId = UInt32Value.FromUInt32(iExcelIndex++);
	nf2decimal.FormatCode = StringValue.FromString("#,##0.00");
	nfs.Append(nf2decimal);

	// @ is also Excel style index 49
	NumberFormat nfForcedText = new NumberFormat();
	nfForcedText.NumberFormatId = UInt32Value.FromUInt32(iExcelIndex++);
	nfForcedText.FormatCode = StringValue.FromString("@");
	nfs.Append(nfForcedText);

	// index 1
	cf = new CellFormat();
	cf.NumberFormatId = nfDateTime.NumberFormatId;
	cf.FontId = 0;
	cf.FillId = 0;
	cf.BorderId = 0;
	cf.FormatId = 0;
	cf.ApplyNumberFormat = BooleanValue.FromBoolean(true);
	cfs.Append(cf);

	// index 2
	cf = new CellFormat();
	cf.NumberFormatId = nf4decimal.NumberFormatId;
	cf.FontId = 0;
	cf.FillId = 0;
	cf.BorderId = 0;
	cf.FormatId = 0;
	cf.ApplyNumberFormat = BooleanValue.FromBoolean(true);
	cfs.Append(cf);

	// index 3
	cf = new CellFormat();
	cf.NumberFormatId = nf2decimal.NumberFormatId;
	cf.FontId = 0;
	cf.FillId = 0;
	cf.BorderId = 0;
	cf.FormatId = 0;
	cf.ApplyNumberFormat = BooleanValue.FromBoolean(true);
	cfs.Append(cf);

	// index 4
	cf = new CellFormat();
	cf.NumberFormatId = nfForcedText.NumberFormatId;
	cf.FontId = 0;
	cf.FillId = 0;
	cf.BorderId = 0;
	cf.FormatId = 0;
	cf.ApplyNumberFormat = BooleanValue.FromBoolean(true);
	cfs.Append(cf);

	// index 5
	// Header text
	cf = new CellFormat();
	cf.NumberFormatId = nfForcedText.NumberFormatId;
	cf.FontId = 1;
	cf.FillId = 0;
	cf.BorderId = 0;
	cf.FormatId = 0;
	cf.ApplyNumberFormat = BooleanValue.FromBoolean(true);
	cfs.Append(cf);

	// index 6
	// column text
	cf = new CellFormat();
	cf.NumberFormatId = nfForcedText.NumberFormatId;
	cf.FontId = 0;
	cf.FillId = 0;
	cf.BorderId = 1;
	cf.FormatId = 0;
	cf.ApplyNumberFormat = BooleanValue.FromBoolean(true);
	cfs.Append(cf);

	// index 7
	// coloured 2 decimal text
	cf = new CellFormat();
	cf.NumberFormatId = nf2decimal.NumberFormatId;
	cf.FontId = 0;
	cf.FillId = 2;
	cf.BorderId = 0;
	cf.FormatId = 0;
	cf.ApplyNumberFormat = BooleanValue.FromBoolean(true);
	cfs.Append(cf);

	// index 8
	// coloured column text
	cf = new CellFormat();
	cf.NumberFormatId = nfForcedText.NumberFormatId;
	cf.FontId = 0;
	cf.FillId = 2;
	cf.BorderId = 1;
	cf.FormatId = 0;
	cf.ApplyNumberFormat = BooleanValue.FromBoolean(true);
	cfs.Append(cf);

	nfs.Count = UInt32Value.FromUInt32((uint)nfs.ChildElements.Count);
	cfs.Count = UInt32Value.FromUInt32((uint)cfs.ChildElements.Count);

	ss.Append(nfs);
	ss.Append(fts);
	ss.Append(fills);
	ss.Append(borders);
	ss.Append(csfs);
	ss.Append(cfs);

	CellStyles css = new CellStyles();
	CellStyle cs = new CellStyle();
	cs.Name = StringValue.FromString("Normal");
	cs.FormatId = 0;
	cs.BuiltinId = 0;
	css.Append(cs);
	css.Count = UInt32Value.FromUInt32((uint)css.ChildElements.Count);
	ss.Append(css);

	DifferentialFormats dfs = new DifferentialFormats();
	dfs.Count = 0;
	ss.Append(dfs);

	TableStyles tss = new TableStyles();
	tss.Count = 0;
	tss.DefaultTableStyle = StringValue.FromString("TableStyleMedium9");
	tss.DefaultPivotStyle = StringValue.FromString("PivotStyleLight16");
	ss.Append(tss);

	return ss;
}

private static Row CreateHeader(UInt32 index)
{
	Row r = new Row();
	r.RowIndex = index;

	Cell c = new Cell();
	c.DataType = CellValues.String;
	c.StyleIndex = 5;
	c.CellReference = "A" + index.ToString();
	c.CellValue = new CellValue("Congratulations! You can now create Excel Open XML styles.");
	r.Append(c);

	return r;
}

private static Row CreateColumnHeader(UInt32 index)
{
	Row r = new Row();
	r.RowIndex = index;

	Cell c;
	c = new Cell();
	c.DataType = CellValues.String;
	c.StyleIndex = 6;
	c.CellReference = "A" + index.ToString();
	c.CellValue = new CellValue("Product ID");
	r.Append(c);

	c = new Cell();
	c.DataType = CellValues.String;
	c.StyleIndex = 6;
	c.CellReference = "B" + index.ToString();
	c.CellValue = new CellValue("Date/Time");
	r.Append(c);

	c = new Cell();
	c.DataType = CellValues.String;
	c.StyleIndex = 6;
	c.CellReference = "C" + index.ToString();
	c.CellValue = new CellValue("Duration");
	r.Append(c);

	c = new Cell();
	c.DataType = CellValues.String;
	c.StyleIndex = 6;
	c.CellReference = "D" + index.ToString();
	c.CellValue = new CellValue("Cost");
	r.Append(c);

	c = new Cell();
	c.DataType = CellValues.String;
	c.StyleIndex = 8;
	c.CellReference = "E" + index.ToString();
	c.CellValue = new CellValue("Revenue");
	r.Append(c);

	return r;
}

private static Row CreateContent(UInt32 index, ref Random rd)
{
	Row r = new Row();
	r.RowIndex = index;

	Cell c;
	c = new Cell();
	c.CellReference = "A" + index.ToString();
	c.CellValue = new CellValue(rd.Next(1000000000).ToString("d9"));
	r.Append(c);

	DateTime dtEpoch = new DateTime(1900, 1, 1, 0, 0, 0, 0);
	DateTime dt = dtEpoch.AddDays(rd.NextDouble() * 100000.0);
	TimeSpan ts = dt - dtEpoch;
	double fExcelDateTime;
	// Excel has "bug" of treating 29 Feb 1900 as valid
	// 29 Feb 1900 is 59 days after 1 Jan 1900, so just skip to 1 Mar 1900
	if (ts.Days >= 59)
	{
		fExcelDateTime = ts.TotalDays + 2.0;
	}
	else
	{
		fExcelDateTime = ts.TotalDays + 1.0;
	}
	c = new Cell();
	c.StyleIndex = 1;
	c.CellReference = "B" + index.ToString();
	c.CellValue = new CellValue(fExcelDateTime.ToString());
	r.Append(c);

	c = new Cell();
	c.StyleIndex = 2;
	c.CellReference = "C" + index.ToString();
	c.CellValue = new CellValue(((double)rd.Next(10, 10000000) + rd.NextDouble()).ToString("f4"));
	r.Append(c);

	c = new Cell();
	c.StyleIndex = 3;
	c.CellReference = "D" + index.ToString();
	c.CellValue = new CellValue(((double)rd.Next(10, 10000) + rd.NextDouble()).ToString("f2"));
	r.Append(c);

	c = new Cell();
	c.StyleIndex = 7;
	c.CellReference = "E" + index.ToString();
	c.CellValue = new CellValue(((double)rd.Next(10, 1000) + rd.NextDouble()).ToString("f2"));
	r.Append(c);

	return r;
}

I am not going to even attempt to explain to you the various parts in detail. That might drive me insane. I’ll just broadly explain why some of the code parts exist.

In the CreateStylesheet() function, you have the CellFormat classes. These represent unique styling information. And for every unique style, you need to create a new CellFormat class. Let’s look at the new unique styles we have.

  • Header text, styled with Palatino Linotype font at 18pt
  • Column header text, Calibri font and size 11pt, bordered on all sides
  • Column header text, Calibri font and size 11pt, bordered on all sides, coloured orange
  • Forced text format, so long consecutive series of digits still render as text
  • DateTime format
  • Numeric value formatted with 4 decimal point
  • Numeric value formatted with 2 decimal point
  • Numeric value formatted with 2 decimal point and coloured orange

For background (and foreground in this case) colour, I added a new fill/patternFill with ARGB colour 00ff9728. This is in addition to the default no-patterns and gray-pattern. That orange colour is thus index 2 (0-based).

For borders, other than the default no-border version, I added the borders-on-all-sides version. This is border index 1 (0-based).

There are 4 different text formatting:

  • Datetime format
  • 4 decimal
  • 2 decimal
  • Forced text

These are represented with NumberFormat classes, and I’ve clumped them together.

Then comes the CellFormat classes. You … I … You know what, just read the code and see if you understand. I’ve already commented the index numbers above each unique style. These index numbers refer to the style index, not the border index or pattern index. These index numbers are the indices used when you want a particular cell to be formatted in a certain way.

For example, the cell containing the header text should have the style index 5, where the FontId is 1 (because we used the second [Spreadsheet] Font, not to confused with the System.Drawing.Font).

If you have a lot of unique styles to set, you have your work cut out for you.

Download the source code. Download the resulting Excel file.

There’s updated material and source code, together with more information on how to work with Open XML. Click here to find out more.

Comments

  1. Thanks for this great tutorial! This is a very useful walk through of working with Open XML. Keep up the fantastic posts! We’d love to hear more at http://www.facebook.com/office

    Cheers,
    Andy
    MSFT Office Outreach

  2. Hey thanks Andy! I hope this helps someone exploring Open XML.

  3. Vincent –

    This article, and your previous stylesheet article, have been completely invaluable. Thank you!

    I have a question, if you have time for an answer:

    I notice your cell widths are nicely expanded to fit the contents of each column. Does this happen automatically? I can’t find where you might be specifying width in your code.

    I ask because my columns are all fixed-width for whatever reason, and too narrow for some of my formatted numbers – they get replaced with “######” until I widen the column manually.

    Any advice?

    I’m also curious why you don’t specify “DataType” for your content cells?

  4. Hi Steven, about the nicely expanded columns… I, uh, cheated for the screenshot… hahaha…

    However, if you want to set the column widths, use the DocumentFormat.OpenXml.Spreadsheet.Column and Columns classes.

    After you create the Worksheet class, instantiate a Columns class. Then add individual Column classes to it. Then add the Columns class to the Worksheet class. It should look something like this:

    Worksheet ws = new Worksheet();
    Columns columns = new Columns();
    columns.Append(CreateColumnData(1, 10.7109375));
    columns.Append(CreateColumnData(2, 8.5703125));
    ws.Append(columns);

    And CreateColumnData() looks like:
    private static Column CreateColumnData(UInt32 ColumnIndex, double ColumnWidth)
    {
    Column column;
    column = new Column();
    column.Min = ColumnIndex;
    column.Max = column.Min;
    column.Width = ColumnWidth;
    column.CustomWidth = true;
    return column;
    }

    If column.Min is 3, and column.Max is 7, then you’ll be setting columns 3 to 7 with the same width with just one column class. I set column.Min = column.Max so I can set individual column widths with the function.

    And I don’t know how to calculate those magic number widths (with any amount of understanding, anyway…). If you have the Open XML SDK help document, look at the remarks for Column.Width. This is an extract:
    [Column width measured as the number of characters of the maximum digit width of the numbers 0, 1, 2, …, 9 as rendered in the Normal style’s font. There are 4 pixels of margin padding (two on each side), plus 1 pixel padding for the gridlines.]

    There’s a whole section on truncation of values and pixel to character width (as in font width). *shudder* I have no idea what that means.

    So I uh, cheated again… To get those widths, you create your Excel file as normal, then open it up. Then manually set the widths. Save it. Change extension to zip. Open up your sheet XML file, and look for this section:

    <cols>
    <col min=”1″ max=”1″ width=”16.5703125″ customWidth=”1″ />
    <col min=”2″ max=”2″ width=”21.140625″ customWidth=”1″ />
    <col min=”3″ max=”3″ width=”14″ customWidth=”1″ />
    </cols>

    There’s your width. Then go back to your code to plug that in.

    And finally, I don’t specify DataType because it’s an optional property. Its default value is the “number” type.

    I hope you find this useful.

  5. Thanks so much! 🙂

  6. Sander B says:

    Hi Vincent,

    this too is a great article!
    Have you been trying this with Ranges as well?
    Let’s say I wanted to put a border at the bottom of the range A1:O1?

    Any input would be greatly appreciated!

    Sander

  7. Hi Sander, ranges will work just fine. The example code I gave is for tabulated data, since that’s what Excel excels in (pun not intended).

    Try out your idea and code, and let me know how it goes. If you hit a problem, remember you can create the look you want in Excel first. Then reverse engineer to get the XML file, and find out what you need to do to create your intended effect.

  8. arif setiawan says:

    Hi Vincent,

    Great article !!
    I’ve been looking for a nice OpenXML example with styling without mentioning template.

    Maybe I missed my google, is there any C# library for OpenXML like PHPExcel?
    I think OpenXML SDK is too complicated.

    Thanks.

  9. Hi arif, you can try ExtremeML
    http://www.extrememl.com/

  10. arif setiawan says:

    Hi Vincent,
    I think, the problem with ExtremeML, for now, it does not extend to styling programmatically. It needs the excel template file pre-styled (I read this from ExtremeML tutorial).

  11. You can try Simple OOXML
    http://simpleooxml.codeplex.com/

    Or Office Open XML C# Library
    http://officeopenxml.sourceforge.net/

    I haven’t personally tried those, so you’ll have to see if they fit your purposes.

  12. arif setiawan says:

    Thank you Vincent.
    I will check both libraries.