Примечание. Код основан на подходе, описанном в Использование макроса для замены текста везде, где он появляется в документе.
Моя цель — вставить новый абзац сразу после слова, найденного в заголовке первой страницы, с помощью 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);
}
Код в вопросе ищет объект 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;
}
Примечание: для этого форматирования было бы правильнее создать стиль. Затем форматирование может быть применено за один шаг так часто, как это необходимо. Стиль имеет следующие преимущества
@NovaSysEng Хорошо. Одна очень важная вещь, которую следует помнить об объектах Word Range
(и это относится к Только объекту Range
, и ничего больше в Word API): это прямые «указатели» на объект. Если вы сделаете Word.Range rngTwo = rngOne;
, эти два будут точно одинаковыми — все, что сделано с одним, также повлияет на другое. rngOne.Find.Execute также изменит расположение rngTwo в документе. Обычно в API-интерфейсах Office мы привыкли ожидать «поверхностного копирования» — это единственное исключение. Так что если вы получите странные результаты, имейте это в виду :-)
Спасибо за совет. Еще один: В качестве альтернативы Word.Range rngFound = workingStoryRange.Duplicate;
допустимо делать workingStoryRange.Select()
, а затем делать, например, workingStoryRange.Selection.Collapse(Word.WdCollapseDirection.wdCollapseEnd);
и т.д. и т.п., да?
@NovaSysEng Ну, я не уверен, что назвал бы это «приемлемым», не зная Зачем, что используется подход ... Но это не приведет к конкретной проблеме в моем предыдущем комментарии. Основываясь на том, что я знаю (ваши комментарии), я бы хотел вернуть объект Range
- независимо от поиска Range
(который является StoryRange
, то есть его нельзя уменьшить). Я бы использовал Selection
только в том случае, если это то, что должно быть представлено пользователю для дальнейшего редактирования.
Этот подход хорошо работает, если вы хотите изменить диапазон в методе, который имеет Find.Execute (в моем случае, мой метод
FindReplaceAnywhere
). Однако я хочу, чтобы вызывающий код (в моем случае мой методSomeEventMethod_Click
) изменял диапазон, поэтому я верну workStoryRange в качестве возвращаемого значения и изменю диапазон там. Спасибо!