C# 操作Excel (二)

根据翻阅LTP.Net知识库系列之四中Office操作功能:

一、记录常用如下

(1)“在自己的程序中宿主Office”的意思就是在我们自己开发的应用程序的窗体上,显示一个就像Office应用程序那样的界面,用来显示和操作Office文档,利用Microsoft Web Browser 这个ActiveX控件,我们能够非常方便的做到。

(2)首先,在Visual Studio 中创建一个C#语言的Windows应用程序,然后在左侧的工具箱中点击鼠标右键,选择“添加/移除选项”,就能够打开自定义工具箱窗口,在窗口中的COM组件列表中,我们就能找到“Microsoft Web Browser”(如果是中文版操作系统,这个控件也许叫“Microsoft Web 浏览器”)这个ActiveX控件,选择它,把它添加到我们的工具箱中。将这个控件直接拖到WinForms窗体上即可。

(3)如果大家使用Visual Studio2005以上版本开发,那么将更加方便,因为WinForms 2.0已经自带了一个叫做“WebBrowser”的WinForms控件,它其实就是对“Microsoft Web Browser”ActiveX控件的封装。这样我们就能在窗体上直接使用“WebBrowser”控件,而不需要额外引用其他ActiverX控件。

(4)载入代码

Object missing = Type.Missing;
Object sFilePath = "d:\\hello.doc";
this.axWebBrowser1.NavigateComplete2 += new AxSHDocVw.DWebBrowserEvents2_NavigateComplete2EventHandler(axWebBrowser1_NavigateComplete2);
this.axWebBrowser1.Navigate2(ref sFilePath, ref missing, ref missing, ref missing, ref missing);

在上面的代码中,我们还给这个控件的NavigateComplete2事件添加了一个事件处理方法。NavigateComplete2事件是在控件载入Word文档完成后触发。

(5)直接打开

http://www.sufeinet.com/thread-3941-1-1.html  不错,大家可以查看

string sFilePath = "D:\\ss.doc";

this.webBrowser1.Navigate(sFilePath, true);

(6)“彻底干净”的关闭Office

1、凡是涉及到使用Office Automation,即通过自定义的代码启动Office,操作Office编程接口完成一些工作(不管实在WinForms程序,或是Asp.net程序中),都不可避免会遇到一个问题,就是如何“彻底干净”的将代码的Office程序关闭掉。实际上,如果没有处理好这个问题,那么会造成应用程序所在计算机上,相关的Office进程始终无法关闭,而如果应用程序运行在一台服务器上,那么造成的后果也更加严重,甚至可能导致服务器资源耗尽而宕机。

服务器端Office Automation就是指我们在一个位于服务器端运行的程序中,访问Office编程接口,启动Office程序,操纵Office完成某些自动化操作。比如,在一个ASP.NET程序,或者在一个Windows Service中,都是服务器端Office Automation场景。

服务器端Office Automation的第一准则就是:不要在服务器端进行Office Automcation操作!甚至在服务器上进行Office Automcation操作是不被微软所Support的!

没错,因为在服务器端进行Office Automcation操作是非常非常危险的行为,Office原本就不是被设计成以无人值守方式运行的,就是说,Office程序在设计的时候,总是默认假定有一个真正的用户坐在计算机前,用鼠标和键盘与Office程序进行交互。而如果我们用代码来操作Office,那么实际上已经打破了这个假定。打破这个假定可能带来哪些问题呢?下面列举了一些常见的问题:

1)由于Office总是假定当前有一个真正的“用户”在使用它,所以,它可能在某些时候会主动弹出一些窗口,要求用户与之交互。比如,当某个操作没有成功完成,或发生一些非预见情况时(比如Office要打印却发现没有打印机、要保存一个文件却发现已存在同名文件),Office会显示一个模式窗口,提示或询问用户一些信息,而在哪些时候会出现这些窗口是不能被完全预见的。由于我们的代码不能取消这样的模式窗口,那么当前进程会被完全堵塞,失去响应。

