Evaluate a relative reference from VBA relative to a known cell - vba

I'm using the trick described here - !A1 - to get the the range of cells up to and including the current one (typically for a Rows() function). This works great for day to day usage, and eliminates a lot of errors that I get when moving ranges around when I previously had to use an adjacent set of rows.
Unfortunately, my formulas need to be evaluatable from VBA. With __THISCELL__ as my !A1 cell, and the cell housing the formula as $Z$100 the following evaluates to an error:
Application.Evaluate(rngCell.formula)
And the following evaluates to $A$1:$Z$50
rngCell.Worksheet.Evaluate(rngCell.formula)
Obviously an approach is to replace __THISCELL__ with rngCell.Address(External:=True) prior to evaluation, but here's the kicker: I'd like to be able to execute my formula parser in a workbook which uses, say THIS_CELL, THISCELL or __THISCELL safely, and I'd also like to be able to safely execute my code in a workbook with a name like __NOT__THIS_CELL__.
All I need for this is a mechanism to evaluate relative references relative to a specific cell address - which since people do use R1C1 references in VBA a fair bit, I imagine must be around. However, I don't know it. Can anyone help?
NB: I like to avoid fiddling with ActiveCell, Selection, etc. where possible, since those smell like the excel equivalent of SendKeys - who knows what the user is doing when you access them. Even then, though, I'm not certain I'll get the right answer, because for the Worksheet.Evaluate approach, I'm not positioned in cell $A$1!

If I understand your question, I believe you're looking for the Range().Offset method.
Range().Offset(rOffset, cOffset) refers to a range that is rOffset lower and cOffset to the right of the given range (negative values for up and left are allowed). Also, .Offset can access and set all of the properties of the range, just like you would do with .Range.

The approach I've taken for the time being is implicit in the question: when a named range is detected, store the current selection and worksheet, select the one which we use as the evaluation context, and then use Evaluate. This seems to work, provided the cell being evaluated is inside the activesheet.
I don't like jumping the selection all over the place - feels dirty - but short of a more elegant solution, it does work.

Related

Deciphering an Excel macro; Range.Select, then Range.Activate, then Selection.ClearContents?

This is the code I have in a [very involved] spreadsheet someone made at work:
Sub ClearSheet()
'
' Macro5 Macro
'
'
Range("E9,E2:F7,C14:I39,Q41:Q55,N14:N39,N41:N55").Select
Range("Q14").Activate
Range("E9,E2:F7,C14:I39,C41:I55,Q41:Q55,N14:N39,N41:N55,L41:L55").Select
Range("Q41").Activate
Selection.ClearContents
I have never so much as glanced at an excel macro before, so I had to look some things up. I get that the first range is selected and then Q14 becomes the active cell. Then that is done again, with some overlapping sections, and Q41 is made into the active cell. All to have the selections just be cleared out. I'm sure this is a simple question but I don't understand what the point is of the .Activates, or why someone would separate the sections that need to be cleared into two separate segments? From my very limited understanding, I thought Activate was something like focus, where that is now that cell that has focus for ease of use on the users side. But what good is that if the focus changes from the first cell to the second cell in a millisecond?
All I know is that I need these cells:
E9,E2:F7,C14:I39,N14:N39,C41:I55,L41:L55,N41:N55,Q41:Q55
to clear out when this code is run, and if this code is doing something in addition to that, what is it?
Is this just poorly written or am I too ignorant to understand? ~the novel~
Use
Range("E9,E2:F7,C14:I39,N14:N39,C41:I55,L41:L55,N41:N55,Q41:Q55").ClearContents
Better still specify the workbook and worksheet to do this in e.g.
ThisWorkbook.Worksheets("Sheet1").Range("E9,E2:F7,C14:I39,N14:N39,C41:I55,L41:L55,N41:N55,Q41:Q55").ClearContents
Using sheet 1 as an example. You want to be sure to be in the right sheet before clearing stuff out. If you don't specify, and leave as just range, then the currently Active sheet is used.
In the code you talked about the each selection was shifting focus from the prior making the prior selections redundant.
Using Select, in particular, is not generally a good thing, it means 'touching' the sheet which incurs potentially unnecessary performance overhead.
As mentioned in comments, and indicated by ' Macro5 Macro, this is, at least in part, likely all, macro generated code. Macro meaning "many". Many instructions in this case. The macro recorder is verbose to say the least. It records everything your are doing including scrolling, mistakes in range selections etc. It is a good learning tool, and can often give useful insights into some objects and methods. The valuable skill is learning which elements to keep and how to turn this verbose code into structured programming.
The way you interpret Select and Activate is correct, one is for the actual selection and the other is somewhat to focus.
Select as the method name suggest selects the object. This method is not limited to Range Objects alone but is shared by most of the objects in Excel. Some of the examples:
Range("A1").Select '/* selecting a Range Object */
Worksheets("Sheet1").Select '/* selecting a Sheet Object */
Activate on the other hand works when you already selected an object.
Activates a single cell, which must be inside the current selection. To select a range of cells, use the Select method.
So what happens when you activate a cell not in the current selection?
It becomes the selected cell and as you've said, Excel executes the Select first and then the Activate in mili or nano or pico seconds (God knows how fast) interval.
In Range Objects the use of Select and Activate is almost interchangeable. But you have to take note that there will be difference always with Selection and ActiveCell. For example:
Range("A1:B10").Select
Range("B5").Activate
Debug.Print Selection.Address
Debug.Print ActiveCell.Address
This means that you can actually do stuff (e.g. format, clear, add formula, add text etc.) on all cells you activate within the current selection but still preserves what Selection object points to.
There are cases that activating the object is vital. For example you want to select multiple worksheets like below and then select Range("A1") of Sheet3.
Worksheets(Array("Sheet1", "Sheet3", "Sheet5")).Select
Worksheets("Sheet3").Activate '/* vital */
Worksheets("Sheet3").Range("A1").Select
Above is the correct select command for multiple worksheet selection and selecting a range within 1 of the worksheets selected. But without the Activate part, there is a chance that it will return:
Run-time error '1004': Select method of Range class failed
because the first sheet in the array will always be the activated sheet object after the select. Now, how to avoid this troubles? Simple, avoid using select and activate. ~the novel sequel~

