Word VBA to add line the length of selection - vba

I would like to write a Word VBA macro that inserts a vertical line the length of the selected text.
apos = Int(Selection.Information(6))
Set aLine = ActiveDocument.Shapes.AddLine(26, apos, 26, apos + 40)
aLine.Select
With Selection
.ShapeRange.Line.Weight = 3#
.ShapeRange.Line.Visible = msoTrue
.ShapeRange.Line.Style = msoLineSingle
.ShapeRange.Line.ForeColor.RGB = RGB(255, 0, 0)
End With
But that code adds the vertical line length of "40"
How do I adjust the length "40" to be the length of the selected text?
Thank you

Use exactly the same method by which you have determined the beginning of the line. The end is at the Information(wdHorizontalPositionRelativeToPage) of the last character in the Selection + 1. Here is the complete code.
Private Sub LineUnderSelection()
' 08 May 2017
Dim Rng As Range
Dim FontHeight As Single, ParaSpace As Single
Dim LineStart As Single, LineEnd As Single
With Selection
With .Range
Do While Asc(.Text) < 48
' remove excluded characters at start
.MoveEnd wdCharacter, 1
Loop
Do While Asc(Right(.Text, 1)) < 48
' remove excluded characters at end
.MoveEnd wdCharacter, -1
Loop
LineStart = .Information(wdHorizontalPositionRelativeToPage)
Set Rng = Selection.Range
Rng.SetRange .End, .End
FontHeight = Int(Rng.Font.Size)
ParaSpace = 2 - Rng.Paragraphs(1).SpaceBefore
If ParaSpace < -3 Then ParaSpace = -3
LineEnd = Rng.Information(wdHorizontalPositionRelativeToPage)
SetLine ActiveDocument, "Underscore", LineStart, LineEnd - LineStart, _
.Information(wdVerticalPositionRelativeToPage) _
+ FontHeight + ParaSpace, 1.5, vbRed
End With
End With
End Sub
As you see, I found out that the extra character isn't needed. Word extends the line to the end of the character automatically. In the process of finding this out I also discovered that Word doesn't like to underline returns. Therefore the code excludes all characters with an ASCII code of less than 48 (represents the character 1). I then applied the same rule to leading characters, likewise removing them from the selection. Please run your own tests if this is enough or too much. There are lots of characters with a code > 128 which might be offensive.
The code takes the size of the last character and adds its height to the vertical position. This is to place the line under the selected text, not above it. I added 2 points to keep a little space between the text and the line.
Word takes note of space before. Your selection might contain several paragraphs. My code only looks at the paragraph of which the last character is a member. Word seems to place the line about 3 points lower if there is SpaceBefore in the paragraph's format, almost regardless of how big that space is. But if the space is smaller than 3pt the line will be lowered correspondingly less. This examination led to this code.
ParaSpace = 2 - Rng.Paragraphs(1).SpaceBefore
If ParaSpace < -3 Then ParaSpace = -3
You may like to amend this code to place the line more precisely. You will see that the vertical position consists of the position of the selection + FondtSize + ParaSpacing.
All of the above code creates the parameters which are fed to another sub which creates the actual line. Observe that the parameters include the line width and setting the Activedocument as target and giving a name to the line. It is possible to give the same name repeatedly. Word will use its own names in additon, and they are unique. Here is the code that inserts the line. (You may prefer to make it Private)
Function SetLine(Story As Object, _
Lname As String, _
Lleft As Single, _
Llength As Single, _
Ltop As Single, _
Lwidth As Single, _
Lcol As Long) As Shape
' 20 Aug 2016
Dim Fun As Shape
Set Fun = Story.Shapes.AddLine(Lleft, Ltop, Lleft + Llength, Ltop)
With Fun
.Title = Lname
.Name = Lname
.LockAspectRatio = msoTrue
With .Line
.Weight = Lwidth
.ForeColor = Lcol
End With
.RelativeHorizontalPosition = wdRelativeHorizontalPositionPage
.RelativeVerticalPosition = wdRelativeVerticalPositionPage
.Visible = msoTrue
.WrapFormat.AllowOverlap = msoTrue
.LayoutInCell = msoFalse
.ZOrder msoSendBehindText
.LockAnchor = msoTrue
End With
Set SetLine = Fun
End Function
This code includes a lot of parameters which are not variable by means of the arguments it receives, such as LockAnchor, ZOrder etc. You may wish to change these to better meet your requirements.

