【问题标题】:Word Interop: How do I set an insertion point based on the results of a Find command?Word 互操作:如何根据查找命令的结果设置插入点?
【发布时间】:2019-07-29 17:12:36
【问题描述】:

注意:代码基于Using a macro to replace text where ever it appears in a document中记录的方法

我的目标是使用Word Interop Find.Execute 在第一页标题中找到的单词之后立即插入一个新段落。为此,我需要在找到的单词的开头或结尾处添加一个插入点(type == wdSelectionIP)。

我的假设是/是由于使用Word Interop Find.Execute 在第一页标题中查找单词,Word 将在开头或结尾设置插入点(类型 == wdSelectionIP)找到的词。你可以在我的SomeEventMethod_Click 方法中看到这一点,也就是说,在这个假设下,找到单词后,我导航到行尾,创建一个新的空段落,设置一些属性,然后输入一些文本。

输入了文本,但它没有跟在第一页标题中的单词后面。而是在最后一页底部的主文本区域(即文档正文)中键入文本。

如何根据 Find 命令的结果设置插入点?

类用于报告查找和替换结果

private class ClsFindReplaceResults
{
    bool isFound = false;
    Microsoft.Office.Interop.Word.Selection selection = null;

    public ClsFindReplaceResults(bool isFound, Selection selection)
    {
        this.IsFound = isFound;
        this.Selection = selection;
    }

    public bool IsFound { get => isFound; set => isFound = value; }
    public Selection Selection { get => selection; set => selection = value; }
}

从中调用 FindReplaceAnywhere 方法的事件方法

private void SomeEventMethod_Click(object sender, RibbonControlEventArgs e)
{
    //Find the text 'foo\r'. No replacement. I just want the insertion point
    ClsFindReplaceResults objFindReplaceResults = FindReplaceAnywhere(findText: "foo^p", replaceWithText: null, enumWdStoryType: WdStoryType.wdFirstPageHeaderStory);

    if (objFindReplaceResults.IsFound)
    {
        objFindReplaceResults.Selection.EndKey(WdUnits.wdStory);
        objFindReplaceResults.Selection.TypeParagraph();
        objFindReplaceResults.Selection.Font.Size = 9;
        objFindReplaceResults.Selection.ParagraphFormat.Alignment = WdParagraphAlignment.wdAlignParagraphJustify;
        objFindReplaceResults.Selection.ParagraphFormat.SpaceAfter = 6f;
        objFindReplaceResults.Selection.TypeText("new paragraph that should appear after 'foo^p'");
    }
}

FindReplaceAnywhere 方法

private ClsFindReplaceResults FindReplaceAnywhere(string findText, string replaceWithText, WdStoryType enumWdStoryType)
{
    bool found = false;
    object wfrFindText = findText;
    object wfrMatchCase = true;
    object wfrMatchWholeWord = true;
    object wfrMatchWildCards = false;
    object wfrMatchSoundsLike = false;
    object wfrMatchAllWordForms = false;
    object wfrForward = true;
    object wfrWrap = WdFindWrap.wdFindContinue;
    object wfrFormat = false;
    object wfrReplaceWith = replaceWithText;
    object wfrReplace = null;

    if (wfrReplaceWith == null)
    {
        wfrReplace = WdReplace.wdReplaceNone;
    }
    else
    {
        wfrReplace = WdReplace.wdReplaceOne;
    }

    object wfrMatchKashida = false;
    object wfrMatchDiacritics = false;
    object wfrMatchAlefHamza = false;
    object wfrMatchControl = false;

    Globals.ThisAddIn.Application.Selection.Find.ClearFormatting();
    Globals.ThisAddIn.Application.Selection.Find.Replacement.ClearFormatting();

    //Fix the skipped blank Header/Footer problem as provided by Peter Hewett. Don't know what the heck this does
    WdStoryType junk = Globals.ThisAddIn.Application.ActiveDocument.Sections[1].Headers[Microsoft.Office.Interop.Word.WdHeaderFooterIndex.wdHeaderFooterPrimary].Range.StoryType;

    Microsoft.Office.Interop.Word.Range workingStoryRange = null;

    foreach (Microsoft.Office.Interop.Word.Range storyRange in Globals.ThisAddIn.Application.ActiveDocument.StoryRanges)
    {
        if (storyRange.StoryType != enumWdStoryType)
        {
            continue;
        }

        workingStoryRange = storyRange;

        do
        {
            // Find and replace text in the current story
            found = workingStoryRange.Find.Execute(FindText: ref wfrFindText, MatchCase: ref wfrMatchCase, MatchWholeWord: ref wfrMatchWholeWord, MatchWildcards: ref wfrMatchWildCards, MatchSoundsLike: ref wfrMatchSoundsLike, MatchAllWordForms: ref wfrMatchAllWordForms, Forward: ref wfrForward, Wrap: ref wfrWrap, Format: ref wfrFormat, ReplaceWith: ref wfrReplaceWith, Replace: ref wfrReplace, MatchKashida: ref wfrMatchKashida, MatchDiacritics: ref wfrMatchDiacritics, MatchAlefHamza: ref wfrMatchAlefHamza, MatchControl: ref wfrMatchControl);

            // The call to SearchAndReplaceInStory above misses text that is contained in a StoryType/StoryRange nested in a different 
            // StoryType /StoryRange. While this won't occur with a nested StoryType/StoryRange in the wdMainTextStory StoryRange, it 
            // will occur in header and footer type StoryRanges. An example is textbox that is located in a header or footer. The fix 
            // makes use of the fact that Textboxes and other Drawing Shapes are contained in a document’s ShapeRange collection. 
            // Check the ShapeRange in each of the six header and footer StoryRanges for the presence of Shapes. If a Shape is found, 
            // check each Shape for the presence of the text, and finally, if the Shape contains text we set our search range to that 
            // Shape's .TextFrame.TextRange. 
            switch (workingStoryRange.StoryType)
            {
                // Case 6 , 7 , 8 , 9 , 10 , 11
                case Microsoft.Office.Interop.Word.WdStoryType.wdEvenPagesHeaderStory:
                case Microsoft.Office.Interop.Word.WdStoryType.wdPrimaryHeaderStory:
                case Microsoft.Office.Interop.Word.WdStoryType.wdFirstPageHeaderStory:
                case Microsoft.Office.Interop.Word.WdStoryType.wdEvenPagesFooterStory:
                case Microsoft.Office.Interop.Word.WdStoryType.wdPrimaryFooterStory:
                case Microsoft.Office.Interop.Word.WdStoryType.wdFirstPageFooterStory:

                    if (workingStoryRange.ShapeRange.Count > 0)
                    {
                        foreach (Microsoft.Office.Interop.Word.Shape shape in workingStoryRange.ShapeRange)
                        {
                            if (shape.TextFrame.HasText != 0)
                            {
                                found = shape.TextFrame.TextRange.Find.Execute(FindText: ref wfrFindText, MatchCase: ref wfrMatchCase, MatchWholeWord: ref wfrMatchWholeWord, MatchWildcards: ref wfrMatchWildCards, MatchSoundsLike: ref wfrMatchSoundsLike, MatchAllWordForms: ref wfrMatchAllWordForms, Forward: ref wfrForward, Wrap: ref wfrWrap, Format: ref wfrFormat, ReplaceWith: ref wfrReplaceWith, Replace: ref wfrReplace, MatchKashida: ref wfrMatchKashida, MatchDiacritics: ref wfrMatchDiacritics, MatchAlefHamza: ref wfrMatchAlefHamza, MatchControl: ref wfrMatchControl);
                            }
                        }
                    }

                    break;

                default:
                    break;
            }

            workingStoryRange = workingStoryRange.NextStoryRange;

        } while (workingStoryRange != null);
    }

    return new ClsFindReplaceResults(found, Globals.ThisAddIn.Application.Selection);
}

