Solved

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?

Posted on 2006-11-04
27
528 Views
Last Modified: 2016-08-29
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?
0
Comment
Question by:jsgreiner
  • 15
  • 12
27 Comments
 
LVL 76

Expert Comment

by:GrahamSkan
Comment Utility
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
0
 

Author Comment

by:jsgreiner
Comment Utility
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
0
 

Author Comment

by:jsgreiner
Comment Utility
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.
0
 
LVL 76

Expert Comment

by:GrahamSkan
Comment Utility
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
0
 

Author Comment

by:jsgreiner
Comment Utility
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.
0
 
LVL 76

Expert Comment

by:GrahamSkan
Comment Utility
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.
0
 

Author Comment

by:jsgreiner
Comment Utility
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
0
 

Author Comment

by:jsgreiner
Comment Utility
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!!!
0
 
LVL 76

Expert Comment

by:GrahamSkan
Comment Utility
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.
0
 

Author Comment

by:jsgreiner
Comment Utility
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
0
 

Author Comment

by:jsgreiner
Comment Utility
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?
0
 
LVL 76

Expert Comment

by:GrahamSkan
Comment Utility
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.

0
 

Author Comment

by:jsgreiner
Comment Utility
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.
0
What Security Threats Are You Missing?

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

 
LVL 76

Expert Comment

by:GrahamSkan
Comment Utility
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

0
 

Author Comment

by:jsgreiner
Comment Utility
Good morning GrahamSkan,
Please send me the GetSelTextBox function that goes with the macro.  
Thanks!
0
 
LVL 76

Expert Comment

by:GrahamSkan
Comment Utility
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
0
 

Author Comment

by:jsgreiner
Comment Utility
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.
0
 
LVL 76

Expert Comment

by:GrahamSkan
Comment Utility
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?
0
 

Author Comment

by:jsgreiner
Comment Utility
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?
0
 
LVL 76

Expert Comment

by:GrahamSkan
Comment Utility
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,
0
 

Author Comment

by:jsgreiner
Comment Utility
It is uploaded under Q_22049255

Thanks,
John
0
 
LVL 76

Expert Comment

by:GrahamSkan
Comment Utility
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.


0
 

Author Comment

by:jsgreiner
Comment Utility
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
 
0
 
LVL 76

Expert Comment

by:GrahamSkan
Comment Utility
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

0
 

Author Comment

by:jsgreiner
Comment Utility
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
0
 
LVL 76

Accepted Solution

by:
GrahamSkan earned 500 total points
Comment Utility
There seems to be a bug or two.

1. Is easily fixed by checking that the canvas isn't empty.
2. Works OK if the code is stepped through. The problem is in the process of selecting the text after it is found. I've put the selecting in a separate procedure to be called a second later. You may wnat to adjust that delay period up or down, but it works OK for me.
3. Nearly every examination of the shapes objects dirties the document, so the status is now saved at the start of the procedure and restored before exiting.

Option Explicit
Public gRange As Range

Sub DoSelect()
    gRange.Select
End Sub

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
    Dim iShapeCount As Integer
    Dim bSaved As Boolean
   
    iStories(0) = wdMainTextStory
    iStories(1) = wdTextFrameStory
    bSaved = Doc.Saved
   
    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)
           
            iShapeCount = Doc.Shapes.Count
            For iShape = IIf(iCurrentShape = 0, 1, iCurrentShape) To iShapeCount
                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
                            Doc.Saved = bSaved
                            'rng.Select
                            Set gRange = rng
                            Application.OnTime DateAdd("s", 1, Now), "DoSelect"
                          Exit Sub
                        End If
                    Case msoCanvas
                        If sh.CanvasItems.Count > 0 Then
                            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
                                    Set gRange = rng
                                    Application.OnTime DateAdd("s", 1, Now), "DoSelect"
                                    Doc.Saved = bSaved
                                    Exit Sub
                                End If
                            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
                Set gRange = rng
                Application.OnTime DateAdd("s", 1, Now), "DoSelect"
                Doc.Saved = bSaved
                Exit Sub
            End If
        End If
    Next stProcessing
    Doc.Saved = bSaved
    MsgBox "Search Complete"
End Sub

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
 
0
 

Author Comment

by:jsgreiner
Comment Utility
GrahamSkan -- You are the BEST!

Thank you, it works perfectly!!!
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

Like many others, we try and discourage users from printing documents unnecessarily and instead send or share them electronically. However, this doesn't always work and documents are still printed. With this simple solution, if the user tries to …
This is written from a 'VBA for MS Word' perspective, but I am sure it applies to most other MS Office components where VBA is used.  One thing that really bugs me is slow code, ESPECIALLY when it's mine!  In programming there are so many ways to…
This video shows where to find templates, what they are used for, and how to create and save a custom template using Microsoft Word.
This Experts Exchange video Micro Tutorial shows how to tell Microsoft Office that a word is NOT spelled correctly. Microsoft Office has a built-in, main dictionary that is shared by Office apps, including Excel, Outlook, PowerPoint, and Word. When …

772 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

9 Experts available now in Live!

Get 1:1 Help Now