2)作为一个在服务器端运行的组件,必须事先被设计成能够被多个客户端重复使用、开销尽可能少,而Office恰恰相反(因为Office原本就是设计成在客户端被使用),每个Office应用程序都占用大量的资源,也很难被重复使用。

3)大家日常使用Office的时候,应该能够经常看到Office会出现一个“正在准备安装…”的对话窗口,这是因为Office引入了一种叫做“首次使用时安装”的安装模式,某些组件有可能在第一次被使用到时才去安装它。而如果在服务器端出现这样的情形,那么服务器的稳定性就很难保证了。

4)Office总是假定当前的运行环境中,是一个真实用户的账号身份,但是服务器端Office Automation有时候却是使用一些系统账号(比如Network Service、IUser_Machine之类的)来运行Office,这时Office很可能会无法正常启动,抛出错误。

所以,除非万不得已,不要进行服务器端Office Automation操作!但是,有时候很多事情并不是由程序员决定的,所以还是有不少人铁了心、咬着牙,非得在服务器端做这个操作不可。如果你真的已经下定了决心,并且有信心克服遇到的一切困难,那么下面提供一些服务器端Office Automation的建议,供大家参考。

    2、在代码中关闭Office应用程序

当我们在.Net代码中访问Office编程接口时,COM Interop在底下会创建一个RCW(Runtime Callable Wrapper,运行时远程访问包装器),来维护对Office COM组件的引用。为了让Office能够被正常关闭,关键就是要在代码中释放掉对Office 相关对象的引用。

下面介绍了多种保障措施,让Office应用程序能够被正常关闭,在某些情况下,使用最简单的一种方式即可,而在某些情况下,则可能需要将多种方式综合起来使用。

1)记得调用Application.Quit()方法

忘记调用这个Quit()方法,那什么都是白搭。

2)让垃圾回收完成所有工作

由于.Net Framework提供了垃圾回收器来进行内存的自动管理,所以原理上,只要我们的代码中释放掉对Office相关对象的引用(将其赋值为null),那么垃圾回收最终会将这个对象回收,那时RCW会相应的释放掉Office COM组件,使Office被关闭。为了保证关闭的即时性,我们最好主动调用垃圾回收,让垃圾回收立即进行。

Microsoft.Office.Interop.Word.Application wordApp = new Microsoft.Office.Interop.Word.Application();
// 进行某些操作…
//如:
if (wordApp != null)
{ string sFilePath = "D:\\ss.doc";
this.webBrowser1.Navigate(sFilePath, true);
} Object missing = Type.Missing;
wordApp.Quit(ref missing, ref missing, ref missing);
wordApp = null; GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();

在大部分情况下,这种方法已经可以让我们的代码释放掉对Office的引用,使Office被关闭掉。

3)调用System.Runtime.InteropServices.Marshal.ReleaseComObject()方法

ReleaseComObject()方法可以使RCW减少一个对COM组件的引用,并返回减少一个引用后RCW对COM组件的剩余引用数量。我们用一个循环,就可以让RCW将所有对COM组件的引用全部去掉。

先创建一个单独的方法,释放一个Office相关对象的所有引用。

private void ReleaseAllRef(Object obj)
{
try
{
while (ReleaseComObject(obj) > );
}
finally
{
obj = null;
}
} 然后,调用这个ReleaseAllRef()方法即可。 Microsoft.Office.Interop.Word.Application wordApp = new Microsoft.Office.Interop.Word.Application();
// 进行某些操作…
Object missing = Type.Missing;
wordApp.Quit(ref missing, ref missing, ref missing); ReleaseAllRef(wordApp); GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();

    3、明确单独声明和释放每一个中间对象变量

中间对象变量就是指代码中直接通过一个对象的属性得到的一个对象,不单独声明它,而再次直接使用它。比如:

Document doc = wordApp.Documents.Add(…);

上面的代码中,wordApp.Documents这个属性实际是一个Microsoft.Office.Interop.Word.Documents类型的对象,但是上面的代码没有单独声明这个对象,而是直接使用了它的Add()方法。如果要单独声明它,则需要更改成如下:

Documents docs = wordApp.Documents;

Document doc = docs.Add(…);

在使用完这些对象后,使用(2)中所描述的方法,再一一释放掉它们。

doc.Close(...);

ReleaseAllRef(doc);

ReleaseAllRef(docs);
wordApp.Quit(...);

ReleaseAllRef(wordApp);

(7)操作Excel的一个类库ExcelHelper

这个类库没有提供Excel样式设置的方法,建议使用模板,可以在模板中把格式控制好;ExcelHelper提供了实现Excel的基本操作的一些方法,包括:
        1)将二维数组和DataTable的数据导入Excel,可以按指定每页的记录行数来分页,还可以将数据相同的行合并
        2)WorkSheet的拷贝、隐藏方法,删除操作会出问题,不过对于WorkSheet这两个方法足够了
        3)Excel行、列的拷贝、删除、插入、合并操作
        4)Excel单元格的拷贝、删除、插入、合并操作,可以将单个文本值写入单元格,也可以将多个值及其对应Excel单元格的位置信息保存在Hashtable中写入单元格
        5)Excel文本框赋值操作,一些Excel控件都没有提供访问文本框的方法,要不我也不用写这个类库了
        6)将Excel文件导出多种格式

下载ExcelHelper.rar     http://files.cnblogs.com/lingyun_k/ExcelHelper.rar

(8)C#操作Excel ,套用模板并对数据进行分页 (代码)

 using System;
