PDA

View Full Version : Get range for text columns



MacroShadow
05-05-2012, 11:48 PM
Is it possible to set a range for a text column (newspaper-style).

I'm trying something along these lines:

Sub test()

Dim iViewType As Integer
Dim iLine As Integer
Dim oRng As Word.range
Dim sec As Word.Section

iViewType = ActiveWindow.View.Type: ActiveWindow.View.Type = wdPrintView
Options.Pagination = True

Dim i As Long
With ActiveDocument
For i = 1 To .Sections.count
With .Sections(i)
If .PageSetup.TextColumns.count > 1 Then
oRng.SetRange Start:???, End:=???
' or
' iLine = oRng.ComputeStatistics(wdStatisticLines)
iLine = oRng.Information(wdFirstCharacterLineNumber)
MsgBox iLine
End If
End With
Next i
End With

End Sub
On msdn were this question originated (http://social.msdn.microsoft.com/Forums/en-US/worddev/thread/dfa28834-dfd0-4644-90bc-3deaf9e9d8e2) JosephFox suggested using something like the following, the problem is that my knowledge is too limited to make it work, (also as he says it will be very slow (if I can get it to work)):

Dim rangeStart As Integer
Dim outOfColFlag As Integer
Dim curColumn As Integer
Dim range As range
Dim content As range
Dim doc As ActiveDocument
Dim count As Integer
Dim totalChars As Integer
Dim totalTextColumns As TextColumns

totalChars = doc.content.range.Text.count
curColumn = 1
rangeStart = 0
outOfColFlag = -1
Set totalTextColumns As doc.PageSetup.TextColumns


For count = 1 To totalChars - 1 Step 1

'Move range position forward one character
range.SetRange(Start:=count, End:=count)

'Get text column, if we're in a text column
If range.PageSetup.TextColumns.count > 0 Then

'If we're transferring into a text column, update flag and range
If outOfColFlag < 0 Then
outOfColFlag = 1
rangeStart = range.Start
End If

If range.PageSetup.TextColumns.Item(1) <> totalTextColumns(curColumn) Then
'We've transfered into a new text column. Increment column index and get lines
curColumn = curColumn + 1

Dim lines As Integer
range.Start = rangeStart
rangeStart = range.Start 'This position may be the start of a new column
range.End = range.End - 1
lines = range.ComputeStatistics(wdStatisticLines)

End If
Else
If outOfColFlag > -1 Then
'We're transferring out of a text column. Increment column index and get lines for the column we've left
curColumn = curColumn + 1

Dim lines As Integer
range.Start = rangeStart
range.End = range.End - 1
lines = range.ComputeStatistics(wdStatisticLines)

'Set flag
outOfColFlag = -1
End If
End If

Next count
He isn't available to follow-up so I turn to the VBA Express experts.

Frosty
05-07-2012, 09:32 AM
Do you mean you'd like to have a function which, basically, you could use to select all the text of column 2 of a 3 column section?

MacroShadow
05-07-2012, 10:02 AM
I need to get the total number of lines per column. I would use this function if I could set a text column as a range.

Function lngLineCount(ByVal rng As Range) As Long
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' '''
''' Function: lngLineCount
''' Comments: Return LONG the count of lines in the given range.
''' Arguments: RANGE The range of text being examined.
''' Returns: LONG
''' Comments:
'''
''' Date Developer Action
''' --------------------------------------------------------------------------
''' 2011/02/11 Chris Greaves Created
'''
Dim lngS As Long
lngS = lngLineStart(rng)
Dim lngE As Long
lngE = lngLineEnd(rng)
lngLineCount = lngE - lngS + 1
If lngLineCount = 0 Then ' only a part of line was given
lngLineCount = 1
Else
End If
'Sub TESTlngLineCount()
' MsgBox lngLineCount(Selection.Range.Paragraphs(1).Range)
'End Sub
End Function

Frosty
05-07-2012, 10:27 AM
I'm telling you... you'd spend less time learning how to make tables work for these purposes, MacroShadow. You see how much effort and questions you have to raise *solely* for the purpose of text wrapping in a newspaper column? You should really really think about how much effort you're putting into making something work which is fundamentally broken, rather than the effort you'd put into using tables instead.

Apart from the education of getting better at vba, the reason you're finding little pieces of solutions (rather than the whole thing), is because people with a lot more experience already know that the textcolumns area of Word is just a stinky piece of poo... so no comprehensive solutions have really been hashed out.

I honestly think your time would be better served investigating:
1. How to use tables in Word to do newspaper-style desktop publishing
2. How to "convert" existing documents into this new style.

If I were to proceed on this, I would work on a function which started with my current page range (ActiveDocument.Bookmarks("/page").Range).

And from there I would attempt to see
1) how many columns I'm looking for (2,3, etc)
2) define the range of each of the columns on this page
3) articulate that back as a collection of those column ranges

