This is a follow-up article to my previous post, OpenXML SDK 2.0:
Export a DataTable to Excel. In that post, I demonstrated how
to install and use the OpenXML SDK, and also provided sample code for
exporting a DataTable to Microsoft Excel 2007. I highly recommend
starting with the previous post if you are not familiar with the
SDK.
This article builds on the original code and demonstrates how to
apply style and value formatting to the cells inside the
workbook.
To enable value and text formatting for the resulting workbook,
I have created new helper methods.
Setting Colors and Styles
This first method will create a new Font object and add it to
the stylesheet collection within the workbook:
- private UInt32Value createFont(
-
Stylesheet styleSheet,
-
string fontName,
-
Nullable<double> fontSize,
-
bool isBold,
-
System.Drawing.
Color foreColor)
- {
-
-
Font font = new Font();
-
-
if (!string.IsNullOrEmpty(fontName))
-
{
-
FontName name = new
FontName()
-
{
-
Val
= fontName
-
};
-
font.Append(name);
-
}
-
-
if (fontSize.HasValue)
-
{
-
FontSize size = new
FontSize()
-
{
-
Val
= fontSize.Value
-
};
-
font.Append(size);
-
}
-
-
if (isBold == true)
-
{
-
Bold bold = new Bold();
-
font.Append(bold);
-
}
-
-
if (foreColor != null)
-
{
-
Color color = new
Color()
-
{
-
Rgb
= new HexBinaryValue()
-
{
-
Value
=
-
System.Drawing.
ColorTranslator.ToHtml(
-
System.Drawing.
Color.FromArgb(
-
foreColor.A,
-
foreColor.R,
-
foreColor.G,
-
foreColor.B)).Replace(
"#", "")
-
}
-
};
-
font.Append(color);
-
}
-
styleSheet.Fonts.Append(font);
-
UInt32Value result = styleSheet.Fonts.Count;
-
styleSheet.Fonts.Count++;
-
return result;
-
}
The return value of the method above is the index number of the
newly added Font object.
Next is the createFill method. This method works exactly like
the createFont method and also returns a new index value after the
Fill has been added to the workbook's styles collection:
- private UInt32Value createFill(
-
Stylesheet styleSheet,
- System.Drawing.Color
fillColor)
- {
- Fill fill =
new Fill(
-
new
PatternFill(
-
new ForegroundColor()
-
{
-
Rgb
= new HexBinaryValue()
-
{
-
Value
=
-
System.Drawing.
ColorTranslator.ToHtml(
-
System.Drawing.
Color.FromArgb(
-
fillColor.A,
-
fillColor.R,
-
fillColor.G,
-
fillColor.B)).Replace(
"#", "")
-
}
-
})
-
{
-
PatternType
= PatternValues.Solid
-
}
- );
-
styleSheet.Fills.Append(fill);
-
-
UInt32Value result =
styleSheet.Fills.Count;
- styleSheet.Fills.Count++;
-
return result;
- }
Finally, once you have created a Font and/or Fill, you need to
create a CellFormat object and add it to your styles
collection:
- private UInt32Value
createCellFormat(
-
Stylesheet styleSheet,
- UInt32Value
fontIndex,
-
UInt32Value fillIndex,
- UInt32Value
numberFormatId)
- {
- CellFormat cellFormat =
new CellFormat();
-
- if (fontIndex !=
null)
-
cellFormat.FontId =
fontIndex;
-
-
if (fillIndex !=
null)
-
cellFormat.FillId =
fillIndex;
-
- if (numberFormatId !=
null)
- {
-
cellFormat.NumberFormatId
= numberFormatId;
-
cellFormat.ApplyNumberFormat
= BooleanValue.FromBoolean(true);
- }
-
-
styleSheet.CellFormats.Append(cellFormat);
-
- UInt32Value result =
styleSheet.CellFormats.Count;
-
styleSheet.CellFormats.Count++;
- return result;
- }
Once you have created your CellFormat objects, you can apply
them to any cell value by setting the StyleIndex property for the
Cell object. This code demonstrates using all of the methods above
to create a header format for the first row of the resulting
workbook:
- //add column names to the first row
-
Row header = new Row();
-
header.RowIndex
= (UInt32)1;
-
-
Stylesheet styleSheet =
workbook.WorkbookStylesPart.Stylesheet;
-
-
//build the formatted header style
-
UInt32Value headerFontIndex =
-
createFont(
-
styleSheet,
-
"Arial",
-
12,
-
true,
-
System.Drawing.
Color.White);
-
//set the background color style
-
UInt32Value headerFillIndex =
-
createFill(
-
styleSheet,
-
System.Drawing.
Color.SlateGray);
-
//create the cell style by combining
font/background
-
UInt32Value headerStyleIndex =
-
createCellFormat(
-
styleSheet,
-
headerFontIndex,
-
headerFillIndex,
-
null);
-
-
foreach (DataColumn column
in table.Columns)
-
{
-
Cell headerCell = createTextCell(
-
table.Columns.IndexOf(column)
+ 1,
-
1,
-
column.ColumnName,
-
headerStyleIndex);
-
-
header.AppendChild(headerCell);
-
}
-
data.AppendChild(header);
The final piece of the solution is to add the styleIndex
parameter to the createTextCell method and set the StyleIndex
property on the Cell:
- //apply the cell style if supplied
- if
(styleIndex.HasValue)
- cell.StyleIndex =
styleIndex.Value;
Formatting Cell Values Correctly
The fourth parameter for the createCellFormat
method allows you to format cell values based on your data types.
Although you can create custom cell formats in the same manner that
you build the font and background color, the OpenXML specification
(detailed in the section 8.8.30 of of the
Standard ECMA-376 Office Open XML File Formats second edition
draft) has a set of implied cell formats, meaning you can
reference them without explicitly adding them to the StyleSheet.
Here is a list of the implied formats from the draft:
| ID |
Format Code |
| 0 |
General |
| 1 |
0 |
| 2 |
0.00 |
| 3 |
#,##0 |
| 4 |
#,##0.00 |
| 9 |
0% |
| 10 |
0.00% |
| 11 |
0.00E+00 |
| 12 |
# ?/? |
| 13 |
# ??/?? |
| 14 |
mm-dd-yy |
| 15 |
d-mmm-yy |
| 16 |
d-mmm |
| 17 |
mmm-yy |
| 18 |
h:mm AM/PM |
| 19 |
h:mm:ss AM/PM |
| 20 |
h:mm |
| 21 |
h:mm:ss |
| 22 |
m/d/yy h:mm |
| 37 |
#,##0 ;(#,##0) |
| 38 |
#,##0 ;[Red](#,##0) |
| 39 |
#,##0.00;(#,##0.00) |
| 40 |
#,##0.00;[Red](#,##0.00) |
| 45 |
mm:ss |
| 46 |
[h]:mm:ss |
| 47 |
mmss.0 |
| 48 |
##0.0E+0 |
| 49 |
@ |
If you run the sample code provided in the first article, one
thing you will notice about the resulting Excel document is that
even cells with numeric values are treated as text. Anyone familiar
with Excel has seen this pop-up tag on many occasions:

Error correction in Excel highlights the values that are
stored as text.
The reason behind this was due to the way cell values were added
in the createTextCell method. Because the DataType
of the cell is set to InlineString, the content passed into the
cell was always treated as text. To overcome this limitation, I
created another new method, createValueCell.
First, I created three new basic styles using the createCellFormat
method (using the cell formats from the table above):
/*
* Create a set of basic cell styles for specific
formats...
* If you are controlling your table then you can
simply create the styles you need,
* this set of code is still intended to be
generic.
*/
_numberStyleId = createCellFormat(styleSheet, null,
null, UInt32Value.FromUInt32(3));
_doubleStyleId = createCellFormat(styleSheet, null,
null, UInt32Value.FromUInt32(4));
_dateStyleId = createCellFormat(styleSheet, null,
null, UInt32Value.FromUInt32(14));
You will notice in the code above that each "style" was created
using the same createCellFormat method; the font and fill values
have just been omitted.
Now while the table rows are being iterated through, we can
check the data type of each value and decide whether or not to
apply one of the formatted styles to each cell:
- private Row createContentRow(
-
DataRow dataRow,
-
int rowIndex)
-
{
-
Row row = new Row
-
-
{
-
RowIndex
= (UInt32)rowIndex
-
};
-
-
Nullable<uint> styleIndex =
null;
-
double doubleValue;
-
int intValue;
-
DateTime dateValue;
-
-
for (int i = 0; i <
dataRow.Table.Columns.Count; i++)
-
{
-
Cell dataCell;
-
-
//check the data type of the cell content to apply basic
formatting
-
if
(DateTime.TryParse(dataRow[i].ToString(),
out dateValue))
-
{
-
styleIndex
= _dateStyleId;
-
//the ToOADate method addresses how Excel stores Date
values...
-
dataCell
= createValueCell(i + 1, rowIndex, dateValue.ToOADate().ToString(),
styleIndex);
-
}
-
else if
(int.TryParse(dataRow[i].ToString(), out
intValue))
-
{
-
styleIndex
= _numberStyleId;
-
dataCell
= createValueCell(i + 1, rowIndex, intValue, styleIndex);
-
}
-
else if
(Double.TryParse(dataRow[i].ToString(),
out doubleValue))
-
{
-
styleIndex
= _doubleStyleId;
-
dataCell
= createValueCell(i + 1, rowIndex, doubleValue, styleIndex);
-
}
-
else
-
{
-
//assume the value is string, use the InlineString value
type...
-
dataCell
= createTextCell(i + 1, rowIndex, dataRow[i],
null);
-
}
-
-
-
row.AppendChild(dataCell);
-
styleIndex
=
null;
-
}
-
return row;
-
}
In most cases, you will know when to apply the correct styles to
each value based on your source data. In either case, the
createValueCell method will store the cell content
correctly, and also allow you to format those values as needed:
- private Cell createValueCell(
-
int columnIndex,
- int rowIndex,
-
object cellValue,
-
Nullable<uint>
styleIndex)
- {
- Cell cell =
new Cell();
-
cell.CellReference =
getColumnName(columnIndex) + rowIndex;
- CellValue value =
new CellValue();
- value.Text
=
cellValue.ToString();
-
-
//apply the cell style if
supplied
- if
(styleIndex.HasValue)
-
cell.StyleIndex =
styleIndex.Value;
-
-
cell.AppendChild(value);
-
-
return cell;
- }
The final result looks much more professional than the
original:

This code does a lot to expand on the original article, but it
still only covers a small percentage of what is possible within the
OpenXML SDK. Hopefully these samples will help you become familiar
with how the SDK works and you can customize your own solutions to
fit your requirements. I am also providing an
updated sample Visual Studio 2008 project with this article so
you can review the entire solution.