using System.IO;
using System.Data;
using System.Reflection;
using System.Diagnostics;
using cfg = System.Configuration;
//using Excel; namespace ExcelHelperTest
{
/// <summary>
/// 功能说明:套用模板输出Excel,并对数据进行分页
/// 作 者:Lingyun_k
/// 创建日期:2005-7-12
/// </summary>
public class ExcelHelper
{
protected string templetFile = null;
protected string outputFile = null;
protected object missing = Missing.Value; /// <summary>
/// 构造函数,需指定模板文件和输出文件完整路径
/// </summary>
/// <param name="templetFilePath">Excel模板文件路径</param>
/// <param name="outputFilePath">输出Excel文件路径</param>
public ExcelHelper(string templetFilePath,string outputFilePath)
{
if(templetFilePath == null)
throw new Exception("Excel模板文件路径不能为空!"); if(outputFilePath == null)
throw new Exception("输出Excel文件路径不能为空!"); if(!File.Exists(templetFilePath))
throw new Exception("指定路径的Excel模板文件不存在!"); this.templetFile = templetFilePath;
this.outputFile = outputFilePath; } /// <summary>
/// 将DataTable数据写入Excel文件(套用模板并分页)
/// </summary>
/// <param name="dt">DataTable</param>
/// <param name="rows">每个WorkSheet写入多少行数据</param>
/// <param name="top">行索引</param>
/// <param name="left">列索引</param>
/// <param name="sheetPrefixName">WorkSheet前缀名,比如:前缀名为“Sheet”,那么WorkSheet名称依次为“Sheet-1,Sheet-2 ”</param>
public void DataTableToExcel(DataTable dt,int rows,int top,int left,string sheetPrefixNa)
{
int rowCount = dt.Rows.Count; //源DataTable行数
int colCount = dt.Columns.Count; //源DataTable列数
int sheetCount = this.GetSheetCount(rowCount,rows); //WorkSheet个数
DateTime beforeTime;
DateTime afterTime; if(sheetPrefixName == null || sheetPrefixName.Trim() == "")
sheetPrefixName = "Sheet"; //创建一个Application对象并使其可见
beforeTime = DateTime.Now;
Excel.Application app = new Excel.ApplicationClass();
app.Visible = true;
afterTime = DateTime.Now; //打开模板文件,得到WorkBook对象
Excel.Workbook workBook = app.Workbooks.Open(templetFile,missing,missing,missing,missing,missing,
missing,missing,missing,missing,missing,missing,missing); //得到WorkSheet对象
Excel.Worksheet workSheet = (Excel.Worksheet)workBook.Sheets.get_Item(); //复制sheetCount-1个WorkSheet对象
for(int i=;i<sheetCount;i++)
{
((Excel.Worksheet)workBook.Worksheets.get_Item(i)).Copy(missing,workBook.Worksheets[i]);
} #region 将源DataTable数据写入Excel
for(int i=;i<=sheetCount;i++)
{
int startRow = (i - ) * rows; //记录起始行索引
int endRow = i * rows; //记录结束行索引 //若是最后一个WorkSheet,那么记录结束行索引为源DataTable行数
if(i == sheetCount)
endRow = rowCount; //获取要写入数据的WorkSheet对象,并重命名
Excel.Worksheet sheet = (Excel.Worksheet)workBook.Worksheets.get_Item(i);
sheet.Name = sheetPrefixName + "-" + i.ToString(); //将dt中的数据写入WorkSheet
for(int j=;j<endRow-startRow;j++)
{
for(int k=;k<colCount;k++)
{
sheet.Cells[top + j,left + k] = dt.Rows[startRow + j][k].ToString();
}
} //写文本框数据
Excel.TextBox txtAuthor = (Excel.TextBox)sheet.TextBoxes("txtAuthor");
Excel.TextBox txtDate = (Excel.TextBox)sheet.TextBoxes("txtDate");
Excel.TextBox txtVersion = (Excel.TextBox)sheet.TextBoxes("txtVersion"); txtAuthor.Text = "KLY.NET的Blog";
txtDate.Text = DateTime.Now.ToShortDateString();
txtVersion.Text = "1.0.0.0";
}
#endregion //输出Excel文件并退出
try
{
workBook.SaveAs(outputFile,missing,missing,missing,missing,missing,Excel.XlSaveAsAccessMode.xlExclusive,missing,missing,missing,missing);
workBook.Close(null,null,null);
app.Workbooks.Close();
app.Application.Quit();
app.Quit(); System.Runtime.InteropServices.Marshal.ReleaseComObject(workSheet);
System.Runtime.InteropServices.Marshal.ReleaseComObject(workBook);
System.Runtime.InteropServices.Marshal.ReleaseComObject(app); workSheet=null;
workBook=null;
app=null; GC.Collect();
}
catch(Exception e)
{
throw e;
}
finally
{
Process[] myProcesses;
DateTime startTime;
myProcesses = Process.GetProcessesByName("Excel"); //得不到Excel进程ID,暂时只能判断进程启动时间
foreach(Process myProcess in myProcesses)
{
startTime = myProcess.StartTime; if(startTime > beforeTime && startTime < afterTime)
{
myProcess.Kill();
}
}
} } /// <summary>
/// 获取WorkSheet数量
/// </summary>
/// <param name="rowCount">记录总行数</param>
/// <param name="rows">每WorkSheet行数</param>
private int GetSheetCount(int rowCount,int rows)
{
int n = rowCount % rows; //余数 if(n == )
return rowCount / rows;
else
return Convert.ToInt32(rowCount / rows) + ;
} /// <summary>
/// 将二维数组数据写入Excel文件(套用模板并分页)
/// </summary>
/// <param name="arr">二维数组</param>
/// <param name="rows">每个WorkSheet写入多少行数据</param>
/// <param name="top">行索引</param>
/// <param name="left">列索引</param>
/// <param name="sheetPrefixName">WorkSheet前缀名,比如:前缀名为“Sheet”,那么WorkSheet名称依次为“Sheet-1,Sheet-2 ”</param>
public void ArrayToExcel(string[,] arr,int rows,int top,int left,string sheetPrefixName)
{
int rowCount = arr.GetLength(); //二维数组行数(一维长度)
int colCount = arr.GetLength(); //二维数据列数(二维长度)
int sheetCount = this.GetSheetCount(rowCount,rows); //WorkSheet个数
DateTime beforeTime;
DateTime afterTime; if(sheetPrefixName == null || sheetPrefixName.Trim() == "")
sheetPrefixName = "Sheet"; //创建一个Application对象并使其可见
beforeTime = DateTime.Now;
Excel.Application app = new Excel.ApplicationClass();
app.Visible = true;
afterTime = DateTime.Now; //打开模板文件,得到WorkBook对象
Excel.Workbook workBook = app.Workbooks.Open(templetFile,missing,missing,missing,missing,missing,
missing,missing,missing,missing,missing,missing,missing); //得到WorkSheet对象
Excel.Worksheet workSheet = (Excel.Worksheet)workBook.Sheets.get_Item(); //复制sheetCount-1个WorkSheet对象
for(int i=;i<sheetCount;i++)
{
((Excel.Worksheet)workBook.Worksheets.get_Item(i)).Copy(missing,workBook.Worksheets[i]);
} #region 将二维数组数据写入Excel
for(int i=;i<=sheetCount;i++)
{
int startRow = (i - ) * rows; //记录起始行索引
int endRow = i * rows; //记录结束行索引 //若是最后一个WorkSheet,那么记录结束行索引为源DataTable行数
if(i == sheetCount)
endRow = rowCount; //获取要写入数据的WorkSheet对象,并重命名
Excel.Worksheet sheet = (Excel.Worksheet)workBook.Worksheets.get_Item(i);
sheet.Name = sheetPrefixName + "-" + i.ToString(); //将二维数组中的数据写入WorkSheet
for(int j=;j<endRow-startRow;j++)
{
for(int k=;k<colCount;k++)
{
sheet.Cells[top + j,left + k] = arr[startRow + j,k];
}
} Excel.TextBox txtAuthor = (Excel.TextBox)sheet.TextBoxes("txtAuthor");
Excel.TextBox txtDate = (Excel.TextBox)sheet.TextBoxes("txtDate");
Excel.TextBox txtVersion = (Excel.TextBox)sheet.TextBoxes("txtVersion"); txtAuthor.Text = "KLY.NET的Blog";
txtDate.Text = DateTime.Now.ToShortDateString();
txtVersion.Text = "1.0.0.0";
}
#endregion //输出Excel文件并退出
try
{
workBook.SaveAs(outputFile,missing,missing,missing,missing,missing,Excel.XlSaveAsAccessMode.xlExclusive,missing,missing,missing,missing);
workBook.Close(null,null,null);
app.Workbooks.Close();
app.Application.Quit();
app.Quit(); System.Runtime.InteropServices.Marshal.ReleaseComObject(workSheet);
System.Runtime.InteropServices.Marshal.ReleaseComObject(workBook);
System.Runtime.InteropServices.Marshal.ReleaseComObject(app); workSheet=null;
workBook=null;
app=null; GC.Collect();
}
catch(Exception e)
{
throw e;
}
finally
{
Process[] myProcesses;
DateTime startTime;
myProcesses = Process.GetProcessesByName("Excel"); //得不到Excel进程ID,暂时只能判断进程启动时间
foreach(Process myProcess in myProcesses)
{
startTime = myProcess.StartTime; if(startTime > beforeTime && startTime < afterTime)
{
myProcess.Kill();
}
}
}
}
}
}
源码下载 http://files.cnblogs.com/lingyun_k/ExcelHelperTest.rar

