Textboxes and their influence on the behaviour of ranges

A

anders.johansen

Hi,

I want to be able to parse the contents of a document, and possibly
change a single word in the text. Unfortunately inserting textboxes
seem to have strange effects on the document.

If I have a document that has this content:

"Hello world"

I get this:

ActiveDocument.Range.Text is "Hello world"

ActiveDocument.Range(7, 12).Text is "world"

However, if I add a Text box in front of "Hello world", so the
document looks like this:

---
BOX
Goodbye world
BOX_END
Hello world
---

I get this:

ActiveDocument.Range.Text is "\x01\x15Hello world"

ActiveDocument.Range(7, 12).Text is " \\* "

ActiveDocument.Range(9, 14).Text is "\\* ME"

What gives?

Sincerely,
Anders S. Johansen
 
F

fumei via OfficeKB.com

If you expanded your Start and End of your range, you would see

"\\* MEREGEFORMAT

When using Range.Text you are picking up the field code text of the textbox.

In your document, press Alt-F9. This will toggle field codes. You should
see something like:

{ SHAPE \* MERGEFORMAT }Hello World

Range.Text is operating literally. And literally, the textbox is made up of
a field code, which is, literally, text.
 
A

anders.johansen

When using Range.Text you are picking up the field code text of the textbox.

All right.

So I guess I am not using the right approach here. Is there another
one? The only alternative I have come up with is manipuating
Selection, and moving it first to the start of the document, then
moving the start and the end in units of characters... which works,
but is slow in large documents...

Parsing any field seems nigh-on impossible.

Anders
 
T

Tony Jollans

The Range Method returns a Range Object. If you qualify the method with
start and end positions they relate to the underlying structure of the
document; if you don't you get the whole document (including hidden
controls) as though you had specified the start and end of the whole
document.

When you use the Text Property of a Range object you get a string which is
just the 'visible' content of that range (you can control what 'visible'
means - see the TextRetrievalMode if interested - but the default is what
you see on screen). If you want, you can take a substring of the Text
property to get what it seems you are looking for.

So ...

... in your example with the textbox ...

Range(9,14) picks up characters 9 through 14 of the underlying document
including invisible controls (" \* ME") as you saw

Range.text picks up the visible part of the document (the shape placeholder,
a control delimiter, then your text). The substring Mid(range.text,9,6) will
pick up characters 9 through 14 of that visible text ("world" followed by a
paragraph mark) .

--
Enjoy,
Tony

When using Range.Text you are picking up the field code text of the
textbox.

All right.

So I guess I am not using the right approach here. Is there another
one? The only alternative I have come up with is manipuating
Selection, and moving it first to the start of the document, then
moving the start and the end in units of characters... which works,
but is slow in large documents...

Parsing any field seems nigh-on impossible.

Anders
 
A

anders.johansen

The Range Method returns a Range Object. If you qualify the method with
start and end positions they relate to the underlying structure of the
document; if you don't you get the whole document (including hidden
controls) as though you had specified the start and end of the whole
document.

When you use the Text Property of a Range object you get a string which is
just the 'visible' content of that range (you can control what 'visible'
means - see the TextRetrievalMode if interested - but the default is what
you see on screen). If you want, you can take a substring of the Text
property to get what it seems you are looking for.

OK. That makes more sense.

Unfortunately, what I want to do is:

1) Get the visible text
2) Parse it
3) Possibly create a Range that covers a part of the visible text
based on the parsing I did.

I.e., I would love to be able to create a Range, based on positions in
the visible text, but what you are telling me is that that is not
possible, right?

Anders
 
T

Tony Jollans

It depends on what you want to do with the Range.

Generally speaking, when working with the document, behind the scenes as it
were, you have to be aware of the whole document. However, using default
settings, your code can, by and large, mimic the behaviour you would see on
screen.

A Range has a Characters Collection, and each Character is, itself, a Range
object. You could use this collection to define the range you want by
picking the start of the range of the first character and the end of the
range of the last character, but be careful ...

... in your example \x01\x15 is a single Character (Range) object, and thus

ActiveDocument.Range( _
ActiveDocument.Range.Characters(8).Start, _
ActiveDocument.Range.Characters(12).End)

(deliberately spread over three lines to stop the newsreader breaking it
inappropriately)

... will return the Range containing the visible "world".

--
Enjoy,
Tony

The Range Method returns a Range Object. If you qualify the method with
start and end positions they relate to the underlying structure of the
document; if you don't you get the whole document (including hidden
controls) as though you had specified the start and end of the whole
document.

When you use the Text Property of a Range object you get a string which is
just the 'visible' content of that range (you can control what 'visible'
means - see the TextRetrievalMode if interested - but the default is what
you see on screen). If you want, you can take a substring of the Text
property to get what it seems you are looking for.

OK. That makes more sense.

Unfortunately, what I want to do is:

1) Get the visible text
2) Parse it
3) Possibly create a Range that covers a part of the visible text
based on the parsing I did.

I.e., I would love to be able to create a Range, based on positions in
the visible text, but what you are telling me is that that is not
possible, right?

Anders
 
A

anders.johansen

 ... in your example \x01\x15 is a single Character (Range) object, and thus

ActiveDocument.Range( _
        ActiveDocument.Range.Characters(8).Start, _
        ActiveDocument.Range.Characters(12).End)

(deliberately spread over three lines to stop the newsreader breaking it
inappropriately)

 ... will return the Range containing the visible "world".

All right, that works pretty much the way I wanted, and having banged
it sufficiently over its head, it now works exacly as it should...
Thank you very much for your help!

Now I have another problem.

Say I have a document that looks like this:

------------------
TEXT BOX BEGINS
Hello world
TEXT BOX ENDS
Goodbye world
------------------

