Link to home
Start Free TrialLog in
Avatar of jsgreiner
jsgreiner

asked on

How do I get the find method in a Word 2003 VBA macro to look not only in the main document, but also in text boxes?

I have a macro that looks for a string.  It finds it if it is in the main document, but does not find it if it is in a text box.  How do I get the find method to look everywhere in the document -- not just in the main part of the document?  

The Find command from the Word menu (Edit -> Find) seems to be able to look everywhere.  Is the code for that available so I can see how it does it?
Avatar of GrahamSkan
GrahamSkan
Flag of United Kingdom of Great Britain and Northern Ireland image

Look through all the storyranges. This will select the first instance found:

Sub FindText()
    FindEveryWhere(ActiveDocument, "dog", True).Select
End Sub

Function FindEveryWhere(doc As Document, strText As String, bWholeWord As Boolean) As Range
    Dim rng As Range
   
    For Each rng In doc.StoryRanges
        If rng.Find.Execute(strText, , bWholeWord) Then
            Exit For
        Else
            rng.Collapse
        End If
    Next rng
    Set FindEveryWhere = rng
End Function
Avatar of jsgreiner
jsgreiner

ASKER

The macro always seems to start searching from the beginning of the document so it ONLY FINDS THE FIRST INSTANCE.  It can find the instance if it is in a header/footer, but it WOULD NOT FIND THE INSTANCE IN A TEXT BOX (which is one of the most important places for it to look for my application).  I tried some modifications as shown below, but still could not get it to work.  I would like it to work like the Edit -> Find command from the Word menu works -- except that when it is done with a story, it does not wrap.

Thanks for your help with this!!!

Sub FindEveryWhere()
    Dim rng As Range
    Dim lclfound As Boolean
   
    lclfound = False
    For Each rng In ActiveDocument.StoryRanges
        If rng.Find.Execute("9001", , , , , , True, wdFindContinue) Then
            rng.Select
            lclfound = True
            Exit For
        Else
            rng.Collapse
        End If
    Next rng
    If lclfound = False Then
        MsgBox "Strng not Found"
    End If
End Sub
I did a little more testing and found that in a new small document, the macro was able to find an instance in a text box.  But in my large complex document, it does not find an instance in a text box even though the Edit -> Find command does.

I still have the problem of it only finding the first instance instead of starting the next serch where the last one ended.
If you want to find all instances, you could do it like this:

Sub FindEveryWhere()
    Dim rng As Range
    Dim doc As Document
    Dim strText As String
    strText = "dog"
    Set doc = ActiveDocument
    For Each rng In doc.StoryRanges
        Do While rng.Find.Execute(strText, , True)
            ProcessFoundRange rng
        Loop
    Next rng
End Sub

Sub ProcessFoundRange(rng As Range)
    MsgBox "StoryType: " & rng.StoryType & ", Start: " & rng.Start
End Sub
The only problem with this is I can not stop and edit my document when someting is found and then continue the search for the next instance.  Is there a way the macro can start the search at the position of the cursor or after the current selection?  (Storing something in a registry is an option if that would be helpful to know where to start the next search.) That way I can find something, manually make whatever changes I want, and then invoke the macro again.  I know this seems a bit odd, but this is one piece of a much larger semi-automated application.
As I understand it:

You want the user to select some text by placing the cursor somewhere in the document.
You then want to start a macro to  search from that position onwards through the whole of the document.
When something is found, you want control to pass back to the user. Do you want the found text to be selected?
The user will then be able to do some editing, probably changing the selection.
You want the user to be able to restart the search.
Do you want the restart search to start from where the user has newly left the cursor, or from the last found place? Either is possible, but if it is the latter, there would need to be a way to distinguish between the start and restart requests.
Do you want the found text to be selected? YES
The user will then be able to do some editing, probably changing the selection.  YES
You want the user to be able to restart the search.  YES
Do you want the restart search to start from where the user has newly left the cursor  YES
Hello GrahamSkan -- I am extremeley interested in the answer to this one and if I could figure out how to do it, I would be happy to increase the points for it.

Thanks for all your help!!!
Thanks for the prompt. I have been working on it.
It seems that we not only have to search through each storyrange, but each textbox needs an individual search. Keeping track of that is a bit of a problem.
GrahamSkan, Thanks!  I will also need the same type of search capability in PowerPoint.  It is a different object model thatn Word.  Are you familiar with the PowerPoint object model?  If yes I have a separate question open for that.

John
GrahamSkan, Are the textboxes somehow numbered internally in Word?  If so, could you just make the macro start at the first text box if no text box is currently selected (after it is done with all the storyranges), and if a textbox is currently selected, start the search from wherever the cursor is in the text box, and then go to the next higher numbered text box to continue the search?
TextBoxes do not have an Index property (like Sections do), but they do have an index number in the Shapes collection.

