Below is a simplified example:
I have contiguous data in A1:E10.
I have a Dynamic Named Range (scope = workbook) defined by the following formula:
DynRange =OFFSET(Sheet1!$A$1,0,0,COUNTA(Sheet1!$A:$A),COUNTA(Sheet1!$1:$1))
I then run the following VBA code:
Dim dynrng As Range
Set dynrng = ThisWorkbook.Names("DynRange").RefersToRange
Range("A8:E10").Delete xlUp
MsgBox ThisWorkbook.Names("DynRange").RefersToRange.rows.Count
MsgBox dynrng.rows.Count
Range("A7:E10").FillDown '(just adding the data back for our next case below)
Both message boxes return 7: i.e. the workbook named range and the VBA range variable we set to it both update as the data is modified.
Now I run the same code but with .ClearContents (or .Clear) instead of .Delete:
Dim dynrng As Range
Set dynrng = ThisWorkbook.Names("DynRange").RefersToRange
Range("A8:E10").ClearContents
MsgBox ThisWorkbook.Names("DynRange").RefersToRange.rows.Count
MsgBox dynrng.rows.Count
The first message box returns 7, but the second returns 10.
Why is the VBA range variable not updating along with the workbook named range in the second case?
(calculation is set to automatic, and I tried Application.Calculate)
Thanks in advance for any illumination!
Name.RefersToRange returns a Range object. Once you have
Set dynrng = ThisWorkbook.Names("DynRange").RefersToRange
dynrng no longer has any reference to the named range, it is simply equivalent to
Sheet1.Range("A1:E10")
If you delete rows, then the Range object is affected accordingly.
If you clear contents, Sheet1.Range("A1:E10") still has 10 rows.
Related
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.
I set up a data to filter by different categories in a drop down list.
The data is on one sheet, and when filters are selected, the results are generated on that same sheet (exactly how a filter would typically work in Excel).
I want that the database is held on a different worksheet, while the filters/drop down menus and results are in another. This is to hide the database with everyone's information, and only populate the results for the filters that were selected.
The autofilter code:
Sub AdvFilt()
Dim rng As Range
Set rng = Range("B13", Range("U") & Rows.Count).End(xlUp))
rng.AdvancedFilter 1, [X1:AE2], 0
End Sub
Is it possible to designate a range from a different worksheet?
If that code is written in the code-behind of, say, your Sheet1 module, then Range("B13") is implicitly Me.Range("B13").
If that code is written in some standard module, say, Module1, then Range("B13") is implicitly referring to whatever worksheet happens to be currently active. Rubberduck (free, open-source VBIDE add-in project that I manage) can locate every instance of such implicit ActiveSheet reference for you.
What you want, is to never use Range, Columns, Rows, Names, or Cells, without a proper, explicit Worksheet qualifier object.
So you declare and assign a Worksheet object:
Dim sourceSheet As Worksheet
Set sourceSheet = ActiveWorkbook.Worksheets("Some Sheet")
Dim destinationSheet As Workshet
Set destinationSheet = ActiveWorkbook.Worksheets("Some Other Sheet")
And now you can get a Range from either sheet:
sourceSheet.Range("B13").Value = 42
destinationSheet.Range("A1").Value = sourceShet.Range("A1").Value
If both sheets exist at compile-time in ThisWorkbook (the host document that contains your VBA project), then you don't need to declare any variables, because VBA already declared them for you - simply locate your sheets in the VBE's Project Explorer, and set their (Name) property to any valid VBA identifier (no spaces, must start with a letter). ...and then you can just use these identifiers in your code as-is.
Watch out here:
Range("U") & Rows.Count
That will throw error 1004, because "U" is not a valid cell reference (unless you have a named range that you conveniently named U, of course), and then the & Rows.Count is being concatenated to whatever Range.[_Default] happens to return for this "U" range (assuming it doesn't just blow up).
You likely meant to concatenate the number of rows with the column heading U, to get a Range off the concatenated cell address:
Range("U" & Rows.Count)
The following excel sub is a filter that is filtering out rows based on the rows in the criteria row.
The code works well when the ranges are set with absolute data. I want to change the code to take the range from references stored as cell values (an indirect reference) but I cannot find a way to adapt other code snippets I see to work and I wonder if anyone can help me. I am not a programmer.
The problem is that as new data is inserted from time to time the range reference will move and the start of the data an the associated filter is in cell (using RC notation 14,14) and the data in cell 13,12. While I know I can’t use the indirect function in vba I wondered if there is a way to dynamically assign a range to be able to use the Advance filter function.
I have the code to find the last column and row of the data block.
I have tried the following code (2 attempts) but it won’t let me use the object in this way
I have tried to crate the cell reference as a string then assign it using the range function. I then read an answer where someone had put the value of the cells directly into the range function and it has worked for them ( they were copying cells). The 2 attempt are broadly the same but in the second I am trying to be more specific.
The issue seems to be as soon as I change from an absolute reference "A50" in the range statement the range no longer works. I am unsure how to resolve this and perhaps it can't be
It may be helpful to know the that data being filtered is rows of name and telephone data along with a tally system to show attendance (one column per week for a year)
The cells with the dynamic data hold them in the form A1 not RC format
Sub UseAdvancedFilterInPlace()
'This version of the sub has absolute references and works perfectly
Dim rdData As Range
Dim rgcriteria As Range
Call TurnOffStuff
Set rgData = Sheet9.Range(“A50”).CurrentRegion
Set rgcriteria = Sheet9.Range(“A46”).CurrentRegion
rgData.AdvancedFilter xlFilterInPlace, rgcriteria
Call TurnOnStuff
End Sub
Sub UseAdvancedFilterInPlace()
'This version of the sub has dynamic references and fails
Dim rdData As Range
Dim rgcriteria As Range
Call TurnOffStuff
Dim Top_of_data As String
Dim Top_of_Criteria As String
Dim My_range As Range
‘Attempt 1
'Set rgData = Range(Sheet9.Cells(13, 12).Value).CurrentRegion
'Set rgcriteria = Range(Sheet9.Cells(14, 14).Value).CurrentRegion
'Attempt 2
Set rgData = Sheet9.Range(Sheet9.Range(Cells(13, 12)).Value).CurrentRegion
Set rgcriteria = Sheet9.Range(Sheet9.Range(Cells(14, 14)).Value).CurrentRegion
rgData.AdvancedFilter xlFilterInPlace, rgcriteria
Call TurnOnStuff
End Sub
The actual error message I get is an application-defined or object-defined error
This worked for me.
Set rdData = Sheet9.Range(Sheet9.Range("L13").Value).CurrentRegion
Set rgcriteria = Sheet9.Range(Sheet9.Range("N15").Value).CurrentRegion
given that Range("L13").Value is A50 and Range("N15").Value is A46.
extra: Use the statement Option Explicit in the first line of every module, out of every sub or function. This option throws an error on undeclared variables, and will help you avoid renameing mistakes on variables.
Complete VBA beginner and on a button click trying to take a value (it will be the eventual column value for pasting command) stored at RawData sheet in A1, convert it to text or a value (i guess?) and store it as revValue then add it to a range in a copy and paste command. I know the copy and paste is working fine when i just put Range values i.e. Destination:=dir.Range("A30"), but with Destination:=dir.Cells(30, revValue) getting 'Compile error: Method or data member not found' ...sure it's a really obvious one but driving me nuts. Any help appreciated.
Private Sub Import_Click()
Dim source As Worksheet
Dim dir As Worksheet
Dim revValue As Range
Set source = Worksheets("RawData")
Set dir = Worksheets("Register")
'Store the RawData Cell value as a text or integer?
Set revValue = source.Cell(1, 1).Value
Worksheets("RawData").Activate
Worksheets("RawData").Columns("A:D").AutoFit
'These two function work fine
source.Range("C1", Range("C1").End(xlDown)).Copy Destination:=dir.Range("A30")
source.Range("D1", Range("D1").End(xlDown)).Copy Destination:=dir.Range("J30")
'This one doesn't
source.Range("E1", Range("E1").End(xlDown)).Copy Destination:=dir.Cells(30, revValue)
End Sub
.Cells uses integers but you're using revValue which is a range variable.
I set up a named range, let's call him RngIn.
He has 3 cells, and his address refers to A1:A3
Next, I delete Row 2.
My RngIn now shows #REF! error (correctly) in its RefersTo property:
"=A1,Sheet1!#REF!,A2"
This means I cannot manipulate the rest of that named range using VBA, because of the Method 'Range' of Global Object error.
The range is created during a process, and if a user subsequently needs to delete one row for whatever reason, my future code will fail because it needs to know where the rest of the named range data is...
I have tried many ways to access the remaining address information for this range, in VBA, but failed so far, e.g.
Dim RngAddress As String
Dim RngIn As Range
Set RngIn = Range("A1:A3")
RngAddress = RngIn.Address
RngAddress = RngIn.RefersToRange.Address
RngAddress = RngIn.RefersTo
RngAddress = Replace(RngIn.Address, "Sheet1!#REF!", "")
What I ideally want to see in a text string as the result for RngIn is:
"=A1,A2"
Because A2 is now the location of the data which was originally in A3.
Not sure I understand this well: your example code does not use Defined Names (aka Named Ranges).
lets suppose you create a Name called RangeIn that refers to A1,A3,A5 and you then delete Row 3.
The RefersTo for RangeIn is now =Sheet1!$A$1,Sheet1!#REF!,Sheet1!$A$4
This code removes the Sheet1!#REF!, to leave the Name RangeIn referring to =Sheet1!$A$1,Sheet1!$A$4
Option Explicit
Option Compare Text
Sub ChangeRef()
Dim strAd As String
strAd = ThisWorkbook.Names("RangeIn").RefersTo
strAd = Replace(strAd, "Sheet1!#REF!,", "")
ThisWorkbook.Names("RangeIn").RefersTo = strAd
End Sub
In cases like this, I set the start and end points of my named ranges to be the cell above and the cell below the range where the user can delete, and then use the OFFSET or INDEX function to resize that range to exclude my bookmarks. Or I use Excel Tables, which can handle row deletions without returning #REF errors.