Can anyone tell me how to use RefersToRange in vba? and what and when is the need of it.
Please provide with simple example first.
Thanks in advance.
In Excel, there is the concept of a named range, that is a range of cells which has a name attached to it. This is represented by the Name object.
The RefersToRange method:
Returns the Range object referred to by a Name object.
For example, let's say I want to read the values only in the print area of the current worksheet. I need the Name object in order to access the print area, but I can't do anything useful with it; if I want to do something useful I have to access the range of cells referred to by that name:
Dim n As Name
Set n = ActiveWorkbook.Names("Print_Area")
Dim rng As Range
Set rng = n.RefersToRange
Dim values() As Variant
values = rng 'I can't read values with a Name object, but I can with a Range object
One thing I have discovered is that sometimes referring to a named range in this fashion:
ThisWorkbook.Range("DownloadPictures").Value = "yes"
results in:
Run-time error '1004':
Method 'Range' of object '_Worksheet' failed
But referring to the named range in this fashion:
ThisWorkbook.Names("DownloadPictures").RefersToRange = "yes"
works fine. My particular circumstance is that the named range ("DownloadPictures") is scoped to the entire workbook, which has several worksheets. I am referring to the name in one of the worksheet's Worksheet_Calculate subroutines. I don't understand why the first method causes an error and the second does not but apparently this is one reason to use the RefersToRange property.
RefersToRange is, one of many ways, to 'point' a range. For example,
ThisWorkbook.Names("DownloadPictures").RefersToRange = "yes"
the above code, points to the range, which can be a cell, named "DownloadPictures" and sets it value to "yes". As a matter of fact, I'd rather use,
ThisWorkbook.range("DownloadPictures").Value = "yes"
The 'Name' in MS Excel is similar to the Variant variable type, but unlike the variables you 'Dim' in the VB code, the 'Name' only be used (or you can consider it is only be used) in the Workbook. The 'Name' and variables are not interfered with each other.
If you open the 'Name Manager', the concept becomes easier to understand. The Name can be created to refer to a cell, a range of cell (a range), some value (so called constant), or a formula.
Each Name object represents a defined name for a range of cells. Names can be either built-in names—such as Database, Print_Area, and Auto_Open—or custom names. ---MSDN
As a Name can refer to range, constant or a formula .RefersToRange specifically refer to a range. And return false if not so.
Related
I am currently experimenting with excel VBAs ListObjects which is the object type of an excel table. I would like to store a table column range into a variable.
Here is what I can do:
'store a group of cells into a range variable
dim rng as Range
set rng = activesheet.Range("A1:A10")
'select a table column
dim table as ListObject
set table = activesheet.listobjects("Table1")
table.ListColumns(1).Range.Select
While both of the above work, I don't understand why the following does't work: (EDIT: it works)
dim rng_column as Range
set rng_column = table.ListColumns(1).Range
I experimented with other variable types, such as variant or ListColumn, however, nothing stored the cells into the variable. In the example above, there is no error, but the variable rng_column remains <empty>
I know there are other workarounds, but I really want to understand what's the issue here.
UPDATE
After some helpful comments I could narrow the problem down. Everything worked normally. However, I made two mistakes.
First, I had a spelling error in the variable names. (I should have included option explicit to notice this earlier).
Second, I passed the range variable on to another range variable, where I forgot to include the set keyword when doing so..
Hopefully someone can learn from my mistakes..
you have a misnomer in the title. technically you cannot store any data into a range variable.
a range variable is a reference (a pointer) to a cell, or a group of cells. the data gets stored in the cells that the range variable points to.
if you need to store data from a range, then assign the range.value to an array
if you want to store data from a single cell then assign the value to a "regular" (non object) variable (string, integer, long ... )
I have the following command which runs correctly if placed inside a macro in vba:
Set elements = doc.getElementsByClassName("media-body")
I try to have this command running while taking the information from within a cell. Thus I place
doc.getElementsByClassName("media-body")
in cell D5 , but when I run:
Set elements = evaluate("Range(""D5"")") it throws an error (Run-time error '424': Object required.
Is there a way to solve it?
No, there isn't a way to solve it. Excel.Application.Evaluate only works with a subset of Excel objects. From the documentation:
Remarks
The following types of names in Microsoft Excel can be used
with this method:
Formulas. A1-style references. You can use any
reference to a single cell in A1-style notation. All references are
considered to be absolute references.
Ranges. You can use the range,
intersect, and union operators (colon, space, and comma, respectively)
with references. Defined names. You can specify any name in the
language of the macro.
External references. You can use the ! operator
to refer to a cell or to a name defined in another workbook ? for
example, Evaluate("[BOOK1.XLS]Sheet1!A1").
Chart Objects. You can
specify any chart object name, such as "Legend", "Plot Area", or
"Series 1", to access the properties and methods of that object. For
example, Charts("Chart1").Evaluate("Legend").Font.Name returns the
name of the font used in the legend.
The reason that you get an error 424 is because doc is an object reference in the code you are trying to run from the cell. Evaluate has no clue what doc is, because there is no way to set it to an instance from within a cell.
The only way to do anything meaningful with the cell contents in the question would be to parse the code in the cell and basically run it through an VBA-VBA interpreter.
EDIT
If I understand correctly what you're after (see the comments), what you'd really like is a way to generically search for HTML elements based on name and type. I'd approach this by selecting how to search the element name based on a type parameter. The example below assumes that the target cell (in row sourceRow and column sourceCol) contains the element name, ie "media-body", and the cell to the right contains its type - i.e. "ClassName".
Public Function GetElement(targetSheet As Worksheet, doc As HTMLDocument, _
sourceRow As Long, sourceCol As Long) As IHTMLElement
With targetSheet
'Get the element name from the passed cell.
Dim elementName As String
elementName = .Cells(sourceRow, sourceCol)
'Get the element type from the adjacent cell.
Dim elementType As String
elementType = .Cells(sourceRow, sourceCol + 1)
Select Case elementType
Case "ClassName"
Set GetElement = doc.getElementsByClassName(elementName)
Case "Id"
Set GetElement = doc.getElementById(elementName)
Case "Name"
Set GetElement = doc.getElementsByName(elementName)
'...
End Select
End With
End Function
This could just as easily be accomplished with a comma delimited string in a single cell - something like "media-body,ClassName", or several other methods but this is the direction I'd go.
Replace your:
Set elements = evaluate("Range(""D5"")")
With:
Set elements = Evaluate("D5")
I am trying to figure out how to work on a specific row among a big range. However it appears that a range created with the rows property does not behave the same as a simple range. Check the following code, the first time the variable SpecificRow is defined, it is not possible to select a specific cell. However with a weird workaround that redefines the range, it works fine. Do you have an idea why and how could I define the range with a more elegant way?
'The following shows the weird behavior of Rows property
Dim SourceRng As Range
Dim SpecificRow As Range
Dim i As Long
i = 3
Set SourceRng = Range("A1:D20")
Set SpecificRow = SourceRng.Rows(i)
'This will show the address of the selected row ("A3:D3")
MsgBox SpecificRow.Address
'Unexplicable behavior of the range when trying to select a specific cell
'where it will instead consider the whole row (within the limits of SourceRng)
MsgBox SpecificRow(1).Address
MsgBox SpecificRow(2).Address
'This would send an error
'MsgBox SpecificRow(1, 1).Address
'Workaround
Set SpecificRow = Intersect(SpecificRow, SpecificRow)
'The following will select the same address than before
MsgBox SpecificRow.Address
'However, now it has a correct behavior when selecting a specific cell
MsgBox SpecificRow(1).Address
MsgBox SpecificRow(2).Address
You should expect weird behavior if you're passing indexed properties the incorrect parameters. As demonstrated by your code, the Range returned by SourceRng.Rows(i) is actually correct. It just isn't doing what you think it's doing. The Rows property of a Range just returns a pointer to the exact same Range object that it was called on. You can see that in its typelib definition:
HRESULT _stdcall Rows([out, retval] Range** RHS);
Note that it doesn't take any parameters. The returned Range object is what you're providing the indexing for, and you're indexing it based on it's default property of Item (technically it's _Default, but the 2 are interchangeable). The first parameter (which is the only one you're passing with Rows(i), is RowIndex. So Rows(i) is exactly the same thing as Rows.Item(RowIndex:=i). You can actually see this in the IntelliSense tooltip that pops up when you provide a Row index:
Excel handles the indexing differently on this call though, because providing any value parameter for the second parameter is a Run-time error '1004'. Note that a similar property call is going on when you call SpecificRow(1).Address. Again, the default property of Range is Range.Item(), so you're specifying a row again - not a column. SpecificRow(1).Address is exactly the same thing as SpecificRow.Item(RowIndex:=1).Address.
The oddity in Excel appears to be that the Range returned by Range.Rows "forgets" the fact that it was called within the context of a Rows call and doesn't suppress the column indexer anymore. Remember from the typelib definition above that the object returned is just a pointer back to the original Range object. That means SpecificRow(2) "leaks" out of the narrowed context.
All things considered, I'd say the Excel Rows implementation is somewhat of a hack. Application.Intersect(SpecificRow, SpecificRow) is apparently giving you back a new "hard" Range object, but the last 2 lines of code are not what you should consider "correct" behavior. Again, when you provide only the first parameter to Range.Items, it is declared as the RowIndex:
What appears to happen is that Excel determines that there is only one row in the Range at this point and just assumes that the single parameter passed is a ColumnIndex.
As pointed out by #CallumDA, you can avoid all of this squirrelly behavior by not relying on default properties at all and explicitly providing all of the indexes that you need, i.e.:
Debug.Print SpecificRow.Item(1, 1).Address
'...or...
Debug.Print SpecificRow.Cells(1, 1).Address
This is how I would work with rows and specific cells within those rows. The only real difference is the use of .Cells():
Sub WorkingWithRows()
Dim rng As Range, rngRow As Range
Set rng = Sheet1.Range("A1:C3")
For Each rngRow In rng.Rows
Debug.Print rngRow.Cells(1, 1).Address
Debug.Print rngRow.Cells(1, 2).Address
Debug.Print rngRow.Cells(1, 3).Address
Next rngRow
End Sub
which returns:
$A$1
$B$1
$C$1
$A$2
$B$2
$C$2
$A$3
$B$3
$C$3
As you would expect
I cannot find any proper documentation on this, but this observed behaviour actually appears to be very logical.
The Range class in Excel has two important properties:
A single instance of Range is enough to represent any possible range on a sheet
It is iterable (can be used in a For Each loop)
I believe that in order to achieve logically looking iterability and yet avoid creating unnecessary entities (i.e. separate classes like CellsCollection, RowsCollection and ColumnsCollection), the Excel developers came up with a design where each instance of Range holds a private property that tells it in which units it is going to count itself (so that one range could be "a collection of rows" and another range could be "a collection of cells").
This property is set to (say) "rows" when you create a range via the Rows property, to (say) "columns" when you create a range via the Columns property, and to (say) "cells" when you create a range in any other way.
This allows you to do this and not become unnecessarily surprised:
For Each r In SomeRange.Rows
' will iterate through rows
Next
For Each c In SomeRange.Columns
' will iterate through columns
Next
Both Rows and Columns here return the same type, Range, that refers to the exactly same sheet area, and yet the For Each loop iterates via rows in the first case and via columns in the second, as if Rows and Columns returned two different types (RowsCollection and ColumnsCollection).
It makes sense that it was designed this way, because the important property of a For Each loop is that it cannot provide multiple parameters to a Range object in order to fetch the next item (cell, row, or column). In fact, For Each cannot provide any parameters at all, it can only ask "Next one please."
To support that, the Range class had to be able to give the next "something" without parameters, even though a range is two-dimensional and needs two coordinates to fetch the "something." Which is why each instance of Range has to remember in what units it will be counting itself.
A side effect of that design is that it is perfectly fine to look up "somethings" in a Range providing only one coordinate. This is exactly what the For Each mechanism would do, we are just directly jumping to the ith item.
When iterating over (or indexing into) a range returned by Rows, we're going to get the ith row, from top to bottom; for a range returned by Columns we're getting the ith column, from left to right; and for a range returned by Cells or by any other method we're going to get the ith cell, counting from top left corner to the right and then to the bottom.
Another side effect of this design is that can "step out" of a range in a meaningful way. That is, if you have a range of three cells, and you ask for the 4th cell, you still get it, and it will be the cell dictated by the shape of the range and the units it's counting itself in:
Dim r As Range
Set r = Range("A1:C3") ' Contains 9 cells
Debug.Print r.Cells(12).Address ' $C$4 - goes outside of the range but maintains its shape
So your workaround of Set SpecificRow = Intersect(SpecificRow, SpecificRow) resets the internal counting mode of that specific Range instance from (say) "rows" to (say) "cells".
You could have achieved the same with
Set SpecificRow = SpecificRow.Cells
MsgBox SpecificRow(1).Address
But it's better to keep the Cells close to the point of usage rather than the point of range creation:
MsgBox SpecificRow.Cells(1).Address
What I want to do is select a range B2:B5and give it a name, just above cells A1 and B1 there is a namebox and I type the name: "My_Test_Range" here and hit enter.
The word Range is this the official name for range as described above in Excel?
Then I want to loop trough the range B2:B5 using VBA and do something like make all the cells to the right of the ranged cells 1.
How do I do the above?
I want to do this with the named range, because I find a name like "Options_For_Red_Car" or "Options_For_Blue_Car" much easier to read than B2:B5 etc., especially if you get say a dozen such ranges.
Range is a very important object in the Excel object model. Here is one of probably thousands of references to information about ranges:
http://msdn.microsoft.com/en-us/library/office/ff838238(v=office.15).aspx
Seems like you like named-ranges which is good as they are very powerful and useful and if you're going to start playing with vba then you'll find them more and more useful.
To loop through the cells in a range you can do something like the following:
Sub loopThroughRangeCells()
Dim cell
For Each cell In Excel.ThisWorkbook.Sheets("Sheet1").Range("A2:C6").Cells
MsgBox cell.Address
Next
End Sub
Although really it is better to create object reference variables when doing something like the above and also to use a `named-range':
Sub loopThroughRangeCells()
Dim cell
Dim s As Excel.Worksheet '<<creates an object variable
Set s = Excel.ThisWorkbook.Sheets("Sheet1") '<<the object variable now references our target sheet
'<<using object variable and named range the following code is simplified
For Each cell In s.Range("helloWorld").Cells
MsgBox cell.Address
Next
End Sub
In the above I've just done MsgBox cell.Address within the loop but you can change this to anything you like - you might like to experiment and try to change the cells to the right to 1.
In the above I've declared a variant typed variable cell. This is standard practice to call each member of the collection cells a cell even though no object cell exists in the object model. This is different than most collection like worksheets where it is a collection of an intuitive object worksheet
I'm new to VBA. I'm attempting to create over 500 xlClusteredColumn charts using two columns of information and I'd like to expedite the work. The first column contains names I'd like to use for named ranges (i.e.: Line1, Line2, etc.) and the second column contains the indirect references of the data ranges (i.e., Sheet1!C4:D28, Sheet1!C28:D90). I noticed that if I use a named range for the "Chart Data Series" field, the data shows up nicely (but I have to first create that named range being sure to include the INDIRECT formula in the reference, (e.g.: Named Range Line1 is equal to =INDIRECT(Sheet1!C4:C28)). The ranges will be static.
In reviewing prior questions I couldn't seem to find a solution that would select the first cell in this set and name it, then uses the second cell to define that range. I think I might need the ActiveWorkbook.Names.Add Name:= formula and combine it with a loop (but I couldn't get it to use a selection or cell to define the Add Name aspect, only a hard coded name).
If the solution requires it, I can go back and extract the individual ranges (i.e.: C4:D28) from the cell and have the chart reference only that if it makes the code simpler. I know my first outlined attempt isn't the only solution and there's probably one much more elegant. I figured using named ranges would speed up the chart work, but perhaps there's a way to cut that step out?
Populating a new sheet with all the charts for each of these ranges would be icing on the cake, but I'll be happy enough receiving help to get the data set up to chart.
Example information:
NameRange1 (let's say in cells A1:A4)
WKD_1_NB
WKD_2_EB
WKD_3_EB
SerRange1 (in cells B1:B4)
WKDpivot!C4:D43
WKDpivot!C84:D140
WKDpivot!C197:D233
(Chart data range requires the reference of named range "WKD_1_NB" to be '=INDIRECT(WKDpivot!C4:D43)' in order for the chart to work.
OK so why don't you try the two-step process. I am going to do this without Indirect because I don't see that it is necessary.
Sub CreateNames()
Dim rng As Range
Dim r As Range
Dim myName As String
Dim addr As String
Set rng = Range("A1:A2") '## Modify as needed
For Each r In rng.Cells
myName = r.Value
addr = "=" & r.Offset(0, 1).Value
ThisWorkbook.Names.Add myName, addr
Next
End Sub
This creates your names (screenshot). Note there are some rules about naming conventions and allowable names, etc., the code above does not take any of these in to account.
From there it should be fairly simple to create a loop that adds your charts one by one, and assigns each named range to each chart.