Utilize this
Dialogs(wdDialogFormatColumns).ColumnNo (which returns the column number the selection is in), or perhaps the .Information(wdFirstCharacterColumnNumber).

I think the methodology described by the Joseph Fox is slower than it needs to be, since it starts with the entire activedocument.content, rather than just the current page (which is what you care about).

However, I can't emphasize this point enough: the hardest part about what you're doing is trying to use code to describe things which are really really fluid: pages and text columns within those pages. One of the reasons this is so hard is because it fluid. One of the reasons tables work so well for this is because a table per page gives you your "page" concept, and the table's columns takes care of that. And the only thing you "lose" as the end-user is the automatic text wrapping.

Now, I haven't tried this... but you could also use "linked" text boxes (at least, it appears available in Word 2003). Having never used that before, I don't really know the pros and cons, but those allow placement *and* the text wraps between them.

MacroShadow
05-07-2012, 01:36 PM
I do have every intention of perusing the table route as I've mentioned before, when I do I'm sure you will notice;). Since I have a working solution (it probably isn't water-tight but so far it hasn't sprung any leaks) I figured I'd try to enhance it before moving on.
This question is related to totally automating the process with out any user input. If I can't get it to work, not much harm done, since my solution works for manual adjustment.
Would it be possible to throw together an example of the function you mentioned? I'm not sure how to go about the steps you outlined, besides isn't it easier (or at least not as hard) to learn how a clock ticks by taking one apart rather then trying to build one without the necessary knowledge)?
Your idea of using linked text boxes does sound interesting, I may get around to it after following-up on the table route.

Frosty
05-07-2012, 01:42 PM
If the sample was easy, I would have posted it rather than giving you a diatribe about how what you're doing is hard *grin*

I'm curious about *how* hard it is, so I was going to give that a whirl (although it will end up being similar to the caveats in the other thread about printing the last "page" of a section)... correctly identifying these kinds of ranges can be highly dependent on a number of settings. So a proof-of-concept would be just that. Not production-ready, since you'd likely need to account for a number of scenarios which would only complicate the concept part of the "proof."

So, to sum up: it's hard, I've got work to do, but I'd like to give you a clock to take apart at some point, but maybe in a few hours. Just don't rely on the clock's alarm if you've got to be somewhere really important :)

MacroShadow
05-07-2012, 01:48 PM
Thank you I'm looking forward to it.

Frosty
05-07-2012, 03:03 PM
Here's a proof of concept... if you're not a fan of the immediate window, you can try adjusting the parameters you pass by throwing different section numbers at it, a la...

fGetColumnRange(2, ActiveDocument.Sections(3).Range)
or
fGetColumnRange(3).Select

'pass in a column number, and it will be returned as the range
'if no specific column passed, then the current column is returned
'this routine relies on the selection object, unfortunately
Public Function fGetColumnRange(iColNum As Integer, Optional rngSearch As Range) As Range
Dim rngWorking As Range
Dim rngOrig As Range
Dim rngPage As Range
Dim rngCurSec As Range
Dim iNumColumns As Integer
Dim iColCur As Integer
Dim iColPrev As Integer
Dim rngWord As Range
Dim rngCol As Range
Dim colColumnRanges As Collection

Application.ScreenUpdating = False
'if we didn't pass in a search range, use the selection
If rngSearch Is Nothing Then
Set rngSearch = Selection.Range.Duplicate
End If

'initialize our collection of ranges
Set colColumnRanges = New Collection

