• More troubles with tables (Word 2002 SP-2)

    Home » Forums » AskWoody support » Productivity software by function » MS Word and word processing help » More troubles with tables (Word 2002 SP-2)

    Author
    Topic
    #417292

    I’m getting consistent bugginess trying to search for the Normal style using a Range if (1) there’s a table in the document, and (2) either there are no Normal paragraphs in the document or the only Normal paragraphs precede the table.

    To take a simple example: If I create a new document consisting of nothing but a handful of Body Text paragraphs and a table (with the table positioned several paragraphs down from the start of the document), and then I run the following code:

        Dim rngX As Word.Range
        Dim lngCount As Long
    
        Set rngX = ActiveDocument.Range(0, 0)
        With rngX.Find
            .Text = ""
            .Style = "Normal"
            .Format = True
        End With
        With rngX
            Do While .Find.Execute = True
                lngCount = lngCount + 1
                Debug.Print "Found at " & .Start & ", " & .End
                .Collapse wdCollapseEnd
                If lngCount = 3 Then
                    Exit Do
                End If
            Loop
        End With
    
        Set rngX = Nothing
    

    the result in the Immediate Window is:

      Found at 0, 0
      Found at 0, 0
      Found at 0, 0
    

    Somehow .Find.Execute is returning True and yet rngX isn’t moving forward to any found target. The code would loop endlessly if not for the “If lngCount = 3” exit line. And what’s worse: If I set up the code so that it’s replacing Normal with another style, Word tends to crash. (During one series of tests, it was pretty consistently crashing the 2nd time I ran the macro — as if the first execution somehow put the document (or Word) in an unstable state, after which the 2nd execution delivered the knockout punch.)

    Some further experimentation involving the Selection object leads me to the conclusion that it’s the end-of-row-markers in the table that are causing the problem. They’re “Normal” style but they confuse Range.Find objects.

    Further note: If I change one of the paragraphs after the table to Normal style and then run the code, it works perfectly. (Likewise if I start the .Find with the Range already located after the table.)

    I realize one possible “solution” is to just make sure none of my documents ever have any “Normal” paragraphs (not counting end-of-row-markers), so I never have any reason to run a procedure that searches for the Normal style. Perhaps another “solution” would be to use the Selection object, although I don’t really know that it’s not also subject to some bugginess in this area.

    Is there a more direct solution — i.e., a way to prevent the end-of-row markers from screwing up the Range.Find object?

    Viewing 0 reply threads
    Author
    Replies
    • #935907

      You could check whether the range actually moves:

      Sub Test()
      Dim rngX As Word.Range
      Dim lngCount As Long
      Dim lngStart As Long

      Set rngX = ActiveDocument.Range(0, 0)
      With rngX.Find
      .Text = “”
      .Style = wdStyleNormal
      .Format = True
      End With
      With rngX
      lngStart = .Start
      Do While .Find.Execute = True
      lngCount = lngCount + 1
      Debug.Print “Found at ” & .Start & “, ” & .End
      .Collapse wdCollapseEnd
      If .Start = lngStart Then
      Exit Sub
      Else
      lngStart = .Start
      End If
      Loop
      End With

      Set rngX = Nothing
      End Sub

      (I replaced “Normal” with wdStyleNormal – on my Dutch language system “Normal” has a different name). You’d still have to build in a check for the very first “find”.

      • #935936

        Thanks for the suggestion. Here’s an alternative that occurred to me that takes care of the initial “false positive” as well:

            Dim rngX As Word.Range
            Dim fSearching4Normal As Boolean
        
            Set rngX = ActiveDocument.Range(0, 0)
            With rngX.Find
                .Text = ""
                .Style = "Normal"
                .Format = True
            End With
            If rngX.Find.Style = "Normal" Then
                fSearching4Normal = True
            End If
            With rngX
                Do While .Find.Execute = True
                    If fSearching4Normal = True Then
                        If .Style  "Normal" Then
                            Exit Do
                        End If
                    End If
                    'Do something here.
                    .Collapse wdCollapseEnd
                Loop
            End With
        
            Set rngX = Nothing

        Another alternative would be to dispense with the .Find object entirely and iterate through the document’s paragraphs, skipping end-of-row markers, like this:

            Dim parX As Word.Paragraph
            Dim rngTest As Word.Range
        
            For Each parX In ActiveDocument.Paragraphs
                With parX
                    If .Style = "Normal" Then
                        If .Range.Information(wdWithInTable) = True Then
                            Set rngTest = .Range.Duplicate
                            rngTest.Collapse wdCollapseStart
                            If rngTest.Information(wdAtEndOfRowMarker) = True Then
                                GoTo Skipped
                            End If
                        End If
                        'Do something here.
                    End If
                End With
        Skipped:
            Next parX
        
            Set parX = Nothing
            Set rngTest = Nothing

        Side question: I realize many professional programmers would scoff at my “GoTo Skipped” construction, but is there a non-clunky alternative? As I see it, the “clunky” alternatives would include (1) putting the “Do something here” code in 2 places, (2) putting the “Do somthing here” code in a separate procedure and calling it in 2 places, (3) combining the two .Information If tests in a single line (but that means a lot of wasted processing, since the rngTest stuff will then run even when you’re not in a table — and yes, I realize that, in this particular case, the first If test would then be redundant), and (4) adding a fDoIt boolean variable that’s reset to True at the start of each loop, gets set to False in the EndOfRowMarker context, and controls whether the “Do something here” code runs. For some strange reason, I seem to prefer the GoTo approach, but maybe I’m missing an approach.

        EDITED TO ADD: I should also have mentioned the GoSub alternative, but my understanding is (1) that those that scoffs at GoTo also scoffs at GoSub, and (2) GoSub won’t survive the move to .NET.

        • #935973

          Hi Steve,

          You seem to be looking for input/suggestions, so here’s my 2 cents:

          You’re spot on with your critique of the Find object. It’s great in some cases, but it’s very difficult to corral in complex documents. Considering how long it can take to tweak the code when using Find in a complex test, I almost always turn to a For..Each loop first, and optimize that. 9 times out of 10, the performance is more than acceptable, without the risk of infinite loops. When it’s still too slow after optimizing, that’s when I’ll take the time to put together code using Find. (I am of course generalizing here, some situations are best suited for Find, and the code can sometimes be quite simple.)

          I also think you’ve pretty much summed up the possibilities for handling this sort of condition. Many other languages have some sort of “Next” construct that allows you to skip to the next iteration in this kind of loop, which is perfect for this sort of situation. That, and variable interpolation are the things I miss most when using VBA.

          You said you didn’t like one of the alternatives because it meant some wasted processing, and that’s a very valid point. But I’d actually come to a different conclusion when looking at the options you described, relating to your code, specifically.

          First of all, evaluating a paragraph to see if it’s actually the end of a table row sounds like a very useful thing to do, and would likely come up again in other macros. I’d break it out into a re-usable function.

          Second, the test you’re using to deterimine if a paragraph is the end of a row is expensive. Duplicating and collapsing ranges repeatedly like that can really slow things down. I’ve suggested an alternate method, shown in the function IsParaEndOfRow shown below.

          Combining these two points, the following is the code I’d use in your situation. It’s about the same number of total lines, but is also re-usable. It’s also substantially faster. On a 500 page document, your macro took 2:38, and the one below, just 9 seconds.

          Sub DoStuffToNormalParas()
          Dim para As Paragraph
          For Each para In ActiveDocument.Paragraphs
              If para.Style = wdStyleNormal Then
                  If Not IsParaEndOfRow(para) Then
                      ' do stuff here
                  End If
              End If
          Next para
          End Sub
          '
          Function IsParaEndOfRow(para As Paragraph) As Boolean
          If para.Range.Information(wdWithInTable) = True Then
              If para.Range.Cells.Count = 0 Then
                  IsParaEndOfRow = True
                  Exit Function
              End If
          End If
          IsParaEndOfRow = False
          End Function
          

          I haven’t exhaustively tested this code, and I welcome any suggestions or feedback.

          Cheers!

          • #935982

            cheers Identifying an EndOfRowMarker by determining (1) that you’re in a table and (2) the .Cells.Count is 0 is a great idea (especially since it’s so much faster than the alternative), and I’ve tucked IsParaEndOfRow away in my macro library.

            Thanks for all your input. I’ll definitely keep the object-iteration approach in mind as (at least) a backup any time a Find loop seems to be unavoidably misbehaving.

          • #954386

            > your macro took 2:38, and the one below, just 9 seconds

            Hi Andrew. Is this what you meant? Is your macro significantly slower than Steve’s?

            Steve led me here after reading my post.

            • #956496

              No, mine is significantly faster than Steve’s, as it uses a less expensive test for the end-of-row condition.

    Viewing 0 reply threads
    Reply To: Reply #935973 in More troubles with tables (Word 2002 SP-2)

    You can use BBCodes to format your content.
    Your account can't use all available BBCodes, they will be stripped before saving.

    Your information:




    Cancel