VSTO个人开发总结
作者:TangHanF(GuoFu)
日期:2018年6月22日
说明:转载请注明出处,谢谢!🤝
联系方式:guofu_gh@163.com
[TOC]
公共代码封装
本文档相关实例可以使用到了类似于:
VSTOCommon.Instance.xxx
的方法或者属性,这是我进行了一定封装,具体部分代码如下:
csharp using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Office.Interop.Excel; /** Copyright (c) 2018 Microsoft All Rights Reserved. CLR 版本: 4.0.30319.42000 机器名称:GUOFUC62B 命名空间:ExcelAddIn_刷新插件.interfaces.impl 文 件 名: VSTOCommon 唯一标识:6cdd46a5-c7de-47e0-874a-6f0d18fe405c 当前的用户域:GUOFUC62B 创 建 人: GuoF 电子邮箱:guofu_gh@163.com 创建时间:2018/6/22 下午7:34:29 描述: **/
namespace ExcelAddIn_刷新插件.interfaces.impl { public class VSTOCommon : VSTOInterface { private static VSTOCommon vSTOCommon;
/// <summary>
/// 获取VSTOCommon对象实例
/// </summary>
/// <returns></returns>
public static VSTOCommon Instance
{
get
{
if (vSTOCommon == null)
{
vSTOCommon = new VSTOCommon();
}
return vSTOCommon;
}
}
/// <summary>
/// 获取Application对象
/// </summary>
/// <returns></returns>
public Application GetApplication => Globals.ThisAddIn.Application;
/// <summary>
/// 获取当前活动工作簿
/// </summary>
/// <returns></returns>
public Workbook GetCurrentActiveWorkbook => Globals.ThisAddIn.Application.ActiveWorkbook;
/// <summary>
/// 获取当前活动单元表
/// </summary>
/// <returns></returns>
public Worksheet GetCurrentActiveWorksheet => GetCurrentActiveWorkbook.ActiveSheet;
/// <summary>
/// 获取Range范围
/// </summary>
/// <param name="range">传入一个范围,例如:"A1:B3"、"A1"</param>
/// <returns></returns>
public Range GetRange(string range) => GetApplication.Range[range];
/// <summary>
/// 获取Range范围
/// </summary>
/// <param name="rowIndex">行,例如:"A1"</param>
/// <param name="colIndex">列,例如:"B1"</param>
/// <returns></returns>
public Range GetRange(string rowIndex, string colIndex) => GetApplication.Range[rowIndex, colIndex];
/// <summary>
/// 获取所有工作表。返回Excel.sheets对象,其中Excel.sheets中的每一个成员都是一个Excel.Worksheet对象。因此一般多对Excel.sheets进行遍历操作
/// </summary>
/// <returns> </returns>
public Sheets GetWorksheets => GetApplication.Worksheets;
/// <summary>
/// 获取活动表中选择的区域。返回一个多维(二维)数组
/// </summary>
/// <returns></returns>
public Range GetSelectRange => GetApplication.Selection;
}
}
# VSTO外接应用程序
## 当前激活的工作簿
csharp
Excel.Workbook workBook = Globals.ThisAddIn.Application.ActiveWorkbook;
当前激活的工作表
csharp Excel.Worksheet workSheet = workBook.ActiveSheet;
可对以上进行封装,方便使用:
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Excel = Microsoft.Office.Interop.Excel;
namespace ExcelAddIn_刷新插件
{
public class CommonExcelUtils
{
/// <summary>
/// 获取当前激活的工作簿
/// </summary>
/// <returns></returns>
public static Excel.Workbook getCurrentActiveWorkbook()
{
return Globals.ThisAddIn.Application.ActiveWorkbook;
}
/// <summary>
/// 获取当前活动的工作表
/// </summary>
/// <returns></returns>
public static Excel.Worksheet getCurrentWorksheet()
{
return getCurrentActiveWorkbook().ActiveSheet;
}
}
}
获取Excel所选区域
Ribbon类中:
csharp Excel.Range rng;//声明一个Excel的单元格区域变量 rng = Globals.ThisAddIn.Application.Selection;//获得Excel所选区域
ThisAddIn类中:
csharp
Excel.Range rng;//声明一个Excel的单元格区域变量。
rng = this.Application.Selection;//获得Excel所选区域
二者其实就 Globals.ThisAddIn 的区别,即获取方式的不同。
自动设置列宽
自动调整列宽要保证单元格不能处于编辑状态,如果单元格处于编辑状态则会触发异常。
EntireColumn的使用
csharp private void button8_Click(object sender, RibbonControlEventArgs e) { //选择区域自动调整列宽 Excel.Range range = VSTOCommon.Instance.GetSelectRange; range.EntireColumn.AutoFit();
//设定指定区域自动调整列宽
Excel.Range range2 = VSTOCommon.Instance.GetRange("A1", "B3");
range2.EntireColumn.AutoFit();
}
## 退出Excel单元的编辑状态
> 思路:模拟发送ESC按键。
>
> 经过测试,Globals.ThisAddIn.Application.SendKeys(object keys,[object wait])这个接口不生效:
>
> Globals.ThisAddIn.Application.SendKeys("{ESC}",true);
>
> 并未等待按钮结束。后改为:System.Windows.Forms.SendKeys.SendWait("{ESC}") 即可。
## 删除行
csharp
// 删除行
Excel.Worksheet sheet = VSTOCommon.Instance.GetCurrentActiveWorksheet;
//删除第二行
Excel.Range sel_row_range = sheet.Rows["2", Type.Missing];
//Excel.Range sel_row_range = sheet.Rows["2:2", Type.Missing];
sel_row_range.Delete(Excel.XlDeleteShiftDirection.xlShiftUp);
删除列
删除B列、C列:
csharp Excel.Range range2 = VSTOCommon.Instance.GetRange("B:B,C:C"); range2.Delete();
## 合并单元格
> 合并单元的本质就是首先获取一个要进行合并的Range范围,然后执行Merge方法进行合并。
csharp
/// <summary>
/// 合并单元格
/// </summary>
/// <param name="startRow">开始点,例如:A1</param>
/// <param name="endCol">结束点,例如:C3</param>
public void MergeCells(string startRow, string endCol)
{
//本质上就是获取一个Range范围,然后执行Merge方法进行合并
Microsoft.Office.Interop.Excel.Range mergeRange = VSTOCommon.Instance.GetRange(startRow, endCol);
mergeRange.Merge(Type.Missing);
}
调用:
VSTOCommon.Instance. MergeCells("A1","A3");//合并行
VSTOCommon.Instance. MergeCells("A1","C3");//合并列
获取所选区域数据
结合以上代码:
csharp object[,] arr = rng.Value ;//返回一个二维数组
>
arr:
[1, 1]: "需求变更(新增)工作量统计表"
[1, 2]: null
[1, 3]: null
[1, 4]: null
[1, 5]: null
[1, 6]: null
[1, 7]: null
[1, 8]: null
[1, 9]: null
[2, 1]: "序号"
[2, 2]: "系统名称"
[2, 3]: "变更编号"
[2, 4]: "变更内容"
[2, 5]: "原合同10%工作量约定"
[2, 6]: "原合同10%工作量使用情况"
[2, 7]: "新增变更工作量(超出10%部分)"
[2, 8]: "分包价(单价*人月)"
[2, 9]: "总包价(分包价*116%)"
[3, 1]: 1
[3, 2]: "xxxxx"
[3, 3]: 1
[3, 4]: "xxxxxx"
[3, 5]: "xx0"
[3, 6]: "xx1"
[3, 7]: "xx2"
[3, 8]: "xx3"
[3, 9]: "xx4" ```
可以把数组[1,1]理解为Excel的第一行第一列数据,[3,9]理解为Excel的第三行第九列数据....
在Ribbon类中调用ThisAddIn类中的方法
只需要:Globals.ThisAddIn.xxxx
即可
工作表的Cells属性
Cells利用两点进行单元格的定位。例如A5单元格,两种写法:
Cells(5,1)或者Cells(5,"A")
当前激活的工作表.Cells[1,2] //表示选择第一个第二列的单元格
新增Sheet表
使用Worksheets对象的Add方法,
dynamic Add(object Before, object After, object Count, object Type)
新增工作表在最后:
Globals.ThisAddIn.Application.Worksheets.Add(After: Globals.ThisAddIn.Application.Sheets[Globals.ThisAddIn.Application.Sheets.Count]);
当前工作薄表1复制到一新建工作薄中
Application.ActiveWorkbook.Sheets[1].Copy();
当前工作薄表1复制到表1的前面
Application.ActiveWorkbook.Sheets[1].Copy(Application.ActiveWorkbook.Sheets[1]);
当前工作薄表1复制到表1的后面
Application.ActiveWorkbook.Sheets[1].Copy(missing,Application.ActiveWorkbook.Sheets[1]);
删除工作表1
this.Application.ActiveWorkbook.Sheets[1].Delete();
有提示框提示,如果不需要提示,加上Application.DisplayAlerts = false;只是别忘了删后要改回来,且在这种情况下被删的表不得为Activesheet.
选择第一张表
this.Application.ActiveWorkbook.Sheets[1].Select();
移动工作表
this.Application.ActiveWorkbook.Sheets[1].Move(this.Application.ActiveWorkbook.Sheets[3]);
隐藏工作表
this.Application.ActiveWorkbook.Sheets[1].Visible = 0;//隐藏工作表,=-1显示,=2深度隐藏
操作Range
有关Range的操作基本是频率比较高的操作,像获取数据、设置数据、拷贝数据、粘贴数据等。
获取ActiveSheet单元格A2的值有以下等等方式:
csharp Excel.Worksheet sht = Globals.ThisAddIn.Application.ActiveWorkbook.ActiveSheet; var s = sht.Range("a2").Value; var s = sht.Range("a1").Offset(1,0).Value; var s = sht.Cells(2,"a").Value; var s = sht.Cells(2, 1).Value; var s = sht.Range("a2").Value; var s = sht.Cells(1, 1)(2,1).Value;
由于C#是强类型的语言,变量必须声明类型,但单元格中的值却有文本、数字等,所以这里的变量s定义为var(也可为object)。可见,单元格的表示与VBA中相比,只是要在前面加点东西就成了,其余都一样,如下面的选择单元格:
### 选择单元格(单个、多个、多列)
csharp
sht.Cells[2, 3].Select();//选择行2列3的单元格,即C2单元格
sht.Range["d3:J8"].Select();//选择D3开始,J8结尾的区域
sht.Range["d:J"].Select();//选择D到J列
复制粘贴单元格
csharp sht.Range["d3:e3"].Copy(sht.Range["d6"]); //拷贝并粘贴。将D3:E3区域的数据拷贝到D6为起点的区域(自动扩充)
sht.Range["d3:e3"].Copy();//仅仅拷贝数据。拷贝D3:E3区域数据到内存 sht.Range["a6:b8"].Select();//选择A6:B8的区域 sht.Paste();//然后将数据粘贴到该区域
sht.Range["h6:i6"].Select(); sht.Paste();
### 单元格区域写进数组、数组写回单元格
csharp
var ar = sht.Range["d3:e3"].Value;
或
csharp object[,] ar = sht.Range["d3:e3"].Value; sht.Range["d5:e5"].Value =ar;
### 给Range区域赋值
csharp
Excel.Range range = Globals.ThisAddIn.Application.Range["A1:B3"];
string[,] arr = new string[,] { { "姓名", "性别" }, { "张三", "男" }, { "李丽", "女" } };
range.Value = arr;
也可以:
csharp range.Value="数据"
但是这样range区域就会被“数据填充”
### Range区域数据对齐
`Microsoft.Office.Interop.Excel.XlHAlign`
csharp
range.HorizontalAlignment = Microsoft.Office.Interop.Excel.XlHAlign.xlHAlignRight;
将数据绑定到Excel单元格(赋值操作)
一开始比较迷茫一点可能是数据有了(数据来源可能是查询数据库得到、可能是读取文件得到等),问题是在填充数据可以使用xxx.Range.Value = "xxxxxx"实现,但是这个Range怎么确定呢?
两点定位法,下面代码有详细说明
csharp string cmd = "select * from t_vipinfos"; //MySQLHelper.GetDataTable为我封装的数据库代码,大家可自行封装,只要返回一个DataTable对象即可。 DataTable dt = MySQLHelper.GetDataTable(null, System.Data.CommandType.Text, cmd);
//得到数据表中总行数 int rowCount = dt.Rows.Count; //得到数据表中总列数 int colCount = dt.Columns.Count;
//根据总行数、总列数创建一个二维数据组来实现对Excel数据表的映射 string[,] arr = new string[dt.Rows.Count, dt.Columns.Count];
//遍历,赋值 for (int i = 0; i < rowCount; i++) { for (int j = 0; j < colCount; j++) { arr[i, j] = dt.Rows[i][j].ToString(); } }
//将数据绑定到Excel中 //首先获取到当前活动工作表 Excel.Worksheet sh = VSTOCommon.Instance.GetCurrentActiveWorksheet; //获取要赋值的区域。这一块我们下面重点说明一下 Excel.Range range = sh.Range[sh.Cells[1, 1], sh.Cells[rowCount, colCount]]; //未指定的区域进行赋值操作 range.Value = arr;
现在需要对上述代码的
`Excel.Range range = sh.Range[sh.Cells[1, 1], sh.Cells[rowCount, colCount]];`
进行拆分说明:
> 我们知道,通过Range可以确定一个范围(单元格、行、列等),例如xxx.Range("A1:B3")或者xxx.Range("A1","B3")可以表示获取单元格A1到单元格B3的区域。这一行代码就是基于这个原理。
- `sh.Cells[1, 1]`:获取A1到A1的单元格,即左上角第一个单元格,作为区域起点,返回一个Range区域(实际就是一个单元格而已)--区域1。
- `sh.Cells[rowCount, colCount]`:以数据表的行、列总数作为获取Range区域的起始点,返回一个Range区域--区域2
- `sh.Range[sh.Cells[1, 1], sh.Cells[rowCount, colCount]]`:通过区域1、区域2两点最终确定一个大区域,即要进行数据填充的区域。
![](https://ws4.sinaimg.cn/large/006tNc79ly1fsop9bibpvj314o0kqdhk.jpg)
## 清空Excel表格
csharp
Excel.Worksheet sh = VSTOCommon.Instance.GetCurrentActiveWorksheet;
int r=sh.Rows.Count;
int c = sh.Columns.Count;
Excel.Range tmpRange = sh.Range[sh.Cells[1, 1], sh.Cells[r, c]];
tmpRange.Clear();
方法/属性
Application
方法
Range/get_Range方法
get_Range方法属于Application级别的方法,表示选取一个范围(注意,一个单元格也属于一个范围)。
get_Range("A1"):表示选取A1这一个区域
get_Range("A1","B5"):表示选取A1到B5这片区域
注意,get_Range方法属于Application父类(_Application)的方法,可用Range属性代替,例如:Application.Range["A1","B4"];
csharp Excel.Range range = Globals.ThisAddIn.Application.get_Range("a1");
int i = 0;
Excel.Range rng = Globals.ThisAddIn.Application.get_Range("a1");
foreach (Excel.Worksheet sht in Globals.ThisAddIn.Application.Worksheets)
{
rng.get_Offset(i++, 0).Value = sht.Name;
}
// 以上代码实现将当前工作簿的所有sheet表名称插入到从A1开始的一列单元格中
![](https://ws1.sinaimg.cn/large/006tNc79ly1fsk1fukmjjj30o0110djb.jpg)
> //Excel.Range range = Globals.ThisAddIn.Application.Range["A1", "B5"];
>
> Excel.Range range = Globals.ThisAddIn.Application.Range["A1:B5"];
以上二者等效。
#### 属性
##### ActiveWorkbook
> 当前处于激活或者活动状态的工作簿,返回`Excel.Workbook`对象:
>
> Excel.Workbook workboot = Globals.ThisAddIn.Application.ActiveWorkbook;
-------
### WorkBook
#### 方法
#####
#### 属性
##### ActiveSheet
> 当前处于激活或者活动状态的工作表,返回`Excel.Worksheet worksheet`对象:
>
> Excel.Worksheet worksheet = 当前活动工作簿.ActiveSheet
>
> (如何获取当前活动工作簿参考上面Application的属性一节)
#### 方法
#####
#### 属性
-------
### WorkSheet
#### 方法
#####
#### 属性
##### Cell属性
> **单元格,返回一个Range对象**。例如:
>
> Globals.ThisAddIn.Application.ActiveWorksheet.Cell[1,1] 获取左上角第一个单元格,即A1:A1单元格
##### Rows属性
> **获取行。返回Range对象。**
>
> 获取第一行:
>
> csharp
> Excel.Range sel_row_range1 = sheet.Rows["1:1", Type.Missing] ;
> Excel.Range sel_row_range2 = sheet.Rows["1", Type.Missing] ;
>
>
Columns属性
获取列,返回Range对象。
Range
方法
Select方法
选择区域。
Range.Select();
Merge 方法
合并单元格。
Range.Merge(Type.Missing)
Clear 方法
清空表数据。
属性
事件
其它部分事件参考:《浅谈 Excel 对象模型.md》
SheetSelectionChange事件
单元格选择改变事件。每次选择一个单元格触发该事件。
案例:点击单元格显示当前单元格的坐标位置
csharp public partial class ThisAddIn { private void ThisAddIn_Startup(object sender, System.EventArgs e) {
this.Application.SheetSelectionChange += Application_SheetSelectionChange;
}
private void Application_SheetSelectionChange(object Sh, Excel.Range Target)
{
MessageBox.Show(Target.Address);
}
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
}
#region VSTO 生成的代码
/// <summary>
/// 设计器支持所需的方法 - 不要修改
/// 使用代码编辑器修改此方法的内容。
/// </summary>
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
#endregion
}
```
SheetChange事件
Application.EnableEvents = false
Application.EnableEvents = true
可以控制是否响应相关事件