VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > Python基础教程 >
  • C#教程之Word报告自动生成(例如 导出数据库结构

       将很早之前写的一个小组件重新整理优化一下,做成一个通用的功能。适用于导出数据库的结构(表、字段等)到Word或将体检数据自动生成Word版的体检报告等。代码:Github

一、主要需要完成功能:

1. 灵活的配置规则及word样式设置(文本、表格、图表、颜色等).

2. 支持表格.

3. 支持图表.

4. 支持章节内容循环生成.

5. 支持目录.

6.支持文档结构图

7.更新指定位置的文字

8.支持pdf导出.

 

最后结果如下:

                                                                            图一

                                    图二

                                         图三

二、需求分析与实现方式

   功能主要涉及3个比较重要的部分:数据源、Word样式、配置规则。 

     为了简单,数据源决定采用一个存储过程返回Dataset的方式, 整张报告的数据来源于此Dataset的多个Datatable.

  样式与配置:首先想到的是写一个config文件,所有配置都放到一个文件里,然后将数据按照这个规则生成word。但无疑这样的配置项太多了,关键是“样式”问题,比如字体、颜色、表格宽度.....想想就头大。而且没有“所见即所得”的效果,配置完都不知道啥样。

后来决定采取修改的方式, 先以一个word文件作为模板,在模板中定义好上面提到的“样式”,然后在模板中做一个个标记,然后将数据按照规则更新到对应的标记。

                                                       图四

    而这个标记我们用到了word的一个叫【书签】的功能,打开word按ctrl+shift+F5, 打开书签功能,如下图:

                                 图五

这样将【规则】通过一系列规则的【书签】定义到word模板中。

三、规则配置

  思路确定了,那就开始设计如何通过【书签】将规则定义到word模板中去,这里决定将所有规则都通过【书签】实现,而放弃config文件的方式,这个更统一而且直观一些。

A.循环

      以图四为例,数据库有多少张表是不固定的,我们在制作模板的时候不可能先画好N(N为表的总数)个表格等待数据填充, 这里就会需要遍历数据源中提供的所有表结构数据,然后逐一形成表格。这里就需要将图四中的表格循环一下,自动复制生成多个这样的表格。当然,这只是一种情况,还有可能会出现循环嵌套循环的情况,那么我将这个循环定义成一个书签的时候按照这样的格式:

      loop_级别_表序号_filter_名称

含义如下:

     loop:代表这是一个循环。

     级别:默认文档级别为0,出现的第一层循环为1,其内部若再次嵌套循环则级别为2,依次类推。

     表序号:取Dataset中的第几张表(从1开始)

     filter:循环的时候可能会用到对datatable的查找过滤,在此写出,多个字段用XX隔开(因为此处不允许有下划线外其他特殊字符, 就用这个XX吧 )

     名称:loop名称,方便与其他 loop区别

 B.更新指定位置的文字

    如图四中的【服务器名】、【表总数】等,只需要替换对应的文字即可:

    label_级别_名称

含义如下:

     label:代表这是一个label。

     级别:默认文档级别为0,出现的第一层循环为1,其内部若再次嵌套循环则级别为2,依次类推。

     名称:label名称

     注意这里省略了表序号,当级别为0的时候 ,自动取最后一个datatable中的数据,因为这个label经常会用到其他表汇总的数据,可能会用到之前几张表的数据,所以放在其他表都处理好后。当级别为1的时候,自然取该级别循环的行数据。

C.表格

     表格的配置原本也想对表格添加书签,后来发现有个表格属性, 觉得写在这里更好一些。

 如上图所示,【标题】格式为:table_级别_取Dataset中的第几张表(从1开始)_filter字段多个用XX隔开(此处不允许有下划线外其他特殊字符, 就用这个XX吧 )_名称

【说明】为可选项,若需要合计行, 则需要标识, summary或缩写s: [合计]行是模板中表格的第几行   summaryfilter或缩写sf:数据集进一步filter到summary行的条件(因为一个表格只取一个Datatable,通过一个标识指定了哪些datarow是用来作为合计的)

D.图表

同样为了方便将配置写在了【标题】,图表生成后会将名称修改过来。

配置格式为:chart_级别_取Dataset中的第几张表(从1开始)_filter字段多个用XX隔开(此处不允许有下划线外其他特殊字符, 就用这个XX吧 )_chart名称_是否将Datatable的columnName作为第一行_从datatable第几列开始(列起始为1)_截止列,

如下图所示配置即可。

 

E.目录

无需标识, 模板中添加目录, 当内容处理完成之后, 会根据处理后的结果动态更新目录.

 

四、主要代码

