.NET编程实现采用COM组件导出Excel文件实例

将.NET数据导出为Excel文件,有许多种方法,我这里介绍采用COM组件来操作Excel文件,并且还会涉及异步、同步、进程管理、文件定位等内容,使用WPF做到一个尽量可用的导出界面。

一、WPF前台

  这个就不用多说了,堆上几个按钮,做一个数据录入的东西,一个状态条:

  我这里的数据录入,就是用了几个Textbox,实际上大家可以用任何东西(DataGrid、ListView等),因为在最后都会转成List<MyData>的形式进行导出的,MyData是表示数据记录的对象:

1 // 自定义数据类
2 public struct MyData
3 {
4      public string Col1, Col2, Col3;
5      public MyData(string col1, string col2, string col3)
6      {
7           Col1 = col1; Col2 = col2; Col3 = col3;
8      }
9 }

二、后台

  1、录入组织数据就不说了,先来说下选择默认导出路径:

1 using Forms = System.Windows.Forms;
2 // 选择导出目录
3 private void SelectPath()
4 {
5         var dialog = new Forms.FolderBrowserDialog();
6         dialog.ShowDialog();
7         string path = dialog.SelectedPath;
8         if (path != "")
9         {
10              _path = path;            
11              if (path[path.Length - 1] != ‘\\‘)
12             _path += ‘\\‘;
13         }
14 }

  代码使用System.Windows.Forms命名空间下的FloderBrowserDialog来选择目录,并把选择的path保存到全局变量中,另外还有一个判断,如果路径结尾不是‘\\‘的话,就加上这个字符,以便于后面合成文件全路径。效果图:

  

  2、如果是导出到非默认的路径,并命名文件,则:

1 // 保存文件到指定目录
2 private void SaveFile()
3 {
4     // ....
5     var dialog = new Forms.SaveFileDialog();
6     dialog.FileOk += new CancelEventHandler((o, e) =>
7     {
8         BTN_Export.Content = "取消";
9         var fullName = dialog.FileName;
10         int i = fullName.LastIndexOf(‘\\‘) + 1;
11         int j = fullName.LastIndexOf(‘.‘);
12         _bgWorker.RunWorkerAsync(new ExportInput<MyData>(_sources,
13             fullName.Substring(0, i),
14             fullName.Substring(i, j - i),
15             fullName.Substring(j, fullName.Length - j), _heads));
16     });
17     dialog.InitialDirectory = ServerPath;
18     dialog.DefaultExt = ".xlsx";
19     dialog.FileName = "MyData";
20     dialog.Filter = "Excel 2010文档|*.xlsx|Excel 2003文档|*.xls";
21     dialog.ShowDialog();
22     // ...                    
23 }

  这里使用了System.Windows.Froms的SaveFileDialog方法,弹出一个文件保存对话框,我们输入、选择路径、文件名、后缀后,点击“保存”,就能通过dialog.FileName得到全路径,然后分别截取目录、文件名、后缀,构成参数类ExportInput<MyData>,以启动后台线程进行导出。

ExportInput参数类

1     /// <summary>
2     /// 导出成Excel文件时需要传入的参数类
3     /// </summary>
4     public class ExportInput<T>
5     {
6         /// <summary>
7         /// 数据源
8         /// </summary>
9         public IEnumerable<T> Sources { get; set; }
10         /// <summary>
11         /// 列的表头
12         /// </summary>
13         public IEnumerable<string> Headers { get; set; }
14         /// <summary>
15         /// 文件的名称
16         /// </summary>
17         public string FileName { get; set; }
18         /// <summary>
19         /// 文件的绝对路径
20         /// </summary>
21         public string Path { get; set; }
22         /// <summary>
23         /// 文件后缀
24         /// </summary>
25         public string Ext { get; set; }
26 
27         /// <summary>
28         /// 构造传入参数
29         /// </summary>
30         /// <param name="sources">数据源</param>
31         /// <param name="filename">文件名</param>
32         /// <param name="path">文件的绝对路径</param>
33         /// <param name="headers">列的表头</param>
34         public ExportInput(IEnumerable<T> sources, string path, string filename, string ext, IEnumerable<string> headers = null)
35         {
36             Sources = sources;
37             FileName = filename;
38             Path = path;
39             Ext = ext;
40             Headers = headers;
41         }
42     }

3、导出时使用的后台线程来自System.ComponentModel.BackgroundWorker,使用它可以非常方便地完成线程运行、取消、通知的功能:

