前段时间项目需要,写了个操作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#中跨线程操作控件