我有一个用于 Word 的 VSTO 插件,其功能之一是设置 CustomDocumentProperties,然后刷新文档内的所有字段。
文档内的字段可以位于文档的主要文本中,但也可以位于形状内部。使用 Document.Fields 属性仅提供文档主文本中的字段集合,而不是形状内的字段。因此,为了确保我们获得所有字段,我们检查每个形状以查看其中是否有任何字段。
这在所有版本的 Word 中都可以正常工作,除非启用了 ModernComments。当启用 ModernComments 并且代码尝试访问形状时,Word 完全崩溃(并退出),并且在文档中工作的用户将丢失所有未保存的更改。我发现其他人遇到类似行为的两个 StackOverflow 帖子:
访问 Word Shape.Type 会抛出 COMException(HRESULT:0x80004005 E_FAIL)
C# COMException 读取 MSWord Shape 对象 Microsoft.Office.Interop.Word 的属性
在第二篇文章中,建议在访问 Shape 的任何属性之前首先执行 Shape.Select() 调用。这确实可以防止 Word 崩溃,但它会产生浏览文档并选择形状的副作用。
我尝试在迭代所有形状(并对所有形状执行 Shape.Select())然后返回书签之前创建一个书签,但是,当用户在(现代)注释中工作时,如果我导航到书签,视图切换到不同的视图。
这是(尝试访问形状时启用 ModernComments 时 Word 崩溃)是已知问题吗?
考虑到上述问题,可靠的解决方案是什么(用于检索所有字段)?
我还在 Microsoft 论坛上针对此问题创建了一个帖子,此处: https://answers.microsoft.com/en-us/msoffice/forum/all/c-vsto-word-crashes-when-attempting-to-access-the/88a33099-6859-43c4-b506-8c9d0e6afcf0
我解决了这个问题。 要向后导航,请使用 Range.Select(),但如果您处于(现代)评论中,请使用 Comment.Edit()。
请参阅下面的代码:
private void GetAllFieldsFromShapes(Word.Shapes shapes, ref List<Word.Field> fields)
{
if (fields == null) { fields = new List<Word.Field>(); }
//We need to keep track of if we are inside of a modern comment.
//The reason being that, performing a selection of a Shape, does indeed select the Shape in the graphical interface (Word/Excel, etc.)
//which could introduce all kinds of problems and it just looks weird for the User...
//We should make sure that the previously selected Shape does not stay selected. The way to do this is different depending on if we're in a (modern) comment or not.
//If we're in a (modern) comment, then we need to enter the edit view of the comment.
//If we're not in a (modern) comment, then we need to select the area which we had selected before selecting the Shape.
var wordApp = Library.OfficeApplication as Word.Application;
var selection = wordApp.Selection;
var isInComment = selection.StoryType == Word.WdStoryType.wdCommentsStory;
var range = selection.Range;
try
{
foreach (var item in shapes)
{
if (item is Word.Shape)
{
Word.Shape shape = null;
try
{
shape = item as Word.Shape;
//====
//I don't know why, but for some shapes, if you do not call 'Shape.Select()' then some of the underlying Properties are released and cannot be accessed.
//More info here: https://stackoverflow.com/questions/36043690/c-sharp-comexception-reading-property-of-msword-shape-object-microsoft-office-in
//and
//here: https://stackoverflow.com/questions/76226042/accessing-word-shape-type-throws-comexception-hresult-0x80004005-e-fail
shape.Select();
//
//However, it is important to note that, by performing this call, the shape is selected and we do actually need to perform an unselect action later on!
//
//====
var fieldsCount = 0;
//Get the Fields in the current Shape
try { fieldsCount = shape.TextFrame?.TextRange?.Fields?.Count ?? 0; }
catch { }
if (fieldsCount > 0)
{
foreach (Word.Field field in shape.TextFrame.TextRange.Fields)
{
fields.Add(field);
}
}
}
finally
{
if (shape != null) { Marshal.ReleaseComObject(shape); }
}
}
}
}
finally
{
//"Unselect" the selected shape
if (range != null)
{
if (isInComment == false)
{
//We can unselect the selected shape by selecting the area the cursor was before we selected the shape
try { range.Select(); }
catch { }
}
else
{
//If we try to select the area where the cursor was when we were in a (modern) comment, then a special window will popup which looks weird.
//So, to navigate to the place we were before, we need to open the edit window for the (modern) comment
if (range.Comments != null)
{
var comment = range.Comments[1];
if (comment != null)
{
//Open the edit window (which is actually still open, but will cause the window to gain focus again and
//which will navigate us back to the selected comment)
try { comment.Edit(); }
catch { }
}
}
}
}
if (range != null) { Marshal.ReleaseComObject(range); }
if (selection != null) { Marshal.ReleaseComObject(selection); }
}
}