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.
Powered by vBulletin® Version 4.2.5 Copyright © 2025 vBulletin Solutions Inc. All rights reserved.