How to Insert Shape at cursor position in MSWORD using VBA - vba

I want to insert a shape in Word above the picture where ever user clicks.
I have written the program below, but sometimes it is placing the rectangle incorrectly and inserting it twice: once where I need it and again somewhere else.
Why is the Shape being inserted twice?
Private WithEvents app As Word.Application
Private Sub app_WindowSelectionChange(ByVal Sel As Selection)
Cancel = True
Call CurosrXY_Pixels
End SuB
Sub CurosrXY_Pixels()
ActiveDocument.Shapes.AddShape(msoShapeRectangle, fcnXCoord, fcnYCoord, 20#, 16#).Select
With Selection
.ShapeRange.TextFrame.TextRange.Select
.Collapse
.Font.Name = "Arial"
.Font.Size = 7
.Font.Bold = False
.Paragraphs.FirstLineIndent = 0
.Paragraphs.RightIndent = -10
.Paragraphs.LeftIndent = -10
.Paragraphs.Alignment = wdAlignParagraphCenter
.TypeText Text:=11
.ShapeRange.LockAspectRatio = msoCTrue
End With
End Sub
Function fcnXCoord() As Double
fcnXCoord = Selection.Information(wdHorizontalPositionRelativeToPage)
End Function
Function fcnYCoord() As Double
fcnYCoord = Selection.Information(wdVerticalPositionRelativeToPage)
End Function

The reason the code is firing more than once is because of the use of the Select method. Code changing the selection is the same as the user doing so. The way to avoid this is to work directly with the Word objects.
The code below illustrates this in the procedure CurosrXY_Pixels. A Shape object is declared, then the newly inserted drawing object assigned to it. This then used for setting the formatting and text, in the With block.
Notice I've also passed the Selection object from the event to this procedure, as well as to the two that calculate the co-ordinates. Since, conceivably, the user could click again before the macro finishes, it's important to pass along the original Selection. (That the original code was not doing so probably contributed to the "randomness" of where things were being created since the code, itself, was changing the selection.)
The code line in the app_WindowSelectionChange event to call the other procedures: CurosrXY_Pixels Sel
Sub CurosrXY_Pixels(Sel As Word.Selection)
Dim shp As Word.Shape
Set shp = ActiveDocument.Shapes.AddShape(msoShapeRectangle, fcnXCoord(Sel), fcnYCoord(Sel), 20#, 16#, Sel.Range)
With shp.TextFrame.TextRange
.Font.Name = "Arial"
.Font.Size = 7
.Font.Bold = False
.Paragraphs.FirstLineIndent = 0
.Paragraphs.RightIndent = -10
.Paragraphs.LeftIndent = -10
.Paragraphs.Alignment = wdAlignParagraphCenter
.Text = 11
End With
shp.LockAspectRatio = msoCTrue
End Sub
Function fcnXCoord(Sel As Word.Selection) As Double
fcnXCoord = Sel.Information(wdHorizontalPositionRelativeToPage)
End Function
Function fcnYCoord(Sel As Word.Selection) As Double
fcnYCoord = Sel.Information(wdVerticalPositionRelativeToPage)
End Function

Related

Replace a text box with a placeholder (Title or Body)

I received a PowerPoint file with multiple slides which were supposed to be templates (designs - customlayouts) but instead were regular slides.
Transforming them into SlideMaster and custom layouts and replacing the titles and bodys (textboxes) with actual placeholders by hand was a pain.
So I came with this script to make the process faster.
If anybody has a better approach, it's welcome.
Had to look for a workaround to get the customlayout object.
Several things are missing, for example error handling.
To test it, copy a textbox into a slidemaster layout slide, select it and run the ReplaceWithPHTitle macro
Option Explicit
Public Sub ReplaceWithPHTitle()
ReplaceTexboxWithPlaceholder ppPlaceholderTitle
End Sub
Public Sub ReplaceWithPHBody()
ReplaceTexboxWithPlaceholder ppPlaceholderBody
End Sub
Private Sub ReplaceTexboxWithPlaceholder(ByVal placeholderType As PpPlaceholderType)
Dim targetLayout As CustomLayout
Dim activeShape As Shape
Dim newPlaceHolder As Shape
Set activeShape = ActiveWindow.Selection.ShapeRange(1)
Set targetLayout = activeShape.Parent
Set newPlaceHolder = targetLayout.Shapes.AddPlaceholder(Type:=placeholderType, Left:=activeShape.Left, Top:=activeShape.Top, Width:=activeShape.Width + 15, Height:=activeShape.Height)
With newPlaceHolder.TextFrame
.TextRange.Font.Name = activeShape.TextFrame.TextRange.Font.Name
.TextRange.Characters.Font.Color.RGB = activeShape.TextFrame.TextRange.Characters.Font.Color.RGB
.TextRange.Font.Size = activeShape.TextFrame.TextRange.Font.Size
.TextRange.Font.Bold = activeShape.TextFrame.TextRange.Font.Bold
.TextRange.ParagraphFormat.Bullet.Type = activeShape.TextFrame.TextRange.ParagraphFormat.Bullet.Type
.TextRange.ParagraphFormat.SpaceWithin = activeShape.TextFrame.TextRange.ParagraphFormat.SpaceWithin
.TextRange.ParagraphFormat.Alignment = activeShape.TextFrame.TextRange.ParagraphFormat.Alignment
.TextRange.ParagraphFormat.SpaceBefore = activeShape.TextFrame.TextRange.ParagraphFormat.SpaceBefore
.TextRange.ParagraphFormat.SpaceAfter = activeShape.TextFrame.TextRange.ParagraphFormat.SpaceAfter
.TextRange.ParagraphFormat.BaseLineAlignment = activeShape.TextFrame.TextRange.ParagraphFormat.BaseLineAlignment
.TextRange.Text = activeShape.TextFrame.TextRange.Text
End With
With newPlaceHolder.TextFrame2
.TextRange.Font.Spacing = activeShape.TextFrame2.TextRange.Font.Spacing
End With
newPlaceHolder.ZOrder msoSendToBack
newPlaceHolder.Select
End Sub
Any improvements are welcome too.

Apply format in a paragraph with multiple fonts

In my docs I use either Arial or Courier New (for code) and sometimes both in the same paragraph. As I share my docs with other people, they tend to use other fonts as well but it is important to keep it aligned, that;s why I am trying to create a macro that will turn all non-Courier New text into Arial and into the correct font size (11).
I face 2 problems with what I have achieved so far:
In paragraphs with mixed fonts it tends to change the whole paragraph (including the code) to Arial, while i need it to change only the non-code text
It changes the font size not only in the body text but in the headings as well.
I think I'm using incorrectly the objects of Word (I'm used in working in Excel) but I can't find anywhere online any clues. Can anyone help me please?
Sub CorrectFont()
Dim p As paragraph
Set p = ActiveDocument.Paragraphs(1)
Application.Visible = False
Application.ScreenUpdating = False
Do
If p.Range.Font.Name <> "Courier New" Then
p.Range.Font.Name = "Arial"
p.Range.Font.Size = 11
End If
Set p = p.Next
Loop Until p Is Nothing
Application.ScreenUpdating = True
Application.Visible = True
End Sub
You can check each individual word, like so:
' Replaces non-Arial fonts with Arial.
' Exception: Courier New is not replaced.
Sub AlignFont()
Dim wd As Range
' Check each word, one at a time.
For Each wd In ActiveDocument.Words
If Not (wd.Font.Name = "Arial" Or wd.Font.Name = "Courier New") Then
wd.Font.Name = "Arial"
End If
Next
End Sub
Thanks to #destination-data 's inputs I reached a final form of the code. I present it here for anyone that might be interested.
Thank you again!
Sub AlignFont()
Dim wd As Range
Application.Visible = False
Application.ScreenUpdating = False
' Check each word, one at a time.
For Each wd In ActiveDocument.Words
'On objects like Contents it may create an error and crash
On Error Resume Next
If wd.Font.Name <> "Courier New" And wd.Style = "Normal" Then
wd.Font.Name = "Arial"
End If
'To avoid any header that may have a "Normal" style
If wd.Font.Bold = False Then
wd.Font.Size = 11
End If
Next
Application.ScreenUpdating = True
Application.Visible = True
End Sub

How can I set OptionButton font using VBA For Loop?

I am trying to manually set every OptionButton's font on a sheet to be a uniform size and type using a For Loop.
I can do them manually by writing out each specific button's information but I have hundreds of buttons.
I can even get VBA to write the correct syntax to a test Worksheet by using this code here:
`Private Sub Thisworkbook_Open()
For i = 1 to Worksheets("Core").OLEObjects.Count
If TypeName(Worksheets("Core").OLEObjects(i).Object) = "OptionButton" Then
Worksheets("testsheet").Range("A" & i).Value = Worksheets("Core").OLEObjects(i).Name
End If
Next i
End Sub`
But what I can't do is put the rest of this below code along with the above code to have ONE clean and concise statement that will manually set all OptionButton values to these settings:
`With Worksheets("Core").OptionButton1
.Font.Size = 11
.Font.Name = "Calibri"
.Font.Bold = False
End With`
Can someone explain to me how I can make this work?
Actually you have your answer in your question, all you have to do is to put your properties to correct location, as follows:
For i = 1 To Worksheets("Core").OLEObjects.Count
If TypeName(Worksheets("Core").OLEObjects(i).Object) = "OptionButton" Then
Worksheets("Core").OLEObjects(i).Object.FontSize = 5
' Remaining code goes here.
End If
Next i

Reading Userform Object Values

I created a Userform (manually in the VBA Projectbrowser). I have written VBA code, which fills this Userform with different Objects in runtime (Labels, Optionbuttons etc.). So far everything worked fine
The Userform is filled with data read from my Excel sheets and correctly displayed. However I'm not able to read the inputs from the objects on it (for example Optionbutton - TRUE or FALSE). These objects do not appear anywhere (except on the userform) so that I can link them and use them in another Module.
I guess they are only displayed and not really read into the memory or whatever (initialized !?).
There are two ways to go about it.
WAY 1
Declare your option button object as Public.
Module Code
Public theOpBut As Object
Sub Fill()
If theOpBut.Value = True Then
ActiveSheet.Cells(1, 5) = 1
Else
ActiveSheet.Cells(1, 5) = "NO"
End If
End Sub
Userform Code
Private Sub UserForm_Initialize()
Set theOpBut = UserForm1.Controls.Add("Forms.optionbutton.1", "OptionButton", True)
With theOpBut
.Caption = "Test Button"
'.GroupName = OpButGroupCounter
.Top = 10
.Left = 20
.Height = 16
.Width = 50
.Font.Size = 12
.Font.Name = "Ariel"
End With
End Sub
Private Sub CommandButton1_Click()
Call Fill
End Sub
WAY 2
Declare a Boolean Variable and create a click event of the Option button and then set the value of the Boolean Variable in that click event. To create the click event of the Option button at Run Time, see THIS EXAMPLE
You can then check the value of Boolean Variable in Sub Fill() and act accordingly.

Finding if a TextBox/Label caption fits in the control

The scenario is trying to adjust font size to get a nice graphic arrangement, or trying to decide where to break a caption/subtitle.
a) In XL VBA is there a way to find out whether a text on a textbox, or caption on a label, still fits the control?
b) Is there a way to know where was the text/caption broken on multiline control?
I gave this a rest, gave it enough back-of-head time (which produces far better results than "burp a non-answer ASAP, for credits"), and...
Function TextWidth(aText As String, Optional aFont As NewFont) As Single
Dim theFont As New NewFont
Dim notSeenTBox As Control
On Error Resume Next 'trap for aFont=Nothing
theFont = aFont 'try assign
If Err.Number Then 'can't use aFont because it's not instantiated/set
theFont.Name = "Tahoma"
theFont.Size = 8
theFont.Bold = False
theFont.Italic = False
End If
On Error GoTo ErrHandler
'make a TextBox, fiddle with autosize et al, retrive control width
Set notSeenTBox = UserForms(0).Controls.Add("Forms.TextBox.1", "notSeen1", False)
notSeenTBox.MultiLine = False
notSeenTBox.AutoSize = True 'the trick
notSeenTBox.Font.Name = theFont.Name
notSeenTBox.SpecialEffect = 0
notSeenTBox.Width = 0 ' otherwise we get an offset (a ""feature"" from MS)
notSeenTBox.Text = aText
TextWidth = notSeenTBox.Width
'done with it, to scrap I say
UserForms(0).Controls.Remove ("notSeen1")
Exit Function
ErrHandler:
TextWidth = -1
MsgBox "TextWidth failed: " + Err.Description
End Function
I feel I'm getting/got close to answer b), but I'll give it a second mind rest... because it works better than stating "impossible" in a flash.
I'm sure there is no way to do this with the ordinary Excel controls on the Forms toolbar, not least because (as I understand it) they are simply drawings and not full Windows controls.
The simplest approach may be to make a slightly conservative estimate of the maximum text length for each control, through a few tests, and use these to manage your line breaks.
This can be achieved by taking advantage of the label or textbox's .AutoSize feature, and looping through font sizes until you reach the one that fits best.
Public Sub ResizeTextToFit(Ctrl As MSForms.Label) 'or TextBox
Const FONT_SHRINKAGE_FACTOR As Single = 0.9 'For more accuracy, use .95 or .99
Dim OrigWidth As Single
Dim OrigHeight As Single
Dim OrigLeft As Single
Dim OrigTop As Single
With Ctrl
If .Caption = "" Then Exit Sub
.AutoSize = False
OrigWidth = .Width
OrigHeight = .Height
OrigLeft = .Left
OrigTop = .Top
Do
.AutoSize = True
If .Width <= OrigWidth And .Height <= OrigHeight Then
Exit Do 'The font is small enough now
.Font.Size = .Font.Size * FONT_SHRINKAGE_FACTOR
.AutoSize = False
Loop
.AutoSize = False
.Width = OrigWidth
.Height = OrigHeight
.Left = OrigLeft
.Top = OrigTop
End With
End Sub