1 private BackgroundWorker _bgWorker = new BackgroundWorker();
2 // 初始化
3 private void Window_Loaded(object sender, RoutedEventArgs e)
4 {
5     // ...
6     _bgWorker.WorkerReportsProgress = true;
7     _bgWorker.WorkerSupportsCancellation = true;
8     _bgWorker.DoWork += new DoWorkEventHandler(ExcelHelper.ExportMyData);
9     _bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(OnWorkCompleted);
10     _bgWorker.ProgressChanged += new ProgressChangedEventHandler(OnProgressChanged);
11 }
12 // 报告进度
13 private void OnProgressChanged(object sender, ProgressChangedEventArgs e)
14 {
15     PB_State.Value = e.ProgressPercentage;
16 }
17 // 导出完成
18 private void OnWorkCompleted(object sender, RunWorkerCompletedEventArgs e)
19 {
20     if (e.Error != null)
21       MessageBox.Show("导出失败:" + e.Error.Message);
22     else if (e.Cancelled)
23         MessageBox.Show("已取消导出!");
24     else
25         MessageBox.Show("导出成功!");
26 }

  WorkerReportsProgress、WorkerSupportsCancellation这两个布尔值分别是是否支持报告后台线程进度、是否支持取消后台线程的功能,DoWork是后台工作线程的委托,在上面代码中,用的是ExcelHelper.ExportMyData这个静态事件处理函数来完成导出功能。RunWorkerCompleted、ProgressChanged 分别是工作完成、进度改变时回调给前台的委托。

  4、终于到了导出的部分了,代码如下:

导出Excel

1     using Excel = Microsoft.Office.Interop.Excel;
2     
3     /// <summary>
4     /// 导出成Excel文件
5     /// </summary>
6     public class ExcelHelper
7     {
8         /// <summary>
9         /// 导出Excel时使用的同步
10         /// </summary>
11         private static object syncRoot = new object();
12 
13         /// <summary>
14         /// 将数据集导出为Excel文件
15         /// </summary>
16         public static void ExportMyData(object sender, DoWorkEventArgs e)
17         {
18             // 创建Excel
19             Monitor.Enter(syncRoot);
20             var proListStart = Process.GetProcessesByName("EXCEL");
21             Excel.Application excelApp = new Excel.Application();
22             var proList = Process.GetProcessesByName("EXCEL").Except(proListStart, new ProcessComparer());            
23             Monitor.Exit(syncRoot);
24             try
25             {
26                 // 检查参数
27                 var input = (ExportInput<MyData>)e.Argument;
28                 var bgWorker = (BackgroundWorker)sender;
29                 // 创建工作簿
30                 Excel.Workbook excelDoc = excelApp.Workbooks.Add();
31                 // 创建工作表
32                 Excel.Worksheet excelSheet = (Excel.Worksheet)excelDoc.Worksheets[1];
33                 // 数字类型以文本格式显示
34                 excelSheet.Cells.NumberFormat = "@";
35                 // 单元格索引从1开始
36                 int i = 1, j = 1, count = input.Sources.Count();
37                 // 导入标题
38                 if (input.Headers != null)
39                 {
40                     foreach (string head in input.Headers)
41                         excelSheet.Cells[1, j++] = head;
42                     ++i;
43                 }
44                 //将数据导入到工作表的单元格
45                 foreach (MyData data in input.Sources)
46                 {
47                     if (bgWorker.CancellationPending)
48                     {
49                         e.Cancel = true;
50                         return;
51                     }
52                     j = 1;
53                     excelSheet.Cells[i, j++] = data.Col1;
54                     excelSheet.Cells[i, j++] = data.Col2;
55                     excelSheet.Cells[i, j++] = data.Col3;
56                     ++i;
57                     bgWorker.ReportProgress((95 * i - 190) / count);
58                 }
59                 //将其进行保存到指定的路径
60                 excelDoc.SaveAs(input.Path + input.FileName + input.Ext,
61                     input.Ext == ".xls" ? Excel.XlFileFormat.xlExcel7 : Excel.XlFileFormat.xlOpenXMLWorkbook);
62                 excelDoc.Close();
63                 // 返回路径
64                 e.Result = input.Path;
65                 bgWorker.ReportProgress(100);
66             }
67             catch (System.Exception ex)
68             {
69                 throw ex;
70             }
71             finally
72             {
73                 excelApp.Quit();
74                 // 释放COM组件,其实就是将其引用计数减1
75                 System.Runtime.InteropServices.Marshal.ReleaseComObject(excelApp);
76                 excelApp = null;
77                 //释放可能还没释放的进程
78                 KillProcess(proList);
79             }
80         }
81     }

  首先,引用Microsoft.Office.Interop.Excel命名空间,如果机器上安装了office,那么它的位置是在
  C:\Windows\assembly\GAC_MSIL\Microsoft.Office.Interop.Excel\14.0.0.0__71e9bce111e9429c\Microsoft.Office.Interop.Excel.dll
  的位置,office版本不同“14.0.0.0__71e9bce111e9429c目录”可能名称会有一点差别。

  接下来,启动Excel进程:
  Excel.Application excelApp = new Excel.Application();

  创建工作簿:
  Excel.Workbook excelDoc = excelApp.Workbooks.Add();

  创建工作表:
  Excel.Worksheet excelSheet = (Excel.Worksheet)excelDoc.Worksheets[1];

  填入数据(注意到行和列都是从1开始的):
  excelSheet.Cells[行, 列] = 数据;
  在填入数据时,每赶往记录前,都判断一次是否取消导出,每填入一条记录后,就使用bgWorker.ReportProgress()汇报工作进度。