...and my cursor is in the text box ("Hello world").

When using Application.ActiveDocument.Characters, I get the Characters
in the "actual" document, e.g. "Goodbye world".

This also happens if instead of ActiveDocument, I use
Selection.Document.

What do I do, if instead I want to get the Characters range that the
cursor is currently on, e.g. "Hello world" if the cursor is in the
text box, and "Goodbye world" if it is not?

Anders
 
T

Tony Jollans

On the plus side, you're making progress :)

Document content is held in several parts, called Stories, which come
together to produce the display (or print). What you have been working with
so far (Activedocument.Range) is the Main Text Story and within that you
have seen a placeholder for your textbox (\x01, more generally a placeholder
for a graphic). The test inside the Textbox is held as a TextRange within a
TextFrame within the Textbox Shape. It is also part of the document's
TextFrame Story (which is actually a series of stories, one per Textbox or,
strictly, one per chain of textboxes - if you have linked ones).

You can access the series of TextFrame Stories from the selection by
starting with ...

Set MyRange = ActiveDocument.StoryRanges(Selection.StoryType)

... and then iterating over them with ...

Set MyRange = MyRange.NextStoryRange

... but this is probably not the best way for you - although it does work
whether in a Textbox or the Main Text - or any other story.

What is probably better is to identify, first of all, that your Selection is
in a Textbox (Selection.StoryType = wdTextFrameStory) and then look at
Selection.ShapeRange.TextFrame.TextRange, which is a Range just like
Application.Range (and can, itself contain nested textboxes, etc.). You can
process the Characters in this Range in the same way you processed those in
the main document.

--
Enjoy,
Tony

... in your example \x01\x15 is a single Character (Range) object, and
thus

ActiveDocument.Range( _
ActiveDocument.Range.Characters(8).Start, _
ActiveDocument.Range.Characters(12).End)

(deliberately spread over three lines to stop the newsreader breaking it
inappropriately)

... will return the Range containing the visible "world".

All right, that works pretty much the way I wanted, and having banged
it sufficiently over its head, it now works exacly as it should...
Thank you very much for your help!

Now I have another problem.

Say I have a document that looks like this:

------------------
TEXT BOX BEGINS
Hello world
TEXT BOX ENDS
Goodbye world
------------------

....and my cursor is in the text box ("Hello world").

When using Application.ActiveDocument.Characters, I get the Characters
in the "actual" document, e.g. "Goodbye world".

This also happens if instead of ActiveDocument, I use
Selection.Document.

What do I do, if instead I want to get the Characters range that the
cursor is currently on, e.g. "Hello world" if the cursor is in the
text box, and "Goodbye world" if it is not?

Anders
 
A

anders.johansen

On the plus side, you're making progress :)

Yup! Also with going nuts ;)
What is probably better is to identify, first of all, that your Selection is
in a Textbox (Selection.StoryType = wdTextFrameStory) and then look at
Selection.ShapeRange.TextFrame.TextRange, which is a Range just like
Application.Range (and can, itself contain nested textboxes, etc.). You can
process the Characters in this Range in the same way you processed those in
the main document.

All right.

I am still having problems getting the TextRange from TextFrame. I am
not using Word - I am accessing Word from Borland C++ Builder - but
everything else works fine.

When (if) I can reliably get the Characters from a TextBox, I just
need a way to create a new range from the start and end characters,
when dealing with a text box. I apparently can't use the
ActiveDocument, as it is not the host of the Characters collection in
question, but none of the parent objects.

Thank you for your help so far!

Anders
 
T

Tony Jollans

Alright! This is not entirely straightforward. There is no immediate route
to the Range of the textbox you are in but this will do it:

Dim MyRange As Range, MyChar As Range
If Selection.StoryType = wdTextFrameStory Then
Set MyRange = ActiveDocument.StoryRanges(wdTextFrameStory)
While Not Selection.InRange(MyRange)
Set MyRange = MyRange.NextStoryRange
Wend
EndIf


MyRange is now a Range like any other. It is a Range Object and there is no
parent with a Range Property that takes start and end parameters. When you
determine (through its Characters collection) the start and end you need you
must then explicitly set them - or maybe if you want to keep the original
you can do this ..

Dim MyNewrange as Range
Set MyNewRange = MyRange.Duplicate
MyNewRange.Start = MyRange.Characters(7).Start ' perhaps
MyNewRange.End = MyRange.Characters(15).End ' perhaps



--
Enjoy,
Tony

On the plus side, you're making progress :)

Yup! Also with going nuts ;)
What is probably better is to identify, first of all, that your Selection
is
in a Textbox (Selection.StoryType = wdTextFrameStory) and then look at
Selection.ShapeRange.TextFrame.TextRange, which is a Range just like
Application.Range (and can, itself contain nested textboxes, etc.). You
can
process the Characters in this Range in the same way you processed those
in
the main document.

All right.

I am still having problems getting the TextRange from TextFrame. I am
not using Word - I am accessing Word from Borland C++ Builder - but
everything else works fine.

When (if) I can reliably get the Characters from a TextBox, I just
need a way to create a new range from the start and end characters,
when dealing with a text box. I apparently can't use the
ActiveDocument, as it is not the host of the Characters collection in
question, but none of the parent objects.

Thank you for your help so far!

Anders
 
A

anders.johansen

Alright! This is not entirely straightforward. There is no immediate route
to the Range of the textbox you are in but this will do it:

<...>

All right! This works very well for me! Thank you ever so much for the
help and insights you have provided.

Sincerely,
Anders
 
T

Tony Jollans

My pleasure!

--
Enjoy,
Tony

<...>

All right! This works very well for me! Thank you ever so much for the
help and insights you have provided.

Sincerely,
Anders
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top