【问题讨论】:

    标签: c# ms-word vsto find-replace word-interop


    【解决方案1】:

    问题中的代码正在搜索Range 对象,因此选择不会改变。只需使用 Range 作为新内容的“目标”。

    问题中的代码非常复杂,很难准确理解发生了什么......但简单来说:

    bool found = workingStoryRange.Find.Execute(FindText: ref wfrFindText, MatchCase: ref wfrMatchCase, 
      MatchWholeWord: ref wfrMatchWholeWord, MatchWildcards: ref wfrMatchWildCards, MatchSoundsLike: ref wfrMatchSoundsLike, 
      MatchAllWordForms: ref wfrMatchAllWordForms, Forward: ref wfrForward, Wrap: ref wfrWrap, Format: ref wfrFormat, 
      ReplaceWith: ref wfrReplaceWith, Replace: ref wfrReplace, MatchKashida: ref wfrMatchKashida, 
      MatchDiacritics: ref wfrMatchDiacritics, MatchAlefHamza: ref wfrMatchAlefHamza, MatchControl: ref wfrMatchControl);
    
    if (found)
    {
      //Work with a duplicate of the original range so as not to "destroy" it
      //may not be needed, but included for "in case"
      Word.Range rngFound = workingStoryRange.Duplicate;
      //go to the end - the point just after the found content
      rngFound.Collapse(Word.WdCollapseDirection.wdCollapseEnd);
      rngFound = "\nText in new paragraph.";
      rngFound.Font.Size = 9
      rngFound.ParagraphFormat.Alignment = WdParagraphAlignment.wdAlignParagraphJustify;
      rngFound.ParagraphFormat.SpaceAfter = 6f;
    }
    

    注意:为这种格式创建 style 会更正确。然后可以根据需要在一个步骤中应用格式化。样式有以下优点

    • 如果稍后要更改格式,只需更改样式定义即可 - 在一个地方,一个操作 - 而不是需要查找和更改文档中的每个格式实例
    • 使用样式应用格式会减少内存管理量,尤其是 Word 用于维护“撤消列表”的“临时文件”。总结:撤消列表更短。

    【讨论】:

    • 如果您想修改具有 Find.Execute 的方法中的范围(在我的例子中,我的 FindReplaceAnywhere 方法),这种方法效果很好。但是,我希望调用代码(在我的情况下,我的 SomeEventMethod_Click 方法)修改范围,所以我将返回 workingStoryRange 作为返回值并在那里修改范围。谢谢!
    • @NovaSysEng 好的。关于 Word Range 对象要记住的一件非常重要的事情(这 应用于 Range 对象,Word API 中没有其他内容):这些是指向对象的直接“指针” .如果您执行Word.Range rngTwo = rngOne;,则两者将完全相同 - 对其中一个所做的任何事情都会影响另一个。 rngOne.Find.Execute 也会改变 rngTwo 在文档中的位置。通常,在 Office API 中,我们已经学会了期待“浅拷贝”——这是一个例外。所以如果你得到奇怪的结果,请记住这一点:-)
    • 感谢您的提示。还有一个:作为Word.Range rngFound = workingStoryRange.Duplicate;的替代方案,先做workingStoryRange.Select(),然后再做,例如workingStoryRange.Selection.Collapse(Word.WdCollapseDirection.wdCollapseEnd);,等等等等,是可以接受的,是吗?
    • @NovaSysEng 好吧,我不确定我是否会称其为“可接受的”,不知道 为什么 使用这种方法......但这不会导致我之前评论中的特定问题。根据我所知道的(您的 cmets),我倾向于返回一个 Range 对象 - 独立于搜索 Range(这是一个 StoryRange,意味着它不能被减少)。我只会使用Selection,如果它应该呈现给用户以供进一步编辑。
    猜你喜欢
    • 2023-04-11
    • 2014-12-22
    • 1970-01-01
    • 1970-01-01
    • 2021-01-06
    • 1970-01-01
    • 2020-06-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多