1 if (bgWorker.CancellationPending)
2 {
3   e.Cancel = true;
4   return;
5 }  

  将工作簿保存到指定的路径,关闭:  

1   excelDoc.SaveAs(input.Path + input.FileName + input.Ext,
2     input.Ext == ".xls" ? Excel.XlFileFormat.xlExcel7 : Excel.XlFileFormat.xlOpenXMLWorkbook);  
3   excelDoc.Close();

  网上很多地方说保存成office2003用的枚举是Excel.XlFileFormat.xlExcel8,经过我实际测试,这个枚举是从office 2007才开始出现的,如果机器上安装了2007及更高版本的office的话是可以正常使用的,如果机器上只安装了office 2003,则只有用xlExcel7这个枚举才能正常保存为excel2003文档。

5、优化

  上面虽然功能完成了,但是还不够,打开任务管理器,每导出一次会发现Excel.exe进程多一个,也就是说Excel.exe进程没有被关闭,需要手动释放资源。首先,释放Com资源非常简单:  

1    excelApp.Quit();
2   // 释放COM组件,其实就是将其引用计数减1
3   System.Runtime.InteropServices.Marshal.ReleaseComObject(excelApp);
4   excelApp = null;

  但是从系统中删除线程就比较麻烦,有一种方式是把所有Excel.exe进程关闭,但是这会影响事先打开的Excel文件。所以我这里创建了一个列表保存用来导出Excel的进程,并在导出结束后关闭这些进程:  

1    // 获取已打开的Excel程序 Interaction.GetObject(null, "Excel.Application") as Excel.Application;
2   Monitor.Enter(syncRoot);
3   var proListStart = Process.GetProcessesByName("EXCEL");
4   Excel.Application excelApp = new Excel.Application();
5   var proList = Process.GetProcessesByName("EXCEL").Except(proListStart, new ProcessComparer());
6   Monitor.Exit(syncRoot);

  在创建Excel应用前进入锁定,并记录当前Excel.exe进程列表,然后创建,对比判断新增的进程,结束锁定。对比判断ProcessComparer类,实现了IEqualityComparer<Process>接口,通过进程的Id来标识唯一性。

  在导出结束之后,我再调用KillProcess函数,把proList列表中的进程全部关闭,以释放资源:

1 foreach (Process theProc in list)
2   if (theProc.CloseMainWindow() == false)
3     theProc.Kill();

三、总结

  这个东西本来就做好很久了,一直没时间写博文,现在感觉写博文有种很想偷懒的感觉,唉,不行了,对文字工作不感冒。这个东西实际上难度不大,关键是各种配合起来,达到谐调的目的,然后资源释放那块也琢磨了不少方法才采用的死办法的,看有没有园友能找到更好的释放进程的方法。

  有一个问题,现在我是使用List<实体对象>这样的数据源的,这就是说每一个实体对象都是会要一个导出处理函数的,希望大家注意,如果是想使用通用性的处理函数,数据源可以更改为一个本身就有行、列概念的对象,然后可以修改一下传入参数应该能完成想要的功能了。

时间: 2024-11-05 19:31:21

.NET编程实现采用COM组件导出Excel文件实例的相关文章

office组件导出excel问题

