I'm using Office Interop with MS Word (Microsoft.Office.Interop.Word) and Microsoft.Office.Tools.Word to modify a word document in a Word Add-in. I have a Range which contains specific text I want to edit.
When I update the Text object, the paragraph formatting of the Range is reset, specifically the Alignment and the LeftIndent. I can save the Alignment and LeftIndent in temp variables and reset them, but this is not ideal. Is there a way to stop the ParagraphFormat from being reset and if not, are there any other properties that I may be forgetting that I need to save (I just realized the before and after paragraph spacing also gets reset...).
Microsoft.Office.Interop.Word.Range range = myObject.range;
var oldAlignment = range.ParagraphFormat.Alignment;
var oldLeftIndent = range.ParagraphFormat.LeftIndent;
range.Text = "new text";
range.ParagraphFormat.Alignment = oldAlignment;
range.ParagraphFormat.LeftIndent = oldLeftIndent;
Edit: I just tried saving the ParagraphFormat as a temp variable and then resetting the formatting with that, but the temp variable loses its formatting as well.
oldParagraphFormat = range.ParagraphFormat;
range.Text = "new text";
range.ParagraphFormat = oldParagraphFormat; // oldParagraphFormat's objects are reset
Try creating a duplicate of the Range.ParagraphFormat object prior to changing the text. You can do this via the ParagraphFormat.Duplicate object. This will retain the old ParagraphFormat value. After you change a range's text and its ParagraphFormat resets, you can restore the value from the duplicate.
// Get current value of ParagraphFormat.
Microsoft.Office.Interop.Word.Range range = myObject.range;
var oldParagraphFormat = myObject.range.ParagraphFormat.Duplicate;
// Change the range's text. This will reset ParagraphFormat, so reapply the previous value.
range.Text = "new text";
range.ParagraphFormat = oldParagraphFormat;
Some background as to what's going on: Changing Range.Text essentially resets the Range object because a Range is text + formatting. So changing the text without including any formatting information will cause all previous formatting to be lost. (Much like how changing an HTML tag's innerText property causes that tag to lose all child tags.)
If duplicating the ParagraphFormat doesn't help then you may want to look into setting the Range.FormattedText property instead of Range.Text.
Related
Simple question that I'm not finding an answer to. My code below is in a loop and finds the first text matching "{{foo}}" in a Word doc. I then want to reset the Find so that it begins its next search at the beginning of the doc again. Currently, it picks up where after the "foo".
Selection sel = application.Selection;
sel.Find.ClearFormatting();
sel.Find.MatchWildcards = true;
sel.Find.Text = #"\{\{?#\}\}";
sel.Find.Forward = true;
sel.Find.Execute();
How do I reset the starting location of Find?
It's always "better" to use Range rather than Selection in Word, whenever possible. You can have only one selection, but code can work with multiple ranges. In addition, the screen is quieter and execution tends to be faster. There are situations where Selection is necessary, but this is not one of them.
To get the Range of the entire document
Word.Range rngDoc = document.Content;
To "find" using the range:
rngDoc.Find.ClearFormatting();
rngDoc.Find.MatchWildcards = true;
rngDoc.Find.Text = #"\{\{?#\}\}";
rngDoc.Find.Forward = true;
rngDoc.Find.Wrap = Word.WdFindWrap.wdFindStop //ensure Word won't entire an infinite loop
rngDoc.Find.Execute();
When "find" is successful, the Range (or Selection) contains only what was found. To "reset" to start again from the beginning of the document (including the whole document):
rngDoc = document.Content;
And (what people ask more frequently) to continue searching from just beyond the "found" term to the end of the document:
object oCollapseEnd = Word.WdCollapseDirection.wdCollapseEnd;
rngDoc.Collapse(ref oCollapseEnd); //go just beyond what was found
rngDoc.End = document.Content.End;
In VBA we'd use:
Selection.HomeKey Word.WdUnits.wdStory
So, in your C# code would that convert to?
Sel.HomeKey(Word.wdUnits.wdStory);
.Find.Execute always resets the range to the found range. Accordingly, you need to either re-set it to the entire document (as per Olivier's comment) or use the C# equivalent of .Wrap = wdFindContinue (VBA) to instruct Word to continue searching from the top after getting to the end of the document.
I got some problems with getting caret index of TextBox in Windows Store App(WP 8.1).
I need to insert specific symbols to the text when button is pressed.
I tried this:
text.Text = text.Text.Insert(text.SelectionStart, "~");
But this code inserts symbol to the beginning of text, not to the place where caret is.
UPDATE
I updated my code thanks to Ladi. But now I got another problem: I'm building HMTL editor app so my default TextBlock.Text is: <!DOCTYPE html>\r\n<html>\r\n<head>\r\n</head>\r\n<body>\r\n</body>\r\n</html>
So for example when user inserts symbol to line 3, symbol is inserted 2 symbols before caret; 3 syms before when caret is in line 4 and so on. Inserting works properly when symbol is inserted to the first line.
Here's my inserting code:
Index = HTMLBox.SelectionStart;
HTMLBox.Text = HTMLBox.Text.Insert(Index, (sender as AppBarButton).Label);
HTMLBox.Focus(Windows.UI.Xaml.FocusState.Keyboard);
HTMLBox.Select(Index+1,0);
So how to solve this? I guess new line chars making trouble.
For your first issue I assume you changed the TextBox.Text before accessing SelectionStart. When you set the text.Text, text.SelectionStart is reset to 0.
Regarding your second issue related to new lines.
You could say that what you observe is by design. SelectionStart will count one "\r\n" as one character for reasons explained here (see Remarks section). On the other hand, method string.Insert does not care about that aspect and counts "\r\n" as two characters.
You need to change slightly your code. You cannot use the value of SelectionStart as the insert position. You need to calculate the insert position accounting for this behavior of SelectionStart.
Here is a verbose code sample with a potential solution.
// normalizedText will allow you to separate the text before
// the caret even without knowing how many new line characters you have.
string normalizedText = text.Text.Replace("\r\n", "\n");
string textBeforeCaret = normalizedText.Substring(0, text.SelectionStart);
// Now that you have the text before the caret you can count the new lines.
// that need to be accounted for.
int newLineCount = textBeforeCaret.Count(c => c == '\n');
// Knowing the new lines you can calculate the insert position.
int insertPosition = text.SelectionStart + newLineCount;
text.Text = text.Text.Insert(insertPosition, "~");
Also you should make sure that SelectionStart does not exhibit similar behavior with other combinations beside "\r\n". If it does you will need to update the code above.
I have an Excel worksheet which I'm adding conditional formatting to from an add-in written in C#.
The condition fires ok and I'm able to change the fill colour but the text always gets hidden.
If I remove the fill colour from the format, the text still gets hidden when the formatting is applied.
If I remove all the formatting and just apply the condition without any format changes, the text is still hidden.
When the format condition is no longer valid, the text appears as you would expect.
The cell value is definitely set correctly.
Why would this happen?
C# code as follows:
var disabledFormat = "IF(blah blah...),FALSE,TRUE)";
var formatCondition = (Excel.FormatCondition)cell.FormatConditions.Add(
Excel.XlFormatConditionType.xlExpression,
Type.Missing, disabledFormat);
formatCondition.Font.Color = ColorTranslator.FromHtml("#C0C0C0");
formatCondition.Interior.Color = ColorTranslator.FromHtml("#F0F0F0");
[Edit]
I have tried changing the colour to white, black, red etc. but it is still invisible when the conditional formatting applies.
[/Edit]
[Edit2]
Full disabledFormat string as requested:
(The code does not look exactly link this as it spans several classes, I've just tried to fill-in the blanks to be helpful :$)
const string FORMAT_DISABLED = "=IF(LEFT(MID('{2}'!{0},FIND(\"|\",'{2}'!{0},FIND(\"|\",'{2}'!{0})+1)+1,999),LEN(INDIRECT(\"'$lookup_grading'!\"&ADDRESS({1},1))))=INDIRECT(\"'$lookup_grading'!\"&ADDRESS({1},1)),FALSE,TRUE)";
var dropdownCell = "Q5";
var disabledFormat = string.Format(FORMAT_DISABLED, cellName, dropdownCell, controlSheetName);
Resolves to:
=IF(LEFT(MID('$controls_Distribution Grid'!W19,FIND("|",'$controls_Distribution Grid'!W19,FIND("|",'$controls_Distribution Grid'!W19)+1)+1,999),LEN(INDIRECT("'$lookup_grading'!"&ADDRESS(Q5,1))))=INDIRECT("'$lookup_grading'!"&ADDRESS(Q5,1)),FALSE,TRUE)
To try to clarify further, what this does is it looks up a value in a cell in another worksheet with the same address, grabs a value from a formatted string in that cell and compares it to the value indicated by the selected item in a dropdown. If there is a match TRUE is returned.
The same formula is also used to return 1 or 0 for the cell value so I know this works ok.
[/Edit2]
[Edit3]
I've narrowed the problem down to the NumberFormat, which is "a";;;.
When the conditional formatting does not trigger this correctly shows a (or a tick with Webdings applied).
But when the conditional formatting triggers the output from the NumberFormat does not appear.
I can't think why this would be intentional so I'm guessing this is a bug in Excel, but I'll do some Googling to check
[/Edit3]
This is the formula in the cell which returns a 1 or 0 (hope it's clear enough!):
=IF(
LEFT(
MID('$controls_Distribution Grid'!$V$19,
FIND("|",'$controls_Distribution Grid'!$V$19,
FIND("|",'$controls_Distribution Grid'!$V$19)+1
)+1,999),
LEN(INDIRECT("'$lookup_grading'!"&ADDRESS(Q5,1)))
)=INDIRECT("'$lookup_grading'!"&ADDRESS(Q5,1)),
1,0)
The definition of a number format is four fields separated by ;
<POSITIVE>;<NEGATIVE>;<ZERO>;<TEXT>
Your number format "a";;; means if the value is positive display a , if it's zero, negative or text display nothing.
Your formula returns TRUE or FALSE, which are treated as text, so when your condition fires, the format hides the cell value.
I suggest you change your formula to return 1 or 0 which will then display a or nothing
try using this
formatCondition.Font.ThemeColor = ColorTranslator.FromHtml("#909090");
formatCondition.Interior.ThemeColor = ColorTranslator.FromHtml("#F0F0F0");
formatCondition.Interior.PatternColorIndex = xlAutomatic;
I don't know if the PatternColorIndex is necessary or not, that is what I got from macro recording.
Edit:
this is the macro if that helps you:
With Selection.Font
.ThemeColor = xlThemeColorDark1
.TintAndShade = -0.499984740745262
End With
With Selection.Interior
.Pattern = xlSolid
.PatternColorIndex = xlAutomatic
.ThemeColor = xlThemeColorDark1
.TintAndShade = -0.149998474074526
.PatternTintAndShade = 0
End With
This sounds like it could be an error in the font file. Try a standard character A-Z to see it it disappears when formatted. If it doesn't disappear then you could have a corrupt font file. Might have to dump the wingdings font and re-install.
I use interop.Word to create a Word document programmatically.
In the document I have a particular range which I would like to insert text to.
When I google it I see that the way to do this is :
range.Text=" Whatever...";
but I have no "Text" property for the range object.
Any ideas?
For the orignal question - this is just an intellisense bug, there is such property in the Range class.
For the problem from comments that
Range range=wordApp.ActiveDocument.TablesOfFigures[i].Range;
range.Text=" Whatever...";
replaces the ToF instead of prepending it with text. If you just want to set a header of the table, you can use Caption:
wordApp.ActiveDocument.TablesOfFigures[i].Caption = "Header text";
If however you need some text preceeding the ToF - check out this thread which is discussing similar case, but for the list instead of Table of Figures.
Another way to set caption is to select range you need and call InsertCaption:
wordApp.ActiveDocument.TablesOfFigures[i].Range.Select();
wordApp.Selection.InsertCaption("Whatever");
Note that InsertCaption accepts various args of various types, make sure to try different.
If you want to insert text at a range position, you can use Range.InsertBefore.
Range range=wordApp.ActiveDocument.TablesOfFigures[i].Range;
range.InsertBefore("My Text here. ");
An rtf document is generated by a data base application, with information from this data base. I have created a software (C#, net framework 4.5), to pick up data, then to record it into Excel file.
I have to read the footer of the rtf file, thing I can do.
But, when software access to footer, the document view is the same when footer/header are active (the same effect when you double click on header/footer to access it when you are under Word. This action action adds a carriage return on header (Word add this to enter something), and this \r causes to have additional page.
Here the code :
Sections oSection = cGlobalVar.varWordApp.ActiveDocument.Sections;
HeaderFooter oFooter = oSection[1].Footers[WdHeaderFooterIndex.wdHeaderFooterFirstPage];
Range oRange = oFooter.Range.Tables[1].Range;//<= at this point, footer is accessible, the empty header of original document has a\r character, causing 2nd page to document that I don't want
strBuffer = oRange.Text;//<= information I need
oRange = oSection[1].Range.Tables[1].Range;//<= try to affect something else to oRange
oFooter = null;//<= try to null the object
oSection = null;//<= same as above
//cGlobalVar.varWordDoc.ActiveWindow.View.Type = WdViewType.wdPrintView;//<= try to use this to return to a normal state
I have tried to manipulate Word to find something to get back to my original document (one page), but without any success.
Nulling the object won't clear its content. If you want to clear it, change the text of the range object
oFooter.Range.Text = "";
oSection.Range.Text = "";
Note: These objects have a reference type. This means that the variable points to the actual object, which is somewhere else. If you set the variable to null, you are just loosing the link to the object, but you are not changing the object. See my answer to the SO question Setting a type reference type to null doesn't affect copied type?
UPDATE
I made an experiment in Word, using a VBA macro that reads the table range of the footer as you did above. It does not change the view type of word.
Sub Macro1()
Dim oSection As Sections
Dim oFooter As HeaderFooter
Dim oRange As Range
Dim strBuffer As String
Set oSection = Application.ActiveDocument.Sections
Set oFooter = oSection(1).Footers(WdHeaderFooterIndex.wdHeaderFooterPrimary)
Set oRange = oFooter.Range.Tables(1).Range
strBuffer = oRange.Text
Debug.Print strBuffer
End Sub