Word Interop: как установить точку вставки на основе результатов команды «Найти»?

Примечание. Код основан на подходе, описанном в Использование макроса для замены текста везде, где он появляется в документе.

Моя цель — вставить новый абзац сразу после слова, найденного в заголовке первой страницы, с помощью Word Interop Find.Execute. Для этого мне нужна точка вставки (введите == wdSelectionIP) либо в начале, либо в конце найденного слова.

Я предполагаю, что в результате нахождения слова в верхнем колонтитуле первой страницы с помощью Word Interop Find.Execute Word установит точку вставки (тип == wdSelectionIP) либо в начале, либо в конце найденного слова. Вы можете увидеть это в моем методе SomeEventMethod_Click, т. е. в этом предположении, после того как я нахожу слово, я перехожу к концу строки, создаю новый пустой абзац, устанавливаю некоторые атрибуты, затем печатаю какой-то текст.

Текст набран, но не соответствует слову в заголовке первой страницы. Вместо этого текст набирается в основной текстовой области (т. е. в основной части документа) внизу последней страницы.

Как установить точку вставки на основе результатов команды «Найти»?

Использование класса для отчета о результатах поиска и замены

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);
}
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
737
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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;
}

Примечание: для этого форматирования было бы правильнее создать стиль. Затем форматирование может быть применено за один шаг так часто, как это необходимо. Стиль имеет следующие преимущества

  • Если форматирование должно измениться позднее, достаточно просто изменить определение стиля — в одном месте, одним действием — вместо того, чтобы искать и изменять каждый экземпляр форматирования в документе.
  • Применение форматирования с использованием стиля уменьшает объем управления памятью, особенно «временные файлы», которые Word использует для ведения «списка отмены». Вывод: список отмены короче.

Этот подход хорошо работает, если вы хотите изменить диапазон в методе, который имеет Find.Execute (в моем случае, мой метод FindReplaceAnywhere). Однако я хочу, чтобы вызывающий код (в моем случае мой метод SomeEventMethod_Click) изменял диапазон, поэтому я верну workStoryRange в качестве возвращаемого значения и изменю диапазон там. Спасибо!

VA systems engineer 31.07.2019 14:11

@NovaSysEng Хорошо. Одна очень важная вещь, которую следует помнить об объектах Word Range (и это относится к Только объекту Range, и ничего больше в Word API): это прямые «указатели» на объект. Если вы сделаете Word.Range rngTwo = rngOne;, эти два будут точно одинаковыми — все, что сделано с одним, также повлияет на другое. rngOne.Find.Execute также изменит расположение rngTwo в документе. Обычно в API-интерфейсах Office мы привыкли ожидать «поверхностного копирования» — это единственное исключение. Так что если вы получите странные результаты, имейте это в виду :-)

Cindy Meister 31.07.2019 14:15

Спасибо за совет. Еще один: В качестве альтернативы Word.Range rngFound = workingStoryRange.Duplicate; допустимо делать workingStoryRange.Select(), а затем делать, например, workingStoryRange.Selection.Collapse(Word.WdCollapseDirectio‌​n.wdCollapseEnd); и т.д. и т.п., да?

VA systems engineer 31.07.2019 14:21

@NovaSysEng Ну, я не уверен, что назвал бы это «приемлемым», не зная Зачем, что используется подход ... Но это не приведет к конкретной проблеме в моем предыдущем комментарии. Основываясь на том, что я знаю (ваши комментарии), я бы хотел вернуть объект Range - независимо от поиска Range (который является StoryRange, то есть его нельзя уменьшить). Я бы использовал Selection только в том случае, если это то, что должно быть представлено пользователю для дальнейшего редактирования.

Cindy Meister 31.07.2019 14:26

Другие вопросы по теме