60

OK, I am finishing up an add-on project for a legacy Excel-VBA application, and I have once again run up against the conundrum of the mysterious range.Rows (?) and worksheet.Rows properties.

Does anyone know what these properties really do and what they are supposed to provide to me? (Note: all of this probably applies to the corresponding *.Columns properties also).

What I would really like to be able to use it for is to return a range of rows, like this:

   SET rng = wks.Rows(iStartRow, iEndRow)

But I have never been able to get it to do that, even though the Intellisense shows two arguments for it. Instead I have to use one of the two or three other (very kludgy) techniques.

The help is very unhelpful (typically so for Office VBA), and googling for "Rows" is not very useful, no matter how many other terms I add to it.

The only things that I have been able to use it for are 1) return a single row as a range ( rng.Rows(i) ) and 2) return a count of the rows in a range ( rng.Rows.Count ). Is that it? Is there really nothing else that it's good for?

Clarification: I know that it returns a range and that there are other ways to get a range of rows. What I am asking for is specifically what do we get from .Rows() that we do not already get from .Cells() and .Range()? The two things that I know are 1) an easier way to return a range of a single row and 2) a way to count the number of rows in a range.

Is there anything else?

RBarryYoung
  • 55,398
  • 14
  • 96
  • 137
  • 3
    RBY - I think that's pretty much it. It's a convenient way of manipulating a whole row of the sheet/a slice of a range (very useful!), with a Count to boot. Most useful for loops when you're running through a number of rows (or columns for that matter). – Joel Goodwin Jun 24 '09 at 15:21
  • 2
    Great question... other than .Count i've wondered what the *** they were for. – Mark Nold Aug 27 '09 at 15:07
  • 3
    I wonder why this question seemingly triggered everyone's urge to post "answers" that are mere speculation or that don't even address the question! -1 to EVERYBODY!! (That last part was a joke. Mostly.) – Jean-François Corbett Jun 29 '11 at 13:00
  • 3
    Yeah its strange. Even stranger is that, even though I have over 6000 points, almost all earned from answering SQL Server question, my only Gold badge is from the almost 25,000 views of this question which was really just idle curiosity on my part. Yet the interest in it is 10x greater than anything else that I have done here. Not exactly an ego-builder. – RBarryYoung Jun 30 '11 at 02:23
  • @RBarryYoung It's because Excel is so much more ubiquitous than SQL Server so the population that is interested in these things is correspondingly greater. You have helped a lot of people asking this question. Enjoy your well-deserved karma. – Carlos A. Ibarra Mar 19 '17 at 20:06

9 Answers9

53

Range.Rows and Range.Columns return essentially the same Range except for the fact that the new Range has a flag which indicates that it represents Rows or Columns. This is necessary for some Excel properties such as Range.Count and Range.Hidden and for some methods such as Range.AutoFit():

  • Range.Rows.Count returns the number of rows in Range.
  • Range.Columns.Count returns the number of columns in Range.
  • Range.Rows.AutoFit() autofits the rows in Range.
  • Range.Columns.AutoFit() autofits the columns in Range.

You might find that Range.EntireRow and Range.EntireColumn are useful, although they still are not exactly what you are looking for. They return all possible columns for EntireRow and all possible rows for EntireColumn for the represented range.

I know this because SpreadsheetGear for .NET comes with .NET APIs which are very similar to Excel's APIs. The SpreadsheetGear API comes with several strongly typed overloads to the IRange indexer including the one you probably wish Excel had:

  • IRange this[int row1, int column1, int row2, int column2];

Disclaimer: I own SpreadsheetGear LLC

Jean-François Corbett
  • 37,420
  • 30
  • 139
  • 188
