Upgrading VBA 6->7 causes error: If Exists in Collection - vba

VBA6 code (Excel) worked great. Upgrade to Office 2010/VBA7, code breaks.
Using code from SO:
Determining whether an object is a member of a collection in VBA
Public Function Contains(col As Collection, key As Variant) As Boolean
Dim obj As Variant
On Error GoTo err
Contains = True
obj = col(key)
Exit Function
err:
Contains = False
End Function
I get run-time error 5: Invalid procedure call or argument.
It doesn't make sense to me though because the error occurs on the obj = col(key) line which should be covered by the On Error GoTo err statement, but it stops.
Other If X exists in collection type solutions have the same problem.
Instead of fixing the broken code, what I really need is to be able to see if a record is already set for a collection, if that can be done some other (new) way in VBA7, that would solve the problem as well (I can dream).

I find that if I change specify an object, e.g., a worsheet, it works:
Public Function Contains(col As Collection, key As Variant) As Boolean
Dim ws As Excel.Worksheet
On Error GoTo err
Contains = True
Set ws = col(key)
Exit Function
err:
Contains = False
End Function
I call it like this:
Sub test()
Dim ws As Excel.Worksheet
Dim coll As Collection
Set coll = New Collection
For Each ws In ThisWorkbook.Worksheets
coll.Add ws, ws.Name
Next ws
Debug.Print Contains(coll, ActiveSheet.Name)
Debug.Print Contains(coll, "not a worksheet name")
End Sub
I get True for the first call and False for the second.

Related

excel vba to open a workbook by testing multiple passwords

I have password-protected .xls files in a directory. I would like to open each of these files and save them without the password.
However, the files can be opened by using either of the sample passwords listed below.
pwd1 = "123"
pwd2 = "456"
pwd3 = "789"
'Check if pwd1 opens
Application.Workbooks.Open(Filename:=fn, Password:=pwd1)
'If fail then use pwd2
Application.Workbooks.Open(Filename:=fn, Password:=pwd2)
'and so on..
How should I implement this?
Once the file has been opened once, you only need to Unprotect it. This will save a lot of time, instead of constantly opening/closing workbooks.
Here's how I'd do it:
Public Sub CrackWorkbook()
Dim fn As String
fn = "C:\Temp\test_password_is_456.xlsx"
Dim wb As Workbook
Set wb = Workbooks.Open(fn)
Dim lst As Variant
lst = Array("123", "456", "789")
Dim item As Variant
For Each item In lst
On Error GoTo did_not_work
Call wb.Unprotect(item)
Call wb.Save
Call wb.Close(False)
Exit Sub
did_not_work:
On Error GoTo 0
Next item
End Sub
In other words, create an array of strings and do a For Each on them, and set some error-handling to deal with all the failed attempts.
I know GoTo statements are a bit yucky, but that's the best way to handle errors in VBA (as far as I know).
I tried #lebelinoz answer however the routine gives a 1004 error. I googled this behavior and found this post:
https://stackoverflow.com/questions/21176638/vba-how-to-force-ignore-continue-past-1004-error
The code below works by using On Error Resume Next. I based this on #lebelinoz answer.
Public Sub CrackWorkbook()
Dim fn As String
fn = "C:\Temp\test_password_is_456.xlsx"
Dim wb As Workbook
Dim item As Variant
Dim lst As Variant
lst = Array("123", "456", "789")
For Each item In lst
On Error Resume Next
Workbooks.Open Filename:=fn, Password:=item
If Err.Number <> 0 Then GoTo did_not_work:
Exit For
did_not_work:
Next item
Set wb = Activeworkbook
wb.SaveAs Filename:=fn, Password:=""
wb.Close
End Sub

Range.Formula error with user defined functions

Hello guy I have a user defined function inside VBA
Function clean(word As String, ParamArray characters() As Variant) As String
For i = 0 To UBound(characters)
word = Replace(word, characters(i), "")
Next i
clean = word
End Function
whenever I try to use it in another subroutine like that
Sub prova()
Dim wb As Workbook
Dim wsB As Worksheet
Set wb = ThisWorkbook
Set wsB = wb.Sheets("Bond Holdings")
wsB.Range("R3").Formula = "=clean(""dfsduuu"",""u"")"
End Sub
I get runtime error 1004. Could you guys help me figure out why? this is driving me crazy.
Thank you
Excel has a built-in function called CLEAN. You have a name-clash. If you call your function e.g. cleaner, it will work as expected.

Can't get value from DatePicker

I have a DatePicker control on a worksheet. When I'm in the embedded code for the worksheet, I can access the control's value as follows:
Public Sub showValue()
Debug.Print Me.DTPicker21.value
End Sub
I would like to get the value from a module. Code here:
Sub getDate()
Dim obj As Object
Dim sht As Worksheet
Set sht = ThisWorkbook.Sheets("Tool interface")
For Each obj In sht.OLEObjects
If obj.Name = "DTPicker21" Then
Debug.Print obj.value
End If
Next
End Sub
When I run this, the obj.value triggers this error:
Object doesn't support this property or method
I checked the list of properties for obj using this procedure, and there is no value property. How can I get the date value that's been set in the DatePicker?
I don't know all of the details, but some of the OLEObjects require that you first access their Object property, then you can access other properties. I think the OLEObject serves as a container, then the "sub-object" is the actual object with which you want to interact. For example, if you run the following two lines of code, you will see that the first returns OleObject and the second returns DTPicker:
Debug.Print "Obj: " & TypeName(obj)
Debug.Print "Obj.Object: " & TypeName(obj.Object)
In your case, try the following code change to remove the error(note the Debug line):
Sub getDate()
Dim obj As Object
Dim sht As Worksheet
Set sht = ThisWorkbook.Sheets("Tool interface")
For Each obj In sht.OLEObjects
If obj.Name = "DTPicker21" Then
Debug.Print obj.Object.Value
End If
Next
End Sub