(9)关于Asp.Net 中调用Excel组件不能结束进程的解决方法(代码)

 using System;
using System.Diagnostics;
using excel = Microsoft.Office.Interop.Excel; namespace ExcelTest
{
/// <summary>
/// Excel的摘要说明。
/// </summary>
public class Excel
{
private DateTime beforeTime; //Excel启动之前时间
private DateTime afterTime; //Excel启动之后时间 excel.Application app;
excel.Workbook wb;
excel.Worksheet ws;
excel.Range rng;
excel.TextBox tb; public Excel(string templetPath)
{
//实例化一个Excel Application对象并使其可见
beforeTime = DateTime.Now;
app = new excel.ApplicationClass();
app.Visible = true;
afterTime = DateTime.Now; wb = app.Workbooks.Open(templetPath,Type.Missing,Type.Missing,Type.Missing,Type.Missing,Type.Missing,Type.Missing,Type.Missing,Type.Missing,Type.Missing,Type.Missing,Type.Missing,Type.Missing,Type.Missing,Type.Missing);
ws = (excel.Worksheet)wb.Worksheets.get_Item();
} public void ExcelMethod()
{
rng = ws.get_Range("B5","C7");
rng.Merge(excel.XlAxisCrosses.xlAxisCrossesAutomatic);
rng.Value2 = "Excel2003"; rng = ws.get_Range("D8","E11");
rng.MergeCells = true;
rng.Value2 = "Excel2003";
rng.HorizontalAlignment = excel.XlHAlign.xlHAlignCenter;
rng.VerticalAlignment = excel.XlVAlign.xlVAlignCenter; rng = ws.get_Range("A1",Type.Missing);
rng.Value2 = ; rng = ws.get_Range("A2",Type.Missing);
rng.Value2 = ; for(int i=;i<;i++)
{
string s = string.Concat("G",i.ToString());
rng = ws.get_Range(s,Type.Missing);
rng.Value2 = i.ToString();
} tb = (excel.TextBox)ws.TextBoxes("文本框 1");
tb.Text = "作 者"; tb = (excel.TextBox)ws.TextBoxes("文本框 2");
tb.Text = "KLY.NET的Blog"; tb = (excel.TextBox)ws.TextBoxes("文本框 3");
tb.Text = "日 期"; try
{
tb = (excel.TextBox)ws.TextBoxes("文本框 5");
tb.Text = DateTime.Now.ToShortDateString();
}
catch
{
//这里用Dispose()方法结束不了Excel进程,所有还是要用Process的Kill()方法配合使用
// this.Dispose();
this.KillExcelProcess();
throw new Exception("不存在ID为\"文本框 5\"的文本框!");
}
finally
{
//如果有异常发生,Dispose()方法放在这里也结束不了Excel进程
// this.Dispose(); //如果发生异常,在这里也可以结束Excel进程
// this.KillExcelProcess();
}
} /// <summary>
/// 另存为Excel文件
/// </summary>
/// <param name="savePath">保存路径</param>
public void SaveAsExcelFile(string savePath)
{
wb.SaveAs(savePath,excel.XlFileFormat.xlHtml,Type.Missing,Type.Missing,Type.Missing,Type.Missing,excel.XlSaveAsAccessMode.xlExclusive,Type.Missing,Type.Missing,Type.Missing,Type.Missing,Type.Missing);
} /// <summary>
/// 结束Excel进程
/// </summary>
public void KillExcelProcess()
{
Process[] myProcesses;
DateTime startTime;
myProcesses = Process.GetProcessesByName("Excel"); //得不到Excel进程ID,暂时只能判断进程启动时间
foreach(Process myProcess in myProcesses)
{
startTime = myProcess.StartTime; if(startTime > beforeTime && startTime < afterTime)
{
myProcess.Kill();
}
}
} /// <summary>
/// 如果对Excel的操作没有引发异常的话,用这个方法可以正常结束Excel进程
/// 否则要用KillExcelProcess()方法来结束Excel进程
/// </summary>
public void Dispose()
{
wb.Close(null,null,null);
app.Workbooks.Close();
app.Quit(); //注意:这里用到的所有Excel对象都要执行这个操作,否则结束不了Excel进程
if(rng != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(rng);
rng = null;
}
if(tb != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(tb);
tb = null;
}
if(ws != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(ws);
ws = null;
}
if(wb != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(wb);
wb = null;
}
if(app != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(app);
app = null;
} GC.Collect();
}
}
}
源码下载 http://files.cnblogs.com/lingyun_k/ExcelTest.rar
上一篇:CentOS6.8安装mysql并设置字符集编码


下一篇:【hdu3555】Bomb 数位dp