VBA EVALUATE() Misbehavior - vba

I have a set of code that seeks out vlookups within formulas within a range of a sheet and calculates them. This worked perfectly and then one morning I came in and it didnt. Offending Code here:
sValue = Application.Evaluate(sVLOOKUP)
If Len(sValue) = 0 Then sValue = "#N/A"
for example
=VLOOKUP(Box,A1:B2,2,0)
in table
A B
Box 1
Car 2
When manually highlighting the VLOOKUP within the in question sheet and calculating all is fine, however, this code always pulls NA because the evaluation always evaluates to ERROR 2042
My question then is what could cause the EVALUATE function to always evaluate to ERROR 2042 (NA) when the vlookup is without a doubt evaluatable?
EDIT
it would seem that the underlying issue is that I am using a form tool to reference the desired sub something to do with
Dim OutputFolder As String, GetBook As String, BookCopy As String, wb As Workbook
OutputFolder = GetFolder("C:\")
GetBook = ActiveWorkbook.Name
BookCopy = OutputFolder & "\Client Copy " & GetBook
ActiveWorkbook.SaveCopyAs BookCopy
Set wb = Workbooks.Open(BookCopy)
Dim wsDataLoad As Worksheet, wsEconomics As Worksheet, wsUI As Worksheet, wsTypeCurves As Worksheet, wsSWM As Worksheet, wsCC As Worksheet, wsSummary As Worksheet
Set wsDataLoad = wb.Sheets("DataLoad")
interaction with the formtool vs running the sub directly alters the desired output of the sub

Application.Evaluate has a counter-part Worksheet.Evaluate - you should use the latter if you want to be sure your string is evaluated in the context of a specific sheet, and doesn't just default to whatever sheet is active when you run your code.
Since you've stated the string sVLOOKUP holds the correct value, this seems to be the likely explanation for what you're seeing.

Related

Worksheet.Select switching screens excel VBA

I currently have 3 sheets: Input, Process, Output and a macro that uses values displayed on the input sheet and various stores on the process sheet. The problem is when the user presses a submit button linked to the macro on the input page the sheet switches to the Process sheet before displaying the Output sheet. I understand that this is because of this line of code:
Worksheets("Process").Select
However whenever I remove it from the macro everything goes madly out of range. Is there any way of selecting a sheet without actually visually moving to it? I need the macro to do its thing and then simply display the output sheet. Thanks in advance!
As #Jeeped stated and referenced, avoid using Select and Activate, in addition it is safer to qualify references.
For example you can use Range("A1").Value to get a value of the cell A1 in the currently active worksheet, but what if the user didn't have that sheet active at the time or another proc had moved the view? you could get the value of cell A1 from potentially any worksheet.
It would be best to create a reference to the worksheet and then send all your work through it, this way you do not need to change the active worksheet and there is no ambiguity about where the range values are coming from.
For example: -
Option Explicit
Dim WkSht_I As Worksheet 'Input
Dim WkSht_P As Worksheet 'Process
Dim WkSht_O As Worksheet 'Output
Public Sub Sample()
Set WkSht_I = ThisWorkbook.Worksheets("Input")
Set WkSht_P = ThisWorkbook.Worksheets("Process")
Set WkSht_O = ThisWorkbook.Worksheets("Output")
MsgBox "Input A1 = " & WkSht_I.Range("A1").Value
MsgBox "Process A1 = " & WkSht_P.Range("A1").Value
MsgBox "Output A1 = " & WkSht_O.Range("A1").Value
Set WkSht_O = Nothing
Set WkSht_P = Nothing
Set WkSht_I = Nothing
End Sub
Converting your procedures to this method should be safer and clearer and you can set the active sheet just once for it to show content while the others or being worked on.
#Gary's method is the best method to go with when you are working with multiple worksheets.
If you are working with only two sheets, (Considering you have activesheet and target sheet) I am going to recommend
With Worksheets("Process")
Debug.Print .Range("A1")
Debug.Print Range("A1")
End With
Notice "." infront of Range.
The "." indicates that it is part of With
In other words, .Range("A1") is same as Worksheets("Process").Range("A1")
Because second Range("A1") does not have "." it is same as Activesheet.Range("B1") even it's inside of the With-End
If the activesheet is Process Then the out put will be same
But when you select worksheet other than Process, because activesheet changed, the output will be different.
This will avoide using Select which changes the activesheet

“Unable to get the VLookup property of the WorksheetFunction Class” vlookup in a loop

I'll just say right off the bat that I'm a newb in coding overall. I've learned a lot along the way creating various things at work for myself and I'm learning all the time.
The problem I have right now is with a loop using application.vlookup(...). I've done a lot of searching around but I gave up because I couldn't get anything to work. I've removed the .WorksheetFunction part from the original code because apparently it doesn't change anything but at least it doesn't give VBA errors when a value is not found and acts similar to the normal function (gives #N/A).
So in the below code I have my data in column A which I need to convert using vlookup on a Query in another worksheet (and put that in the same sheet as the data, in column C).
Sub vlookup()
Dim wsdata As Worksheet
Dim lr As Long
Dim rng As Range
Set wsdata = ThisWorkbook.Worksheets("Data")
wsdata.Columns("C").Delete
wsdata.Range("D1") = "Correct number"
lr = wsdata.Cells(Rows.Count, "A").End(xlUp).Row
Set rng = wsdata.Range("A1:D" & lr)
Dim lrQuery As Long, iNr As Long
Dim wsQuery As Worksheet
Dim LookUpRange As Range
Set wsQuery = ThisWorkbook.Sheets("IDMquery")
lrQuery = wsQuery.Cells(Rows.Count, "A").End(xlUp).Row
Set LookUpRange = wsQuery.Range("D:F")
For iNr = 2 To lr
wsdata.Cells(iNr, 4).Value = Application.VLookup(wsdata.Cells(iNr, 1).Value, LookUpRange, 3, 0)
Next iNr
End Sub
I have to add that there are some issues with the formatting (or whatever they are), because the original data is external. Eg. for "17935" VarType would give 8 (string) instead of a numeric type.
In the spreadsheet it's easy to fix it with the double unary and the following formula works correctly:
=VLOOKUP(--A2,IDMquery!D:F,3,false)
Of course I could use a helper column and just use that, which might be an option if I can't do it any other way, but I would prefer strictly within VBA.
Do you guys know if this is fixable?
Cheers!
EDIT: Btw, I also changed the formatting of column A to "0" in VBA but it doesn't actually want to change these values to numeric, not sure why.

Using Vlookup to copy and paste data into a separate worksheet using VBA

Alright I'm a beginner with VBA so I need some help. Assuming this is very basic, but here are the steps I am looking at for the code:
-Use Vlookup to find the value "Rec" in column C of Sheet1, and select that row's corresponding value in column D
-Then copy that value from column D in Sheet1 and paste it into the first blank cell in column B of another worksheet titled Sheet2
I've got a basic code that uses Vlookup to find Rec as well as it's corresponding value in column D, then display a msg. The code works fine, and is the following:
Sub BasicFindGSV()
Dim movement_type_code As Variant
Dim total_gsv As Variant
movement_type_code = "Rec"
total_gsv = Application.WorksheetFunction.VLookup(movement_type_code,Sheet1.Range("C2:H25"), 2, False)
MsgBox "GSV is :$" & total_gsv
End Sub
I also have another one that will find the next blank cell in column B Sheet2, it works as well:
Sub SelectFirstBlankCell()
Dim Sheet2 As Worksheet
Set Sheet2 = ActiveSheet
For Each cell In Sheet2.Columns(2).Cells
If IsEmpty(cell) = True Then cell.Select: Exit For
Next cell
End Sub
Not sure how to integrate the two, and I'm not sure how to make the code paste the Vlookup result in Sheet2. Any help would be greatly appreciated, thanks!
So for being a beginner you're off to a good start by designing two separate subroutines that you can confirm work and then integrating. That's the basic approach that will save you headache after headache when things get more complicated. So to answer your direct question on how to integrate the two, I'd recommend doing something like this
Sub BasicFindGSV()
Dim movement_type_code As Variant
Dim total_gsv As Variant
movement_type_code = "Rec"
total_gsv = Application.WorksheetFunction.VLookup(movement_type_code, Sheet1.Range("C2:H25"), 2, False)
AssignValueToBlankCell (total_gsv)
End Sub
Sub AssignValueToBlankCell(ByVal v As Variant)
Dim Sheet2 As Worksheet
Set Sheet2 = ActiveSheet
For Each cell In Sheet2.Columns(2).Cells
If IsEmpty(cell) = True Then cell.Value2 = v
Next cell
End Sub
That being said, as Macro Man points out, you can knock out the exact same functionality your asking for with a one liner. Keeping the operational steps separate (so actually a two liner now) would look like this.
Sub FindGSV()
AssignValueToBlankCell WorksheetFunction.VLookup("Rec", Sheet1.Range("C2:H25"), 2, False)
End Sub
Sub AssignValueToBlankCell(ByVal v As Variant)
Sheet3.Range("B" & Rows.Count).End(xlUp).Offset(1, 0).Value2 = v
End Sub
Like I said, if you plan to continue development with this, it's usually a good idea to design your code with independent operations the way you already have begun to. You can build off of this by passing worksheets, ranges, columns, or other useful parameters as arguments to a predefined task or subroutine.
Also, notice that I use Value2 instead of Value. I notice you're retrieving a currency value, so there's actually a small difference between the two. Value2 gives you the more accurate number behind a currency formatted value (although probably unnecessary) and is also faster (although probably negligible in this case). Just something to be aware of though.
Also, I noticed your use of worksheet objects kind of strange, so I thought it'd help to mentioned that you can select a worksheet object by it's object name, it's name property (with sheets() or worksheets()), index number (with sheets() or worksheets()), or the "Active" prefix. It's important to note that what you're doing in your one subroutine is reassigning the reference of the Sheet2 object to your active sheet, which means it may end up being any sheet. This demonstrates the issue:
Sub SheetSelectDemo()
Dim Sheet2 As Worksheet
Set Sheet2 = Sheets(1)
MsgBox "The sheet object named Sheet2 has a name property equal to " & Worksheets(Sheet2.Name).Name & " and has an index of " & Worksheets(Sheet2.Index).Index & "."
End Sub
You can view and change the name of a sheet object, as well as it's name property (which is different) here...
The name property is what you see and change in the worksheet tab in Excel, but once again this is not the same as the object name. You can also change these things programmatically.
Try this:
Sub MacroMan()
Range("B" & Rows.Count).End(xlUp).Offset(1, 0).Value = _
WorksheetFunction.VLookup("Rec", Sheet1.Range("C2:H25"), 2, False)
End Sub
The Range("B" & Rows.Count).End(xlUp) command is the equivalent of going to the last cell in column B and pressing Ctrl + ↑
We then use .Offset(1, 0) to get the cell after this (the next blank one) and write the value of your vlookup directly into this cell.
If Both work, then good, you have two working subs and you want to integrate them. You probably want to keep them so they might be useful for some other work later. Integrating them means invoking them in some third routine.
For many reasons, it is surely better and advised to avoid as much as possible to use (select, copy, paste) in VBA, and to use rather a direct copying method (range1.copy range2).
You need to make your routines as functions that return ranges objects, then in some third routine, invoke them
Function total_gsv() as range
Dim movement_type_code As Variant: movement_type_code = "Rec"
Set total_gsv = Application.WorksheetFunction.VLookup(movement_type_code,Sheet1.Range("C2:H25"), 2, False)
End Sub
Function FindFirstBlankCell() as Range
Dim Sheet2 As Worksheet: Set Sheet2 = ActiveSheet
For Each cell In Sheet2.Columns(2).Cells
If IsEmpty(cell) Then Set FindFirstBlankCell= cell: exit For
Next cell
End Sub
Sub FindAndMoveGsv()
total_gsv.copy FindFirstBlankCell
... 'some other work
End Sub

.find finding empty cell

I have a VBA code that searches for a value in another workbook, in all sheets of it, and then writes yes or no in the source file. I use the .find argument inside a loop so it searches in all sheets, and this is inside another loop so it searches for multiple values. The searched value is set as a variable. Turns out that when I went to look in a sample if it was doing it right, sometimes it returns the address of an empty cell, for example: the searched value is 8 and it says that it found the value but when I look at the address of it, it is an empty cell, so in reality the find didn't really work.
Also, I set a variable using a value from the source file that needs to be the same value of a cell offset from the result, and if it is not, I made the code continue searching. When it finds this empty cells, it stops, I don't know why...
The code is something like this, I left some parts of it that are not useful.
dim NFE as range
dim searchedvalue as range
dim validation as range
dim sh as worksheet, wb as workbook
set searchedvalue = thisworkbook.worksheets (name).range
set validation = thisworkbook.worksheets (name).range
for each sh in wb.worksheets
set nfe = Workbooks(wbname).Sheets(sh.Name).Columns([column]).Find(searchedvalue, LookIn:=xlValues, lookat:=xlWhole)
if nfe.offset ().value = validation then
exit for
thisworkbook.range().value = "yes"
else
thisworkbook.range ().value = "no"
end if
next sh
What is wrong?
I discovered the issue. My code was missing a thisworkbook.sheets string when defining searchedvalue, so when it would set it, it would get an empty cell and that would give me the wrong result. Works fine now, thank you all for the help.

loop through specified sheets in VBA

I am trying to use a bit of code I found here For Each Function, to loop through specifically named worksheets to loop through specified sheets in a workbook, run a small amount of code and move to next sheet.
Sub LoopThroughSheets()
Dim Assets As Worksheet
Dim Asset As Worksheet
Assets = Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables")
For Each Asset In Assets
'my code here
MsgBox ActiveSheet.Name 'test loop
Next Asset
End Sub
This is not looping through the sheets. I tried Dim Assets as Worksheet but this broke the code.
Any help much appreciated,
Cheers
The code you show in your question fails because of:
Assets = Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables")
Assets is a Worksheet which is a type of Object and you must use Set when assigning a value to an Object:
Set Assets = Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables")
This would fail because Array("…") is not a worksheet.
You imply that an earlier version of your code would run but did not loop through the worksheets. The reason is:
MsgBox ActiveSheet.Name
This displays the name of the active worksheet but nothing in this loop changes the active worksheet.
I am not happy with your solution although there is nothing explicitly wrong with it. I have seen too many programs fail because the programmer did too much in a single statement. Firstly, the more complicated a statement, the longer it will take to get right in the first place and the longer it takes to understand during subsequent maintenance. Sometimes the original programmer got the statement slightly wrong; sometimes the maintenance programmer got it wrong when trying to update it. In every case, any saving in runtime was not justified by the extra time spend by the programmers.
Alex K has fixed your code by redefining Assets and Asset as Variants, as required by VBA, and adding Sheets(Asset).Select to change which worksheet is active. I cannot approve of this because Select is a slow statement. In particular, if you do not include Application.ScreenUpdating = False, the duration of your routine can go through the roof as the screen is repainted from each Select.
Before explaining my solutions, some background on Variants.
If I write:
Dim I as Long
I will always be a long integer.
At runtime, the compiler/interpreter does not have to consider what I is when it encounters:
I = I + 5
But suppose I write:
Dim V as Variant
V = 5
V = V + 5
V = "Test"
V = V & " 1"
This is perfectly valid (valid but not sensible) code because a Variant can contain a number, a string or a worksheet. But every time my code accesses V, the interpreter has to check the type of the current contents of V and decide if it is appropriate in the current situation. This is time consuming.
I do not want to discourage you from using Variants when appropriate because they can be incredibly useful but you need to be aware of their overheads.
Next I wish to advocate the use of meaningful and systematic names. I name my variables according to a system that I have used for years. I can look at any of my programs/macros and know what the variables are. This is a real time saver when I need to update a program/macro I wrote 12 or 15 months ago.
I do not like:
Dim Assets As Variant
Assets = Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables")
because "pipe_mat_tables" and so on are not assets; they are the names of worksheets. I would write:
Dim WshtNames As Variant
WshtNames = Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables")
My first offering is:
Option Explicit
Sub Test1()
Dim WshtNames As Variant
Dim WshtNameCrnt As Variant
WshtNames = Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables")
For Each WshtNameCrnt In WshtNames
With Worksheets(WshtNameCrnt)
Debug.Print "Cell B1 of worksheet " & .Name & " contains " & .Range("B1").Value
End With
Next WshtNameCrnt
End Sub
I could have named WshtNameCrnt as WshtName but I was taught that names should differ by at least three characters to avoid using the wrong one and not noticing.
The Array function returns a variant containing an array. The control variable of a For Each statement must be an object or a variant. This is why I have defined WshtNames and WshtNameCrnt as Variants. Note, your solution worked because a worksheet is an object.
I have used With Worksheets(WshtNameCrnt) which means any code before the matching End With can access a component of this worksheet by having a period at the beginning. So .Name and .Range("B1").Value reference Worksheets(WshtNameCrnt) without selecting the worksheet. This is faster and clearer than any alternative.
I have used Debug.Print rather than MsgBox because it is less bother. My code runs without my having to press Return for every worksheet and I have a tidy list in the Immediate Window which I can examine at my leisure. I often have many Debug.Print statements within my code during development which why I have output a sentence rather than just a worksheet name or cell value.
My second offering is:
Sub Test2()
Dim InxW As Long
Dim WshtNames As Variant
WshtNames = Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables")
For InxW = LBound(WshtNames) To UBound(WshtNames)
With Worksheets(WshtNames(InxW))
Debug.Print "Cell B1 of worksheet " & .Name & " contains " & .Range("B1").Value
End With
Next InxW
End Sub
This macro has the same effect as the first. I sometimes find For more convenient than For Each although I can see no advantage either way in this case. Note that I have written LBound(WshtNames) even though the lower bound of WshtNames will always be zero. This is just me being (over? excessively?) precise.
Hope this helps.
Solved it but always happy to hear other methods
Sub loopsheets()
Dim Sh As Worksheet
For Each Sh In Sheets(Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables"))
MsgBox Sh.Range("b1")
Next
End Sub
Cheers
Use variants instead of worksheets.
Array returns a Variant array of string so cant be cast to Worksheet, the Each variable must be a Variant.
Dim Assets As Variant
Dim Asset As Variant
Assets = Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables")
For Each Asset In Assets
'my code here
Sheets(Asset).Select
MsgBox ActiveSheet.Name 'test loop
Next Asset