统计Excel表格是我们工作学习中会碰到的事情,对于批量报表的统计运算,编写程序对Excel表格处理是高效,错误率低的办法,总结一下我近期的学习情况。
1. 引用程序集
Excel二次开发的程序集很多,我用的是VS自带的com组件,com组件的优点是实现功能完整,比较强大;缺点是代码很复杂,
一写一长串,不易理解,而且接口繁多,很多我们日常基本用不上。
首先需要在引用的COM中找到Microsoft Excel 16.0 Object Library(看各自版本),添加到引用中。
添加引用后,Using
using MyExcel = Microsoft.Office.Interop.Excel;//起别名MyExcel
using System.IO;
2. 读写Excel
使用Com组件读写Excel,需要创建一系列对象,自上而下为:
Excel进程Application,工作簿集合Workbooks,工作簿Workbook,工作表Worksheet,单元格
我们想要操作表格,读写数据,就要一步一步的创建下来。
Com组件读写excel表格,必须要打开Excel程序,所以要先创建Excel进程;
然后是工作簿,一个进程可以打开很多工作簿,就需要创建Workbooks对象,它是一个集合,可以包含多个Workbook,Workbook对象就对应单个excel文档,Workbook对象构成Workbooks,是Workbooks的子集。Workbooks由Excel进程打开;
最后是工作表,一个Workbook对象包含Worksheet,Worksheet是Workbook的子集。
主要操作包括Workbook(工作簿)的打开,新建,删除,另存;Worksheet(工作表)的新建,删除,获取;单元格的数据读取写入,格式公式的设置等。
示例一、
创建进程,创建工作簿集合,操作完成后要关闭工作簿和Excel进程,不然会在后台遗留,下次开机后会自动打开Excel,后边介绍杀进程的代码。
MyExcel.Application application = new MyExcel.Application();//创建名为application的Excel进程
application.Visible = true;//设置可见性,false的话程序全程在后台运行,不会看到
application.DisplayAlerts = false;//不现实提示对话框
MyExcel.Workbooks workbooks = application.Workbooks;//创建名为workbooks的工作簿集合
workbooks.Close();//关闭工作簿
application.Quit();//退出进程
示例二、
文档操作,新建与打开
MyExcel.Application application = new MyExcel.Application();
application.Visible = true;
application.DisplayAlerts = false;//不现实提示对话框
MyExcel.Workbooks workbooks = application.Workbooks;
MyExcel.Workbook wb = workbooks.Add("文档路径");//新建wb的工作簿对象,它为我们打开创建的文件
MyExcel.Workbook wb1 = workbooks.Open("文档路径");
wb.Save();
wb1.Save();//com组件直接对文档操作,所以处理文档后一定要Save
workbooks.Close();
application.Quit();
注:workbooks的Add()方法,若有路径参数,可打开文档,若不输入参数,则表示新建文档,需要用 wb.SaveAs()方法保存出来。
workbooks的Open()方法只用于打开文件。
示例三、
工作表获取,新建与删除
MyExcel.Application application = new MyExcel.Application();
application.Visible = true;
application.DisplayAlerts = false;//不现实提示对话框
MyExcel.Workbooks workbooks = application.Workbooks;
MyExcel.Workbook wb = workbooks.Open(@"C:\Userdata\EX0122\3.xlsx");
//以下两种读取工作表的方法,索引和名称,名称可能报错
MyExcel.Worksheet wsheet = wb.Sheets[1];//工作表索引从1开始,不是0
//MyExcel.Worksheet wsheet1 = (MyExcel.Worksheet)wb.Worksheets["义教经费支出明细表"];
wsheet.Activate();//激活才可以操作工作表
wsheet.Name = "表名AAA";//工作表更改名称
//新建工作表
object missing = Missing.Value;//创建缺省值,需要using
MyExcel.Worksheet newsheet = (MyExcel.Worksheet)wb.Worksheets.Add(missing, missing, 1, missing);//默认新建到最前边
//删除工作表,两种方法
newsheet.Delete();//表对象的Delete()方法就可删除
//string sheetName = "Sheet1";//通过表名称删除
//((MyExcel.Worksheet)wb.Worksheets[sheetName]).Delete();
wb.Save();
workbooks.Close();
application.Quit();
示例四、
单元格数据读取,C#中有Range对象和Cells对象。顾名思义,range是区域的意思,cells是单元格的意思,range包含cells。
一个 Range 对象可以代表一个单元格、一行、一列、包含一个或者更多单元块(可以是连续的单元格,也可以式不连续的单元格)的选定单元格,甚至是多个工作表上的一组单元格。而单独用cells只能获取获取一个单元格的数据。
若要获取excel中A1到E5的区域,当然用Range对象会得心应手。或者用cells和循环读取。
//激活工作表后接如下代码
//获取单个单元格,行列的索引都是从1开始,不是0
//1、直接取出单元格的值,值类型转换,一定要有.Value才是取出来的值
string value = Convert.ToString(wsheet.Cells[17, 1].Value);
//2、新建range对象,将单元格强转为range对象,列号用字母索引也可
MyExcel.Range range = (MyExcel.Range)wsheet.Cells[17, "A"];
string value1 = Convert.ToString(range.Value);
//3、也可以用如下方法
MyExcel.Range range1 = wsheet.Range["A17"];
string value2 = Convert.ToString(range.Value)
//获取区域数据
object[,] data = wsheet.Range["D9:H11"].Value;//赋给数组也可以
注意:
1、拿取值要转换range.Value得到的才是对的,range.Text可能是单元格的显示内容,
比如,A17单元格的值为333.123,range.Text拿到的值是带格式的值¥333.12,或者是省略小数的
2、对于大规模数据,用Range对象整个读到数组中再循环处理,要比一个单元格一个单元格取值快N倍。
示例五、
数据写入
格式的调整功能很多,暂时就摸索这么多吧,对于报表统计运算这么多内容就够了吧,后边学了再来补
//用行列索引直接赋值
wsheet.Cells[17, 1].Value = "表二";
//用Range对象,写入表格
MyExcel.Range range3 = (MyExcel.Range)wsheet.Cells[19, "A"];
range3.Value = "";
range3.Formula = "=A18";//公式
range3.Font.Name = "宋体";//字体
range3.Font.Size = 12;//字号
range3.Font.Color = Color.Red;//字体颜色
//合并单元格
MyExcel.Range range4 = wsheet.Range["D14:D15"];//选择区域,用Merge()方法,合并后的值默认为区域左上单元格的值
range4.Merge();
1
3. 其他问题
1、批量读取excel表格后,虽然退出了进程,(application.Quit();),但后台还有。。。
网上找了杀进程的办法,
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern int GetWindowThreadProcessId(IntPtr hwnd, out int ID);
public static void Kill(Microsoft.Office.Interop.Excel.Application excel)
{
IntPtr t = new IntPtr(excel.Hwnd);//得到这个句柄,具体作用是得到这块内存入口
int k = 0;
GetWindowThreadProcessId(t, out k); //得到本进程唯一标志k
System.Diagnostics.Process p = System.Diagnostics.Process.GetProcessById(k); //得到对进程k的引用
p.Kill(); //关闭进程k
}
直接调用该方法,把我们创建的application传进去就可
try
{
//杀进程
Kill(application);
//释放EXCEL应用程序的进程
System.Runtime.InteropServices.Marshal.ReleaseComObject(application);
}
catch
{ }
2、单元个字母索引和数字索引的转换,10进制与26进制转换
/// <summary>
/// 用于excel表格中列号字母转成列索引,从1对应A开始
/// </summary>
/// <param name="column">列号</param>
/// <returns>列索引</returns>
public static int ColumnToIndex(string column)
{
int index = 0;
char[] chars = column.ToUpper().ToCharArray();
for (int i = 0; i < chars.Length; i++)
{
index += ((int)chars[i] - (int)'A' + 1) * (int)Math.Pow(26, chars.Length - i - 1);
}
return index;
}
/// <summary>
/// 用于将excel表格中列索引转成列号字母,从A对应1开始
/// </summary>
/// <param name="index">列索引</param>
/// <returns>列号</returns>
private static string IndexToColumn(int index)
{
if (index <= 0)
{
throw new Exception("Invalid parameter");
}
index--;
string column = string.Empty;
do
{
if (column.Length > 0)
{
index--;
}
column = ((char)(index % 26 + (int)'A')).ToString() + column;
index = (int)((index - index % 26) / 26);
} while (index > 0);
return column;
}
//行列号转换为字母索引
public static string TransIndex(int row, int column)
{
string a = IndexToColumn(column);
return a + row.ToString();
}