Related

Character Spacing in powerpoint vba

I would like to change the character spacing to the selected text from the paragraph. But my below code change the character spacing for the entire text box. Can anyone help me with this.
For e.g., I just need to increase the character spacing only for the selected text from the entire sentence. Shown in the below picture
ActiveWindow.Selection.ShapeRange.TextFrame2.TextRange.Font.Spacing = 3
Also below code can help me with my purpose. But here the issue is, how can i get the 20 and 45 from the macro.
ActiveWindow.Selection.ShapeRange.TextFrame2.TextRange.Characters(20, 45).Font.Spacing = 3
Your code:
ActiveWindow.Selection.ShapeRange.TextFrame2.TextRange.Font.Spacing = 3
refers to the entire text range of the selected shape. Instead, use:
ActiveWindow.Selection.TextRange2.Font.Spacing = 3
This will act only on the selected text.
You will need to loop over the string and check the character font format for each letter, marking the beginning and end of any bold section of text, then use those positions to apply the spacing:
Dim shp As Shape, i As Long, pStart As Long, n As Long
Set shp = ActivePresentation.Slides(1).Shapes(1)
pStart = 0
With ActiveWindow.Selection.ShapeRange.TextFrame2.TextRange
n = .Characters.Count
For i = 1 To n
If .Characters(i, 1).Font.Bold Then
If pStart = 0 Then
pStart = i
ElseIf pStart > 0 And i = n Then 'ending on bold text
.Characters(pStart, i - pStart).Font.Spacing = 3
End If
Else
If pStart > 0 Then 'transitioning to non-bold
.Characters(pStart, i - pStart).Font.Spacing = 3
pStart = 0
End If
End If
Next i
End With

Modifying format of individual characters per cell per range based on capitalization. Excel random errors

