这是使用jacob操作Word和Excel的教程,因为看不懂官方文档(英语不好,翻译太菜)所以选择去找找教程。
jacob可以操作的
对象方法和操作,里面的例子都是
http://entercad.ru/acadauto.en/
这个微软官方有中文版翻译在左下角,点一下中文的即可。
https://docs.microsoft.com/zh-cn/office/vba/api/overview/word
文档中的对象和方法都是VBA的,其实本来也是用dll文件操作VBA的方式。
如果有条件在开发过程中最好使用MSoffice作为调试的首选程序,因为WPS有的时候是无法返回报错信息的,亲测
[jacob地址][https://sourceforge.net/projects/jacob-project/]
我们下载下来的压缩包的目录结构大约是这样的
我们需要将两个dll文件放入C:\Windows\System32 中。
这个我就不多赘述了,不懂得朋友只能够去百度了。
在写代码之前需要大家理解一些层次观念,所以在代码中会讲解,请大家结合自己的使用word的过程 每一步每一步,以方便理解代码。
public class WordService {
private static ActiveXComponent application; //代码整个word程序
private static Dispatch documents;//代表这个程序打开的所有文档
private static Dispatch doc;//代表程序打开某一个文档
private Dispatch tables; //自动释放 所以每次重新打开的时候需要重新赋值,即使你没有手动释放不然会报空指针
private Dispatch table; //自动释放 同上
private Dispatch selection; //选择器
public void openWord(String filepath){
ComThread.InitSTA();//ComThread也就是我们包中的对象 jacob包中 进行初始化
//创建应用
application = new ActiveXComponent("Word.Application");//开启word 它会指定我们的默认程序MSoffice或者WPS程序
application.setProperty("Visible",new Variant(true));//设置程序可见
// Variant类,是jacob操作用于封装变量的类,常用的封装的类型如下:
//java基本数据类型 int float double String char boolean 等。。。
//可以封装Dispath类,这个类是我们获取到的文档中的某个可以处理对象
Dispatch documents = application.getProperty("Documents").toDispatch();//使用Word程序获取到所有的文档对象使用getProperty,
// 如果想要获取其他的属性或者调用方法,可以查看上面提到的文档的地址
//以下是文档对象的操作
//文档的打开操作 这里涉及到两个方法一个类
// 第一个是call方法,代表调用文档中某个对象的方法
// 第二个是toDispatch方法 将call中调用的"Open"方法的返回值转化成一个可处理的对象 任何call方法返回的默认都是Variant对象,
// 可以转化成其他形式的封装好的一些常用的对象包括toDispath(Dispath 可处理对象) toString(String 字符串 一般获取表格内容、正文等) toSafeArray(向量 这个到现在为止很少用到)
//一个类是Dispatch类,是我们用于操作对象的类 主要调用其中的三个静态方法 call(调用文档中各个对象的方法)、 get(获取文档中对象的各个属性)、put(给文档中的各个对象赋值)
// 还需要说的另外一个是 ,call方法的几个参数
// Dispatch.call() 第一个参数,代表你要调用哪个对象的方法,这里是文档集合documents对象的方法 具体有哪些对象和属性方法 请看官方文档的对象介绍
//第二个参数是你调用的这个对象的 哪个方法 同上想知道有哪些方法看文档,
//如果没有参数的情况下前两个参数是必填的,后面不管多少个参数都是我们调用的方法中需要的参数,
//比如 open方法的 第一个参数是 文件路径 第二个参数是 是否把word程序显示到前台,意思就是是否可见
doc = Dispatch.call(documents, "Open", new Variant(filepath),new Variant(false)).toDispatch();
//当前doc代表的是已经打开的文档,get方法如上面所说是获取到对象的属性的,
// 这里代表获取当前文档的所有表格集合 注意不是一个表格 而是所有表格的集合
tables = Dispatch.get(doc, "Tables").toDispatch();
//获取当前应用的选择器
//选择器怎么说呢就是我们在操作文档的时候 能执行选择文字的操作的东西,我们一定需要获取它
selection = Dispatch.get(application, "Selection").toDispatch();
}
}
在代码中向大家大概解释了Variant、Dispathch等 这里我再总结一下
Variant 用于传参的时候或者接受返回值的时候的封装 翻译过来就是变量 一般方法中的变量都需要用它封装
Dispatch 用于操作文档中对象的类
toDispatch 将接收到的 Variant转化成可以处理的对象
在word中我们如果想要获取某些东西需要按照一定的层级结构才能够获取的到,接下来向大家展示一般的对象层级,当然你可以在官方的文档中查看到这些对象在上一级的属性或者方法中
大多数的对象都是通过上一层获取的,只有少量的可以通过下层获取上层 例如 Cell(单元格)的 Rows(行)属性和Columns(列)属性
说一个特殊的对象Range 区域,它代表文档中的某个对象或者某一些对象的 的区域,很多对象都有Range属性
上面的这些talbes rows columns对象等都可以在官方文档中查看到。
简单的介绍了层级之后 大家一定有感觉了,但是还是有点懵,如果大家有点蒙我们再来看下一段代码,大家就很快能够上手了。
如果大家有点基础可以直接去另外一个博主的博客上看,他这个适合一定基础的同学学习[地址][https://www.cnblogs.com/qinwangchen/p/5894236.html],
/**
* @Description: 获取某一table
* @Param: [index] table的索引 从1开始 表示第一个表格
* @return: com.jacob.com.Dispatch
* @Author: peach
* @Date: 2019/04/10
*/
public Dispatch getTable() {
ComThread.InitSTA();//ComThread也就是我们包中的对象 jacob包中 进行初始化
ActiveXComponent application = new ActiveXComponent("Word.Application");//开启word 它会指定我们的默认程序MSoffice或者WPS程序
application.setProperty("Visible",new Variant(true));//设置程序可见
Dispatch documents = application.getProperty("Documents").toDispatch();//使用Word程序获取到所有的文档对象使用getProperty,
Dispatch doc = Dispatch.call(documents, "Open", new Variant(filepath),new Variant(false)).toDispatch();
//当前doc代表的是已经打开的文档,get方法如上面所说是获取到对象的属性的,
// 这里代表获取当前文档的所有表格集合 注意不是一个表格 而是所有表格的集合
Dispatch tables = Dispatch.get(doc, "Tables").toDispatch();
//获取当前应用的选择器
//选择器怎么说呢就是我们在操作文档的时候 能执行选择文字的操作的东西,我们一定需要获取它
Dispatch selection = Dispatch.get(application, "Selection").toDispatch();
//获取文档中第一个表格,可以调用add方法添加 详情查看文档。
table = Dispatch.call(this.getTables(), "Item",new Variant(1)).toDispatch();
return table;
}
/**
* @Description:通过行数和列数获取到单元格
* @Param: [row, column]
* @return: com.jacob.com.Dispatch
* @Author: peach
* @Date: 2019/04/17
*/
public Dispatch getCell(Integer row,Integer column){
Dispatch cell = Dispatch.call(table, "Cell", new Variant(row), new Variant(column)).toDispatch();//这里是通过此单元格的行数和列数类进行定位,行数和列数从1开始
return cell;
}
/**
* @Description: 取消当前表格中所有字体的加粗
* @Param: []
* @return: void
* @Author: peach
* @Date: 2019/04/11
*/
private void cancelBold(){
Dispatch.call(table,"Select");
Dispatch font = Dispatch.get(selection, "Font").toDispatch();
Dispatch.put(font,"Bold",new Variant(false));
}
/**
* @Description: tables集合对象get方法
* @Param: []
* @return: com.jacob.com.Dispatch
* @Author: peach
* @Date: 2019/04/10
*/
public Dispatch getTables() {
//获取所有的tables对象
if (tables == null || tables.m_pDispatch == 0){
tables = Dispatch.get(doc,"Tables").toDispatch();
}
return tables;
}
如果想要看到更多方法和属性请点击[微软开发者平台][https://docs.microsoft.com/zh-cn/office/vba/api/overview/word]
讲一下怎么看这个开发文档吧,如果不习惯中文可以使用英文,
我们可以看到代码中的语法这一列,他写的是
expression.tables 并且tables是Document的属性,我们一定要看清楚这个是对象的属性还是方法
然后下面说了这个expression代表的是一个Document对象,意思我们可以通过获取Document的tables属性获取到tables,前面说的返回一个Table集合, 该集合代表指定文档中的所有表格。 此为只读属性。
那么我们在代码里面怎么写呢?
当前是tables属性,那么我们的调用方式如下:
Dispatch tables = Dispatch.get(doc,"Tabels").toDispatch(); //因为Tables是属性,所以调用get方法
Dispatch是所有处理类的存放地址,doc代表的是Document对象,第二个参数是需要拿到的第一个参数的属性名称。最后调用toDispatch转换成可操作的对象。
如果调用有参数的方法
我们可以看到文档中,tables对象的item方法需要传递参数,类型是long类型的,且参数是必须的,参数代表的意思是index 也就是索引,返回值是单个Table对象,
VBA中的调用方式是
expression.item(Long index);
我们在jacob的调用方式如下
Dispatch table = Dispatch.call(tables,"Item",new Variant(1)).toDispatch();//因为item是方法,所以使用call方法,获取到Tables集合的第一个表格, 注意索引是从1开始不是从0
这里需要说明下,如果是平常逻辑 我们通过获取tables->table->rows->row即可删除 我为什么需要获取到 row->cell->Rows呢
原因如下:
如果你的表格里面有纵向合并的单元格或者横向合并的单元格,让我们通过rows的item方法获取到某一行的或者columns的item方法获取到某一列的时候会报错
报错信息大概是:
com.jacob.com.ComFailException: Invoke of: Item
Source: Microsoft Word
Description: 无法访问此集合中单独的行,因为表格有纵向合并的单元格。
没错这个报错是中文的,所以我们需要通过获取到某一个单元格反向选择这个单元格的rows属性,虽然是Rows,但是返回的却是一行。
/**
* @Description: 通过获取某个单元格而删除这个单元格所在的某一行
* @Param: [cell]
* @return: void
* @Author: peach
* @Date: 2019/04/11
*/
private void deleteRow(Dispatch cell){
//此处如果调用rows的select方法,再将选择的删除 那么删除的是选择的rows里面得内容 如果直接调用rows的delete方法则删除的是表格的行
Dispatch range = Dispatch.call(cell, "Range").toDispatch();
Dispatch rows = Dispatch.get(range, "Rows").toDispatch();
Dispatch.call(rows,"Delete"); //删除行 包括表格
//下面的方式会删除行里面得内容 保留表格结构
// Dispatch.call(rows,"Select");
// Dispatch.call(selection,"Delete");
}
这个方法非常神奇,当我们需要获取某个对象的时候,一定要先调用它的select方法将其选中,选中之后才能够获取Text或者Value属性获取他的值。
当我们选择一个对象之后,我们在打开文档时初始化的selection对象(选择器对象)中自动就会将我们调用select的这个对象放到选择器中,之后调用
例如:
/**
* @Description: 获取传递过来的单元格的值
* @Param: [cell]
* @return: java.lang.String
* @Author: peach
* @Date: 2019/04/11
*/
private String getCellValue(Dispatch cell){
Dispatch.call(cell,"Select"); //首先调用select方法
return Dispatch.call(selection, "Text").toString(); //注意这里的第一个参数是选择器哦,返回的是该单元格中的值
}
因为使用的同样都是jacob,所以开启方法和调用方法的方式都差不多,但是依然有不同的地方,接下来向大家介绍。
private static ActiveXComponent excelApplication; //excel程序对象 防止打开多个
private static Dispatch workbooks; //全部工作簿对象
private Dispatch workbook;//具体某一个工作簿对象
private Dispatch sheets;//获取所有的sheets对象
private Dispatch currentSheet;//获取当前sheet
/**
* @Description: 打开文档
* @Param: [filepath, visible]
* @return: void
* @Author: peach
* @Date: 2019/04/11
*/
public void openExcel(String filepath,boolean visible){
try {
//初始化线程
ComThread.InitSTA();
excelApplication = new ActiveXComponent("Excel.Application");
excelApplication.setProperty("Visible",new Variant(visible));//设置不可见
//前面的操作和word一致,从这里开始,有点不同
//首先这里不再是文档集合Documents ,而是工作表集合Workbooks,但是和文档集合是一个层级
workbooks = excelApplication.getProperty("Workbooks").toDispatch();
//打开已经存在的Excel文档
workbook = Dispatch.invoke(
workbooks,//第一个参数是被执行invoke的对象
"Open",//第二个参数是 被执行参数的什么属性或者方法
Dispatch.Method,//这里第三个参数, 如果第二个参数是方法这里就调用call 如果是属性就调用get
new Object[]{ // 第四个参数 将文档成功打开之后 workerbooks的 Open方法/参数将会变成这个参数的值
filepath, //文件路径
new Variant(false),//是否以只读的方式打开,因为要操作所以需要不使用只读的方式
new Variant(false),//这个参数目前未知
"1",
"pwd" //此处为密码,如果该文档有蜜蜡聚设置密码
},
new int[1] //第五个参数为错误级别,一般为1即可
).toDispatch();//invoke 就是没有返回值的call方法
}catch (Exception e){
//执行清空操作
e.printStackTrace();
}
}
工作表的概念我们应该都清除,也就是在左下角通过sheet1,sheet2,sheet3的方式进行转换。
每一个sheet就是一个工作表,之后再获取到这个工作表中所有元素或者其他,所以我们获取的顺序也应该是如下:
application(程序、应用)->Workbooks(工作簿的集合)->workbook(某一个工作簿)->sheet(工作表)->表中的其他元素(usedRange等)
/**
* @Description: 获取当前活动的sheet ,当Excel第一次打开的时候的sheet即为activesheet,当我们切换之后 这个activesheet就会改变
* @Param: []
* @return: com.jacob.com.Dispatch
* @Author: peach
* @Date: 2019/04/03
*/
private Dispatch getCurrentSheet(){
currentSheet = Dispatch.get(workbook,"ActiveSheet").toDispatch();
return currentSheet;
}
/**
* @Description: 获取当前单元格所有已经使用的区域的集合
* @Param: []
* @return: com.jacob.com.Dispatch
* @Author: peach
* @Date: 2019/04/22
*/
private Dispatch getUsedRange(){
Dispatch useRange = Dispatch.get(this.getCurrentSheet(), "UsedRange").toDispatch();
return useRange;
}
/**
* @Description: 获取单元格的值,和word不通过的是,在excel中是有横纵坐标的,所以我们传递的位置值类似于C1 C2这样
* @Param: [position] 位置信息 可以通过getposition方法生成
* @return: java.lang.String
* @Author: peach
* @Date: 2019/04/03
*/
private String getCellValue(String position){
Dispatch cell = Dispatch.invoke(getCurrentSheet(),"Range",Dispatch.Get,new Object[]{new Variant(position)},new int[1]).toDispatch();
return Dispatch.get(cell,"value").toString(); //todo 验证了 有区别 尽量使用value,获取到的是全部的值,如果使用text则获取excel显示出来的值
}
在表格中,没有第一个表第二个二表这样的概念,所以我们可以直接通过A1 A2的方式进行获取,同时如果表格中存在合并的单元格,则以第一个单元格位置为整个单元格的位置,例如:
可以通过定位到某一个range(区域,能够选择的部分)来获取到其中的值,代码如下:
/**
* @Description: 获取单元格的值,和word不通过的是,在excel中是有横纵坐标的,所以我们传递的位置值类似于C1 C2这样
* @Param: [position] 位置信息 可以通过getposition方法生成
* @return: java.lang.String
* @Author: peach
* @Date: 2019/04/03
*/
private String getCellValue(String position){
Dispatch cell = Dispatch.invoke(getCurrentSheet(),"Range",Dispatch.Get,new Object[]{new Variant(position)},new int[1]).toDispatch();//由于get方法无法传值,所以只有使用invoke方法进行获取。
return Dispatch.get(cell,"value").toString(); //todo 验证了 Value和text 有区别 尽量使用value 大小写没啥区别 text和value区别看下方
}
这里说一下value和text的区别,也就是我们调用get方法的时候获取的是这个对象中的哪个属性,excel中如果使用
text:获取到的是这个文本中显示给用户看的值
value:获取到全部长度的值
其次invoke和get,call方法的差别,这三个方法在jacob操作的任何文档都可以调用,他们三个有什么区别呢,我们慢慢说,先看看调用方式。
call方法
//没有参数没有返回值的call
Dispatch.call(currentSheet,"Select");
//有返回值的call
Dispatch chartObjects = Dispatch.call(currentSheet,"ChartObjects").toDispatch();
//有参数的call
Dispatch chartObject = Dispatch.call(chartObjects"Item", new Variant(1)).toDispatch();
get方法
//get方法会有返回值,但是get方法不能够传递参数,因为这个Chart本身就是个参数
Dispatch chart = Dispatch.get(chartObject, "Chart").toDispatch();
invoke方法是get和call方法的集合
Dispatch.invoke(
sheets, //第一个参数需要调用的 对象 当前sheets对象
"Item", //调用的地向的哪个 方法或者属性 当前sheets对象的Item方法
Dispatch.Method, //调用方式,如果是 方法则使用Dispatch.Method 如果是属性则使用Dispatch.Get
new Object[]{new Variant{name}}, //此方法的参数 如果有多个则在Object数组中添加多个
new int[1] //错误级别,默认为1即可
)
.toDispatch();
我们可以发现,其实invoke和get、call方法相同,在底层get和call方法其实都是调用的invoke方法,无非是这样方便我们调用。
我们都知道前面在word中的介绍我们获取表格的某一个单元格,我们使用的方式是行,列的方式如下:
/**
* @Description:通过行数和列数获取到单元格
* @Param: [row, column]
* @return: com.jacob.com.Dispatch
* @Author: peach
* @Date: 2019/04/17
*/
public Dispatch getCell(Integer row,Integer column){
Dispatch cell = Dispatch.call(table, "Cell", new Variant(row), new Variant(column)).toDispatch();//这里是通过此单元格的行数和列数类进行定位,行数和列数从1开始
return cell;
}
但是在excel中,我们并没有按照这个方式,而是通过字母+数字的方式定位,举个例子,第一行第一列的单元格在EXCEL表示为A1单元格, 所以在excel中定位单元格需要遵循这个方式,下面给了两个方法,一个是生成坐标,另外一个则是通过坐标获取到单元格中的值。
/**
* @Description: 通过行数和列数获取到当前表格中的position
* @Param: [row, column] 行和列
* @return: java.lang.String
* @Author: peach
* @Date: 2019/04/03
*/
private String getPosition(Integer row,Integer column){
Integer columnCount = getColumnCount(getCurrentSheet());
String position = "";
if (columnCount >26){ //第27列表示的是AA,所以我们需要转换成
int multiple = columnCount/26; //1 用列数除以26 看我们需要多少个A、获取十位
int remainder = columnCount%26; // 取出余下的数字 加上A的ascii码 就得到个位数的字母
char rchar = (char)(remainder+64); //通过ascii码转换的形式获取大写的A ,这个是余下的数代表的字母 1代表A
char mchar = (char)(multiple+64); //这是左边的字母
position = mchar+""+rchar;
}else{
position = (char)(column+64)+"";
}
return position+""+row;
}
/**
* @Description: 获取单元格的值,和word不通过的是,在excel中是有横纵坐标的,所以我们传递的位置值类似于C1 C2这样
* @Param: [position] 位置信息 可以通过getposition方法生成
* @return: java.lang.String
* @Author: peach
* @Date: 2019/04/03
*/
private String getCellValue(String position){
Dispatch cell = Dispatch.invoke(getCurrentSheet(),"Range",Dispatch.Get,new Object[]{new Variant(position)},new int[1]).toDispatch();
return Dispatch.get(cell,"value").toString(); //todo 验证了 有区别 尽量使用value 大小写也没啥区别
}
我们通过当前表格的Range属性,传入参数即可。
更多对象和方法参照官方文档,这一部分在页面最顶端。
jacob还可以操作AutoCAD,
AutoCAD的操作是我踩坑最多的,因为之前没有用过AutoCAD这方面的软件,话不多说直接上代码吧。
注意,我是用的是AutoCAD的经典模式,也就是黑色的平面图的那种。版本是AutoCAD2007 因为客户的版本较低,所以我们只能用低版本,但是实际上没多大差别。
ActiveXComponent app;
Dispatch documents;
Dispatch doc;
Dispatch cads;
Dispatch cad;
/**
* @Description: 打开AutoCAD文件
* @Param: []
* @return: void
* @Author: peach
* @Date: 2019/04/22
*/
public void openFile(){
ComThread.InitSTA();
app = new ActiveXComponent("AutoCAD.Application");
app.setProperty("visible",new Variant(true));//设置可见
documents = app.getProperty("Documents").toDispatch();//获取到文档集合对象 这里和word一致
doc = Dispatch.invoke(documents, "Open", Dispatch.Method, new Object[]{
"C:\\Users\\Administrator\\Desktop\\桩顶水平位移变化曲线图11.07.dwg",
new Variant(false), //不以 只读 方式打开
new Variant(false)
}, new int[1]).toDispatch();
}
/**
* @Description: 画线,通过两点坐标画线和圆
* @Param: []
* @return: void
* @Author: peach
* @Date: 2019/04/22
*/
public void DrawLine(){
Dispatch ModelSpace = Dispatch.get(doc,"ModelSpace").toDispatch();
double[] startPoint={100.0,100.0,0}; //注意 此处只能使用基本类型小写double,不能使用封装类Double 大写的Double 不然会报错该方法的数据类型在文档中已经提出
double[] endPoint={200.0,200.0,0};
double radius = 25;
Dispatch.call(modelSpace, "AddLine", startPoint, endPoint).toDispatch();
}
关于double参数的问题,在文档的方法中,已经写得很清楚,他是基本类型中的double而不是封装类Double,我们一起来看看
[addLine方法][http://entercad.ru/acadauto.en/idh_addline.htm]
翻译之后如下,使用的是chrom浏览器的翻译功能,如果不能再当前页翻译,就右键点击链接新开一页之后翻译:
这个在官方文档中提出了更加详细,一起看看吧
[AutoCAD官方文档地址][http://entercad.ru/acadauto.en/]
可以看到我们常用的对象层级关系如下:
关于在工作空间中画图
Application-> Documents->Document->ModelSpace->其他子组件 最常用
Application-> Documents->Document->Blocks->Block->其他子组件
关于获图层
Application-> Documents->Document->Layers->Layer
关于视图
Application-> Documents->Document->Views->View
关于坐标轴
Application-> Documents->Document->UCSs->UCS
其他还有很多方式可以获取到我们想要的方法或者属性。
这位博主给大家整理了一些[地址][https://www.cnblogs.com/yzuzhang/p/5134655.html]
我这里说一些很奇葩的错误吧,
1、com.jacob.com.ComFailException: Can’t map name to dispid: XXXX
这样的错误通常都是属性写错了,没有找到XXXX这个属性或者方法
2、com.jacob.com.ComFailException: Invoke of: Item
Source: Microsoft Word
Description: 集合所要求的成员不存在。
这样的错误表示你想要获取的这个对象的索引超过了集合的最大值,
3、com.jacob.com.ComFailException: Invoke of: Item
Source: Microsoft Word
Description: 无法访问此集合中单独的行,因为表格有纵向合并的单元格。
这个错在word中很常见,因为word表格中有合并单元格 所以没有办法定位到某一行,解决办法在上方的word中一个方法叫做 通过单元格反向获取此列可以获取到这一列。
4、com.jacob.com.ComFailException: Invoke of: AddLine
Source:
Description:
对 这样的错 什么报错信息都没有的,这样的错代表的是 通过java调用文档底层的方法出错,首先排查我们的数据类型是否填写正确,正确的数据类型在官网都是有的,可以对照。其次看看官方的调用实例代码,排查是否出错。
类似于数据需要基本类型double 不能是否装类型Double
5、java.lang.IllegalArgumentException: Can’t pass in null Dispatch object
代表不能够转换null对象为Dispatch,怎么看我们拿到的对象是不是空的呢,我们可以通过
table.m_pDispatch();//它的值来判断 如果值为0则是空的
6、com.jacob.com.ComFailException: Invoke of: Open
Source: Microsoft Word
Description: 命令失败
一般原因是我们打开了某一文档但是并没有关闭,之后又以非只读的方式打开了,所以导致冲突,我们只需要关闭之前的文档重新运行即可。
以上就是全部内容