John Snow
John Snow

Reputation: 107

VBA WORD: find a paragraph that has a specific style

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

Answers (3)

deku
deku

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

freeflow
freeflow

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.

  1. 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.

  2. 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.

http://rubberduckvba.com/

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

macropod
macropod

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

Related Questions