Trying to make an Acronym list for work. First column list the acronym. Second column spells out the acronym while keeping the main components capitalized.
Ex. | POC | Point Of Contact |
Goal is to format the capitalized characters for easier viewing by making them bold, increasing the size, and changing the color to red.
Ex. | POC | Point Of Contact | ------------imagine letters are red and bigger
Since I have 1,000+ acronyms to deal with I created a VBA code to check each character per cell and format the correct ones. Below you can find my code.
Excel can handle some phrases fine while choking then crashing on others. I've tried to check for patterns as to why without any luck.
Other times Excel will act in unpredictable ways such as duplicating the leading letter or highlighting the rest of the phrase red. When comparing the text value in the formula bar vs what's visible in the cell, one can see a difference
Example of error
These troubled cells have a tendency to corrupt the file if saved and re-opened.
Is there something inherently wrong with my code, or is Excel just being buggy for some reason? Would there be a different way to do this without causing excel to have bugs and corrupt the file?
UPDATE: Another Example of Error Running the suggested code
Sub Acronym_List_Formatting()
Dim cll As Range
Dim i As Long
Dim q As Integer
Dim Char As String
Dim UChar As String
Dim Phrase() As String
q = Application.InputBox("Set the base font size", Default:=12, Type:=1)
'| Set initial formatting of everything |'
With Selection.Font
.Name = "Calibri"
.Size = q
.Bold = False
.Color = vbBlack
End With
'| Main Code |'
For Each cll In Selection
ReDim Phrase(Len(cll.Value))
For i = 1 To Len(cll.Value)
Char = Mid$(cll.Value, i, 1)
UChar = UCase$(Char)
Phrase(i) = Char
If Asc(UChar) >= 65 And Asc(UChar) <= 90 Then '|Asc returns the ASCII value ; Continues only if character is a letter|'
If Char = UChar Then
With cll.Characters(i, 1).Font
.Bold = True
.Size = .Size + 1.5
.Color = vbRed
End With
End If
End If
Next i
'Debug.Print "Phrase: " & Join(Phrase)
MsgBox ("Phrase: " & Join(Phrase, ""))
Next cll
End Sub
UPDATE(2): An excerpt of my data for testing
Amcom[aviation and missile command] Engineering Directorate
c2BmC[command and control, battle management and communication] element lead
Bmds[ballistic missile defense system] Opir[overhead persistent infrared] Architecture
Jtids[joint tactical information distribution system] Interface Control
Nato[north atlantic treaty organization] General Communications System
Osf[objective simulation framework] Public Interface
Patriot[phased array tracking radar intercept on target] Advanced Capability 3 SIMulation
Patriot[phased array tracking radar intercept on target] Anti‐Cruise Missile
Patriot[phased array tracking radar intercept on target] Conduct Of Fire Trainer
RW[] Integrated ToolSet
Sm‐3[standard missile‐3] Cooperative Development
SPAWAR[Space & Naval Warfare Systems Command] Systems Center PACIFIC
THaad[terminal high altitude area defense] element lead
If you only need to identify and format upper case letters you can use this:
Option Explicit
Public Sub AcronymListFormatting()
Dim fntSz As Variant, cll As Range, i As Long, char As String
fntSz = Application.InputBox("Set the base font size", Default:=12, Type:=1)
If fntSz <> False And fntSz > 7 Then 'validate user input and Cancel
Application.ScreenUpdating = False
With Selection.Font
.Name = "Calibri"
.Size = fntSz
.Bold = False
.Color = vbBlack
End With
For Each cll In Selection.Cells
For i = 1 To Len(cll.Value2)
char = Mid$(cll.Value2, i, 1)
If Asc(char) >= 65 And Asc(char) <= 90 Then 'A-Z = 65-90, a-z = 97-122
With cll.Characters(i, 1).Font
.Bold = True
.Size = .Size + 1.5
.Color = vbRed
End With
End If
Next
Next
Application.ScreenUpdating = True
End If
End Sub
To convert to proper case:
cll.Value2 = WorksheetFunction.Proper(cll.Value2)
or
cll.Value2 = StrConv(cll.Value2, vbProperCase)
Edit 1
Testing with new data:
Edit 2
The issues (random errors) were caused by corrupt text imported from external file, as seen in P. McInturff's comment bellow

Visual Basic excel, How to ask for letter colors