SUMIFS returns 0 using dynamic criteria, with criteria range and sum range on another sheet

Anyone,
I've chatted with and called excel customer service with no luck. I used the formula builder (please see attached screenshot) to make sure each element of the formula is correct and returns the value for the criteria I'm trying to reference.
Everything is accurate, but it returns a value of 0. When I do the same thing in the actual sheet the data is stored in (and click a criteria cell within the criteria range) it returns the accurate value?! I'm not sure why it won't work on the other sheet. The values I am using to select are dynamic and change with a drop down. I have another, advanced, workbook (I did not create) that does the same thing and completes an even more complicated formula, but actually works so I'm not sure why this is returning a 0 value.
Photos and code/syntax: Dynamic Selection, Example 2 of it working, Example 1 of it working, Formula Builder, CountIFs, Advanced Spreadsheet working, VLOOKUP
=SUMIFS('GFEBS Pull'!Q:Q,'GFEBS Pull'!G:G,FMCOP!$C$20,'GFEBS Pull'!H:H,FMCOP!B23)
or:
=SUMIFS('GFEBS Pull'!Q:Q,'GFEBS Pull'!G:G,'FMCOP'!$C$20,'GFEBS Pull'!H:H,'FMCOP'!B23)
When I type ' around FMCOP sheet name, they disappear? I've also tried to lock the columns on the 'GFEBS Pull' sheet with no luck. Cell B23 is not locked because I'm going to copy the formula down to reference other cells. Any help is appreciated!
In this screenshot you can clearly see that both FMCOP!C20 ansd FMCOP!B23 have prefacing spaces; e.g. " HHC".
Since " HHC" will never match "HHC", fix the data returned from 'the lower table in the same screenshot'.
A Text-to-Columns, Fixed Width, Finish should do this. You could adjust the original formula like,
=SUMIFS('GFEBS Pull'!Q:Q, 'GFEBS Pull'!G:G, TRIM(FMCOP!$C$20), 'GFEBS Pull'!H:H, TRIM(FMCOP!B23))
I would caution against the latter 'bandaid' fix. Fix the original data; do not apply bandaids on-the-fly.

Excel // Dynamic Range in Sum If Function

I’m trying to have a quite complicated Sum if function more reliable. The situation is that I’m using that formula (see below) to sum values between two dates from a separate sheet in the workbook.
=SUMIFS(wochen!$F11:$BM11;wochen!$F$8:$BM$8;">="&DATE(YEAR(T$8);1;1);wochen!$F$8:$BM$8;"<="&DATE(YEAR(T$8);MONTH(T$8);DAY(T$8)))
The handicap is that I was hoping to implement some Kind of Lookup function or match function to always get the values from the correct row.
So far the sum_range is static and I can’t make sure its picking the correct line.
I already played around with this kind Address / Match Function
=ADDRESS(MATCH($B13;'F_P&L'!$B$1:$B$267;0);MATCH(T$6;'F_P&L'!$F$6:$CP$6;0);;;"wochen!")
into that Sum_Range of the Sum If Formular but that doesn’t work either as it comes out as Text
I’m happy for any Idea which van be implemented via excel Formulas or VBA
I hope I explained everything clear, if not I'm happy to provide more information's
Many Thanks in Advance
Dennis
Here is a simple example which you can extend to your case. It uses the =INDIRECT() formula.
If you know through some way what the correct rows are, say you know your range of interest is in line 11, ...
Put the following in a cell
A1: = 11 ' You can use VLOOKUP etc. to generate the row number
B1: = "F" & A1 & ":BM"&A1 ' That makes B1 read: F11:BM11
C1: = SUM(INDIRECT(B1)) ' This will make it the equivalent of writing =SUM(F11:BM11)
Writing =function(INDIRECT(*anything*)) where *anything* is a string describing the address of a range is the equivalent of writing =function(** selecting the range **)
Note on Performance: Do note, however, that INDIRECT is a volatile functions so any changes in the workbook with calculations set to automatic will cause it to recalculate. So there might be performance implications in certain cases.

