COM异常:文档提示锁定无法编辑但实际未锁定的问题排查
Word COM异常:添加注释时文档锁定错误(由ContentControlOnEnter事件触发)
问题背景
在Word文档设置为WdProtectionType.wdAllowOnlyComments保护模式下,调用代码添加注释时,偶尔会触发文档被锁定无法编辑的COM异常:
public IWordComment AddComment(int scopeStart, int scopeEnd, string text) { var scopeRange = _document.Range(scopeStart, scopeEnd); object cText = text; Comment newComment = null; try { newComment = _document.Comments.Add(scopeRange, cText); } catch (Exception ex) { var itisntlockedthough = _document.ProtectionType; // 异常处理逻辑 ... } // 后续逻辑 }
- 异常触发无规律:同一组参数调用5次,会随机出现2次错误,输入参数无明显共性。
- 重试可成功但遗留空注释:立即用相同参数重试操作能成功,但目标位置会留下一条空注释,且catch块中无法直接在
Comments集合中检测到该空注释。 - 已排除常规锁定因素:文档保护模式未改动,无受保护区域或锁定的内容控件。
问题定位
经过排查,确认异常由Document_ContentControlOnEnter事件处理程序触发。推测当添加注释的范围位于当前光标所在的内容控件之外时,Word会自动切换焦点到目标内容控件,触发该事件,进而引发锁定异常。
事件处理代码如下:
private void Document_ContentControlOnEnter(ContentControl cc) { // 同步选中ReviewViewModel中对应的项 if (cc.ParentContentControl == null ) { var tag = cc.Tag; if (tag == null) { return; } if (tag.StartsWith("tbl:")) { var id = int.Parse(cc.Tag.Substring(4)); _reviewViewModel.SelectTable(id); } else if (!tag.StartsWith("var:")) { Guid id; if (Guid.TryParse(cc.Tag, out id)) { _reviewViewModel.SelectItem(id); } } } }
原因分析
虽然事件处理程序中没有显式执行文档锁定操作,但Word对象模型的操作存在状态竞争:
- 添加注释时,Word内部会调整文档焦点到目标内容控件,触发
ContentControlOnEnter事件。 - 事件中对ViewModel的操作(
SelectTable/SelectItem)可能间接引发UI状态更新,导致Word对象模型的内部状态与添加注释的操作产生冲突。 - 这种冲突会被Word判定为文档锁定,抛出COM异常。第一次调用失败时,Word可能已经创建了空注释对象但未完成文本写入,导致重试后出现重复注释。
解决方案
在添加注释的前后,临时解绑并重新绑定ContentControlOnEnter事件,避免事件触发干扰注释添加操作:
修改AddComment方法如下:
public IWordComment AddComment(int scopeStart, int scopeEnd, string text) { var scopeRange = _document.Range(scopeStart, scopeEnd); object cText = text; Comment newComment = null; // 临时解绑事件 _document.ContentControlOnEnter -= Document_ContentControlOnEnter; try { newComment = _document.Comments.Add(scopeRange, cText); } catch (Exception ex) { var itisntlockedthough = _document.ProtectionType; // 清理目标位置的空注释后重试 foreach (Comment existingComment in _document.Comments) { if (existingComment.Range.Start == scopeStart && existingComment.Range.End == scopeEnd && string.IsNullOrEmpty(existingComment.Range.Text)) { existingComment.Delete(); break; } } // 重试操作 newComment = _document.Comments.Add(scopeRange, cText); } finally { // 无论成功或失败,重新绑定事件 _document.ContentControlOnEnter += Document_ContentControlOnEnter; } return newComment; }
额外验证点
- 空注释检测:在catch块中遍历
_document.Comments集合,根据注释的范围和文本内容判断是否存在空注释,若存在则删除后再重试。 - 事件操作优化:若ViewModel的
SelectTable/SelectItem操作涉及UI线程同步,可考虑改为异步执行,避免阻塞Word对象模型的操作。
内容的提问来源于stack exchange,提问作者Peter Dongan
相关产品推荐
相关产品推荐

