(一)在Word开发中,Range对象是用的最频繁的对象。获取Range的方法有很多,大多数对象都能返回range对象,例如:
(1)从对象模型中获取Range,例如
document.Range()
table.Cell(1,1).Range
(2) 搜索字符,获取找到的字符串的range
为了搜索字符串,首先需要一个range,通过这个range来获得Find对象,然后进行查找。
Word.Range myRange = myDocument.Range();
MessageBox.Show("After:start:" + myRange.Start.ToString() + ";end:" + myRange.End.ToString()); //这时,myRange对象是初始值,其起始和结束位置已经是查找到
的字符的位置。
Word.Find f = myRange.Find;
f.Text = “查找的字符串”;
f.ClearFormatting();
object G_Missing = System.Reflection.Missing.Value;
bool finded = f.Execute(ref G_Missing, ref G_Missing, ref G_Missing,
ref G_Missing, ref G_Missing, ref G_Missing, ref G_Missing,
ref G_Missing, ref G_Missing, ref G_Missing, ref G_Missing,
ref G_Missing, ref G_Missing, ref G_Missing, ref G_Missing
);
全文遍历查找的用法如下(重要,有坑):
Word.Range range=document.Range();
Word.Find f = myRange.Find;
f.Text = “查找的字符串”;
f.ClearFormatting();
object G_Missing = System.Reflection.Missing.Value;
bool finded = f.Execute(ref G_Missing, ref G_Missing, ref G_Missing,
ref G_Missing, ref G_Missing, ref G_Missing,
ref G_Missing, ref G_Missing, ref G_Missing,
ref G_Missing, ref G_Missing, ref G_Missing,
ref G_Missing, ref G_Missing, ref G_Missing
);
Do somework here,but do not alter myRange and f
while(finded){
finded = f.Execute(ref G_Missing, ref G_Missing, ref G_Missing,
ref G_Missing, ref G_Missing, ref G_Missing,
ref G_Missing, ref G_Missing, ref G_Missing,
ref G_Missing, ref G_Missing, ref G_Missing,
ref G_Missing, ref G_Missing, ref G_Missing
);
if(finded)
{
//Do somework here,but do not alter myRange and f
//To get the range corresponding to the finded text position, do not
// directly use range, just create a copy of this range and then cache it for
//later use or tracking purpose
//Copy Range Method1:
Word.Range rangeFinded=doc.Duplicate
//Copy Range Method2:
object start=range.Start, end=range.End;
Word.Range rangeFinded=doc.Range(ref start,ref end);
//Then rangeFinded can be used anywhere c
//这里面关键一条,就是不要在while循环体中去更改Find对象及对应的Range对象。常规感觉是:每一次查找成功后,range的Start和End属性总是聚焦到查到到的文字段上面,常规想法是,如果不修改range,那么下一次循环是不是仅仅还在前一次查找到的文字段范围内进行查找呢?然后并不是这样的,它是接着前一次查找到的文字末尾向后继续查找的。反而是,如果手工去修改了这个range,比如将其start设置为前一次查找到的文字的末尾,并将end设置为文档的末尾,或者根据新的Start和End重新创建(获取)一个Range用来再次基础上重新获取Find来查找,这时会发生随机性错误,表现为有时能够正常工作,有时还是查找到的是前一次查找到的文字,这显然是不正确的。
}
}
上面代码中,关键一条,就是不要在while循环体中去更改Find对象及对应的Range对象。常规感觉是:每一次查找成功后,range的Start和End属性总是聚焦到查到到的文字段上面,常规想法是,如果不修改range,那么下一次循环是不是仅仅还在前一次查找到的文字段范围内进行查找呢?然后并不是这样的,它是接着前一次查找到的文字末尾向后继续查找的。反而是,如果手工去修改了这个range,比如将其start设置为前一次查找到的文字的末尾,并将end设置为文档的末尾,或者根据新的Start和End重新创建(获取)一个Range用来再次基础上重新获取Find来查找,这时会发生随机性错误,表现为有时能够正常工作,有时还是查找到的是前一次查找到的文字,这显然是不正确的。
以下是Execute方法原型:
【
public bool Execute (ref object FindText, ref object MatchCase, ref object MatchWholeWord, ref object MatchWildcards, ref object MatchSoundsLike, ref object MatchAllWordForms, ref object Forward, ref object Wrap, ref object Format, ref object ReplaceWith, ref object Replace, ref object MatchKashida, ref object MatchDiacritics, ref object MatchAlefHamza, ref object MatchControl);
参数
FindText
可选对象。 要搜索的文本。 使用空字符串 ("") 仅搜索格式。 可以指定适当的字符代码来搜索特殊字符。 例如,"^p"对应段落标记,"^t"对应制表符。
MatchCase
可选对象。 True 指定查找文本是区分大小写。 对应于“查找和替换”对话框(“编辑”菜单)中的“区分大小写”复选框。
MatchWholeWord
可选对象。 如果该属性值为 True ,则查找操作仅查找整个单词,而不是较长单词的一部分的文本。 对应于“查找和替换”对话框中的“全字匹配”复选框。
MatchWildcards
可选对象。 为 要查找的文字是特殊搜索操作符。 对应于“查找和替换”对话框中的“使用通配符”复选框。
MatchSoundsLike
可选对象。 类似于查找的文字发音单词 ,则返回 true ,则查找操作定位。 对应于 查找和替换对话框中的 听起来象复选框。
MatchAllWordForms
可选对象。 ,则返回 true ,则查找操作定位查找文字的所有形式 (例如,"sit"查找"坐",而"坐")。 对应于 查找和替换对话框中的 查找单词的各种形式复选框。
Forward
可选对象。 真 要向前搜索 (向文档的末尾)。
Wrap
可选对象。 如果从文档开头之外的某个位置开始搜索,并到达文档结尾处(反之亦然,如果 Forward
设置为False),则控制所发生的操作。 此参数还控制在选定内容或区域中找不到搜索文本时,将会发生什么情况。可以是下列 WdFindWrap 常量之一:wdFindAsk在搜索所选内容或区域后,Microsoft Word 将显示一条消息,询问是否搜索文档的其余部分。wdFindContinue当到达搜索范围的开始或结尾时,继续执行查找操作。wdFindStop如果到达了搜索范围的开始或结尾,则结束查找操作。
Format
可选对象。 如果该属性值为 True ,则查找操作定位于格式,而不是查找文本。
ReplaceWith
可选对象。 替换文字。 若要删除参数指定的文本 Find
,请使用空字符串("")。 您可以指定特殊字符和高级搜索条件,就像对参数执行的操作一样 Find
。 若要将图形对象或其他非文本项指定为替换,请将该项移动到剪贴板,并为指定 "^ c" ReplaceWith
。
Replace
可选对象。 指定要执行替换的个数:一个、全部或者不替换。 可以是任何 WdReplace 常量:wdReplaceAll wdReplaceNone wdReplaceOne
MatchKashida
可选对象。 如此 如果查找操作用 kashida 阿拉伯语文档中匹配的文本。 根据您所选择或安装的语言支持(例如,美国英语),此参数可能不可用。
MatchDiacritics
可选对象。 如此 如果查找操作用匹配从右到左语言的文档中的音调符号来匹配文字。 根据您所选择或安装的语言支持(例如,美国英语),此参数可能不可用。
MatchAlefHamza
可选对象。 如此 如果查找操作用匹配 alef hamza 适用于阿拉伯语文档中的匹配的文本。 根据您所选择或安装的语言支持(例如,美国英语),此参数可能不可用。
MatchControl
可选对象。 如此 如果查找操作用匹配的从右到左语言的文档中的双向控制字符来匹配文字。 根据您所选择或安装的语言支持(例如,美国英语),此参数可能不可用。
】
MessageBox.Show("After:start:" + myRange.Start.ToString() + ";end:" + myRange.End.ToString()); //这时,myRange对象已经发生了改变,其起始和结束位置已经是查找到的字符的位置。
说明:如果在循环中更改myRange.Start和myRange.End的值,对于查找来讲,这种重新设置Start和End的手段似乎是无效的,即使把Start设置得比较大,依然会找到在Start之前的字符串,而不会找到原先Range对应的文本中第2次或第3次...出现的字符串。原因????
(3)range的常用操作
void Collapse(ref Object Direction),参数Object可选,表示折叠方向,Can be either of the following WdCollapseDirection constants: wdCollapseEnd or wdCollapseStart. The default value is wdCollapseStart.
例如:
myRange.Collapse(Word.WdCollapseDirection.wdCollapseEnd);表示将range对象的Start属性设置为与End属性一样的数值,即折叠到末尾;
myRange.Collapse(Word.WdCollapseDirection.wdCollapseStart);表示将range对象的End属性设置为与Start属性一样的数值,即折叠到开始;
如果采用默认值, myRange.Collapse();表示将range对象的End属性设置为与Start属性一样的数值,即折叠到开始;
(4)文字的上标、下标
找到对应文字的range,通过range.Font.Superscript=1来将其设置未上标。range.Font.Subscript=1 (0?待测试)将其设为下标。
//table.Cell(rowNumber, 3).Range.Font.Superscript = 1;
(二)Tables对象
(1)Tables对象是Table的集合,从1开始计数,不是C或C#语言常见集合的从0开始计数的那种。不能用Tables[0],否则会出现异常出错。
(2)根据range对象获取Cells或Cell对象
range接口具有Cells属性,可以利用Cells属性来完成一些操作,比如通过Find搜索找到相应的Range,然后利用range来获取相应的Cell。通过range.Cells,然后可以选择集合中的第一个或遍历的方式完成后续操作。
(3)利用Cell获取其行或列位置
Cell.RowIndex或Cell.ColumnIndex
(4)在表格Table中查找到对应文字的Cell
方法一:用佛reach循环遍历
Word.Cells theCells = table.Range.Cells;
foreach(Word.Cell c in theCells)
{
string t = c.Range.Text.Trim(new Char[] {'\r','\t','\n','\a',' ' });
string findText = "查找的文字”;
if(t==indext)
{
MessageBox.Show(“columnIndex=”+c.ColumnIndex.ToString() + ":RowIndex=" + c.RowIndex.ToString());
break;
}
}
方法二:利用LINQ找到Cell
string findText = "查找的文字”;
Word.Cell c = table.Range.Cells.Cast<Word.Cell>().Where<Word.Cell>(x=>x.Range.Text.Trim(new Char[] { '\r', '\t', '\n', '\a', ' ' })==findText).FirstOrDefault();
if(c!=null)
MessageBox.Show(c.ColumnIndex.ToString() + ":" + c.RowIndex.ToString());
说明:有Cells获取Cell集合的方式是用Cells.Cast<Word.Cell>(),而无法用Cells直接调用Where<Word.Cell>(),其它集合比如table.Rows,Tables等等也是一样的需要cast转换;当然,如果仅仅提取Count,是不需要转换; 此外,从表格单元格中直接提取的文本字符的末尾是包含"\r\a“的,因此需要先去掉这样的字符。用字符串的Trim()方法时,如果仅仅使用无参数的默认方法时,是不会去掉'\a'字符的,所以需要调用含有char[]参数的Trim方法。此处写了Trim(new Char[] { '\r', '\t', '\n', '\a', ' ' }),这里面char[]包含了多个符号。实际上可能不需要这么多,不知道char[]存在冗余字符是否会影响性能,由于时间关系此处未做测试。
(5) 在表格中增加一行
table.Rows.Add();
如果增加一列,则用table.Columns.Add().
如果要在中间插入,则调用带有参数的Add方法。
例如:table.Rows.Add(BeforeRow: table.Rows[theRowNumber]);表示要在行号为theRowNumber之前插入一行。需要注意的是,插入的行的格式(比如有几列)与theRowNumber行的格式是一致的,而不是与theRowNumebr-1行的格式一致。这点一定要注意,与手工操作的时候在某一行后面追加一行的方式有区别。
(三)InlineShape
插入照片,则先获得InlineShapes对象,用AddPicture方法插入,方法第一个参数filePath,第四个参数为插入位置的Range。注意,第四个参数对应的range包含其他如文字之类的,则插入点在这个range的开始位置,不会删除原range中的内容。
myRange.InlineShapes.AddPicture(filePath, G_Missing, G_Missing, myRange);
有网络文档上讲的步骤为:选中range,获取Selection,利用Selection.InlineShapes.AddPicture(string filePath_)来做,
Word.Range pRange = ...;
pRange.Select();
app.Selection.InlineShapes.AddPicture(picFilePath); //不能正确插入照片
但是,上面的方法经过在word2016版本上测试后,发现如果pRange是表格中的,则并不能把照片查到正确位置(都插在表格的第一列中),不知道为什么。但是如采用下面的代码,即在AddPicture方法中放进去对应的Range,就可行。
Word.Range pRange = ...;
pRange.Select();
app.Selection.InlineShapes.AddPicture(picFilePath,missing,missing,pRange); //可以正确插入照片
另外,对于以上代码,即使中间那一行不写, 即不写"pRange.Select();", 同样能够把照片插入到pRange所在的位置。简化后的代码为:
Word.Range pRange = ...;
app.Selection.InlineShapes.AddPicture(picFilePath,missing,missing,pRange); //可以正确插入照片
此外,经过测试,用document.InlineShapes.AddPicture()方法也能正常插入照片。代码如下:
Word.Range pRange = ...;
pRange.Document.InlineShapes.AddPicture(picFilePath,missing,missing,pRange); //可以正确插入照片
但同以上情况一样,如果不在方法体中添加pRange也是不能正确完成照片插入的。即下述代码不能正常插入照片到指定位置。
Word.Range pRange = ...;
pRange.Document.InlineShapes.AddPicture(picFilePath); //不能正确插入照片
(四)copy and paste
拷贝的时候,直接用range.Copy 方法,paste的时候,首先确定一下拷贝的位置range,然后先调用range.Select(),接着调用range.Paste(). 例如,对具有书签bm的内容进行赋值并拷贝到原内容的前面,代码如下:
Word.Range originalRange=bm.Range; //bm的类型是 Word.Bookmark
originalRange.Copy();
Word.Range targetRange=originalRange;
targetRange.Collapse(Word.WdCollapseDirection.wdCollapseStart);
targetRange.Start = targetRange.Start - 1;
targetRange.End = targetRange.End - 1;
targetRange.Select(); //非常重要,曾经因为没写这个方法,导致复制拷贝的bug花了一天才搞定
targetRange.Paste();
说明:这个过程和我们手动拷贝复制的方式有区别,在word中手动拷贝复制的过程是:CTRL+C选中需要拷贝的内容(获取originalRange,并且select),鼠标点击需要复制的位置(相当于找到目标位置的targetRange。估计在点击一个位置的时候,word自己也执行了Select操作,只是由于仅仅点击一个点所以没有highlight一段文字,导致我们感觉不出来,不知道对不对???),然后CTRL+V复制(相当于调用paste方法)。但是,但是,但是,代码里面和手动拷贝是存在差异的,代码的过程是:找到需要拷贝的内容(获取originalRange。不需要select就行,当然如果加上select的结果也是一样的),拷贝内容(调用copy),定位到粘贴文字的目标位置(相当于找到目标位置的targetRange),选择目标位置(Select the targetRange),粘贴内容(Paste)。在应用过程中,一定要注意到这一点。
此外,Word.Application.Selection对象也有Paste()方法,但是最好还是用range.Paste()方法,好处在于:用range.Paste方法执行后,range对象就会动态地改变为包含新粘贴内容的新的range,可以对其进一步操作,例如文字查找、替换等等。