The difference is that, given a Shape object, you can't look at its Index property to identify it, but you have to step through them all looking for some characteristic that will tell you that you have the one in question. I'm still working on that.

Also, I was assuming, perhaps wrongly, that you would want the earlier boxes to be searched if nothing was found in the later ones, so it would have to be, say,  boxes 5 to 10, then 1 to 4.

Ideally I would like to only search forward through the text boxes to the end of the document without wrapping back to the beginning.  But that might not work -- if the text boxes get numbered in the order they are inserted, then if I have a bunch of text boxes already in the document and add one mor at the begining of the document, the last text box would be at the beginning of the document.  Are the text boxes numbered in the order they are created?  

For my application I only really need to search the main document and the text boxes.  I don't really care that much about headers, footers, footnotes, etc.  It would be cool to be able to search them too, but I can easiloy live without that.
Sorry it took so long.

Try this and see how it fits.

Sub FindEveryWhere(Doc As Document, strText As String)
    Dim rng As Range
    Dim sh As Shape
    Dim rng2 As Range
   
    Dim iStories() As WdStoryType
    Dim iTextBoxes() As Integer
    Dim iStoryCount As Integer
    Dim stProcessing As WdStoryType
    Dim iCurrentStory As Integer
    Dim iCurrentShape As Integer
    Dim iShapeCount As Integer
    Dim iTextBoxCount As Integer
    Dim iCurrentTextBox As Integer
    Dim i As Integer
    Dim s As Integer
    Dim iSh As Integer
   
    Set rng = Selection.Range
   
    'collect available storytypes in active document
    'to avoid errors from missing types
    For Each rng2 In Doc.StoryRanges
        ReDim Preserve iStories(iStoryCount)
        iStories(iStoryCount) = rng2.StoryType
        If rng.StoryType = rng2.StoryType Then
            iCurrentStory = iStoryCount
        End If
        iStoryCount = iStoryCount + 1
    Next rng2
       
    'step through story ranges, starting with current
    For s = 0 To iStoryCount - 1
        stProcessing = (iCurrentStory + s) Mod iStoryCount
        If iStories(stProcessing) = wdTextFrameStory Then
            'Searches through a textbox don't carry on to the next, so each
            'must be  searched explicitly
            iCurrentShape = GetSelTextBox(Doc)
            For i = 1 To Doc.Shapes.Count
                If iCurrentShape = 0 Then
                    iSh = i
                Else
                    'start with current text box
                    iSh = (i + iCurrentShape - 2) Mod Doc.Shapes.Count + 1
                End If
                Set sh = Doc.Shapes(iSh)
                If sh.Type = msoTextBox Then
                    Set rng = sh.TextFrame.TextRange
                    'ensure that we haven't stepped back to start search
                    'at beginning of selection
                    If Selection.InRange(rng) Then
                        Set rng = Selection.Range
                    End If
                    If rng.Find.Execute(strText) Then
                        rng.Select
                        Exit Sub
                    End If
                End If
            Next i
        Else
            Set rng = Doc.StoryRanges(iStories(stProcessing))
            If Selection.InRange(rng) Then
                Set rng = Selection.Range
            End If
            If rng.Find.Execute(strText) Then
                rng.Select
                Exit Sub
            End If
        End If
    Next s
    'not found, look at beginning of current story
    Set rng = Selection.Range
    If rng.Find.Execute(strText, Wrap:=wdFindContinue) Then
        rng.Select
        Exit Sub
    End If
   

    MsgBox "Search Complete"
End Sub

Good morning GrahamSkan,
Please send me the GetSelTextBox function that goes with the macro.  
Thanks!
That was silly of me, considering the difficulty I had in writing it.

Function GetSelTextBox(Doc As Document) As Integer
    Dim sh As Shape
    Dim i As Integer
    For Each sh In Doc.Shapes
        i = i + 1
        If sh.Type = msoTextBox Then
            If Selection.Range.InRange(sh.TextFrame.TextRange) Then
                GetSelTextBox = i
                Exit Function
            End If
        End If
    Next sh
End Function
Grahamskan -- Almost!  
I made a simple test document with the serach string once in the main document, once in a text box, and once in a text box that is inside a drawing box.

There are two problems:
1.)  It does not find the string that is in the text box that is inside the drawing box.
2.)  After it finds the text in the text box, it wraps back and finds the text in the main document instead of saying the search is complete and no more instances were found.

I will always start my first search for a given string from the top of the main document.  After it gets finished searching the main document, it should search the rest of the stories including the text boxes and text boxes inside drawing boxes.  It should not wrap back to the beginning of the main document or to a previously found text box.  It should just say it is done searching after the last text box is searched.

