I used to highlight the 'word' using this code.It is used inside a 'for each' loop which loops through collection of strings.
But the issue is after the all the words are highlighted .. if we try to change any one word in the document all the highlight removed automatically.
word.Find find = rng.Find;
find.Wrap = word.WdFindWrap.wdFindContinue;
find.Font.UnderlineColor = word.WdColor.wdColorRed;
find.HitHighlight(
FindText: wd,
MatchCase: true,
TextColor:word.WdColor.wdColorRed,
MatchWholeWord: true,
HighlightColor: word.WdColor.wdColorLightYellow
);
By design, HitHighlight only leaves the highlight until the document is edited - this is how the Find task pane works when the user does a non-Advanced Find.
If you want a permanent highlight, then you need to do this a bit differently, by using Replacement.Highlight = true, as in the following example.
Word.Document doc = wdApp.ActiveDocument;
Word.Range rng = doc.Content;
Word.Find f = rng.Find;
object oTrue = true;
object missing = Type.Missing;
//Find and highlight
wdApp.Options.DefaultHighlightColorIndex = Word.WdColorIndex.wdPink;
f.ClearFormatting();
f.Replacement.Highlight = -1;
f.Text = "the";
f.Execute(ref missing, ref missing, ref missing, ref missing, ref missing, ref missing,
ref missing, Word.WdFindWrap.wdFindStop, ref oTrue, ref missing, Word.WdReplace.wdReplaceAll,
ref missing, ref missing, ref missing, ref missing);
VBA equivalent for interested VBA readers:
Sub FindXAndHighlight()
Dim rng As word.Range
Set rng = ActiveDocument.content
Options.DefaultHighlightColorIndex = wdPink
With rng.Find
.Replacement.Highlight = True
.Execute findText:="the", Replace:=wdReplaceAll
End With
End Sub
Related
I am trying to reverse document paragraphs with the following code:
using Word = Microsoft.Office.Interop.Word;
object filePath = #"input.docx";
Word.Application app = new();
app.Visible = false;
object missing = System.Type.Missing;
object readOnly = false;
object isVisible = false;
Word.Document doc = app.Documents.Open(
ref filePath,
ref missing, ref readOnly, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref isVisible, ref missing,
ref missing, ref missing, ref missing);
try
{
Word.Range cachedPara2 = doc.Paragraphs[2].Range.Duplicate;
doc.Paragraphs[2].Range.FormattedText = doc.Paragraphs[1].Range.FormattedText;
doc.Paragraphs[1].Range.FormattedText = cachedPara2.FormattedText;
doc.SaveAs(#"output.docx");
}
finally
{
doc.Close();
app.Quit();
}
I expect this:
but the actual result is this:
How to get expectations?
UPDATE
With the answer below, I was able to get the expected result for my first case.
Now, in another case, I wanna do the following:
Unfortunately, I couldn't quite figure it out how .Collabse() method works. I am trying to do it with .InsertParagraphAfter():
doc.Paragraphs[2].Range.InsertParagraphAfter();
doc.Paragraphs[3].Range.FormattedText = doc.Paragraphs[5].Range.FormattedText;
doc.Paragraphs[5].Range.FormattedText = doc.Paragraphs[2].Range.FormattedText;
doc.Paragraphs[2].Range.Delete();
Where does this empty paragraph come from? How avoid it?
A range object does not have any content itself, it merely points to the location of the content, rather like a set of map co-ordinates.
What you need to do is add the content of the second paragraph before the first, which will create a new first paragraph. You can then delete what is now the third paragraph. For example:
Word.Range target = doc.Paragraphs[1].Range;
target.Collapse wdCollapseStart;
target.FormattedText = doc.Paragraphs[2].Range.FormattedText;
doc.Paragraphs[3].Range.Delete;
I am trying to create a Word document with checkboxes in it using Microsoft.Office.Interop.Word. I have used the following references to do so:
Create a Word document in C#
Create and edit a CheckBox in Word with c#
I have successfully generated a Word document with a checkbox but unfortunately it is disabled. What I am trying to achieve is to have a checkbox that can be checked/unchecked.
In the screenshot below, you can see that I have 3 checkboxes. The 1st one is generated using Microsoft.Office.Interop.Word and the 2nd and 3rd ones were created manually in Word 2016. The first one cannot be marked as checked/unchecked while the 2nd and 3rd ones behave just like a normal checkboxes
And this is the code I used to generate the Word document.
private void btnCreateWordInterop_Click(object sender, EventArgs e)
{
Word._Application word_app = new Word.ApplicationClass();
word_app.Visible = true;
object missing = Type.Missing;
Word._Document word_doc = word_app.Documents.Add(ref missing, ref missing, ref missing, ref missing);
Word.Paragraph para = word_doc.Paragraphs.Add(ref missing);
para.Range.Text = "Chrysanthemum Curve";
object style_name = "Heading 1";
para.Range.set_Style(ref style_name);
para.Range.InsertParagraphAfter();
//Microsoft.Office.Interop.Word.Range range =
para.Range.Collapse(ref missing);
Word.FormField checkBox = word_doc.FormFields.Add(para.Range, Word.WdFieldType.wdFieldFormCheckBox);
para.Range.InsertAfter(" Checkbox generated by Microsoft.Office.Interop.Word");
// Save the document.
object filename = #"C:\Users\Username\Desktop\InteropWord.docx";
word_doc.SaveAs(ref filename, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing,
ref missing);
//Close.
object save_changes = false;
word_doc.Close(ref save_changes, ref missing, ref missing);
word_app.Quit(ref save_changes, ref missing, ref missing);
MessageBox.Show("Saved");
}
How can I make the generated checkbox enabled?
Instead of using FormFields I'd recommend using Content Controls for this. These are more 'User Friendly' and easier to work with in general.
Change this line:
Word.FormField checkBox = word_doc.FormFields.Add(para.Range, Word.WdFieldType.wdFieldFormCheckBox);
Using a Content Control it would be something like (from the top of my head)
Word.ContentControl checkbox = para.Range.ContentControls.Add(Word.WdContentControlType.wdContentControlCheckBox);
I am try to automate a process for changing the document templates of word files.
If the templates are similar structure, ie they both use heading1, then when the document is linked to the new template, it works.
However, the template structure is completely different, heading1 is no longer used, it is now section1. How can I change these section titles with code? Something along the lines of if(heading1) rename to section1;
I am using Interop.Word to perform these operations.
Below is the code I'm using:
public string UpdateDocumentWithNewTemplate(string document, string theme, string template, Word.Application wordApp)
{
try
{
object missing = System.Reflection.Missing.Value;
Word.Document aDoc = null;
object notReadOnly = false;
object isVisible = false;
wordApp.Visible = false;
// create objects from variables for wordApp
object documentObject = document;
// open existing document
aDoc = wordApp.Documents.Open(ref documentObject, ref missing, ref notReadOnly, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref isVisible,
ref missing, ref missing, ref missing, ref missing);
aDoc.Activate();
// set template and theme to overwrite the existing styles
aDoc.CopyStylesFromTemplate(template);
aDoc.ApplyDocumentTheme(theme);
aDoc.UpdateStyles();
// save the file with the changes
aDoc.SaveAs(ref documentObject, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing, ref missing, ref missing);
// close the document
aDoc.Close(ref missing, ref missing, ref missing);
if (aDoc != null)
System.Runtime.InteropServices.Marshal.ReleaseComObject(aDoc);
aDoc = null;
return documentObject.ToString();
}
catch (Exception exception)
{
return "Error: " + exception;
}
}
For the specific example you need to first import the styles from the other template, then do a Find/Replace to replace the styles applied. I see from your code that you've got the first part (aDoc.CopyStylesFromTemplate(template); aDoc.ApplyDocumentTheme(theme); aDoc.UpdateStyles();).
What many don't realize about Word's Find/Replace functionality is that it can also work with formatting. The best way to get the necessary syntax is to record a successful Find/Replace in a macro, then port the VBA to C#. In the UI:
Ctrl+H to open the Replace dialog box
With the cursor in the "Find what" box, click "More" then "Format" and choose "Style"
Select the name of the style you want to find and have replaced
Click in the "Replace with" box
Use Format/Style, again, to choose the style you want to use
Click "Replace All".
Here's the result I get:
Selection.Find.ClearFormatting
Selection.Find.Style = ActiveDocument.styles("Heading 1")
Selection.Find.Replacement.ClearFormatting
Selection.Find.Replacement.Style = ActiveDocument.styles("section2")
With Selection.Find
.Text = ""
.Replacement.Text = ""
.Forward = True
.wrap = wdFindContinue
.Format = True
.MatchCase = False
.MatchWholeWord = False
.MatchByte = False
.CorrectHangulEndings = False
.HanjaPhoneticHangul = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute Replace:=wdReplaceAll
You should use Range, not Selection. So the C# code would look something like the following code block. Note how
I get the Range of the entire document
Create a Find object for the Range and use that
To reference Styles for the Find; I show two possibilities
You can list almost all the properties for Find before using Find.Execute. It would also be possible to create object objects for each of these, with only one necessary for true and false then list these "by ref" in Find.Execute. As far as I know, this is simply a matter of personal preference. I did it this way to the most literal "translation" of the VBA to C# code.
In any case, Find.Execute "remembers" these settings, so ref missing can then be used for all the parameters you don't set specifically. In this case, only the "replace all" command is used specifically in the method.
Word.Document doc = wdApp.ActiveDocument;
Word.Range rngFind = doc.Content;
Word.Find fd = rngFind.Find;
fd.ClearFormatting();
Word.Style stylFind = doc.Styles["Heading 1"];
fd.set_Style(stylFind);
fd.Replacement.ClearFormatting();
fd.Replacement.set_Style(doc.Styles["section2"]);
fd.Text = "";
fd.Replacement.Text = "";
fd.Forward = true;
fd.Wrap = Word.WdFindWrap.wdFindStop;
fd.Format = true;
fd.MatchCase = false;
fd.MatchWholeWord = false;
fd.MatchByte = false;
fd.CorrectHangulEndings = false;
fd.HanjaPhoneticHangul = false;
fd.MatchWildcards = false;
fd.MatchSoundsLike = false;
fd.MatchAllWordForms = false;
object replaceAll = Word.WdReplace.wdReplaceAll;
object missing = Type.Missing;
fd.Execute(ref missing, ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing, ref missing,
ref replaceAll, ref missing, ref missing, ref missing, ref missing);
I am having trouble trying to remove a list of data/text from a Word document using Word Interop. So far I thought that I could read through the document to find the starting text, then find the ending text, and save each of those index's to their own variable. Next I would just loop through the data from the starting index to the ending index and delete all the text in between.
Problem is that it works incorrectly and doesn't provide expected results. I must not be understanding how the Range interface works in document.Paragraphs[i+1].Range.Delete();. It deletes some lines but not all, and seems to go beyond the paragraphs that I care about to delete. What am I missing? There must be a better way to do this. Documentation seems low with Interop.
string text = " ";
int StartLocation = 0;
int EndLocation = 0;
//I roughly know the starting location
//starting at I=2248 so I don't
//through entire document
for (int i = 2248; i < 2700; i++)
{
text = document.Paragraphs[i + 1].Range.Text.ToString();
if (text.Contains("firstWordImSearchingFor"))
{
StartLocation = i;
}
if (text.Contains("lastWordImSearchingFor"))
{
EndLocation = i;
}
}
//delete everything between those paragraph locations
//(not working correctly/ skips lines)
for(int i = StartLocation; i<EndLocation-1i++)
{
document.Paragraphs[i+1].Range.Delete();
}
The drawback to the approach you're trying is that the Start and End locations (number of characters from the beginning of the Document story) will vary depending on what non-visible / non-printing characters are present. Content Controls, field codes and other things affect this - all in different ways depending on how things are being queried.
More reliable would be to store the starting point in one Range then extend it to the end point.
I also recommend using Range.Find to search for the start and end points.
Bare-bones pseudo-code example, since I don't really have enough information to go on to give you full, working code:
Word.Range rngToDelete = null;
Word.Range rngFind = document.Content;
bool wasFound = false;
object missing = System.Type.Missing;
object oEnd = Word.WdCollapseDirection.wdCollapseEnd;
wasFound = rngFind.Find.Execute("firstWordImSearchingFor", ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing, ref missing, ref missing);
if (wasFound)
{
rngToDelete = rngFind.Duplicate //rngFind is now where the term was found!
//reset the range to Find so it moves forward
rngFind.Collapse(ref oEnd);
rngFind.End = Document.Content.End
wasFound = rngFind.Find.Execute("lastWordImSearchingFor", ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing, ref missing, ref missing);
if (wasFound)
{
rngToDelete.End = rngFind.End;
rngToDelete.Delete();
}
}
This is completely untested and is offered as a suggestion:
var docRange = document.Content;
bool inDelete = false;
foreach(var para in docRange.Paragraphs)
{
if(para.ToString().Contains("Start flag") || inDelete)
{
inDelete = true;
docRange.Delete(para);
}
if (para.ToString().Contains("End flag"))
{
// remove following line to retain this paragraph
docRange.Delete(para);
break;
}
}
I need to start reading a word document from a specific point.
That key word is taken from a dropdown combo box.
The keyword is something like [blah blah, blah, 001]
So, I need to read only the content from that keyword to next heading ...
I used this to read heading numbers and line by line
but heading num notworking
string headNum = objparagraph.Range.ListFormat.ListString;
string sLine = objparagraph.Range.Text;
Word.Application word = new Word.Application();
Word.Document doc = new Word.Document();
object fileName = #"C:\wordFile.docx";
// Define an object to pass to the API for missing parameters
object missing = System.Type.Missing;
doc = word.Documents.Open(ref fileName,
ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing);
string ReadValue = string.Empty;
// Activate the document
doc.Activate();
foreach (Word.Range tmpRange in doc.StoryRanges)
{
ReadValue += tmpRange.Text;
}
If I understood correctly, you need to read the Word document starting from your keyword to next heading. In other words, something like the red text in the following document:
In that case, here is how you can accomplish that with GemBox.Document:
string keyword = " [blah blah, blah, 001]";
DocumentModel document = DocumentModel.Load("input.docx");
ContentPosition start = document.Content
.Find(keyword)
.First()
.End;
ContentPosition end = new ContentRange(start, document.Content.End)
.GetChildElements(ElementType.Paragraph)
.Cast<Paragraph>()
.First(p => p.ParagraphFormat.Style != null && p.ParagraphFormat.Style.Name.Contains("heading"))
.Content
.Start;
string text = new ContentRange(start, end).ToString();
The text variable's value will be:
Sample text content that we want to retrieve.
Another sample paragrap.
Also, here are additional Reading and Get Content examples, they contain some useful information.