Joe Erickson
  • 7,077
  • 1
  • 31
  • 31
  • 3
    `essentially the same Range...` misses a valuable part of these properties which is that they provide a new iterator for `For Each` loops which goes through the `Range` row-by-row or column-by-column. See [answer by Nile](http://stackoverflow.com/a/23394965/4288101) for reference. This is very powerful when combined with `SpecialCells` or other discontinuous ranges (e.g. from an `AutoFilter`). Concrete example: quickly applying banded row coloring to visible data after an `AutoFilter` when you don't have a `Table` in place. – Byron Wall Jun 01 '15 at 16:49
  • @ByronWall thank you for this comment as I found myself at this page looking for specific info on using the range.rows property for this exact purpose. I appreciate very much your pointer on this use case. – Alex M Nov 09 '19 at 00:20
11

Range.Rows, Range.Columns and Range.Cells are Excel.Range objects, according to the VBA Type() functions:

?TypeName(Selection.rows)
Range
However, that's not the whole story: those returned objects are extended types that inherit every property and method from Excel::Range - but .Columns and .Rows have a special For... Each iterator, and a special .Count property that aren't quite the same as the parent Range object's iterator and count.

So .Cells is iterated and counted as a collection of single-cell ranges, just like the default iterator of the parent range.

But .Columns is iterated and counted as a collection of vertical subranges, each of them a single column wide;

...And .Rows is iterated and counted as a collection of horizontal subranges, each of them a single row high.

The easiest way to understand this is to step through this code and watch what's selected:

Public Sub Test() 
Dim SubRange As Range Dim ParentRange As Range
Set ParentRange = ActiveSheet.Range("B2:E5")

For Each SubRange In ParentRange.Cells SubRange.Select Next
For Each SubRange In ParentRange.Rows SubRange.Select Next
For Each SubRange In ParentRange.Columns SubRange.Select Next
For Each SubRange In ParentRange SubRange.Select Next
End Sub
Enjoy. And try it with a couple of merged cells in there, just to see how odd merged ranges can be.
Nigel Heffernan
  • 4,636
  • 37
  • 41
  • 4
    **THIS** is the missing piece from all the answers that say "`Rows` and `Columns` are the same except for `Count`". The ability to iterate through the rows and columns of a range without regard for how the `Range` was created is very powerful. It provides a very clean way to iterate cells that avoids `Cells(i,j)` type logic inside loops. It also works well with discontinuous ranges which can come up in certain scenarios. – Byron Wall Jun 01 '15 at 16:32
7

Your two examples are the only things I have ever used the Rows and Columns properties for, but in theory you could do anything with them that can be done with a Range object.

The return type of those properties is itself a Range, so you can do things like:

Dim myRange as Range
Set myRange = Sheet1.Range(Cells(2,2),Cells(8,8))
myRange.Rows(3).Select

Which will select the third row in myRange (Cells B4:H4 in Sheet1).

update: To do what you want to do, you could use:

Dim interestingRows as Range
Set interestingRows = Sheet1.Range(startRow & ":" & endRow)

update #2: Or, to get a subset of rows from within a another range:

Dim someRange As Range
Dim interestingRows As Range

Set myRange = Sheet1.Range(Cells(2, 2), Cells(8, 8))

startRow = 3
endRow = 6

Set interestingRows = Range(myRange.Rows(startRow), myRange.Rows(endRow))
e.James
  • 116,942
  • 41
  • 177
  • 214
  • I knew that it returned a range (mentioned it in my original post) and that there are other ways to get a range of rows (also mentioned). What I am asking for is specifically what do we get from .Rows() that we do not already get from .Cells() and .Range()? I will clarify this in my question ... – RBarryYoung Jun 24 '09 at 15:01
  • I'm afraid that's it. The Rows and Columns properties are simply a quick shortcut to get access to Range objects conveniently set to contain single rows (or single columns) of your initial range. There's no more magic to it than that. – e.James Jun 24 '09 at 16:38
  • I feel like an idiot for not thinking of _Set interestingRows = Sheet1.Range(startRow & ":" & endRow)_. (Some would agree with me even if I _had_ thought of it.) <8^|. It's the simplest form, don't even need to convert longs to strings, and it works. – riderBill Dec 05 '15 at 12:11
  • 'As it turns out, _interestingRows = Sheet1.Range(startRow & ":" & endRow)_ didn't work afterall. It didn't crash, but whatever range I wound up with was not what I wanted. _testStr = 65 & ":" & 5_ evaluates to "65:5", but I'm not sure what range that resulted in. It's exasperating that MS doesn't see fit to provide useful functions for this kind of thing. AFAIK (albeit it isn't that far in VBA) there is no range-like constructor that takes row and column indices, or cell pairs for that matter. The need comes up often enough. – riderBill Dec 07 '15 at 13:40
  • S I put my old, tedious method for getting a reference to a sub-range of a larger range in a function [shown here](http://stackoverflow.com/questions/33311263/how-do-i-select-subrange-from-a-range-object-in-vba/34135793#34135793). The solution I have is to construct an "A4:G7" style string and feed it to the range constructor like this: _rangeStr = Chr(AM1 + iCol1) & CStr(iRow1) & ":" & Chr(AM1 + iCol2) & CStr(iRow2)_ ; _ Set getSubRange = sourceRange.Range(rangeStr)_. But surely there is a more direct method using the indices directly, right? – riderBill Dec 07 '15 at 15:38
  • Timed out while editing. AM1 = (ASCII value of 'A' ) minus 1, I.e. 65-1 = 64. I suppose these days I should call it the _UNICODE_ or _UTF-8_ value, but it's the same number. – riderBill Dec 07 '15 at 15:47
5

Since the .Rows result is marked as consisting of rows, you can "For Each" it to deal with each row individually, like this:

Function Attendance(rng As Range) As Long
Attendance = 0
For Each rRow In rng.Rows
    If WorksheetFunction.Sum(rRow) > 0 Then
        Attendance = Attendance + 1
    End If
Next
End Function

I use this to check attendance in any of a few categories (different columns) for a list of people (different rows).

(And of course you could use .Columns to do a "For Each" over the columns in the range.)

mrento
  • 51
  • 1
  • 2
2

I'm not sure, but I think the second parameter is a red herring.

Both .Rows and .Columns take two optional parameters: RowIndex and ColumnIndex. Try to use ColumnIndex, e.g. Rows(ColumnIndex:=2), generates an error for both .Rows and .Columns.

My feeling it's inherited in some sense from the Cells(RowIndex,ColumnIndex) Property but only the first parameter is appropriate.

Joel Goodwin
  • 5,026
  • 27
  • 30
  • 5
    *Feelings, nothing more than feelings* -1 – Jean-François Corbett Jun 29 '11 at 12:57
  • 2
    @Jean-François Corbett - Sometimes those are the only answers you can get and it's better to have the anecdotes of people who have been in the field than nothing at all. I probably wouldn't post something like this these days (the answer is 2 years old!!) - it's too woolly and I absolutely can't be bothered to post something which has little hope of votes. VBA questions do not earn much rep in general. But I'd strongly argue against your implication that "answers based on feelings are wrong" simply because that closes debate on questions that have no obvious or easy answers. – Joel Goodwin Jun 30 '11 at 07:46
  • 1
    I see your point. To be honest, I hadn't looked at the date and didn't realize it was so old. Still, I feel (sic) that there are all too many of these guess-answers on SO, and that only a tiny minority are useful while most qualify as "not useful" i.e. legitimate downvote candidates, and I stand by my earlier judgment for this one. Others apparently see things differently, since you still got a net upvote for your answer/feeling. – Jean-François Corbett Jun 30 '11 at 13:09
2

I've found myself using range.Rows for its effects in the Copy method. It copies the height of the rows from the origin to the destination, which is the behaviour I want.

rngLastRecord.Rows.Copy Destination:=Sheets("Availability").Range("a" & insertRow)

If I had used rngLastRecord.Copy instead of rngLastRecord.Rows.Copy, the row heights would be whatever was there before the copy.

1

I've found this works:

Rows(CStr(iVar1) & ":" & CStr(iVar2)).Select
  • 1
    @RBarryYoung `Rows(Cstr(iVar1) & ":" & CStr(iVar2))` most certainly is not the same as `Cells(Cstr(iVar1) & ":" & CStr(iVar2))`. The latter results in a type-mismatch error. – Daniel Sep 18 '12 at 05:12
1

It's perhaps a bit of a kludge, but the following code does what you seem to want to do:

Set rng = wks.Range(wks.Rows(iStartRow), wks.Rows(iEndRow)).Rows
jswolf19
  • 2,303
  • 15
  • 16
1

There is another way, take this as example

Dim sr As String    
sr = "6:10"
Rows(sr).Select

All you need to do is to convert your variables iStartRow, iEndRow to a string.

Siddharth Rout
  • 147,039
  • 17
  • 206
  • 250
blash
  • 21
  • 1