Thanks for your efforts -- it does seem close to working correctly.
Not wrapping around would have been a bit easier. I'll have to simplify it a bit.

What exactly do you mean by a text box inside a drawing box? How is that achieved?
Is there a way I can send you a very small sample document that shows what I think is a text box inside a drawing box?
Yes, indeed.
You can upload a zipped file to this site: www.ee-stuff.com

You use the same credentials as this site. Pick the Experts tab. You will need to paste the URL for the question, so that it can be found again for downloading.

Tell us here that you've done it. There is no automatic notification,
It is uploaded under Q_22049255

Thanks,
John
Sorry John, I can't find it.

I tried it with the format in your comment and there was a rather inconspicuous message in the text in orange saying "Invalid Format".
There should be an even more inconspicuous message in blue saying "Your file has been uploaded". There will also be a couple of URLs to the file and the question files respectively

Try the URL or just 22049255.


GrahamSkan,  Lets try this...

Your file has successfully been uploaded!
To download the file, you must be logged into EE-Stuff. Here are two pages that will display your file, if logged in:

View all files for Question ID: 22049255
https://filedb.experts-exchange.com/incoming/ee-stuff/1349-jsg-test-2-9001.ziphttps://filedb.experts-exchange.com/incoming/ee-stuff/1338-Dummy.txt
https://filedb.experts-exchange.com/incoming/ee-stuff/1339-jsg-test-tgpp-2.zip
 

Direct link to your file
https://filedb.experts-exchange.com/incoming/ee-stuff/1339-jsg-test-tgpp-2.zip 
 
Ah the dreaded Drawing Canvas. When they find out that it's possible, most users prevent that from being automatically created.

Sub FindInMainAndTextBoxesNoWrap(Doc As Document, strText As String)
    Dim rng As Range
    Dim sh As Shape
   
    Dim iStories(1) As WdStoryType
    Dim iStoryCount As Integer
    Dim stProcessing As WdStoryType
    Dim iCurrentStory As Integer
    Dim iCurrentShape As Integer
    Dim iShape As Integer
   
    iStories(0) = wdMainTextStory
    iStories(1) = wdTextFrameStory
    Set rng = Selection.Range
    If rng.StoryType = wdTextFrameStory Then
        iCurrentStory = 1
    End If
    iStoryCount = 2
       
    'step through story ranges, starting with current
    For stProcessing = iCurrentStory To iStoryCount - 1
        If iStories(stProcessing) = wdTextFrameStory Then
            'Find in textbox doesn't continue to another, so each
            'must be  searched
            iCurrentShape = GetSelTextBox(Doc)
            For iShape = IIf(iCurrentShape = 0, 1, iCurrentShape) To Doc.Shapes.Count
                Set sh = Doc.Shapes(iShape)
                Select Case sh.Type
                    Case msoTextBox
                        Set rng = sh.TextFrame.TextRange
                        'ensure that we haven't stepped back to start search
                        'at beginning of selection
                        If Selection.InRange(rng) Then
                            Set rng = Selection.Range
                        End If
                        If rng.Find.Execute(strText) Then
                            rng.Select
                            Exit Sub
                        End If
                    Case msoCanvas
                        If sh.CanvasItems(1).Type = msoTextBox Then
                            Set rng = sh.CanvasItems(1).TextFrame.TextRange
                            If Selection.InRange(rng) Then
                                Set rng = Selection.Range
                            End If
                            If rng.Find.Execute(strText) Then
                                rng.Select
                                Exit Sub
                            End If
                        End If
                    Case Else
                     
                End Select
            Next iShape
        Else
            Set rng = Doc.StoryRanges(iStories(stProcessing))
            If Selection.InRange(rng) Then
                Set rng = Selection.Range
            End If
            If rng.Find.Execute(strText) Then
                rng.Select
                Exit Sub
            End If
        End If
    Next stProcessing
    MsgBox "Search Complete"
End Sub

GrahamSkan -- This seems to be very close!  there are just a couple of things that are not working that will hopefully be very easy for you to take care of:

1.  The macro aborts if there is an empty drawing canvas.
2.  ***With a larger document the macro quits with no message before it finds all the 9001 markers.
3.  Even if there were not changes made to the document, Word asks if you want to save the changes upon exit.

I have uploaded a document that shows the problems.  To see the empty canvas problem, delete the picture on the first page but leave the canvas on the page.  There should be 5 of the 9001 markers.  The macros you gave me are included in the document.

Big Thanks,
John
ASKER CERTIFIED SOLUTION
Avatar of GrahamSkan
GrahamSkan
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
GrahamSkan -- You are the BEST!

Thank you, it works perfectly!!!