.NET導出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<實體對象>這樣的數據源的,這就是說每一個實體對象都是會要一個導出處理函數的,希望大家注意,如果是想使用通用性的處理函數,數據源可以更改為一個本身就有行、列概念的對象,然后可以修改一下傳入參數應該能完成想要的功能了。

  疲勞中~~~

轉載請注明原址:http://www.cnblogs.com/lekko/archive/2012/10/19/2696121.html 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM