思想决定人生,态度改变一切

成功者找方法,失败者找借口! 做事先做人;安分做人,本分做事!

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
在C#中利用Excel做高级报表

Visual Studio.Net 自2001年2月问世以来,受到越来越多人的喜爱,C#做为主力军,集VB、Delphi的简单和VC的简炼与强大于一体,更是让许多人爱不释手,纷纷倒向它的怀抱。通常的软件都要用到数据库,数据库中必然要用到报表,在Visual Studio.Net中自带了一个水晶报表,虽然功能十分强大,但市面上相关资料非常缺乏,网上全面介绍其使用的文章也屈指可数。Excel是微软公司办公自动化套件中的一个软件,主要是用来处理电子表格。Excel以其功能强大,界面友好等受到了许多用户的欢迎,几乎每一台机器都安装了它,因此,我们可以将数据导入Excel进行排版。

由于Excel的格式是封闭的,无法直接创建一个Excel文件然后来排版,只有借助Com组件来完成,同样,介绍用C#操作Excel的文章也就那么几篇,基本上都是告诉你如何新建一个Excel文件,然后,将数据写入某单元格,最多再零星告诉你如何合并单元格,真正使用起来根本无法用C#做出漂亮报表。本文巧妙利用Excel的宏来自动排版,大大减少了工作量,而且可以随时修改模板而无须修改程序,非常实用。

本人使用的是Office 2000,操作系统为windows 2000 professinal,为使问题简单化,这里不介绍数据库的知识,我一个二维数组来代表一个数据库中的表,我们的目的是将该数组放到Excel中,并排版成需要的格式,数组如下:

车牌号 类 型 品 牌 型 号 颜 色 附加费证号 车架号
浙KA3676 危险品 货车 铁风SZG9220YY 1110708900 022836
浙KA4109 危险品 货车 解放CA4110P1K2 223132 010898
浙KA0001A 危险品 货车 南明LSY9190WS 1110205458 0474636
浙KA0493 上普货 货车 解放LSY9190WS 1110255971 0094327
浙KA1045 普货 货车 解放LSY9171WCD 1110391226 0516003
浙KA1313 普货 货车 解放9190WCD 1110315027 0538701
浙KA1322 普货 货车 解放LSY9190WS 24323332 0538716
浙KA1575 普货 货车 解放LSY9181WCD 1110314149 0113018
浙KA1925 普货 货车 解放LSY9220WCD 1110390626 00268729
浙KA2258 普货 货车 解放LSY9220WSP 1110481542 00320

为了在C#中使用Excel,我们要先做一点准备工作,通过查找(前提是你安装Visual Studio.Net和Excel 2000),在你的计算机中找到TlbImp和Excel9.olb,将他们复制到一个文件夹中,在DOS窗口中执行 TlbImp Excel9.olb,这时会产生以下三个文件:Excel.dll、Office.dll和VBIDE.dll。

我们来完成两项任务,一是按网上文章介绍的方法,增加将数据写入一个Excel文件,也就是做一个简单报表,二是用Excel创建一个文件,然后以此文件为模板生成高级报表。

打开Visual Studio.Net,新建一个C#的windows应用程序,取名为MyExcel。根据个人爱好,对窗口做一些美化工作,然后放两个按钮:btnNormal和btnAdvance,Caption分别为“普通报表”和“高级报表”。

打开Visual Studio.Net,新建一个C#的windows应用程序,取名为MyExcel。根据个人爱好,对窗口做一些美化工作,然后放两个按钮:btnNormal和btnAdvance,Caption分别为“普通报表”和“高级报表”。

点“打开”按钮,再点“确定”按钮。

切换到代码窗口中,在文件头添加下面两个引用:

using System.IO; using System.Reflection;

再添加一个二维数组来表示数据表:

private string [,] myData= { {"车牌号","类型","品 牌","型 号","颜 色","附加费证号","车架号"}, {"浙KA3676","危险品","货车","铁风SZG9220YY","白","1110708900","022836"}, {"浙KA4109","危险品","货车","解放CA4110P1K2","白","223132","010898"}, {"浙KA0001A","危险品","货车","南明LSY9190WS","白","1110205458","0474636"}, {"浙KA0493","上普货","货车","解放LSY9190WS","白","1110255971","0094327"}, {"浙KA1045","普货","货车","解放LSY9171WCD","蓝","1110391226","0516003"}, {"浙KA1313","普货","货车","解放9190WCD","蓝","1110315027","0538701"}, {"浙KA1322","普货","货车","解放LSY9190WS","蓝","24323332","0538716"}, {"浙KA1575","普货","货车","解放LSY9181WCD","蓝","1110314149","0113018"}, {"浙KA1925","普货","货车","解放LSY9220WCD","蓝","1110390626","00268729"}, {"浙KA2258","普货","货车","解放LSY9220WSP","蓝","111048152","00320"} };

切换回设计窗口,双击“普通报表”按钮,设计普通报表,代码如下:

private void btnNormal_Click(object sender, System.EventArgs e) { //创建一个Excel文件 Excel.Application myExcel = new Excel.Application ( ) ; myExcel.Application.Workbooks.Add ( true ) ; //让Excel文件可见 myExcel.Visible=true; //第一行为报表名称 myExcel.Cells[1,4]="普通报表"; //逐行写入数据, for(int i=0;i<11;i++) { for(int j="0;j<7;j++) " { //以单引号开头,表示该单元格为纯文本 myExcel.Cells[2+i,1+j]="'"+myData[i,j]; } } }

说明一下,Cells[2,1]指第2行第1个单元格,是以1为基准的,而在C#中的数组是以0为基准的,另外,我们还发现,对于编号之类的数据,实际是文本,而Excel将它认成了数字,由于太长,自动换成了科学计数,这不是我们要求的,在Excel中,如果某单元格以单引号“’”开头,表示该单元格为纯文本,因此,我们在每个单元格前面加单引号。

运行结果如下:

可以看出,该报表非常简陋,标题行没有合并局,字体大小也不合适,连表格线都没有。当然,我们可以写代码来设置单元格字体、大小等等工作,这类技巧网上很多,但如果真要用C#来完成,是一件非常难的事情,还有个办法就是将想要的操作录制成宏,研究一下宏代码,但宏是用VBA写的,要转换成果C#可不是件容易的事情。

第一种办法不是本文的重点,就到此为止

下面进行高级报表设计,该方法的原理为:首先打开Excel,按照要求排好版,保存为一个文件做为模板,然后在C#中将该文件复制为一个新文件,在指定位置填入数据就可以了,为了添加表格线,我们录制了一个宏,在C#中执行该宏即可。

参考模板如下:

当然,你还可以排得更漂亮,因为是单纯的Excel操作,不需要特殊说明。如果记录很多,往往一页无法打印完成,我们要求在每一页都显示报表标题和小标题,也就是上图中的第1、2行,这里有一个技巧:选择Excel的菜单“文件”→“页面设置”,选择“工作表”,在“顶端标题行”后的框中输入“$1:$2”,也就是1~2行,当然,你也点右边的红箭头,然后用鼠标选择。当你的记录超过一页时,会自动在下一页加入标题,非常方便。

表格中目前还没有表格线,因为我们不知道到底有多少数据,因此,也无法知道为多少单元格设置边框,我们借助宏来完成。

按下面步骤录制一个宏:

1、随便选择几个单元格; 2、选择菜单“工具”→“宏”→“录制新宏”,输入宏的名称,就用默认的“宏1”吧,点确定; 3、选择菜单“格式”→“单元格”,在对话框中选择“边框”,将内边框和外边框均选中,按确定; 4、此时,刚才选择的单元格就有了边框,再点工具栏中的“停止录制宏”按钮 来结束宏录制。

刚才的操作目的是录制宏而不是加边框,因此,我们按“Ctrl+Z”来撤消刚才的操作,通过按Alt+F8来调出宏,选择“宏1”,选择编辑,看到的代码应该如下:

Sub 宏1() ' 宏1 Macro ' xx 记录的宏 2003-5-1 Selection.Borders(xlDiagonalDown).LineStyle = xlNone Selection.Borders(xlDiagonalUp).LineStyle = xlNone With Selection.Borders(xlEdgeLeft) .LineStyle = xlContinuous .Weight = xlThin .ColorIndex = xlAutomatic End With With Selection.Borders(xlEdgeTop) .LineStyle = xlContinuous .Weight = xlThin .ColorIndex = xlAutomatic End With With Selection.Borders(xlEdgeBottom) .LineStyle = xlContinuous .Weight = xlThin .ColorIndex = xlAutomatic End With With Selection.Borders(xlEdgeRight) .LineStyle = xlContinuous .Weight = xlThin .ColorIndex = xlAutomatic End With With Selection.Borders(xlInsideVertical) .LineStyle = xlContinuous .Weight = xlThin .ColorIndex = xlAutomatic End With With Selection.Borders(xlInsideHorizontal) .LineStyle = xlContinuous .Weight = xlThin .ColorIndex = xlAutomatic End With End Sub

图中表的数据都是供排版参考用的,结束前将实际内容删除掉,即只留下排好版的格式,包括标题、列标题等,将实际内容去掉。将文件保存到一个地方,如D:\Normal.xls,当然,实际开发时,可以放到执行文件所在目录下,为了防止用户随便修改,可以将文件名改为normal.rpt之类。

有了上面的准备,我们就可以在C#中使用了,添加“高级报表”按钮的响应代码。下面是全部代码清单。

using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.IO; using System.Reflection; namespace MyExcel { /// <summary> /// Form1 的摘要说明。 /// </summary> public class Form1 : System.Windows.Forms.Form { private System.Windows.Forms.Button btnNormal; private System.Windows.Forms.Button btnAdvace; /// <summary> /// 必需的设计器变量。 /// </summary> private System.ComponentModel.Container components = null; public Form1() { // // Windows 窗体设计器支持所必需的 // InitializeComponent(); // // TODO: 在 InitializeComponent 调用后添加任何构造函数代码 // } /// <summary> /// 清理所有正在使用的资源。 /// </summary> protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Windows Form Designer generated code /// <summary> /// 设计器支持所需的方法 - 不要使用代码编辑器修改 /// 此方法的内容。 /// </summary> private void InitializeComponent() { this.btnNormal = new System.Windows.Forms.Button(); this.btnAdvace = new System.Windows.Forms.Button(); this.SuspendLayout(); // // btnNormal // this.btnNormal.Location = new System.Drawing.Point(49, 55); this.btnNormal.Name = "btnNormal"; this.btnNormal.TabIndex = 0; this.btnNormal.Text = "普通报表"; this.btnNormal.Click += new System.EventHandler(this.btnNormal_Click); // // btnAdvace // this.btnAdvace.Location = new System.Drawing.Point(169, 55); this.btnAdvace.Name = "btnAdvace"; this.btnAdvace.TabIndex = 1; this.btnAdvace.Text = "高级报表"; this.btnAdvace.Click += new System.EventHandler(this.btnAdvace_Click); // // Form1 // this.AutoScaleBaseSize = new System.Drawing.Size(6, 14); this.ClientSize = new System.Drawing.Size(292, 133); this.Controls.AddRange(new System.Windows.Forms.Control[] { this.btnAdvace,this.btnNormal}); this.Name = "Form1"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "Form1"; this.ResumeLayout(false); } #endregion /// <summary> /// 应用程序的主入口点。 /// </summary> [STAThread] static void Main() { Application.Run(new Form1()); } private string [,] myData= { {"车牌号","类型","品 牌","型 号","颜 色","附加费证号","车架号"}, {"浙KA3676","危险品","货车","铁风SZG9220YY","白","1110708900","022836"}, {"浙KA4109","危险品","货车","解放CA4110P1K2","白","223132","010898"}, {"浙KA0001A","危险品","货车","南明LSY9190WS","白","1110205458","0474636"}, {"浙KA0493","上普货","货车","解放LSY9190WS","白","1110255971","0094327"}, {"浙KA1045","普货","货车","解放LSY9171WCD","蓝","1110391226","0516003"}, {"浙KA1313","普货","货车","解放9190WCD","蓝","1110315027","0538701"}, {"浙KA1322","普货","货车","解放LSY9190WS","蓝","24323332","0538716"}, {"浙KA1575","普货","货车","解放LSY9181WCD","蓝","1110314149","0113018"}, {"浙KA1925","普货","货车","解放LSY9220WCD","蓝","1110390626","00268729"}, {"浙KA2258","普货","货车","解放LSY9220WSP","蓝","111048152","00320"} }; //普通报表,即单纯的文件导出功能 private void btnNormal_Click(object sender, System.EventArgs e) { //创建一个Excel文件 Excel.Application myExcel = new Excel.Application ( ) ; myExcel.Application.Workbooks.Add ( true ) ; //让Excel文件可见 myExcel.Visible=true; //第一行为报表名称 myExcel.Cells[1,4]="普通报表"; //逐行写入数据, for(int i=0;i<11;i++) { for(int j="0;j<7;j++) " { //以单引号开头,表示该单元格为纯文本 myExcel.Cells[2+i,1+j]="'"+myData[i,j]; } } } //高级报表,根据模板生成的报表 private void btnAdvace_Click(object sender, System.EventArgs e) { string filename="" ; //将模板文件复制到一个新文件中 SaveFileDialog mysave="new" SaveFileDialog(); mySave. filter="Excel文件(*.XLS)|*.xls|所有文件(*.*)|*.*" ; if(mySave.ShowDialog()!=DialogResult.OK) { return; } else { filename="mySave.FileName; " //将模板文件copy到新位置,建议实际开发时用相对路径,如 //Application.StartupPath.Trim()+"\\report\\normal.xls" string filenameold="mySave.FileName; " FileInfo mode="new" FileInfo("d:\\normal.xls"); try { mode.CopyTo(filename,true); } catch(Exception ee) { MessageBox.Show(ee.Message); return; } } //打开复制后的文件 object missing="Missing.Value; " Excel.Application myexcel="new" Excel.Application ( ); //打开新文件 myExcel.Application.Workbooks.Open(filename,missing,missing,missing,missing, missing,missing,missing,missing,missing,missing, missing,missing); //将Excel显示出来 myExcel. visible="true; " //逐行写入数据,数组中第一行我列标题,忽略 for(int i="1;i<11;i++) " { for(int j="0;j<7;j++) " { //以单引号开头,表示该单元格为纯文本 myExcel.Cells[4+i,1+j]="'"+myData[i,j]; } } //将列标题和实际内容选中 Excel.Workbook mybook="myExcel.Workbooks[1]; " Excel.Worksheet mysheet="(Excel.Worksheet)myBook.Worksheets[1]; " Excel.Range r="mySheet.get_Range(mySheet.Cells[3,1],mySheet.Cells[14,7]); " r.Select(); //=====通过执行宏来格表格加边框=======// try { myExcel.Run("宏1",missing,missing, missing,missing,missing,missing,missing,missing,missing, missing,missing,missing,missing,missing,missing,missing, missing,missing,missing,missing,missing,missing,missing, missing,missing,missing,missing,missing,missing,missing); } catch { } //保存修改 myBook.Save(); } }//end of form }

在上述代码中,我们指定了选定范围:

Excel.Range r=mySheet.get_Range(mySheet.Cells[3,1],mySheet.Cells[14,7]);

具体开发时,我们可以根据数据库中的实际数据来计算范围,我们的列标题是从.Cells[3,1]开始的,在程序中定死了,为灵活使用,我们完全可以在模板的Cells[1,1]或者其他单元格填入一些基本信息,如实际数据起始位置等等,操作时,从该单元格读入数据,然后将该单元格内容替换成需要的内容。还有个问题,我们往单元格中写内容时假设某列应该放什么内容,为灵活起见,我们在得到了列标题起始位置后,读入该单元格内容(即该列应该是什么字段),再从数据库中找到相应的字段来填充该列,可以保证所填内容与设计的报表对应起来,还可以忽略数据库中无用的字段,也就是说同一个数据库表可以有许多种报表,只要有相应的模板就可以了,读入某单元格内容的代码如下:

Excel.Range r; r=mySheet.get_Range(mySheet.Cells[2,1],mySheet.Cells[2,1]); //取得值存放的区域 string strValue=r.Value.ToString();

一次只能读一个单元格,否则得不到相应的数据,即=mySheet.get_Range(mySheet.Cells[2,1],mySheet.Cells[2,1])中两个参数都必须是同一个单元格,本例中为mySheet.Cells[2,1]。

有了上面的知识,再做报表就简单多了,本软件运行结果如下:



出处:http://dotnet.aspx.cc/ShowDetail.aspx?id=4EB79F05-B9A4-4E8A-836F-864393F40405

posted on 2008-01-10 12:57  投石问路  阅读(557)  评论(0编辑  收藏  举报