前段時間項目需要,寫了個操作Excel表格的程序。先介紹背景,合作單位每天有氣井生產數據產生,他們的慣例是將數據存放在一個Excel表格中,通過日期及井口名稱標識記錄的唯一性,為陳述方便,此表稱為總表。由於數據管理的落后,他們已經在總表中存放所有井口(約200口)近3年的生產數據,約有2萬條記錄,每天新增記錄有200條。另外,單位要對每個井口的生產狀況進行分析,他們現在的做法是為井口建立Excel文件,一般一個文件包含10個工作表,每個工作表存放一個井口從生產到當日的生產數據,這些Excel文件稱為單井表。現在的一個工作流程是,手工將總表中每個井口當日生產數據復制到單井表中,一般熟練人員需要兩三個小時才能完成此項單調工作。復制結束后,需要更新單井表中每口井的生產狀態曲線,將當日數據追加上去。程序的目標是,自動化完成數據的復制及生產狀態曲線的更新。
考慮到復制數據和更新曲線圖會耗費一段時間,有必要在界面上對處理進度進行提示,由此選用BackgroundWorker,將耗時的復制、更新操作放入后台進行就變得必須了。
BackgroundWorker對象有三個主要的事件:
DoWork - 當BackgroundWorker對象的多線程操作被執行時觸發。
RunWokerCompleted - 當BackgroundWoker對象的多線程操作完成時觸發。
ProgressChanged - 當BackgroundWorker對象的多線程操作狀態改變時觸發。
另外還有一個非常重要的屬性WorkerReportsProgress - 如果想讓BackgroundWorker對象以異步的方式報告線程實時進度,必須將該屬性的值設為true。
BackgroundWorker對象的ReportProgress方法用於向主線程返回后台線程執行的實時進度。
下面上代碼:
1 public void DoWork(object sender, DoWorkEventArgs e) 2 { 3 // 事件處理,指定處理函數 4 e.Result = ProcessProgress(bkWorker, e); 5 } 6 7 private int ProcessProgress(object sender, DoWorkEventArgs e) 8 { 9 //從一個excel表格中復制數據到另外一個表格中 10 //打開一個工作表,找到日期一欄為當日日期的一行的位置 11 //找到此工作表中日期為當日日期的最后一條記錄的位置 12 //循環訪問中間的行,將其copy到對應的工作表中 13 //循環體中要首先判斷工作表中最后一條記錄的位置 14 //之后將copy的數據粘貼到其之后一行,保存文檔 15 16 //配置打開文件及存放文件路徑,以后修改為使用xml文件保存參數的形式 17 //string openFileName = @"D:\ExcelData\各氣井每日生產數據.xls"; 18 //string saveFileName = @"D:\ExcelData\單井每日生產數據.xls"; 19 string openFileName = strAllData; 20 string saveFileFolder = strSingleData; 21 22 try 23 { 24 //查詢某一列第一次出現某個值所在單元格的位置 25 //查詢某一列最后一次出現某個值所在單元格的位置 26 Excel.Workbook wb = p_eh.GetWBFromExcel(openFileName); 27 Excel.Worksheet ws = (Excel.Worksheet)wb.Worksheets[1]; 28 string columnStart = "A1"; 29 //需要查看此處date的格式 30 //strDate 2013-07-12格式 31 string strDate = DateTime.Now.Date.ToShortDateString(); 32 strDate = strDate.Replace("-0", "-"); 33 Excel.Range firstRange = p_eh.FindFirstMatch(ws, columnStart, strDate); 34 int rowIndexStart = firstRange.Row; 35 Excel.Range lastRange = p_eh.FindLastMatch(ws, columnStart, strDate); 36 int rowIndexEnd = lastRange.Row; 37 if (rowIndexStart.Equals(rowIndexEnd)) 38 { 39 MessageBox.Show("輸入文件數據有誤"); 40 } 41 42 Excel.Workbook swb = null; 43 Excel.Worksheet sws = null; 44 string saveFileName; 45 for (int i = 0; i <= rowIndexEnd - rowIndexStart; i++) 46 { 47 //將所在行的數據拷貝到對應的文件worksheet中 48 //獲取名稱單元格 49 Excel.Range nameRange = ws.get_Range("B" + (i + rowIndexStart).ToString(), Type.Missing); 50 string wellName = nameRange.Text.ToString(); 51 if (wellName != null) 52 { 53 //根據獲取的名稱,查詢所在的xls文件名稱 54 saveFileName = GetSaveFileName(wellName); 55 swb = p_eh.GetWBFromExcel(saveFileFolder + "\\" + saveFileName); 56 sws = p_eh.GetWSByName(swb, wellName); 57 //需要找到當前工作表最后一條記錄所在的位置 58 int lastUsedRowIndex = p_eh.GetLastUsedRow(sws, columnStart); 59 int lastUsedColumnIndex = p_eh.GetLastUsedColumn(sws, "A5"); 60 string columnName = p_eh.GetExcelColumnName(lastUsedColumnIndex); 61 Excel.Range dataRange = ws.get_Range("A" + (rowIndexStart + i).ToString(), columnName + (rowIndexStart + i).ToString()); 62 Excel.Range distinationRange = sws.get_Range("A" + (lastUsedRowIndex + int.Parse("1")).ToString(), columnName + (lastUsedRowIndex + int.Parse("1")).ToString()); 63 //此種復制不能保持原有格式 64 //dataRange.Copy(Type.Missing); 65 //distinationRange.PasteSpecial(Microsoft.Office.Interop.Excel.XlPasteType.xlPasteValues, Microsoft.Office.Interop.Excel.XlPasteSpecialOperation.xlPasteSpecialOperationNone, false, false); 66 //需要保持原有格式 67 sws.Cells.Columns.AutoFit(); 68 dataRange.Copy(distinationRange); 69 //Excel.Range dateRange = sws.get_Range("A" + (lastUsedRowIndex + int.Parse("1")).ToString(), Type.Missing); 70 //dateRange.Value2 = strDate; 71 //dataRange.Cells.Font.Size = 10; 72 //dataRange.VerticalAlignment = Excel.XlVAlign.xlVAlignCenter; 73 //dataRange.HorizontalAlignment = Excel.XlVAlign.xlVAlignCenter; 74 swb.Save(); 75 } 76 // 狀態報告 77 bkWorker.ReportProgress((int)(100*i / (rowIndexEnd - rowIndexStart))); 78 // 等待,用於UI刷新界面,很重要 79 System.Threading.Thread.Sleep(1); 80 } 81 wb.Close(Type.Missing, Type.Missing, Type.Missing); 82 swb.Close(Type.Missing, Type.Missing, Type.Missing); 83 //此處注銷使用的各種excel對象 84 if (ws != null) 85 { 86 System.Runtime.InteropServices.Marshal.ReleaseComObject(ws); 87 } 88 if (wb != null) 89 { 90 System.Runtime.InteropServices.Marshal.ReleaseComObject(wb); 91 } 92 if (sws != null) 93 { 94 System.Runtime.InteropServices.Marshal.ReleaseComObject(sws); 95 } 96 if (swb != null) 97 { 98 System.Runtime.InteropServices.Marshal.ReleaseComObject(swb); 99 } 100 } 101 catch (System.Exception ex) 102 { 103 p_eh.Dispose(); 104 } 105 p_eh.KillAllExcelProcess(); 106 return -1; 107 } 108 109 public void ProgessChanged(object sender, ProgressChangedEventArgs e) 110 { 111 this.progressBar1.Value = e.ProgressPercentage; 112 int percent = e.ProgressPercentage; 113 this.label1.Text = "處理進度:" + Convert.ToString(percent) + "%"; 114 }
其他有關BackgroundWorker設置如下:
1 CheckForIllegalCrossThreadCalls = false; 2 bkWorker.WorkerReportsProgress = true; 3 bkWorker.WorkerSupportsCancellation = true; 4 bkWorker.DoWork += new DoWorkEventHandler(DoWork); 5 bkWorker.ProgressChanged += new ProgressChangedEventHandler(ProgessChanged); 6 bkWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(CompleteWork);
以上設置中第一行不檢測跨線程操作,是一種簡單的避免跨線程操作異常的方式,第二行則提示狀態更改。
程序運行界面如下:
這個程序中主要有兩點,一個是BackgroundWorker的用法,另一個是寫了個操作Excel表格的類,完成數據表格和內存中DataTable的互相轉換,Excel表格的各種查詢。
寫這篇文章是因為復習到多線程,想起來要把這點總結下,已經看到一篇寫的不錯的文章,貼下地址:C#多線程總結
另外本文寫作參考鏈接:1.多線程:C#.NET中使用BackgroundWorker在模態對話框中顯示進度條;2.C#中跨線程操作控件