I want to ask for a letter color in an If conditional:
string="asdfghjkl"
for i=1 to len(string)
letter = mid(string, i, 1)
input_letter = inputbox("Write a letter")
if letter = input_letter 'and letter.Font.Color = RGB(31,78,120)
'my code here
endif
next
The and letter.Font.Color = RGB(31,78,120) is not working. It says i need an object.
Is there any similar way to ask this? This RGB color is blue, and I am using this code to transform the entire sentence to blue (with the record macro excel setting)
With Selection.Font
.ThemeColor = xlThemeColorAccent1
.TintAndShade = -0.499984740745262
End With
Thanks
Regarding your question's problem:
The .Font.Color is a property of the class Range, but in your line of code:
if letter = input_letter 'and letter.Font.Color = RGB(31,78,120)
... you're trying to access this property in the variable letter, which is a String (you don't explicitly declare it as such, but it gets automatically declared when you execute letter = mid(string, i, 1) just above).
That is why you get an Object required exception: you're trying to access the property .Font.Color on something that is not a Range object (actually, not an Object at all).
Regarding your real need:
I'm not sure to understand what you're trying to do. Are you trying to reach a multi-colored text into a single cell in Excel? If I've got it right, you'll have a string:
string="asdfghjkl"
(please note: you can't call your variable String, that's a reserved keyword for the code. Think of calling it something else, though I guess you already do that in your real code or you wouldn't be able to execute it at all).
... and, for each letter of that string,
for i=1 to len(string)
... you want the user to give you a color. In that case, you can't do it in Excel. If not that, could you please express better your real need?
The code below comes closest to your OP logic and comment using the .Characters property of a cell Range (B11) containing your string value:
Code
Option Explicit
Sub test()
Dim blue As Long: blue = RGB(31, 78, 120)
Dim s As String: s = "asdfgh"
Dim letter As String
Dim input_letter As String
Dim i As Integer
Dim rng As Range
Set rng = ThisWorkbook.Worksheets("MySheet").Range("B11")
With rng
.Value = s
' color whole string
.Characters(1, Len(s)).Font.Color = blue
For i = 1 To Len(s)
letter = Mid(s, i, 1)
input_letter = InputBox("Write a letter")
If letter = input_letter And .Characters(i, 1).Font.Color = blue Then
'color found character
.Characters(i, 1).Font.Color = vbWhite
ElseIf input_letter = "" Then Exit For
End If
Next
End With
End Sub
Notes
Always use Option Explicitin your modules declaration head. So you would see that String isn't allowed as variable name as it's a function.
The extra color check in the If condition seems redundant, as characters so long a r e blue.
You seem to prefer repeated InputBoxes within the For - Next loop, could be reduced to a single call.

relative length of line in word

I want to calculate the length of the line, relative to a full line, using VBA. I mean that the last line in the paragraph (when the text is justified) is not full, so I want to calculate the percent that the text fills out of a full line.
I want to calculate the physical size, not the number of characters.
I found that question here, but anyone actually answered...
This is not straight-forward in Word since a "line" is dynamic - it breaks wherever Word thinks it should when it lays out the page. Therefore, only way to determine a "line" is to use the Selection object.
Sub LengthOfLine()
Dim sel As word.Selection
Dim pgSetup As word.PageSetup
Dim iStart, iEnd As Long, dblWidth As Double
Dim dblLineLen As Double
Set pgSetup = sel.Sections(1).PageSetup
dblWidth = pgSetup.PageWidth - pgSetup.LeftMargin - pgSetup.RightMargin
Set sel = Selection
'Get to the front of the line and determine its position
sel.MoveEnd wdLine, -1
iStart = sel.Information(wdHorizontalPositionRelativeToPage)
'Get to the end of the line and determine its position
sel.MoveStart wdLine, 1
sel.MoveEnd wdCharacter, -1
iEnd = sel.Information(wdHorizontalPositionRelativeToPage)
'Calculate the length of the line
dblLineLen = PointsToCentimeters(iEnd - iStart)
Debug.Print "line length: " & dblLineLen
Debug.Print "line space remaining: " & PointsToCentimeters(dblWidth) - dblLineLen
End Sub

fast way to copy formatting in excel

I have two bits of code. First a standard copy paste from cell A to cell B
Sheets(sheet_).Cells(x, 1).Copy Destination:=Sheets("Output").Cells(startrow, 2)
I can do almost the same using
Sheets("Output").Cells(startrow, 2) = Sheets(sheet_).Cells(x, 1)
Now this second method is much faster, avoiding copying to clipboard and pasting again. However it does not copy across the formatting as the first method does. The Second version is almost instant to copy 500 lines, while the first method adds about 5 seconds to the time. And the final version could be upwards of 5000 cells.
So my question can the second line be altered to included the cell formatting (mainly font colour) while still staying fast.
Ideally I would like to be able to copy the cell values to a array/list along with the font formatting so I can do further sorting and operations on them before I "paste" them back on to the worksheet..
So my ideal solution would be some thing like
for x = 0 to 5000
array(x) = Sheets(sheet_).Cells(x, 1) 'including formatting
next
for x = 0 to 5000
Sheets("Output").Cells(x, 1)
next
is it possible to use RTF strings in VBA or is that only possible in vb.net, etc.
Answer*
Just to see how my origianl method and new method compar, here are the results or before and after
New code = 65msec
Sheets("Output").Cells(startrow, 2) = Sheets(sheet_).Cells(x, 1)
Sheets("Output").Range("B" & startrow).Font.ColorIndex = Sheets(sheet_).Range("A" & x).Font.ColorIndex 'copy font colour as well
Old code = 1296msec
'Sheets("Output").Cells(startrow, 2).Value = Sheets(sheet_).Cells(x, 1)
'Sheets(sheet_).Cells(x, 1).Copy
'Sheets("Output").Cells(startrow, 2).PasteSpecial (xlPasteFormats)
'Application.CutCopyMode = False
You could have simply used Range("x1").value(11)
something like below:
Sheets("Output").Range("$A$1:$A$500").value(11) = Sheets(sheet_).Range("$A$1:$A$500").value(11)
range has default property "Value" plus value can have 3 optional orguments 10,11,12.
11 is what you need to tansfer both value and formats. It doesn't use clipboard so it is faster.- Durgesh
For me, you can't. But if that suits your needs, you could have speed and formatting by copying the whole range at once, instead of looping:
range("B2:B5002").Copy Destination:=Sheets("Output").Cells(startrow, 2)
And, by the way, you can build a custom range string, like Range("B2:B4, B6, B11:B18")
edit: if your source is "sparse", can't you just format the destination at once when the copy is finished ?
Remember that when you write:
MyArray = Range("A1:A5000")
you are really writing
MyArray = Range("A1:A5000").Value
You can also use names:
MyArray = Names("MyWSTable").RefersToRange.Value
But Value is not the only property of Range. I have used:
MyArray = Range("A1:A5000").NumberFormat
I doubt
MyArray = Range("A1:A5000").Font
would work but I would expect
MyArray = Range("A1:A5000").Font.Bold
to work.
I do not know what formats you want to copy so you will have to try.
However, I must add that when you copy and paste a large range, it is not as much slower than doing it via an array as we all thought.
Post Edit information
Having posted the above I tried by own advice. My experiments with copying Font.Color and Font.Bold to an array have failed.
Of the following statements, the second would fail with a type mismatch:
ValueArray = .Range("A1:T5000").Value
ColourArray = .Range("A1:T5000").Font.Color
ValueArray must be of type variant. I tried both variant and long for ColourArray without success.
I filled ColourArray with values and tried the following statement:
.Range("A1:T5000").Font.Color = ColourArray
The entire range would be coloured according to the first element of ColourArray and then Excel looped consuming about 45% of the processor time until I terminated it with the Task Manager.
There is a time penalty associated with switching between worksheets but recent questions about macro duration have caused everyone to review our belief that working via arrays was substantially quicker.
I constructed an experiment that broadly reflects your requirement. I filled worksheet Time1 with 5000 rows of 20 cells which were selectively formatted as: bold, italic, underline, subscript, bordered, red, green, blue, brown, yellow and gray-80%.
With version 1, I copied every 7th cells from worksheet "Time1" to worksheet "Time2" using copy.
With version 2, I copied every 7th cells from worksheet "Time1" to worksheet "Time2" by copying the value and the colour via an array.
With version 3, I copied every 7th cells from worksheet "Time1" to worksheet "Time2" by copying the formula and the colour via an array.
Version 1 took an average of 12.43 seconds, version 2 took an average of 1.47 seconds while version 3 took an average of 1.83 seconds. Version 1 copied formulae and all formatting, version 2 copied values and colour while version 3 copied formulae and colour. With versions 1 and 2 you could add bold and italic, say, and still have some time in hand. However, I am not sure it would be worth the bother given that copying 21,300 values only takes 12 seconds.
** Code for Version 1**
I do not think this code includes anything that needs an explanation. Respond with a comment if I am wrong and I will fix.
Sub SelectionCopyAndPaste()
Dim ColDestCrnt As Integer
Dim ColSrcCrnt As Integer
Dim NumSelect As Long
Dim RowDestCrnt As Integer
Dim RowSrcCrnt As Integer
Dim StartTime As Single
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
NumSelect = 1
ColDestCrnt = 1
RowDestCrnt = 1
With Sheets("Time2")
.Range("A1:T715").EntireRow.Delete
End With
StartTime = Timer
Do While True
ColSrcCrnt = (NumSelect Mod 20) + 1
RowSrcCrnt = (NumSelect - ColSrcCrnt) / 20 + 1
If RowSrcCrnt > 5000 Then
Exit Do
End If
Sheets("Time1").Cells(RowSrcCrnt, ColSrcCrnt).Copy _
Destination:=Sheets("Time2").Cells(RowDestCrnt, ColDestCrnt)
If ColDestCrnt = 20 Then
ColDestCrnt = 1
RowDestCrnt = RowDestCrnt + 1
Else
ColDestCrnt = ColDestCrnt + 1
End If
NumSelect = NumSelect + 7
Loop
Debug.Print Timer - StartTime
' Average 12.43 secs
Application.Calculation = xlCalculationAutomatic
End Sub
** Code for Versions 2 and 3**
The User type definition must be placed before any subroutine in the module. The code works through the source worksheet copying values or formulae and colours to the next element of the array. Once selection has been completed, it copies the collected information to the destination worksheet. This avoids switching between worksheets more than is essential.
Type ValueDtl
Value As String
Colour As Long
End Type
Sub SelectionViaArray()
Dim ColDestCrnt As Integer
Dim ColSrcCrnt As Integer
Dim InxVLCrnt As Integer
Dim InxVLCrntMax As Integer
Dim NumSelect As Long
Dim RowDestCrnt As Integer
Dim RowSrcCrnt As Integer
Dim StartTime As Single
Dim ValueList() As ValueDtl
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
' I have sized the array to more than I expect to require because ReDim
' Preserve is expensive. However, I will resize if I fill the array.
' For my experiment I know exactly how many elements I need but that
' might not be true for you.
ReDim ValueList(1 To 25000)
NumSelect = 1
ColDestCrnt = 1
RowDestCrnt = 1
InxVLCrntMax = 0 ' Last used element in ValueList.
With Sheets("Time2")
.Range("A1:T715").EntireRow.Delete
End With
StartTime = Timer
With Sheets("Time1")
Do While True
ColSrcCrnt = (NumSelect Mod 20) + 1
RowSrcCrnt = (NumSelect - ColSrcCrnt) / 20 + 1
If RowSrcCrnt > 5000 Then
Exit Do
End If
InxVLCrntMax = InxVLCrntMax + 1
If InxVLCrntMax > UBound(ValueList) Then
' Resize array if it has been filled
ReDim Preserve ValueList(1 To UBound(ValueList) + 1000)
End If
With .Cells(RowSrcCrnt, ColSrcCrnt)
ValueList(InxVLCrntMax).Value = .Value ' Version 2
ValueList(InxVLCrntMax).Value = .Formula ' Version 3
ValueList(InxVLCrntMax).Colour = .Font.Color
End With
NumSelect = NumSelect + 7
Loop
End With
With Sheets("Time2")
For InxVLCrnt = 1 To InxVLCrntMax
With .Cells(RowDestCrnt, ColDestCrnt)
.Value = ValueList(InxVLCrnt).Value ' Version 2
.Formula = ValueList(InxVLCrnt).Value ' Version 3
.Font.Color = ValueList(InxVLCrnt).Colour
End With
If ColDestCrnt = 20 Then
ColDestCrnt = 1
RowDestCrnt = RowDestCrnt + 1
Else
ColDestCrnt = ColDestCrnt + 1
End If
Next
End With
Debug.Print Timer - StartTime
' Version 2 average 1.47 secs
' Version 3 average 1.83 secs
Application.Calculation = xlCalculationAutomatic
End Sub
Just use the NumberFormat property after the Value property:
In this example the Ranges are defined using variables called ColLetter and SheetRow and this comes from a for-next loop using the integer i, but they might be ordinary defined ranges of course.
TransferSheet.Range(ColLetter & SheetRow).Value = Range(ColLetter & i).Value
TransferSheet.Range(ColLetter & SheetRow).NumberFormat = Range(ColLetter & i).NumberFormat
Does:
Set Sheets("Output").Range("$A$1:$A$500") = Sheets(sheet_).Range("$A$1:$A$500")
...work? (I don't have Excel in front of me, so can't test.)