How can I create sheet-specific named ranges without using Indirect?

I've come up with a useful trick, where I create a named range that refers to the current worksheet, by using the following formula:
=RIGHT(CELL("filename",INDIRECT("A1")), LEN(CELL("filename",INDIRECT("A1"))) - FIND("]",CELL("filename",INDIRECT("A1"))))&T(NOW())
Where the INDIRECTs are there ONLY to stop Excel from Converting A1 --> Sheet1!A1. This works beautifully until I need to call evaluate on it from VBA (which does happen).
Can anyone tell me how either (1) to evaluate a name with this formula in VBA or (2) to get a sheet non-specific reference into the formula. I'd rather not use VBA, since it'll get evaluated ~12000 times, and that's likely to be slow, but if need be, it's probably ok. However, please bear in mind that the sheet it is calculated from is quite unlikely to be ActiveSheet, so the context for the Range() function in VBA is a little tricky - hence why I'm asking in the first place.
One possible approach: use a simple UDF which just returns the name of the sheet it's called from.
Eg:
Function SheetName()
SheetName = Application.Caller.Parent.Name
End Function

Proper procedure for declaring global variables in VBA?

I have a macro workbook with a number of worksheets that exist permanently, which are constantly cleared, updated, etc. Since they are referred to in various subroutines, I have made each corresponding worksheet object a pseudo-global variable in the following manner, for example for the "Main" sheet:
Function MAIN() As Worksheet
Set MAIN = ThisWorkbook.Sheets("Main")
End Function
By doing so, I can then refer to each sheet in the other subroutines, for example:
MAIN.Cells.ClearContents
I have also defined some pseudo-global constants which are located in a fixed place on the "Main" sheet in a similar way, for example:
Function NumLines() As Integer
NumLines = MAIN.Range("C3").Value
End Function
In this way, I use "NumLines" just like any variable throughout the code.
I expect that there is a more efficient way to manage globally accessed variables like these and was wondering, what would be a better way to accomplish this?
For reliable sheet reference I would suggest to use Sheet.CodeName Property. Each sheet has its unique CodeName which you could find in the place marked yellow on the picture below.
For quick reference to cell value I would suggest to use Range name. After you select you C3 cell you need to put unique name in the box marked yellow below. All Range names are unique in the workbook.
As a result you can use sheet and cell reference as presented below in each of your subroutines in your project.
Sub Test_Macro()
Debug.Print MAIN.Name '>> result: Sheet1
Debug.Print Range("CellC3").Value '>> result: 100
End Sub
I expect that there is a more efficient way to manage globally accessed variables like these and was wondering, what would be a better way to accomplish this?
When I use global variables in VBA, I do three things.
I always preface global variables with a g_ prefix. It seems often that a global variable in VBA is useful. But I've also spent far too long trying to track down "what variables are global or not?" in other people's code. Keeping a very clear naming convention will save you and whoever looks at your code a TON of hassle in the future.
This is even more important if you are less experienced as a developer. Avoiding globals is hard in VBA, and the less experience you have, the more likely it is you will use globals. For others to help or maintain the code this becomes so important.
If you are going to be using even a small number of global variables, you must use Option Explicit unless you want to cause nightmares in maintaining code. It's hard enough to track down these errors when you wrote code let alone months or years later.
I always create a module which is called "GlobalVariables" or something similar. That module contains all of the global declarations in one location. For larger code bases this can become longer but it has always paid off for me because I know exactly where all my globals are defined. None of the "which file is this variable actually being defined in?" game.
Just an unrelated note, too, in your first example - I would use the code name rather than that function. Each VBA worksheet has a sheet name ("Main" in your case) as well as a codename, which you can set in VBA and remains the same. This prevents users from changing the name of "Main" and breaking code.
You can also refer directly to them similar to how you are using MAIN.Cells. KazJaw has a good example of this.