复制代码
  1 using System;
  2 using System.Collections.Generic;
  3 using System.Data;
  4 using System.Diagnostics;
  5 using System.IO;
  6 using System.Linq;
  7 using System.Reflection;
  8 using Excel = Microsoft.Office.Interop.Excel;
  9 using Word = Microsoft.Office.Interop.Word;
 10 
 11 namespace FlyLolo.WordReport.Demo
 12 {
 13     public class WordReportHelper
 14     {
 15         private Word.Application wordApp = null;
 16         private Word.Document wordDoc = null;
 17         private DataSet dataSource = null;
 18         private object line = Word.WdUnits.wdLine;
 19         private string errorMsg = "";
 20 
 21         /// <summary>
 22         /// 根据模板文件,创建数据报告
 23         /// </summary>
 24         /// <param name="templateFile">模板文件名(含路径)</param>
 25         /// <param name="newFilePath">新文件路径)</param>
 26         /// <param name="dataSource">数据源,包含多个datatable</param>
 27         /// <param name="saveFormat">新文件格式:</param>
 28         public bool CreateReport(string templateFile, DataSet dataSource, out string errorMsg, string newFilePath, ref string newFileName, int saveFormat = 16)
 29         {
 30             this.dataSource = dataSource;
 31             errorMsg = this.errorMsg;
 32             bool rtn = OpenTemplate(templateFile)
 33                 && SetContent(new WordElement(wordDoc.Range(), dataRow: dataSource.Tables[dataSource.Tables.Count - 1].Rows[0]))
 34                 && UpdateTablesOfContents()
 35                 && SaveFile(newFilePath, ref newFileName, saveFormat);
 36 
 37             CloseAndClear();
 38             return rtn;
 39         }
 40 
 41         /// <summary>
 42         /// 打开模板文件
 43         /// </summary>
 44         /// <param name="templateFile"></param>
 45         /// <returns></returns>
 46         private bool OpenTemplate(string templateFile)
 47         {
 48             if (!File.Exists(templateFile))
 49             {
 50                 return false;
 51             }
 52 
 53             wordApp = new Word.Application();
 54             wordApp.Visible = true;//使文档可见,调试用
 55             wordApp.DisplayAlerts = Word.WdAlertLevel.wdAlertsNone;
 56             object file = templateFile;
 57             wordDoc = wordApp.Documents.Open(ref file, ReadOnly: false);
 58             return true;
 59         }
 60 
 61         /// <summary>
 62         /// 为指定区域写入数据
 63         /// </summary>
 64         /// <param name="element"></param>
 65         /// <returns></returns>
 66         private bool SetContent(WordElement element)
 67         {
 68             string currBookMarkName = string.Empty;
 69             string startWith = "loop_" + (element.Level + 1).ToString() + "_";
 70             foreach (Word.Bookmark item in element.Range.Bookmarks)
 71             {
 72                 currBookMarkName = item.Name;
 73 
 74                 if (currBookMarkName.StartsWith(startWith) && (!currBookMarkName.Equals(element.ElementName)))
 75                 {
 76                     SetLoop(new WordElement(item.Range, currBookMarkName, element.DataRow, element.GroupBy));
 77                 }
 78 
 79             }
 80 
 81             SetLabel(element);
 82 
 83             SetTable(element);
 84 
 85             SetChart(element);
 86 
 87             return true;
 88         }
 89 
 90         /// <summary>
 91         /// 处理循环
 92         /// </summary>
 93         /// <param name="element"></param>
 94         /// <returns></returns>
 95         private bool SetLoop(WordElement element)
 96         {
 97             DataRow[] dataRows = dataSource.Tables[element.TableIndex].Select(element.GroupByString);
 98             int count = dataRows.Count();
 99             element.Range.Select();
100 
101             //第0行作为模板  先从1开始  循环后处理0行;
102             for (int i = 0; i < count; i++)
103             {
104 
105                 element.Range.Copy();  //模板loop复制
106                 wordApp.Selection.InsertParagraphAfter();//换行 不会清除选中的内容,TypeParagraph 等同于回车,若当前有选中内容会被清除. TypeParagraph 会跳到下一行,InsertParagraphAfter不会, 所以movedown一下.
107                 wordApp.Selection.MoveDown(ref line, Missing.Value, Missing.Value);
108                 wordApp.Selection.Paste(); //换行后粘贴复制内容
109                 int offset = wordApp.Selection.Range.End - element.Range.End; //计算偏移量
110 
111                 //复制书签,书签名 = 模板书签名 + 复制次数
112                 foreach (Word.Bookmark subBook in element.Range.Bookmarks)
113                 {
114                     if (subBook.Name.Equals(element.ElementName))
115                     {
116                         continue;
117                     }
118 
119                     wordApp.Selection.Bookmarks.Add(subBook.Name + "_" + i.ToString(), wordDoc.Range(subBook.Start + offset, subBook.End + offset));
120                 }
121 
122                 SetContent(new WordElement(wordDoc.Range(wordApp.Selection.Range.End - (element.Range.End - element.Range.Start), wordApp.Selection.Range.End), element.ElementName + "_" + i.ToString(), dataRows[i], element.GroupBy));
123             }
124 
125             element.Range.Delete();
126 
127             return true;
128         }
129 
130         /// <summary>
131         /// 处理简单Label
132         /// </summary>
133         /// <param name="element"></param>
134         /// <returns></returns>
135         private bool SetLabel(WordElement element)
136         {
137             if (element.Range.Bookmarks != null && element.Range.Bookmarks.Count > 0)
138             {
139                 string startWith = "label_" + element.Level.ToString() + "_";
140                 string bookMarkName = string.Empty;
141                 foreach (Word.Bookmark item in element.Range.Bookmarks)
142                 {
143                     bookMarkName = item.Name;
144 
145                     if (bookMarkName.StartsWith(startWith))
146                     {
147                         bookMarkName = WordElement.GetName(bookMarkName);
148 
149                         item.Range.Text = element.DataRow[bookMarkName].ToString();
150                     }
151                 }
152             }
153 
154             return true;
155         }
156 
157         /// <summary>
158         /// 填充Table
159         /// </summary>
160         /// <param name="element"></param>
161         /// <returns></returns>
162         private bool SetTable(WordElement element)
163         {
164             if (element.Range.Tables != null && element.Range.Tables.Count > 0)
165             {
166                 string startWith = "table_" + element.Level.ToString() + "_";
167                 foreach (Word.Table table in element.Range.Tables)
168                 {
169                     if (!string.IsNullOrEmpty(table.Title) && table.Title.StartsWith(startWith))
170                     {
171                         WordElement tableElement = new WordElement(null, table.Title, element.DataRow);
172 
173                         TableConfig config = new TableConfig(table.Descr);
174 
175                         object dataRowTemplate = table.Rows[config.DataRow];
176                         Word.Row SummaryRow = null;
177                         DataRow SummaryDataRow = null;
178                         DataTable dt = dataSource.Tables[tableElement.TableIndex];
179                         DataRow[] dataRows = dataSource.Tables[tableElement.TableIndex].Select(tableElement.GroupByString); ;
180 
181                         if (config.SummaryRow > 0)
182                         {
183                             SummaryRow = table.Rows[config.SummaryRow];
184                             SummaryDataRow = dt.Select(string.IsNullOrEmpty(tableElement.GroupByString) ? config.SummaryFilter : tableElement.GroupByString + " and  " + config.SummaryFilter).FirstOrDefault();
185                         }
186 
187                         foreach (DataRow row in dataRows)
188                         {
189                             if (row == SummaryDataRow)
190                             {
191                                 continue;
192                             }
193 
194                             Word.Row newRow = table.Rows.Add(ref dataRowTemplate);
195                             for (int j = 0; j < table.Columns.Count; j++)
196                             {
197                                 newRow.Cells[j + 1].Range.Text = row[j].ToString(); ;
198                             }
199 
200                         }
201 
202                         ((Word.Row)dataRowTemplate).Delete();
203 
204                         if (config.SummaryRow > 0 && SummaryDataRow != null)
205                         {
206                             for (int j = 0; j < SummaryRow.Cells.Count; j++)
207                             {
208                                 string temp = SummaryRow.Cells[j + 1].Range.Text.Trim().Replace("\r\a", "");
209 
210                                 if (!string.IsNullOrEmpty(temp) && temp.Length > 2 && dt.Columns.Contains(temp.Substring(1, temp.Length - 2)))
211                                 {
212                                     SummaryRow.Cells[j + 1].Range.Text = SummaryDataRow[temp.Substring(1, temp.Length - 2)].ToString();
213                                 }
214                             }
215                         }
216 
217                         table.Title = tableElement.Name;
218                     }
219 
220 
221                 }
222             }
223 
224             return true;
225         }
226 
227         /// <summary>
228         /// 处理图表
229         /// </summary>
230         /// <param name="element"></param>
231         /// <returns></returns>
232         private bool SetChart(WordElement element)
233         {
234             if (element.Range.InlineShapes != null && element.Range.InlineShapes.Count > 0)
235             {
236                 List<Word.InlineShape> chartList = element.Range.InlineShapes.Cast<Word.InlineShape>().Where(m => m.Type == Word.WdInlineShapeType.wdInlineShapeChart).ToList();
237                 string startWith = "chart_" + element.Level.ToString() + "_";
238                 foreach (Word.InlineShape item in chartList)
239                 {
240                     Word.Chart chart = item.Chart;
241                     if (!string.IsNullOrEmpty(chart.ChartTitle.Text) && chart.ChartTitle.Text.StartsWith(startWith))
242                     {
243                         WordElement chartElement = new WordElement(null, chart.ChartTitle.Text, element.DataRow);
244 
245                         DataTable dataTable = dataSource.Tables[chartElement.TableIndex];
246                         DataRow[] dataRows = dataTable.Select(chartElement.GroupByString);
247 
248                         int columnCount = dataTable.Columns.Count;
249                         List<int> columns = new List<int>();
250 
251                         foreach (var dr in dataRows)
252                         {
253                             for (int i = chartElement.ColumnStart == -1 ? 0 : chartElement.ColumnStart - 1; i < (chartElement.ColumnEnd == -1 ? columnCount : chartElement.ColumnEnd); i++)
254                             {
255                                 if (columns.Contains(i) || dr[i] == null || string.IsNullOrEmpty(dr[i].ToString()))
256                                 {
257 
258                                 }
259                                 else
260