I have PowerPoints that are automatically generated from software. The software puts the content (text) into Text Boxes instead of placeholders. I need to create and run a macro that will add all of the text to the Outline View (for Accessibility purposes).
I have a script that will move the text box content into the placeholder which by default shows up in the outline view. The only problem with this is that it is not retaining the styling (bulleted lists with subbullets are not working). The styling becomes especially problematic when I combine multiple Text Boxes from one slide into a single placeholder.
Any thoughts?
Here is my current script (the important stuff):
For Each sld In ActivePresentation.Slides
With ActivePresentation
sld.CustomLayout = ActivePresentation.Designs(1).SlideMaster.CustomLayouts(2)
Set hypCollection = New hyperColl 'Set the collection of arrays - 1 for each shape
Set shp = sld.Shapes(1)
For j = sld.Shapes.Count To 1 Step -1
Set shp = sld.Shapes(j)
bolCopy = False
If j = 3 Then
sld.Shapes.Placeholders.Item(1).TextFrame.TextRange = shp.TextFrame.TextRange.Characters
sld.Shapes.Placeholders.Item(1).Visible = msoTrue
shp.Delete
ElseIf j > 3 And shp.Type = msoTextBox Then
sld.Shapes.Placeholders.Item(2).TextFrame.TextRange.InsertBefore (shp.TextFrame.TextRange.TrimText) '.ParagraphFormat.Bullet.Type = shp.TextFrame.TextRange.ParagraphFormat.Bullet.Type
If hypCollection.Exists(shp.Name) Then
hypArray = hypCollection.GetArray(shp.Name)
For i = LBound(hypArray) To UBound(hypArray)
Set hypToAdd = hypArray(i)
With sld.Shapes.Placeholders.Item(2).TextFrame.TextRange.Characters(hypToAdd.getchrStart, Len(hypToAdd.getHypText)).ActionSettings.Item(1)
.Action = ppActionHyperlink
.Hyperlink.Address = hypToAdd.getHypAddr
End With
Next i
End If
shp.Delete
End If
Next j
End With
Next sld
Here are some examples:
The First Image is what I start with:
This is what it looks like after running my script:
This is what I want it to look like (simply maintaining formatting):
Would resetting the slide help?
You could add the line:
CommandBars.ExecuteMso ("SlideReset")
Just before:
Next sld
That should set the formatting in the textbox to the way it is on the master.
The fix was to Paste Special into the the new placeholder without replacing all contents. Since I was iterating through the textboxes in reverse order, I simply copied each TextBox and then Pasted Special into the placeholder at position 0 (leaving all current content there).
I converted the code to C#, and this is the full solution:
private void FixPPTDocument()
{
PPT.Application pptApp = new PPT.Application();
PPT.Shape currShp;
PPT.Shape shp2;
if (File.Exists((string)fileLocation))
{
DateTime today = DateTime.Now;
PPT.Presentation pptDoc = pptApp.Presentations.Open(fileLocation, Microsoft.Office.Core.MsoTriState.msoFalse, Microsoft.Office.Core.MsoTriState.msoTrue, Microsoft.Office.Core.MsoTriState.msoFalse);
foreach (PPT.Slide slide in pptDoc.Slides)
{
slide.CustomLayout = pptDoc.Designs[1].SlideMaster.CustomLayouts[2];
for (int jCurr = slide.Shapes.Count; jCurr >= 1; jCurr--)
{
currShp = slide.Shapes[jCurr];
if (jCurr == 3)
{
slide.Shapes.Placeholders[1].TextFrame.TextRange.Text = currShp.TextFrame.TextRange.Text;
slide.Shapes.Placeholders[1].Visible = Microsoft.Office.Core.MsoTriState.msoTrue;
currShp.Delete();
}
else if (jCurr > 3 && currShp.Type == Microsoft.Office.Core.MsoShapeType.msoTextBox)
{
currShp.TextFrame.TextRange.Copy();
slide.Shapes.Placeholders[2].TextFrame.TextRange.Characters(0, 0).PasteSpecial();
currShp.Delete();
}
}
}
pptDoc.SaveAs(fileNewLocation);
pptDoc.Close();
MessageBox.Show("File created!");
}
}
Related
Here's the question I'm having. I need to rotate Word.Shapes in a single Word-Document, but my script will only rotate the first one, and i can't figure out why.
Here's how the Word-Document comes to be (opens a PDF with one Shape per page):
Set wrdDoc = wrdAppMain.Documents.Open(FileName:=sToSaveAs, Visible:=False)
Here's how the loop is designed:
For Each wrdShape In wrdDoc.Shapes
If CheckFormat(wrdShape) = False Then
FitToPage = False
GoTo ExitScript
End If
Next wrdShape
And now the part that's acting up:
Private Function CheckFormat(oShapeToCheck As Word.Shape) As Boolean
On Error GoTo Failed
Dim siAspectRatio As Single
Dim iRotation As Integer
'---- Seitenverhältnis und Rotation berechnen ----
If oShapeToCheck.Height > 0 And oShapeToCheck.Width > 0 Then
siAspectRatio = oShapeToCheck.Height / oShapeToCheck.Width
iRotation = oShapeToCheck.Rotation
Else
ErrorCode = " (PDF)"
GoTo Failed
End If
'---- Kontrolle ob Bild im Querformat vorliegt ----
If siAspectRatio < 1 Then
'---- Kontrolle ob rotiert oder natives Querformat ----
Select Case iRotation
Case 0
oShapeToCheck.IncrementRotation 90
Case 180
oShapeToCheck.IncrementRotation 270
Case 90
oShapeToCheck.IncrementRotation 0
Case 270
oShapeToCheck.IncrementRotation 180
End Select
So and here's where the problem is. Although I the first Word.Shape meeting the criteria will be rotated, any others will not. Additionally if I set the visibility for the Word-Document to TRUE, debug through, and fullscreen the Word-Document before the script performs the rotation, it will rotate any Word.Shape every time.
I tried messing around with .Activate and the like but nothing seems to work. Hope you can help me there!
Thanks!
Markus
So I found a way to make this work. Instead of rotating every Word.Shape individually, I gather them all in a ShapeRange via their Indexes (or whatever the plural is on that one) and rotate them all at once.
Select Case iRotation
Case 0
If bIsDimensioned = False Then
ReDim Preserve RotationArray(0 To 0) As Variant
RotationArray(0) = iShapeIndex
bIsDimensioned = True
Else
ReDim Preserve RotationArray(0 To UBound(RotationArray) + 1) As Variant
RotationArray(UBound(RotationArray)) = iShapeIndex
End If
End Select
And after the ShapeRange is fully populated:
If bIsDimensioned = True Then
Set RotationShapeRange = wrdDoc.Shapes.Range(RotationArray)
RotationShapeRange.IncrementRotation 90
RotationShapeRange.WrapFormat.Type = wdWrapTight
RotationShapeRange.RelativeHorizontalPosition = wdRelativeHorizontalPositionPage
RotationShapeRange.RelativeVerticalPosition = wdRelativeVerticalPositionPage
RotationShapeRange.Left = wdShapeCenter
RotationShapeRange.Top = wdShapeCenter
End If
That should be it!
Frustrating, that new code is repasted in broken sections - can't get to work.
I am trying to use VBA to manipulate text in a PowerPoint.
I have formatted text in a frame with greek symbols, superscript and subscript.
I want to divide that text into two frames.
For example I use something like this:
Dim frame1Text As String
Dim frame2Text As String
Set frame1 = ActivePresentation.Slides(1).Shapes(1).TextFrame
Set frame2 = ActivePresentation.Slides(1).Shapes(2).TextFrame
frame1Text = frame1.TextRange.Text
frame2Text = Right(frame1Text, Len(frame1Text) - 10)
frame1Text = Left(frame1Text, Len(frame1Text) - Len(frame2Text))
frame1.TextRange.Text = frame1Text
frame2.TextRange.Text = frame2Text
As a result there are symbols and formatting lost. Is there any way to make it better?
Thanks for any help.
If possible, I would duplicate the shape and then delete what you don't want in the text. That way all formatting will be retained, character-by-character. Something along the lines of this:
Option Explicit
Sub CopyText()
Dim oShp1 As Shape
Dim oShp2 As Shape
Set oShp1 = ActivePresentation.Slides(1).Shapes(1)
oShp1.Copy
ActiveWindow.View.Slide.Shapes.Paste
Set oShp2 = ActivePresentation.Slides(1).Shapes(ActivePresentation.Slides(1).Shapes.Count)
With oShp1.TextFrame.TextRange
.Text = Left(.Text, 10)
End With
With oShp2.TextFrame.TextRange
.Text = Right(.Text, Len(.Text) - Len(oShp1.TextFrame.TextRange.Text))
End With
End Sub
This is my code:
Dim oWord As Word.Application
Dim oDoc As Word.Document
Dim oTable As Word.Table
Dim oPara1 As Word.Paragraph, oPara2 As Word.Paragraph
Dim oPara3 As Word.Paragraph, oPara4 As Word.Paragraph
Dim oRng As Word.Range
oWord = CreateObject("Word.Application")
oWord.Visible = True
oDoc = oWord.Documents.Add
oPara1 = oDoc.Content.Paragraphs.Add
oPara1.Range.InlineShapes.AddPicture(sNewData.Picture)
oPara1.Format.SpaceAfter = 24
oPara1.Range.InsertParagraphAfter()
oPara2 = oDoc.Content.Paragraphs.Add
oPara2.Range.Text = nTP.Nama
oPara2.Range.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphCenter
oPara2.Range.Font.Bold = True
oPara2.Range.Font.Size = 18
oPara2.Range.Font.Color = Word.WdColor.wdColorBlue
oPara2.Format.SpaceAfter = 6
oPara2.Range.InsertParagraphAfter()
oPara3 = oDoc.Content.Paragraphs.Add
oPara3.Range.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphLeft
oPara3.Range.Text = "Special arrangement for Mr/Mrs. " & customer
oPara3.Range.Font.Color = Word.WdColor.wdColorOliveGreen
oPara3.Format.SpaceAfter = 3
oPara3.Range.InsertParagraphAfter()
The strange thing is the oPara1 is aligned at left, while oPara2 is aligned at center. But the oPara3 is not aligned at left.
The output is that oPara3 is following the previous alignment which is center.
So how could I apply for a specific paragraph its own alignment or style?
I found some discussion about "Style" but I confused.
This C# code aligns the whole Word document successfully:
foreach (Paragraph p in oWord.ActiveDocument.Paragraphs)
{
p.Alignment = WdParagraphAlignment.wdAlignParagraphLeft;
}
Your proposal was right, BUT:
When you you modify the property TEXT in the code, the Alignment property restart to Left.
You can try this inserting several times this code to "debug" the status of the Alignment property. One before you set the alignment, one after you modify the alignment, one after you change the "Text" property, then run the program.
Msgbox(oPara3.Range.ParagraphFormat.Alignment)
So the solution is to modify the Alignment just before inserting the Paragraph:
oPara3 = oDoc.Content.Paragraphs.Add
/*oPara3.Range.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphLeft*/
oPara3.Range.Text = "Special arrangement for Mr/Mrs. " & customer
oPara3.Range.Font.Color = Word.WdColor.wdColorOliveGreen
oPara3.Format.SpaceAfter = 3
oPara3.Range.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphLeft
oPara3.Range.InsertParagraphAfter()
AFAIK, Adding a new paragraph is like pressing Enter / Return, so at first format of a new paragraph sets based on Normal Style then after changing any property of current paragraph, it stored as current format that will apply to new paragraphs from now on.
So to change this behavior that makes all new paragraphs to use same format instead of some situation I can suggest to use a method like this - but in C# -:
public static void AddParagraph(ref Document doc, string text,
WdParagraphAlignment align = WdParagraphAlignment.wdAlignParagraphLeft, ...)
// ... means for other format specifications you can do it same
{
var para = doc.Content.Paragraphs.Add();
para.Alignment = align;
para.Range.Text = text;
}
This is what worked for me
Range content = doc.Content;
Paragraph pText = content.Paragraphs.Add();
Text.Range.Text = "sadfsadfsda"
pText.Range.ParagraphFormat.Alignment = WdParagraphAlignment.wdAlignParagraphCenter;
I know this is old question, but the solutions mentioned here didn't help me.
So i'll post the solution here for ppl like me in hope it will help them.
The thing you need to do is set the Alignment property AFTER setting the Text property (see the code below).
'p1 is going to be right aligned
var p1 = document.Paragraphs.Add(System.Reflection.Missing.Value)
p1.Range.Font.Name = "Calibri"
p1.Range.Font.Size = 18
p1.Range.Text = "right"
p1.Range.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphRight
p1.Range.InsertParagraphAfter()
'p2 is going to be center aligned
var p2 = document.Paragraphs.Add(System.Reflection.Missing.Value)
p2.Range.Font.Name = "Calibri"
p2.Range.Font.Size = 16
p2.Range.Text = "center"
p2.Range.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphCenter
p2.Range.InsertParagraphAfter()
'p3 is going to be left aligned
var p3 = document.Paragraphs.Add(System.Reflection.Missing.Value)
p3.Range.Font.Name = "Calibri"
p3.Range.Font.Size = 14;
p3.Range.Text = "left"
p3.Range.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphLeft
p3.Range.InsertParagraphAfter()
This is the way it worked for me.
This is worked for me!
Range content = doc.ActiveDocument.Content;
Paragraph pText = content.Paragraphs.Add();
pText.Range.ParagraphFormat.Alignment =
WdParagraphAlignment.wdAlignParagraphRight;
doc.Selection.TypeText("some text");
Recently we upgraded one our PowerPoint addin to support 2007 and 2010. most of the items we were able to port without problem. one problem we have is that indentations doesn't work when create tables or shapes using the addin.
for eg: same table gets dropped with proper indentation in 2003 but same thing doesn't get indentation when added to using 2007.
below is the code snippet that allows indenting:
With PropertyValues.ObjShape.Table.Cell(row, col).Shape.TextFrame.Ruler
For rulerCount = 0 To 5
.Levels(rulerCount).FirstMargin = rulerFirstMargin(rulerCount) '.LeftMargin = rulerLeftMargin
.Levels(rulerCount).LeftMargin = rulerLeftMargin(rulerCount) 'Left indent marker
Next rulerCount
End With
any idea why this is not working ?
I read the following thread too but didn't help much http://answers.microsoft.com/en-us/office/forum/office_2007-customize/why-shapetextframerulerlevelsi-cant-set-the-bullet/9eac3e46-b13b-433e-b588-216ead1d9c1a?tab=AllReplies#tabs
Updated Code:
PropertyValues.ObjShape.Table.Cell(row, col).Shape.TextFrame.TextRange.Text = "N/A"
With PropertyValues.ObjShape.Table.Cell(row, col).Shape.TextFrame
'Dim rulerCount As Short
For rulerCount = 1 To 5
.Ruler.Levels(rulerCount).FirstMargin = 10 * rulerCount 'rulerFirstMargin(rulerCount) '.LeftMargin = rulerLeftMargin
.Ruler.Levels(rulerCount).LeftMargin = 20 * rulerCount 'rulerLeftMargin(rulerCount) 'Left indent marker
Next rulerCount
End With
PropertyValues.ObjShape.Table.Cell(row, col).Shape.TextFrame.TextRange.Text = text
FWIW, in 2007 and up, you can now have up to 9 ruler levels instead of 5 as in earler versions. But your code should work as is. Here's a simplified version that does work on an arbitrary cell (2,2) of a table:
Dim oSh As Shape
Dim x As Long
Set oSh = ActiveWindow.Selection.ShapeRange(1)
With oSh.Table.Cell(2, 2).Shape.TextFrame
For x = 1 To 9
.Ruler.Levels(x).LeftMargin = x * 10
.Ruler.Levels(x).FirstMargin = x * 20
Next
End With
The other thing you might be running into is that you can apply certain types of formatting (including ruler settings) all you like; if there's no text at the level you're applying it to, PPT won't bark. It'll ignore you. Your settings will have no effect. Sometimes you need to check for text, supply some if there's none there (something highly improbable in the real world) then delete all instances of your improbable text afterwards.
Ugly. Yes.
Here we add text and set indent levels before trying to FORMAT each indent level:
Sub test()
Dim oSh As Shape
Set oSh = ActiveWindow.Selection.ShapeRange(1)
Dim RulerCount As Long
Dim sTemp As String
sTemp = "##$%" ' dummy text
With oSh.Table.Cell(2, 3).Shape.TextFrame
For RulerCount = 1 To 5
.TextRange.Paragraphs(RulerCount).Text = sTemp & vbCrLf
.TextRange.Paragraphs(RulerCount).IndentLevel = RulerCount
Next
For RulerCount = 1 To 5
.Ruler.Levels(RulerCount).FirstMargin = 10 * RulerCount 'rulerFirstMargin(rulerCount) '.LeftMargin = rulerLeftMargin
.Ruler.Levels(RulerCount).LeftMargin = 20 * RulerCount 'rulerLeftMargin(rulerCount) 'Left indent marker
Next RulerCount
End With
End Sub
In PowerPoint 2007 and using VBA, how can I get the placeholder shape on a Slide Master layout that is the "master" for a placeholder shape on the slide?
I am currently using a loop to compare the position and size of the slide placeholder with the position and shape of each placeholder shape in the slide's layout, but this isn't fool-proof. For example, if the placeholder shape is moved on the slide, its position may no longer match the position of any placeholder shapes in the slide's layout. I could reapply the slide's layout to snap placeholders back into position, but that's not what I want to do.
Something in the object model like Shape.Master would be ideal but, of course, that doesn't exist.
Seeing as there is still no answer to this here or elsewhere I might as well post my code.
For example, if the placeholder shape is moved on the slide,
Here's what I came up with to handle that:
store the locations of all shapes
reset the slide layout
match slide shapes and master slide shapes
restore the locations of all shapes.
This is the function that does that and returns a mastershapename - shapename mapping.
private Dictionary<string, string> GetShapeMasters(Powerpoint.Slide s)
{
Dictionary<string, string> shapeMasters = new Dictionary<string, string>();
List<ShapeLocation> shapeLocations = new List<ShapeLocation>();
//store locations
foreach (Powerpoint.Shape sh in s.Shapes)
{
shapeLocations.Add(new ShapeLocation()
{
Name = sh.Name,
Location = new System.Drawing.RectangleF(sh.Left, sh.Top, sh.Width, sh.Height)
});
}
//have powerpoint reset the slide
//ISSUE: this changes the names of placeholders without content.
s.CustomLayout = s.CustomLayout;
//compare slide and master
foreach (Powerpoint.Shape sh in s.Shapes)
{
foreach (Powerpoint.Shape msh in s.CustomLayout.Shapes)
{
if (IsShapeMaster(sh, msh))
{
shapeMasters[msh.Name] = sh.Name;
}
}
}
//restore locations
//TODO: might be replaced by undo
foreach (var shm in shapeLocations)
{
Powerpoint.Shape sh = null;
try
{
sh = s.Shapes[shm.Name];
}
catch
{
//Fails for renamed placeholder shapes.
//Have yet to find a decent way to check if a shape name exists.
}
//placeholders do not need to be restored anyway.
if (sh != null)
{
sh.Left = shm.Location.Left;
sh.Top = shm.Location.Top;
sh.Width = shm.Location.Width;
sh.Height = shm.Location.Height;
}
}
return shapeMasters;
}
With this you can do
Dictionary<string, string> shapeMasters = GetShapeMasters(theSlide);
if(shapeMasters.ContainsKey("KnownPlaceholderName"))
Powerpoint.Shape KnownShape = theSlide[shapeMasters["KnownPlaceholderName"]];
And here is the comparison function that takes two shapes and checks if they are "equal". Could be extended to make it more precise.
private bool IsShapeMaster(Powerpoint.Shape sh, Powerpoint.Shape msh)
{
return
sh.Left == msh.Left
&& sh.Top == msh.Top
&& sh.Width == msh.Width
&& sh.Height == msh.Height
&& sh.Type == msh.Type
&& sh.PlaceholderFormat.Type == msh.PlaceholderFormat.Type;
}
Little class that stores original shape location
class ShapeLocation
{
public string Name;
public System.Drawing.RectangleF Location;
}
This is code from a C# VSTO Add-in but I suppose it is not that different from VB or other PPT automation types.
Here you go Ryan, I believe this is what you're asking for.
Sub GetLayoutShapeDetails()
Dim myPPT As Presentation
Set myPPT = ActivePresentation
Dim mySlide As Slide
Set mySlide = myPPT.Slides(6)
Dim slideShape As Shape
Dim slideLayoutShape As Shape
Set slideShape = mySlide.Shapes(1)
If slideShape.Type = msoPlaceholder Then
Dim placeHolderType As Integer
placeHolderType = slideShape.PlaceholderFormat.Type
Set slideLayoutShape = mySlide.CustomLayout.Shapes.Placeholders(placeHolderType)
Dim modifiedPlaceHolder As String
modifiedPlaceHolder = "Shape Name: " & slideShape.Name & _
", Left: " & slideShape.Left & _
", Width: " & slideShape.Width
Dim originalPlaceHolder As String
originalPlaceHolder = "Shape Name: " & slideLayoutShape.Name & _
", Left: " & slideLayoutShape.Left & _
", Width: " & slideLayoutShape.Width
Debug.Print modifiedPlaceHolder
Debug.Print originalPlaceHolder
End If
End Sub
EDIT: JAN 16, 2010
Based off of further research, there doesn't not appear to be a way in VBA to find a shape's corresponding exact match in its slide's layout.