I'm creating a Microsoft Word 365 Add-in where I need to be able to add and remove inline shapes. I currently have the following test code:
bookmark.Range.InlineShapes.AddPicture("c:\\temp\\test.png");
And although the InlineShape gets added to the page, and seemingly on the correct position, i.e. within the provided bookmark, the bookmark.Range.InlineShapes collection stays empty:
Assert.IsTrue(bookmark.Range.InlineShapes.Count > 0); // This fails
As far as I can see, the shape is actually not added to the range, but directly after it.
This behavior is odd and causes problems in my situation, where I need to be able to iterate the inline shapes of the bookmark later on, especially to be able to remove (toggle) the image again. But without the inline shape as part of the bookmark
What am I doing wrong and how can I fix this, in such way that the inline shape is becoming part of the bookmark again?
That's because your code doesn't insert the shape within the bookmark. This is a quite common misunderstanding. You need to first point a Range object to the bookmark range. Then, add the InlineShape to your Range object's range, then extend the range by one character. Finally, re-apply the bookmark to the Range object's range.
Following is a VBA example:
Sub Demo()
Dim wdRng As Word.Range, BkMkNm As String
BkMkNm = "MyBookmark"
With ActiveDocument
'Confirm that the bookmark exists
If .Bookmarks.Exists(BkMkNm) Then
Set wdRng = .Bookmarks(BkMkNm).Range
'Delete existing contents
wdRng.Text = vbNullString
'Insert Pic
.Range.InlineShapes.AddPicture FileName:="PictureFullName", Range:=wdRng
'Extend Range
wdRng.End = wdRng.End + 1
'Re-apply bookmark
.Bookmarks.Add BkMkNm, wdRng
End If
End With
End Sub
Related
I have a template Word document which contains multiple text boxes on top of shapes (to give it a better border outline than what can be achieved my the outline of a text box). These text boxes contain mail merge fields that I wish to merge to. I have the following code in an attempt to do this
foreach (Microsoft.Office.Interop.Word.Range range in document.StoryRanges)
{
foreach (Microsoft.Office.Interop.Word.Field field in range.Fields)
{
if (field.Code.Text.Contains("Test Field"))
{
field.Select();
application.Selection.TypeText("test");
}
}
The problem is this only changes the fields within the first text box, I have searched both on here and MSDN for a solution, however I am still having trouble actually finding a solution. I have also added the following lines in an attempt to figure out something
Console.WriteLine(document.StoryRanges.Count);
And within the foreach loop I also have
Console.WriteLine(range.Fields.Count);
The first call to WriteLine indicates there are two StoryRanges, one being the main document, the other being the range that all the text boxes are on, I presume. However, the second WriteLine indicates the first range has 0 fields, whereas the second range only has 1 field, even though the template document I am using contains over 10 fields.
Are the StoryRanges nested ? Are they of different type?
If so, you should consider using StoryRange.NextStoryRange as proposed here:
http://word.mvps.org/faqs/customization/ReplaceAnywhere.htm
'Iterate through all story types in the current document
For Each rngStory In ActiveDocument.StoryRanges
'Iterate through all linked stories
Do
With rngStory.Find
.Text = "find text"
.Replacement.Text = "I'm found"
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
'Get next linked story (if any)
Set rngStory = rngStory.NextStoryRange
Loop Until rngStory Is Nothing
Next
I'm developing C# addin for MS Word. I can grab all words of current document - it's something like that:
app = (Word._Application )Application; // Application object comes on addin's connection
foreach(Word.Word word in app.Application.Words)
{
doSmth(word);
}
My question, is how to grab all words not from entire document but from current active(visible for user) page?
In other words, I need to define active page/paragraph of app.Application.ActiveDocument and do something with "active" words.
Interesting question. [See update at end]
Word's object model doesn't really have a "page" object, because the pagination of the document is constantly changing as you add and remove content (or change the font size, the paper size, etc.). So, there is no "ActiveDocument.Pages(1)" sort of thing.
What's more, there's no easy way to tell what page is currently displayed. In part, that's because the user doesn't necessarily see only one page at a time. He may be viewing the end of one page and the start of the next, or several pages may be displayed - depending on his view settings.
If I can make the question slightly easier, then perhaps I can answer it in a way that helps you. Let me re-define "current active (visible for user) page" as the page where the selection is. (Actually, since the selection can span several pages, let's define it as "the page where the active end of the selection is").
I'll also answer using VBA because it's easier to play around with it in the VBA immediate window, and it's trivial to convert to C# when you need to (it's the same object model, after all).
Word's Selection object has the properties of a Range, and if you simply wanted all the selected words, then this would be trivial (Selection.Words!). However, if we want all the words on that page, then we need to work a little harder.
First, let's find out what page the (start of the) selection is on. For this, we can use the Information method:
pageNumber = Selection.Information(wdActiveEndPageNumber)
So now we know what page we're interested in. We now need to get a Range object that includes all the text on that page. We need to do this in two steps - by finding first the start and then the end of that range.
To find the start of the range, we can use the Goto function, which returns a Range object representing the start of a specified item:
startOfRange = ActiveDocument.GoTo(WdGoToItem.wdGoToPage, WdGoToDirection.wdGoToAbsolute, pageNumber).Start
The end of the range is either the start of the next page (minus one character, but let's not quibble), or the end of the document (if we're on the last page):
If pageNumber = ActiveDocument.Content.Information(wdNumberOfPagesInDocument) Then
endOfRange = ActiveDocument.Content.End
Else
endOfRange = ActiveDocument.GoTo(WdGoToItem.wdGoToPage, WdGoToDirection.wdGoToAbsolute, pageNumber + 1).Start
End If
Now we can construct a Range object encompassing all the text on the page:
Set pageRange = ActiveDocument.Range(startOfRange, endOfRange)
... and from there we can get the words:
Set words = pageRange.Words
Here is a short VBA macro that uses the above technique to report the number of words on the active page:
Sub Test()
Dim pageNumber As Integer
Dim startOfRange As Integer
Dim endOfRange As Integer
Dim pageRange As Range
pageNumber = Selection.Information(wdActiveEndPageNumber)
startOfRange = ActiveDocument.GoTo(WdGoToItem.wdGoToPage, WdGoToDirection.wdGoToAbsolute, pageNumber).Start
If pageNumber = ActiveDocument.Content.Information(wdNumberOfPagesInDocument) Then
endOfRange = ActiveDocument.Content.End
Else
endOfRange = ActiveDocument.GoTo(WdGoToItem.wdGoToPage, WdGoToDirection.wdGoToAbsolute, pageNumber + 1).Start
End If
Set pageRange = ActiveDocument.Range(startOfRange, endOfRange)
MsgBox pageRange.Words.Count
End Sub
UPDATE
OK, it turns out that there's a much easier way to do this. Word has a "special bookmark" that points to the text on the current page, so this will do the same as all that code above:
words = ActiveDocument.Bookmarks("\page").Range.Words
I'm having big problems with a code that should add watermark pictures (used as stationery) to a document before saving it as PDF. Inserting the picture to all relevant headers is no problem. But as soon as I try to strech the picture (shape) to the whole page width and height, Word 2007 (SP3) throws an exception. The same code works fine in Word 2010 (SP1). It doesn't matter if I use the Office 12 or Office 14 interop assemblies (always used with "Embed Interop Types" true).
The exception thrown is the following:
System.Runtime.InteropServices.COMException (0x800A122C): Falscher Zeichnungselement-Typ für diesen Befehl.
at Microsoft.Office.Interop.Word.Shape.set_RelativeHorizontalSize(WdRelativeHorizontalSize prop)
at BEKO.PDB.AuxiliaryServices.Documents.WordCreationService.AddWatermarkToHeader(HeaderFooter header, String watermarkFilePath)
I don't know exactly what the english error message would be, but the translation is something like "Invalid painting type (or maybe shape type) for this command".
The weird thing is that it doesn't always error on the same interop call. If I remove the line that sets the RelativeHorizontalSize property it fails when setting another property, like WidthRelative (with the same exception). If I add a line that sets shape.LeftRelative (to the "do not use" constant) it even fails on a line that otherwise works like shape.Top (again with the same exception).
The code I'm using is from a macro that was recorded in the failing Word 2007. I'm also correctly switching to the header SeekView before executing any header related code because I already needed that for some other header/footer code.
Here is the full code that adds the shape. It should just insert the picture and strech it to the full page size. Note: This method is only called for headers that are actually existing (headerFooter.Exists) and that are not linked to the previous (!headerFooter.LinkToPrevious).
private static void AddWatermarkToHeader(HeaderFooter header, string watermarkFilePath) {
header.Range.Editors.Add(WdEditorType.wdEditorEveryone);
Shape shape = header.Shapes.AddPicture(
FileName: watermarkFilePath,
LinkToFile: false,
SaveWithDocument: true
);
shape.WrapFormat.AllowOverlap = (int)MsoTriState.msoTrue;
shape.WrapFormat.Type = WdWrapType.wdWrapNone;
shape.RelativeHorizontalPosition = WdRelativeHorizontalPosition.wdRelativeHorizontalPositionPage;
shape.RelativeVerticalPosition = WdRelativeVerticalPosition.wdRelativeVerticalPositionPage;
shape.Left = 0;
shape.Top = 0;
shape.RelativeHorizontalSize = WdRelativeHorizontalSize.wdRelativeHorizontalSizePage;
shape.RelativeVerticalSize = WdRelativeVerticalSize.wdRelativeVerticalSizePage;
shape.WidthRelative = 100;
shape.HeightRelative = 100;
shape.ZOrder(MsoZOrderCmd.msoSendBehindText);
}
Please give any advice how to fix this so that the code works with both Word 2007 and Word 2010.
I realize this does not fix the code to run on both versions of Word as requested, but have you tried using absolute sizing for the image instead? Keep relative positionning, but use absolute sizing. Do you actually need relative sizing (i.e. do your documents contain multiple page sizes?).
shape.Width = page.Width;
shape.Height = page.Height;
From Word 97 through to Word 2003 there has been a little known bug in the Word Object Model that causes WdRelativeHorizontalPosition.wdRelativeHorizontalPositionPage and WdRelativeVerticalPosition.wdRelativeVerticalPositionPage to retrieve the incorrect information if the magnification of the active Word document in not 100%. I suspect that this issue still exists in Word 2007 and may be causing your exception. Here are two threads (both dealing with the same issue in VBA) that refer to this issue:
Points-returned-by-Information-wdHorizontalPositionSubroutines
Word 97 wdHorizontalPositionRelativeToPage
I suggest adding code after the addition of the shape in the header (and prior to retrieving the relative horizontal and vertical positions to the page) that changes the Active Document's zoom to 100% and the View Type to the Print Layout view. (You may have to experiment with which part of the Word document is displayed when the code for changing the positioning and sizing of the shape is executed. Sometimes it will be necessary for the Active Document to display/be able to edit the main document instead of the header.)
I want to read the notes of a PowerPoint Slide in C#.
Following Snippet works for me.
slide.NotesPage.Shapes[2].TextFrame.TextRange.Text
However, this doesn't work for some presentations.
Then it throws an "Out of range" exception.
What ist the meaning of index 2? Are there any alternative to do this?
You can't assume that the notes text placeholder will be at any specific index or even that it'll have a specific name. Here's a simple function in VBA that returns the notes text placeholder for a slide:
Function NotesTextPlaceholder(oSl As Slide) As Shape
Dim osh As Shape
For Each osh In oSl.NotesPage.Shapes
If osh.Type = msoPlaceholder Then
If osh.PlaceholderFormat.Type = ppPlaceholderBody Then
' we found it
Set NotesTextPlaceholder = osh
Exit Function
End If
End If
Next
End Function
It means that you are trying to access the third element of the slide.NotesPage.Shapes collection. If the collection has 2 elements or less, the exception is thrown because the element at the specified index 2 could not be accessed since it doesn't exist — you simply cannot retrieve a collection's third element if it doesn't have one.
(The index is zero-based, meaning that the first element is given the index 0, the second one is given the index 1 and so on. Thus, the greatest possible index of a collection with N elements is N-1.)
It's dangerous to try and access an index object without checking if it exists first, since this might throw exceptions. You can check if the slide has notes with the HasNotesPage property of the slide object:
if(slide.HasNotesPage == Microsoft.Office.Core.MsoTriState.msoTrue)
{
}
If you want to get all the notes at once, you might want to use NotesPage property to retrieve a range with all notes.
I am creating a MS Word document entirely through C# in VS 2008. I am trying to insert a page break, and when the page break is inserted, instead of inserting it and adding a new page at the bottom, it is inserting a break and adding a page at the top. This results in the first page being the last page.
Here is the code for inserting the page break:
start = 0;
end = 0;
Word.Range rngDoc = Range(ref start, ref end);
rngDoc.Collapse(ref CollapseEnd);
rngDoc.InsertBreak(ref pageBreak);
rngDoc.Collapse(ref CollapseEnd);
Also, each page is consumed by a table, if this helps with the diagnostics
InsertBreak never inserts after the selection. Note the MSDN remarks:
When you insert a page or column break, the selection is replaced by the break. If you don't want to replace the selection, use the Collapse method before using the InsertBreak method. When you insert a section break, the break is inserted immediately preceding the Selection object.
(My emphasis.) To get a break at the end of the page, I think you'll have to select nothing (as you are here) at the end of the document.
I can't recall whether the Document has its own range. Can you just get an all-encompassing range from myDoc.Characters?
If not, the first thing I would try is
start = int.MaxValue;
end = int.MaxValue;
If that doesn't work, you might resort to ComputeStatistics(). Something like this:
WdStatistic stats = WdStatistic.wdStatisticCharacters;
var chars = myDoc.ComputeStatistics(stats, false);
And then create your range from that value. Wish I could help more, but it's been a while for me. Good luck!
You need to set the rngDoc to insert at the end of ActiveDocument.Range. Here's some VBA that would be easy to port to C# that does this.
Sub InsertNewPageAtEndofDoc()
Dim rng As Range
Dim adRange As Range
Set adRange = ActiveDocument.Range
Set rng = ActiveDocument.Range(adRange.Start, adRange.End)
rng.Collapse (wdCollapseEnd)
rng.InsertBreak (wdPageBreak)
rng.Collapse (wdCollapseEnd)
End Sub
I used Todds approach but in vb.net:
Private Sub InsertNewPageAtEndofDoc(app As Word.Application)
Dim rng As Word.Range
Dim adRange As Word.Range
adRange = app.ActiveDocument.Range
rng = app.ActiveDocument.Range(adRange.Start, adRange.End)
rng.Collapse(Word.WdCollapseDirection.wdCollapseEnd)
rng.InsertBreak(Word.WdBreakType.wdPageBreak)
rng.Collapse(Word.WdCollapseDirection.wdCollapseEnd)
End Sub
I'm not an expert at this so this is just a guess, it looks a bit strange though that you're selecting a range from 0 to 0 and then collapsing that. A range from 0 to 0 sounds like it'll give you the very start of the document, I'd guess that you need to select the last bit of the document instead but not sure how to do that.