VBA Loop through named ranges - using Indirect on variants - excel

I'm trying to loop through the multiple named ranges, every range is just one cell, on a column, so it starts with item1, item2...item100.Each of these ranges has a cell reference in it (like "AM100").
I want to be able to set up a for/while loop that just selects(and does something) each of the referenced cells in those named ranges.
I'm stuck at VBA not considering "item"&"1" the same with "item"&"i" when i =1 and from what I deduct this is purely a data type issue. It's able to refer to a pure string range but when it's variant/string type (which any concatenation of string or converted string variable results in).
item1 range has in it $AM$10 reference. That cell, has a value in it. Eventually I want to change the values in multiple similar cells by referring to the name ranges that hold their reference.
Without the "for" loop, I've tested the following:
Sub test()
Dim i as integer
i=1
'These don't work
Range([indirect("item"&CSTR(i))]).Select
Range([indirect("item"&i)]).Select
'What works is, but this is not useful since I want to loop through i:
Range([indirect("item" & "1")]).Select
Range([indirect("item1")]).Select

Sub Test()
Dim oDefName As Name
For Each oDefName In ThisWorkbook.Names
If Left(UCase(oDefName.Name), 4) = "ITEM" Then
Range(oDefName.RefersToRange.Value).Select
End If
Next
End Sub
Note: There is no error checking to ensure that the value within the named range is actually a cell reference.
Edit- Above is how I would solve the problem. Indirect is an in cell function and not usable directly in vba. Below is how you would get the indirect functionality in VBA with a counter.
Range(Range("Item" & oCounter).Value).Select

Related

How often are dynamic named ranges/formulae recalculated?

I have a formula assigned to a name in excel which is slow to calculate.
Let's say it's this:
Name: ImportantItems
Scope: Workbook
RefersTo: =FILTER(A1:A10000, complexCondition(A1:A10000))
I have some VBA macros which run a simulation that modifies A1:A10000 on every iteration, however the simulation only needs to access the ImportantItems array every 100 iterations. If
=FILTER(A1:A10000, complexCondition(A1:A10000))
... was a normal formula in a cell, I know that Excel would observe its precedent (A1:A10000) had changed and trigger a recalc every iteration. However I'm hoping named ranges not referred to in the spreadsheet anywhere - only via VBA - will be calculated on demand. FWIW my VBA code is just
Dim items As Variant 'read fancy filtered array of stuff into 1D array
items = Application.Transpose(Sheet1.Range("ImportantItems").Value)
My alternative is to refactor and move the ImportantItems code into VBA so I can control when it is calculated. Application.Calculations = xlManual is not an option without sprinkling my code with ...
Anyway this isn't meant to be an A/B question, I'm just wondering how the calculation engine works as I can't find documentation on it, and it will influence future design decisions.
Your hope/assumption is correct. As long as the named range is not referred to in the workbook in any range (other named ranges don't count) then the formula in the named range is not recalculated when the source range is changed.
The reason it doesn't recalculate is because the named range is storing a formula string (see Name object .RefersTo in VBA). Excel propagates the change (in source) only if the named range is referenced from a range at which point it runs an Evaluate on the formula string. When the source is resized (cut/insert/delete cells), the named range formula string is updated but in essence it's still a string.
To test, create the following named range:
Name: TestCalc
Scope: Sheet1
Refers to: =NamedRangeWatch(Sheet1!$A$1:$A$5)
Then add the following code in a standard .bas VBA module:
Option Explicit
Public Function NamedRangeWatch(ByVal rng As Range) As Range
Application.Volatile False
Debug.Print "NamedRangeWatch was called at " & Now
Set NamedRangeWatch = rng
End Function
Sub Test()
Dim i As Long
For i = 1 To 10
Sheet1.Range("A1:A5").Value2 = i
If i Mod 5 = 0 Then
Debug.Print Application.Sum(Sheet1.Names("TestCalc").RefersToRange)
'Or
'Debug.Print Application.Sum(Sheet1.Range("TestCalc").Value)
End If
Next i
End Sub
and then run the Test method. You should see something like:
which shows that the function inside the named range only gets called twice although the source range was changed 10 times.

Why does Worksheet.Hyperlink.Range sometimes return a series of ranges as opposed to just one when iterating through the Worksheet?

I have a macro which iterates through all of the Hyperlinks on all Worksheets in a Workbook, and if a Hyperlink's range is equal to a previously specified range, copies the Hyperlink to the same range on a destination sheet in a new Workbook and then moves onto the next Worksheet. This works flawlessly through the course of about 5 Worksheets, then suddenly throws a mismatch error and the whole process fails.
While debugging I've determined that every time, at the same Hyperlink on the same Worksheet, Worksheet.Hyperlink.Range (hyperlink.range in my code) returns a series of ranges rather than just one (as far as I can tell), and since the loop compares this to a single range, this is where the mismatch is occurring. The problem is that I can not determine why this is happening. I can not find any difference in the properties of this Hyperlink compared to the others that would cause it to be expressed as a series of ranges.
Public Sub CopyLink(fromRange As range, toRange As range)
Dim hyperlink As Hyperlink
Dim fromSheet As Worksheet
Set fromSheet = fromRange.Worksheet
For Each hyperlink In fromSheet.Hyperlinks
If hyperlink.range = fromRange Then 'mismatch happens here after several successful iterations
hyperlink.range.Copy
toRange.PasteSpecial
Exit For
End If
Next
End Sub
I would like to figure out how to either
A. alter the comparison of the two ranges in such a way that avoids this issue
or
B. understand the cause of the issue so that I can hopefully rectify it.
If you select multiple cells an then add a hyperlink, you only get one link displayed, but all of the selected cells respond as if they are hyperlinked.
The hyperlink.Range.Address property here is $B$2:$B$15
fromRange is always a single cell range, and the hyperlink.Range may refer to multiple cells. The mismatch here is with your comparison:
if hyperlink.range = fromRange
When a Range represents a single cell, it will evaluate to its Value property by default (the actual implementation is a little more complicated), and this will return the underlying data (string, numeric, date, etc.). When a Range represents multiple cells, its Value is an array of the individual cell values.
So you can expect a mismatch as a result because you have an array on the left side, and something else on the right. The = comparison operator can't handle an array on either side. Try it:
If Range("A1:B1") = Range("C1:D1") Then
The above will also raise a mismatch error, even though both sides of the = are arrays of the same size!
You may want to consider comparing the Address property instead?
hyperlink.range.Address = fromRange.Address
Or
' tests the top/left cell
hyperlink.range(1,1).Address = fromRange.Address

Using concatenated strings as a variable

Evening you fine people!
I am struggling with a particular issue, there are two separate solutions that I have identified but I haven't specifically solved them
I have a list of 37 sheet names in a sheet (A1:A37) and the code I want to run is shown below - I don't know how to set 'z' to the particular cell reference - for example if A1 was Sheet1 I want Z to be Sheet1 and work as a variable. I am using a For loop to loop through the cells.
Workbooks("ED Test.xlsx").Sheets(z).Range("E2:E21").Value = Workbooks("TPT.xlsm").Sheets(z).Range("A2:A21").Value
The second method, more messy was to have the variables set within VBA and using the For loop (i.e. For x = 1 to 37) to concatenate the two values into a variable (e.g. "Sheet" and x) When I do this it gives a different error as it treats the concatenation as a string and not a variable
Please halp :)
You'll need a loop. If you are trying to set E2:E21 in each worksheet to whatever is in that same worksheet's (but in another workbook) range A2:A21 you will loop through those sheets and do pretty much what you have above:
Sub dothething()
Dim cellSheetName As Range
'loop through all the cells holding sheet names in sheet1 (assuming here)
For Each cellSheetName In Sheet1.Range("A1:A37").Cells
'Copy the values in whatever sheet we found
'Noting that the sheetname is held in the cell's value (cellSheetName.value)
Workbooks("ED Test.xlsx").Sheets(cellSheetName.Value).Range("E2:E21").Value = Workbooks("TPT.xlsm").Sheets(cellSheetName.Value).Range("A2:A21").Value
Next cellSheetName
End Sub
This could get more robust, but it's a good starting point.

Excel VBA Evaluate Function Wrong When Reference is Not Active Sheet for Named Range

EDITED WITH BETTER EXAMPLE
I'm trying to use the Evaluate function to evaluate a formula reference for a named range. However, when using the Evaluate function, if you do not explicitly state the sheet reference along with the cell reference, it will assume the active sheet as the cell reference. This causes the wrong result
In my real project I'm trying to only evaluate a part of the named range's formula, so it makes it even trickier.
Using a basic example of what I'm trying to do, let's say you have the following formula in Sheet 1 cell A1 whose name is MyCell:
="Don't evaluate this part"&"My Result Is " & A2
If the Active Sheet is Sheet 2 and you run the following code it will give you the wrong results (this is a quick and dirty example to illustrate the problem)
Dim s As String
s = Replace(Range("MyCell").Formula, """Don't evaluate this part""&", "")
Debug.Print Evaluate(s)
Instead of giving me the value that is in cell A2 of Sheet 1, it gives me the value that is in cell A2 of Sheet2.
Any ideas around this?
This is closest I found, but it is not my exact problem (despite similar titles) and it doesn't provide a solution:
Excel VBA evaluate formula from another sheet
The problem you are having is that by design Excel will assume all unspecific cell references are referring to the existing worksheet. This is why whenever possible it is recommended to explicitly state the worksheet in all code.
The cleanest way (verified with some MSDN definintion hunting) is to just explicitly state the worksheet without activating it:
Sub test2()
Debug.Print Range("MyCell").Worksheet.Evaluate(Range("MyCell").Formula)
End Sub
Alternatively this code will change the active worksheet to the correct one and then change it back after evaluation. Not recommended to perform sheet activations like the code below without extenuating circumstances. Not even here.
Sub test()
Dim ws As Worksheet
Set ws = ActiveSheet
Dim s As String
s = Replace(Range("MyCell").Formula, """Don't evaluate this part""&", "")
Range("MyCell").Worksheet.Activate ' Don't remember if .Worksheet or .Parent ??
Debug.Print Evaluate(s)
ws.Activate
End Sub
As pointed out in the comments by ThunderFrame, it is important to remember that this code assumes MyCell is a simple cell reference as stated in the question. Otherwise you will need to use other methods to determine the target worksheet name (or hardcode it).
Its nearly always better to use Worksheet.Evaluate rather than the default Application.Evaluate: as Mark Balhoff points out that allows you to control unqualified references.
But Worksheet.Evaluate is also usually twice as fast as Application.Evaluate.
See my blog post here for details
https://fastexcel.wordpress.com/2011/11/02/evaluate-functions-and-formulas-fun-how-to-make-excels-evaluate-method-twice-as-fast/
Your line:
Debug.Print Evaluate(Range("MyCell").Formula)
is equivalent to:
Debug.Print Evaluate("=""My Result Is "" & A2")
which is why you get results according to the value of A2 in the ActiveSheet.
If you want to inspect the contents of the formula, you can use this line:
Debug.Print [MyCell].Formula
If you want the value of MyCell with respect to Sheet1, then you have 2 options:
1 - Use Debug.Print Range("Sheet1!MyCell").Value
2 - Use Debug.Print Sheet1.Range("MyCell").Value

Reference a named Excel variable using VBA

Situation: I'm using Excel 10. I have a named variable that uses a formula to compute it's value. This is a named variable, not a named range (in the 'Name Manager' the name "MaxDate" refers to "=MAX(Sheet1!B:B)" where column B is a list of dates). The computed value does not appear in any cells by itself, but rather is used in various formulas within the spreadsheet.
My problem: How can I reference this named variable in VBA code? Using range("MaxDate").value does not work. I know I can simply put the value into a cell and reference the cell, but I'd like to find a way to reference the variable directly. Any ideas?Thanks.
Here's some examples of working with your named variable:
Sub TestNamedVariable()
Dim varFormula As String
Dim varValue As Variant
'This will print the formula if you need it:
varFormula = ActiveWorkbook.Names("MaxDate")
'This will evaluate the named variable/formula
varValue = Application.Evaluate("MaxDate")
End Sub
For a given workbook with a Named variable "MyVar", valued "=MAX(Sheet1!B:B)".
Try the following code:
Evaluate(ActiveWorkbook.Names("MyVar").RefersTo)
Replace the ActiveWorkbook with the object you're referring if you need to.

Resources