Workbook not found when trying to call function from Excel-add in

I'm trying to call a function from a 3rd party Excel-add in a VBA-sub. The function loads data from a database into specified cells in the Excel workbook.The function I'm calling is huge and unfortunaly I can't post it in its entirety, but here are the first two lines:
Public Function loadFromDatabase(ByVal XLname As String, ByVal sMark As String)
Dim xlWB As Workbook
Then it declares a bunch of variables before running the following tests:
'
' Get the excel book and check if it is run in compatibility mode
'
Set xlWB = getXLBook(XLname)
If xlWB Is Nothing Then
loadFromDatabase = "Workbook '" + XLname + "' not found!"
Exit Function
End If
bExcel8Limits = True
If isExcel2007orLater Then
bExcel8Limits = bCheckCompMode(xlWB)
End If
Here I get this message: "Workbook " not found!" http://imgur.com/HQFAzoC .
The getXLBook function looks like this:
'
' Routine to get a specified Workbook
'
Function getXLBook(sName As String) As Workbook
Dim xlWB As Workbook
On Error Resume Next
Set xlWB = Nothing
Set xlWB = Application.Workbooks(sName)
On Error GoTo 0
Set getXLBook = xlWB
End Function
A hint here may be that I'm able to call the function from a Private Sub place in a worksheet like this...
Private Sub loadFromDB()
Dim res As Variant
res = Application.Run("loadFromDatabase", Me.Parent.Name, "")
If res <> "OK" Then
MsgBox res
End If
End Sub
...but not from a module in the same workbook like this
Sub loadFromDB_test()
Dim res As Variant
res = Application.Run("loadFromDatabase", XLname, sMark)
If res <> "OK" Then
MsgBox res
End If
End Sub
Any suggestions?
Edit: To clarify, it's when running loadFromDB_test the "Workbook not found" message pops up.
Edit 2: An obvious hotfix (that I didnt think of) is to just call the Private Sub in the worksheet from the Sub in the module.
Sub load_test_new()
Application.Run "Sheet1.loadFromDB"
End Sub
From a learning point of view this is clearly not a good solution as it is inefficient coding.
Based on the msgbox you display, you're passing an empty string to the function getXLBook. (within the scope of getXLBook this value is stored as sName, but the cause of the error is before you call this function).
So, somewhere in your code, before this:
Set xlWB = getXLBook(XLname)
You should have a line like this, where the right side of the statement assigns a string representing a full, valid filepath:
XLName = "C:\filename.xlsx"
I suspect that your code does not contain this assignment statement, so that should explain the error.

How to error handle 1004 Error with WorksheetFunction.VLookup?

I have this code:
Dim wsFunc As WorksheetFunction: Set wsFunc = Application.WorksheetFunction
Dim ws As Worksheet: Set ws = Sheets("2012")
Dim rngLook As Range: Set rngLook = ws.Range("A:M")
'within a loop
currName = "Example"
cellNum = wsFunc.VLookup(currName, rngLook, 13, False)
VLookup is not expected to always find a result; but when it does not find a result the line errors out before I can even error check it the next line.
The error:
Run-time error '1004': Unable to get the VLookup property of the WorksheetFunction class
It works fine when a result is found. What's a good way to handle errors here?
Instead of WorksheetFunction.Vlookup, you can use Application.Vlookup. If you set a Variant equal to this it returns Error 2042 if no match is found. You can then test the variant - cellNum in this case - with IsError:
Sub test()
Dim ws As Worksheet: Set ws = Sheets("2012")
Dim rngLook As Range: Set rngLook = ws.Range("A:M")
Dim currName As String
Dim cellNum As Variant
'within a loop
currName = "Example"
cellNum = Application.VLookup(currName, rngLook, 13, False)
If IsError(cellNum) Then
MsgBox "no match"
Else
MsgBox cellNum
End If
End Sub
The Application versions of the VLOOKUP and MATCH functions allow you to test for errors without raising the error. If you use the WorksheetFunction version, you need convoluted error handling that re-routes your code to an error handler, returns to the next statement to evaluate, etc. With the Application functions, you can avoid that mess.
The above could be further simplified using the IIF function. This method is not always appropriate (e.g., if you have to do more/different procedure based on the If/Then) but in the case of this where you are simply trying to determinie what prompt to display in the MsgBox, it should work:
cellNum = Application.VLookup(currName, rngLook, 13, False)
MsgBox IIF(IsError(cellNum),"no match", cellNum)
Consider those methods instead of On Error ... statements. They are both easier to read and maintain -- few things are more confusing than trying to follow a bunch of GoTo and Resume statements.
There is a way to skip the errors inside the code and go on with the loop anyway, hope it helps:
Sub new1()
Dim wsFunc As WorksheetFunction: Set wsFunc = Application.WorksheetFunction
Dim ws As Worksheet: Set ws = Sheets(1)
Dim rngLook As Range: Set rngLook = ws.Range("A:M")
currName = "Example"
On Error Resume Next ''if error, the code will go on anyway
cellNum = wsFunc.VLookup(currName, rngLook, 13, 0)
If Err.Number <> 0 Then
''error appeared
MsgBox "currName not found" ''optional, no need to do anything
End If
On Error GoTo 0 ''no error, coming back to default conditions
End Sub
From my limited experience, this happens for two main reasons:
The lookup_value (arg1) is not present in the table_array (arg2)
The simple solution here is to use an error handler ending with Resume Next
The formats of arg1 and arg2 are not interpreted correctly
If your lookup_value is a variable you can enclose it with TRIM()
cellNum = wsFunc.VLookup(TRIM(currName), rngLook, 13, False)