'first, we need to define a couple of boundaries...
'the current page
Set rngPage = ActiveDocument.Bookmarks("\page").Range
'and the current section
Set rngCurSec = rngSearch.Sections(1).Range

'and start with our current selection (so we can restore later?)
Set rngOrig = rngSearch.Duplicate
Set rngWorking = rngOrig.Duplicate
rngWorking.Collapse wdCollapseStart

'now get the smaller of either our page, our our section, first get our .Start
If rngPage.Start < rngCurSec.Start Then
rngWorking.Start = rngCurSec.Start
Else
rngWorking.Start = rngPage.Start
End If
'and then our .End
If rngPage.End < rngCurSec.End Then
rngWorking.End = rngPage.End
Else
rngWorking.End = rngCurSec.End
'and remove the section break for our purposes
rngWorking.MoveEnd wdCharacter, -1
End If

'now, our rngWorking is the area we're going to try to determine the various columns
'how many columns do we think we're in?
iNumColumns = rngWorking.PageSetup.TextColumns.Count

'initialize our first column range
Set rngCol = rngWorking.Words(1)
'and our first column
iColPrev = 1
'now we're going to go through each word, and see if we switch columns
For Each rngWord In rngWorking.Words
'using the dialog requires the selection
rngWord.Select
iColCur = Dialogs(wdDialogFormatColumns).ColumnNo
'if we're still in the same column, move our end range
If iColCur = iColPrev Then
rngCol.End = rngWord.End
'otherwise, add our range to the collection, and start over
Else
colColumnRanges.Add rngCol.Duplicate
Set rngCol = rngWord.Duplicate
End If
iColPrev = iColCur
Next
'and don't forget about our last column
colColumnRanges.Add rngCol.Duplicate

If iColNum <= colColumnRanges.Count Then
Set fGetColumnRange = colColumnRanges(iColNum)
Else
MsgBox "Unable to select the column. Column " & iColNum & " does not exist", vbInformation, "fGetColumnRange"
Set fGetColumnRange = rngOrig.Duplicate
End If

Application.ScreenRefresh

End Function
Again... not really thrilled about this methodology (requires use of the selection, since the primary "special sauce" of this approach is getting the .ColumnNo property of a word dialog called wdDialogFormatColumns -- something I found in a couple of quick google searches on the topic).

By the way... regarding your lngLineCount function. I think it's bad practice to use the same naming convention for you function name as you use for your variable typing. It's very confusing to have a function called "lngLineCount" and use "lng" as a prefix for your variables which are Longs.

The function you posted is very incomplete, since it relies on a couple of other functions. I'm guessing by your commented out code that you aren't using the immediate window yet. You should. It makes development so much faster.

More to come in a moment, re: line counting.

Frosty
05-07-2012, 03:10 PM
Actually, just even doing a little bit of testing... I realize there's a bit of a flaw already... since I establish a boundary of the current page, but then allow passing in a search range. So a multiple page multiple column kind of thing will only give you the column of whatever page you're on. So you may need to always limit by selection, or create an additional parameter to pass in two boundaries: a page and a section (since you want to search whichever range is smaller-- the page or the section).

fumei
05-07-2012, 03:20 PM
<sits chuckling in the next room>

Frosty
05-07-2012, 03:50 PM
<glares at the next room>
I'm going to stand by the code above... it *sort of* works, except that at the moment, it only gives you the column of the first page your selection is currently in (it relies on both ActiveDocument.Bookmarks("/Page").Range and whatever range you've passed in).

But I'm not going to modify, for now, since I suspect you're going to need to rely on the selection any way.

If, as you say, you've got something that mostly works right now... I'm happy to help you keep learning. But you should really start learning the other area of this experience: a good end-product which is going to use Word tables, or maybe linked text boxes (although my instinct is to shy away from them... there are "glitches" with textboxes in Word 2000-2003, and there are some serious errors with floating objects in 2007/2010).

fumei
05-07-2012, 09:12 PM
Linked text boxes, if used for only that purpose, AND you are not trying to get text OUT work of them *sort of* OK, but there are most definitely glitches in them. IMO, unless you have a real and well defined need to use them - avoid them. Another kludge that MS shoved into the application.

I find them ugly to work with.