Reputation: 107
I am trying to write a code in VBA, where I will get the number of a paragraph that will have a specific style (let's say Heading 1). I am going through a loop and unfortunately I am getting such an error:
"Object variable or With block variable not set"
Here is my code:
Public Function FindParagraph(ByVal pStyle As String) As Integer
Dim doc As Document
Dim pNum As Integer
Set doc = ActiveDocument
For pNum = 1 To doc.Paragraphs.Count
Debug.Print pNum, doc.Paragraphs(pNum).Range.Style
If doc.Paragraphs(pNum).Range.Style = pStyle Then
FindParagraph = pNum
Exit For
End If
Next pNum
End Function
Sub DoSth()
Dim i As Integer
i = FindParagraph("Heading 1")
Debug.Print i
End Sub
Debugger shows that the problem is in this line:
pStyle = doc.Paragraphs(i).Range.Style
And actually I am looking at my Word document and it is the first line of the Table of Contents.
Do You know why is it like this?
Upvotes: 0
Views: 3481
Reputation: 105
Instead for i=1 to ....Count
try for each
loop for better efficency
Private Sub StylesCount()
Dim p As Paragraph
Dim story As Range
Dim counter As Long
For Each story In ActiveDocument.StoryRanges
For Each p In story.Paragraphs
If StrComp(p.Style, "Heading 1", vbTextCompare) = 0 Then
counter = counter + 1
End If
Next p
Next story
Debug.Print counter
End Sub
Its works for paragraph styles. For character styles you should use .find method.
Upvotes: 0
Reputation: 4355
The code you have provided does not compile. It gives an error at
While Not (IsEmpty(pStyle))
because the Method IsEmpty should only be used on a Variant type and the type you have assigned to pStyle is a String. To achieve your intent you will need to change this line to
While Not pStyle = vbNullString
Updated to provide revised function
Sub TestFindParagraph()
Dim IsFound As Boolean
IsFound = FindParagraph(ActiveDocument.StoryRanges(wdMainTextStory), "Heading 1")
End Sub
Public Function FindParagraph(ByVal SearchRange As Word.Range, ByVal ParaStyle As String) As Long
Dim ParaIndex As Long
For ParaIndex = 1 To SearchRange.Paragraphs.Count
If doc.Paragraphs(ParaIndex).Range.Style = ParaStyle Then
FindParagraph = ParaIndex
Exit Function
End If
Next
End Function
Update 2020-Apr-15 Resolving the TOC issue
The code posted by the OP and by myself both fail when the paragraph is a Table of Contents field. This failure occurs because of the default member feature of VBA Objects.
The default member of an object is a method that is called if the object instance is given without a qualifying Method. This feature can be helpful as it simplifies code, but, can lead to strange errors similar to that which we are experiencing.
In Word, Styles are objects with numerous properties and methods. The default Method of Style is NameLocal (See the object browser for Word.Style) which returns a Variant containing a string (See the type for style in the locals windows). Consequently, even though pStyle is defined as a String type, VBA coercion allows the variant/string to be assigned to the String pStyle and everything appears to be OK.
However, in the case of a TOC field it appears that Word does not return the style wrapping the TOC field, instead it returns the value of 'nothing' i.e. there is no style for a TOC. Nothing (not the same as vbNullString) cannot be assigned to a string and consequently an error occurs.
There would appear to be two solutions to the problem encountered above.
Change the code to use correct syntax for the information we require. i.e. Style.NameLocal. Unfortunately, this solution will also fail because we cannot call a method (NameLocal) on an object that is nothing.
Change the type of variable for pStyle from String to Variant. Variant types can hold objects and consequently can hold the value of nothing which is generated when a paragraph is a TOC field.
Solution 2 would appear to work fine however from a purist perspective you would have an intermediate variant variable that captures the result from Style and then tests the variant for nothing before assigning the string value or vbNullString to pStyle.
Final Update 2020-Apr-15
I was unfortunatly called away by Mrs F for some urgent jobs so forgot to add this final peice.
The Default member trap can be avoided easily. This is due to the amazing work done by the folks over at Rubberduck.
The Rubberduck addin for VBA (which is free) has, as one of its many talents, a much more rigorous code analysis (Code Inspections) of VBA. One of the inspections is to warn wherever a default member has been used in code. Rubberduck really does remove a significant amount of pain when writing VBA code as it helps you understand just exactly the assumptions you have made in your code (that you didn't realise you'd made)...
Upvotes: 2
Reputation: 13490
It is not possible for a paragraph in Word to not have a paragraph Style, so testing for whether a paragraph lacks any Style at all is pointless.
Also, looping through all paragraphs is much less efficient than using Find. For example, the following code retrieves the paragraph index # of each Heading 1:
Sub Demo()
Application.ScreenUpdating = False
Dim Rng As Range, i As Long
With ActiveDocument.Range
Set Rng = .Duplicate
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = ""
.Replacement.Text = ""
.Style = wdStyleHeading1
.Format = True
.Forward = True
.Wrap = wdFindStop
.MatchWildcards = False
.Execute
End With
Do While .Find.Found
i = i + 1
Rng.End = .Duplicate.End
MsgBox Rng.Paragraphs.Count
'The next If ... End If block is only needed if the Found content might be in a table
If .Information(wdWithInTable) = True Then
If .End = .Cells(1).Range.End - 1 Then
.End = .Cells(1).Range.End
.Collapse wdCollapseEnd
If .Information(wdAtEndOfRowMarker) = True Then
.End = .End + 1
End If
End If
End If
'The next line is only needed if the Found content might include the document's final paragraph break
If .End = ActiveDocument.Range.End Then Exit Do
.Collapse wdCollapseEnd
.Find.Execute
Loop
End With
Application.ScreenUpdating = True
MsgBox i & " instances found."
End Sub
More code, but much quicker.
Upvotes: 2