Openxml关于excel样式更改的那点事

Openxml关于excel样式更改的那点事


最近在做关于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出来的,所以没有特定的上下文不保证代码好使,主要还是写了个实现的原理。

你可能感兴趣的:(感悟随笔)