最近在做关于Excel表格处理部分的事情。之前用的是oledb方式将excel导入datatable并且最后的结果也是由oledb方式导出的excel。本来相安无事,但是新的需求需要对excel的样式进行更改时,我就懵圈了。暂时用dom操作先交了任务,但是还隐藏着一个大bug:在线程里的excel程序并不能完全杀死。思来想去,还是决定用openxml解决这个问题,以防万一。
以上是一些废话,再多说一句:如果对实现过程不感兴趣,只在乎结果,那么我推荐closeXMl,具体的东西自己去搜吧,只是顺带说一下。
样式表
07之后的office系列都是用xml的方式存储。将一个.xlsx的文件后缀名改为.rar打开之后会发现里面有个xl文件夹,其中styles.xml就是整个文件的样式存储地。目前的理解是样式表初始化的时候需要包含几个默认值。若是单元格(cell)未指定styleIndex时,会自动默认的去找id=0的cellFormat作为自己的样式。cellFormats里有很多的cellFormat,一个cellFormat里包含了多少可配的属性,font,fill,border,numberFormat(大概叫这个)等等,font改变字体,若需要定义字体格式请顺手把aplyFont设置为true,同理的还有fill,border。实践发现,初始化的时候最好搞一下border,因为默认会取id=0的border,还是自己搞出来个初始化比较好。同时,fill里面包含一个默认选项none,即不填充,还必须包含一个patternType=PatternValues.Gray125的属性值。尽管你并不需要填充这个颜色,但是为了某种原因,还是要在初始化的时候设置一下。今天查了资料发现fill属性中默认的前两个为fillid=0的项必须为不填充,fillid=1的项为gray125,自定义项从id=2,也就是第三项开始设置。不这样设定无法正常打开excel,office会提示你必须修复,而修复的结果是将以上两个提到的属性强制添加并覆盖你的属性。同时需要提及一下的是fill里的count并不会自己进行统计,因为只是xml文件中的一个属性值,需要自己进行赋值操作。
如下为初始化设置:
SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.
Create(filepath, SpreadsheetDocumentType.Workbook);
// Add a WorkbookPart to the document.
WorkbookPart workbookpart = spreadsheetDocument.AddWorkbookPart();
workbookpart.Workbook = new Workbook();
WorkbookStylesPart stylepart = workbookpart.AddNewPart();
stylepart.Stylesheet = new Stylesheet();
stylepart.Stylesheet.Fonts = new Fonts();
stylepart.Stylesheet.Fills = new Fills(
new Fill(new PatternFill() { PatternType = PatternValues.None }),
new Fill(new PatternFill() { PatternType=PatternValues.Gray125})
);
stylepart.Stylesheet.Borders = new Borders(
new Border(
new RightBorder(),
new TopBorder(),
new BottomBorder(),
new DiagonalBorder())
);
stylepart.Stylesheet.CellFormats = new CellFormats();
Stylesheet styleSheet = stylepart.Stylesheet;
var fontIndex = createFont(styleSheet, "Microsoft YaHei", (double)11, false, System.Drawing.Color.Black);
_fontStyleIndex = createCellFormat(styleSheet, fontIndex, null, null);
var fillIndex = createFill(styleSheet, System.Drawing.Color.FromArgb(177, 160, 199));
_fillRedStyleIndex = createCellFormat(styleSheet, fontIndex, fillIndex, null);
关于openxml的操作
上面这个链接讲的内容挺好,我基本也是参考的这篇文章,但是有几个问题是搞了好久自己发现的,特别的写下来留待查看。
顺便将我用的createFont,createFill,createCellFormat粘出来供参考。
//createFont 返回的是一个Uint32Value 记录该样式索引号
private UInt32Value createFont(Stylesheet styleSheet, string fontName, Nullable fontSize, bool isBold, System.Drawing.Color foreColor)
{
if (styleSheet.Fonts.Count == null)
{
styleSheet.Fonts.Count = (UInt32Value)0;
}
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;
}
//createFill 创建填充方案,生成一个索引值与该方案对应
private UInt32Value createFill(Stylesheet styleSheet, System.Drawing.Color fillColor)
{
if (styleSheet.Fills.Count == null)
{
styleSheet.Fills.Count = (UInt32Value)2;
}
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;
}
//createCellFormat 最为关键的函数,也是excel中cell关联的样式。目前经测试发现初始化时至少需要提供字体样式,填充样式,边界样式。我不需要设定border,所以我的borderId直接赋值0,指向上文中初始化生成的无边框的border样式。
cellformat的作用就是将设定好的字体样式,填充样式,边界样式,通过FontId,FillId,BorderId关联起来,生成一个新的cellFormat,按照id号排列,0起始,可在/xl/style.xml中查看。(即将来用的styleIndex所对应的样式)。同时一定记得设置applyFont属性(应用字体改变,不然关联上了还是用默认的font样式),
private UInt32Value createCellFormat(Stylesheet styleSheet, UInt32Value fontIndex, UInt32Value fillIndex, UInt32Value numberFormatId)
{
if (styleSheet.CellFormats.Count == null)
{
styleSheet.CellFormats.Count = (UInt32Value)0;
}
CellFormat cellFormat = new CellFormat();
cellFormat.BorderId = 0;
if (fontIndex != null)
{
cellFormat.ApplyFont = true;
cellFormat.FontId = fontIndex;
}
if (fillIndex != null)
{
cellFormat.FillId = fillIndex;
cellFormat.ApplyFill = true;
}
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;
}
感觉说的不够清楚,简单举例:fontId1=1 为自己设置的宋体,fonid2=2为微软雅黑且forecolor设置为红色,fillId0=0,fillId1=1被office占用,fillid2=2为自定义填充蓝色,fillId3=3为自定义填充紫色,borderId=0还是无边框。那么
第一次调用
Uint32Value result[1]=createCellFormat(stylesheet,fontid2,fillId0,null)
得到result[1]=0;那么现在生成的样式就为红色的微软雅黑字体,背景无填充,无边框的状态。因为result【1】=0,那么当Cell不指定其styleIndex时,会默认取0,即当前excel默认字体为微软雅黑且字体色为红色。
第二次调用:
Uint32Value result[2]=createCellFormat(stylesheet,fontid1,fillId3,null);
会得到result[2]=1;
那么,当讲cell.styleIndex=1时,该cell将会变为宋体黑字,背景色为紫色,无边框的样式。
依此类推,id自增,对应相应的样式。
特别的,提一下关于cell单元格style的更改。有两种,一种是赋值之前将cell.styleIndex的设置好。另一种:已经赋值结束后根据reference查找到特定的 cell更改其styleindex。
Cell specitalCell = new Cell();
contentRow = givenTable.Rows[i];
String position = "D" + (i + 2);
specitalCell = sheetData.Descendants().Where(c => c.CellReference == position).First();
specitalCell.StyleIndex = 0;//这里指定上面创建的cellformat的ID。 |
以上大概自己看的可能性偏大,所以就挑自己认为重要的记了一下,若有人不懂可以提问哈~有人看就再写点详细的,没人看的话自己看这样足够。
第一次写,想表达的东西太多,所以有点啰嗦,有兴趣的人仔细看,没兴趣的也可以看看代码参考下~。
因为代码是从我工程里直接copy出来的,所以没有特定的上下文不保证代码好使,主要还是写了个实现的原理。