传统企业的项目开发,采用技术都还比较陈旧,项目中涉及excel部分,采用office组件导出excel,进程采用强杀方式,涉及web服务器权限部分整合如下,均来自博客园. .NET导出Excel遇到的80070005错误的解决方法:   原文出处检索 COM 类工厂中 CLSID 为 {00024500-0000-0000-C000-000000000046}的组件时失败,原因是出现以下错误: 80070005基本上.net导出excel文件,都需要如此配置一下,不配置有的时候没错,而配置后基本

(C#)利用Aspose.Cells组件导入导出excel文件

Aspose.Cells组件可以不依赖excel来导入导出excel文件: 导入: [csharp] view plain copy print? public static System.Data.DataTable ReadExcel(String strFileName) { Workbook book = new Workbook(); book.Open(strFileName); Worksheet sheet = book.Worksheets[0]; Cells cells = 

【转】 (C#)利用Aspose.Cells组件导入导出excel文件

Aspose.Cells组件可以不依赖excel来导入导出excel文件: 导入: public static System.Data.DataTable ReadExcel(String strFileName) { Workbook book = new Workbook(); book.Open(strFileName); Worksheet sheet = book.Worksheets[0]; Cells cells = sheet.Cells; return cells.Export

百万级数据记录量优化查询以及导出EXCEL文件编程

通过对完整软件实例(工程设计流水管理系统)编程讲解,让学员熟悉完整软件布局架构及开发思路.比如从界面布局.登录验证.软件注册程序.到软件发布等知识点,贯穿知识点间联系,提升编程整合能力. 中文编程完整软件实例编程解析之工程设计流水管理系统(8课时) 第1课:整体布局.EXCEL表数据导入到软件数据库编程a.整体布局b.EXCEL表数据导入到软件数据库编程第2课:基本信息预先设置编程a.项目名称预设置 第3课:子项目工程日志流水编程解析a.工程日志流水编程 第4课:子项目设计费发放流水编程解析a.

基于Vue + axios + WebApi + NPOI导出Excel文件

一.前言 项目中前端采用的Element UI 框架, 远程数据请求,使用的是axios,后端接口框架采用的asp.net webapi,数据导出成Excel采用NPOI组件.其业务场景,主要是列表页(如会员信息,订单信息等)表格数据导出,如表格数据进行了条件筛选,则需要将条件传至后端api,筛选数据后,导出成Excel. 思考过前端导出的3种方案: 1.使用location.href 打开接口地址.缺点: 不能传token至后端api, 无法保证接口的安全性校验,并且接口只能是get方式请求.

PHP导出excel文件,第一步先实现PHP模板导出不带数据

今天继续研究PHP导出excel文件,把复杂的事情简单化,一步步实现功能,首先实现模板文件的导出,随后再实现写入数据后导出,最终实现功能,这是基本思路.中间可以加一步,先自己写入数据导出试试,随后再数据库导入.我首先把程序提交到自建的eubexcel.php文件,选用post提交,导出excel文件的程序在这个页面里书写,参考昨天下载的PHPExcel-1.8组件里的参考文档,先部署导出excel,具体代码如下 <?php error_reporting(E_ALL);ini_set('disp

JQGrid导出Excel文件

系列索引 Web jquery表格组件 JQGrid 的使用 - 从入门到精通 开篇及索引 Web jquery表格组件 JQGrid 的使用 - 4.JQGrid参数.ColModel API.事件及方法 Web jquery表格组件 JQGrid 的使用 - 5.Pager翻页.搜索.格式化.自定义按钮 Web jquery表格组件 JQGrid 的使用 - 6.准备工作 & Hello JQGrid Web jquery表格组件 JQGrid 的使用 - 7.查询数据.编辑数据.删除数据

PHP从数据库导出EXCEL文件

参考博客链接:http://www.cnblogs.com/huangcong/p/3687665.html 我的程序代码 原生导出Excel文件 <?phpheader('Content-type: text/html; charset=utf-8');header("Content-type:application/vnd.ms-excel");header("Content-Disposition:filename=test.xls"); $conn =

TXLSReadWriteII2版本导出Excel文件:

//TXLSReadWriteII2版本导出Excel文件: procedure TForm1.N1Click(Sender: TObject); var i: Integer; aSaveDialog: TSaveDialog; aFileName, aStampTime: AnsiString; aXlsObj: TXLSReadWriteII2; p: PDataRec; begin aSaveDialog := TSaveDialog.Create(Self); try aSaveDia