Excel Workbook compile error after calling sub - arrays

I'm trying to make a worksheet of dynamically generated checkboxes, so I made the below sub to re-name them so I would be able to refer to them later (since I get what the checkboxes are representing from another sub and can't hardcode there names in). It works fine, however it seems to be the cause of a strange error I'm getting. After I run the main sub in my macro I get a compile error whenever I try to run any macro's. The exact error is:
Compile error:
Object library invalid or contains references to object definitions that could not be found
This is an error that a bunch of other people have got but none seem to be in the same circumstances and none of the suggested fixes have worked for me.
In the actual macro reNameCheckBox1 (the class that seems to be causing the issue) is ran numerous times but when ran only once no problem occurs.
The problem class:
Sub reNameCheckBox1(newName As String, mainWB As Workbook)
Dim obj As OLEObject
Dim i As Integer
i = 1
With mainWB.Worksheets("Sheet1")
For Each obj In .OLEObjects
Debug.Print obj.name
If TypeName(obj.Object) = "CheckBox" And Left(obj.name, 8) = "CheckBox" Then
Debug.Print "Found it"
obj.name = newName
mainWB.Worksheets("Sheet1").OLEObjects(newName).Object.Caption = newName
Exit For
End If
Next obj
i = i + 1
End With
End Sub
How its called used when its causing problems:
For n = 1 To numberOfNames
mainWB.Worksheets("Sheet1").OLEObjects.Add(ClassType:="Forms.CheckBox.1", Link:=False, _
DisplayAsIcon:=False, Left:=48, Top:=checkBoxTop, Width:=96, Height:=30). _
Select
Call reNameCheckBox1("Name=" & arrayOfNames(n), mainWB)
checkBoxTop = checkBoxTop + 45
Next n
If anyone knows why this is causing problems or a better way to do what I'm trying to do it would be MUCH appreciated.
EDIT
As per comintern's suggestion, how the arrayOfNames is inilialized is shown below. Just a few things to note. 1) searchForParameters returns two lists, which i'll ill call arrayOfNames and otherNames, and in an attempt to return them both I stored them in one comfusingly organized array. 2) I didn't know LBound or UBound existed when I made this (i'm new to vba) and 3) results is organized as such,
-At zero the length of the entire results array is stored
-At one the length of arrayOfNames
-at the beginning of OtherNames its length is stored
So, taking that confusing mess and turning into arrayOfNames and otherNames looks like this.
Dim results As Variant
Dim arrayOfNames As String
Dim otherNames As String
Dim length as Integer
Dim arrayOfNamesLength as Integer
Dim otherNamesLength As Integer
Dim n, i As Integer
results = searchForParameters(currentWB) 'Current wb is the name of the wb its searching, which isnt the one the code's being ran out of
length = results(0)
arrayOfNamesLength = results(1)
otherNamesLength = results (arrayOfNamesLength + 2)
ReDim arrayOfNames(arrayOfNamesLength + 1)
ReDim otherNames(otherNamesLength)
For i = 0 To arrayOfNamesLength
arrayOfNames(i) = results(i + 1)
Next i
For n = 0 To otherNamesLength
OtherNames(n) = results(i + n + 1)
Next n
EDIT
As it turns out, the compile error this question was created to solve was not caused by any of the code here, but by a separate class. So if your having the same error I'm having, the solution won't be here. Since it has some really good answers on fixing the code I did have, I guess I'll just leave this here and hope someone finds it useful.
Cheers
-Ben J. Man

It's unclear whether your arrayOfNames has a one-based or zero-based index. I suspect the former. Use the LBound
UBound functions to define the scope of your array elements.
I am also suspect of the quoted 'named parameter'. You can use named parameters but not in that manner.
For n = LBound(arrayOfNames) To UBound(arrayOfNames)
mainWB.Worksheets("Sheet1").OLEObjects.Add(ClassType:="Forms.CheckBox.1", Link:=False, _
DisplayAsIcon:=False, Left:=48, Top:=checkBoxTop, Width:=96, Height:=30). _
Select
reNameCheckBox1 newName:=CStr(arrayOfNames(n)), mainWB:=mainWB
checkBoxTop = checkBoxTop + 45
Next n
I'm not sure that the Workbook Object Application.Selection property needs to be used like that. I recommend simply referring to the Workbook object as a parameter.
You might get a problem passing a variant element of a variant array as a string type parameter so I've cast it as a string.
Call isn't necessary and some people think it is antiquated. Use it if you like; it does no harm.

It isn't exactly clear why you are selecting the CheckBox returned from OLEObjects.Add and then immediately searching for it in reNameCheckBox1. Just grab a reference and rename it in your loop:
Dim cb As OLEObject
For n = LBound(arrayOfNames) To UBound(arrayOfNames)
Set cb = mainWB.Worksheets("Sheet1").OLEObjects.Add( _
ClassType:="Forms.CheckBox.1", _
Link:=False, _
DisplayAsIcon:=False, _
Left:=48, _
Top:=checkBoxTop, _
Width:=96, _
Height:=30)
cb.Name = arrayOfNames(n)
cb.Object.Caption = arrayOfNames(n)
checkBoxTop = checkBoxTop + 45
Next n

Related

My Array in VBA is returning Empty Values when trying to get data from a range

I have the following VBA code. It is incomplete, but before I can go any further, I would like to understand why my DatesArray nd WorkDayArray show many Empty value, even though there are no empty value in the range I am taking my values from.
Public Function ResinOutDate(ByVal Tday As Date, ByVal WDays As Long) As Integer
Dim DatesArray As Variant
DatesArray = ThisWorkbook.Sheets("Reorder Calculator").Range("A6:A371").Value
Dim WorkDayArray As Variant
WorkDayArray = ThisWorkbook.Sheets("Reorder Calculator").Range("D6:D371").Value
Dim i As Integer
Dim Workdaycount As Integer
Workdaycount = 0
For i = 1 To 366
If DatesArray(i) = Tday Then
Do Until Workdaycount = WDays
Workdaycount = Workdaycount + WorkDayArray(i)
i = i + 1
Loop
Exit For
End If
Next i
ResinOutDate = i
End Function`
DatesArray Values
WorkDayArray Values
Source Data
I have tried searching online if I am doing anything wrong, but all I see are suggestions to use the similar code to the following
Dim DatesArray As Variant
DatesArray = ThisWorkbook.Sheets("Reorder Calculator").Range("A6:A371").Value
to take a range of value and transform them into an array. Not sure why I am running into this issue.
I was testing the code by putting a stop before the part where I would get the syntax error and testing to see if my arrays have been created correctly. Turns out that the Watch did not work properly when referencing data from another sheet (Not sure why). It is working now, and I still appreciate the help on the other issue in the code. There was another error in the code where I needed to either reduce the arrays into a single dimension using transpose or use DatesArray(i,1) & WorkDayArray(i,1).

how to insert a value of an Excel cell into String array in VB.NET?

i need help ,
i want to insert the value of the excel cell into an array(String array)
how to do that ?
my code:
Dim DB_Columns(5) As String 'Columns values Array
Dim i As Integer
Dim tmp_col As String
For i = 0 To 5
tmp_col = worksheet1.Cells(0, i).value
DB_Columns(i) = tmp_col
i = i + 1
Next
every time i run the code i got an exception.
Not sure it's going to fix your problem as you haven't actually told us what the exception is - but you increment i manually in your code even though it's already in a For loop. This means you're going to exit the loop early anyway.
Also - Cells(0, i) will cause an exception because the Cells collection isn't zero-indexed. It expects a row and column index (there isn't a row/column "0").
Finally, you haven't shown us where you assign worksheet1 - unless you have a reference set to the workbook you will probably need to explicitly qualify it (this is a good habit to get into anyway)
FWIW this is how I would write that code:
Dim worksheet1 As Excel.Worksheet = MyXLApp.Workbooks("My Workbook.xlsx").Sheets(1) '// For Example
'// or alternatively something like
'// Dim worksheet1 As Excel.Worksheet = MyXLApp.ActiveWorkbook.Sheets(1)
Dim DB_Columns(0 To 5) As String
For i = LBound(DB_Columns) To UBound(DB_Columns)
DB_Columns(i) = worksheet1.Cells(1, i + 1).value '// Notice I've added 1 to "i" to prevent an exception as rows/columns start from 1
Next
You can actually do this without a loop also:
Dim DB_Columns() As String
DB_Columns = worksheet1.Cells(1, 1).Resize(1, 5).Value
Try this:
tmp_col = worksheet1.Cells.Cells(0, i).value
it works for me.
have a nice day.

How to display a 1 or 2 dim varraint array() return from an excel UDF function in an excel range without Ctrl+Shift+Enter array forumla method

I've searched for solutions to this question for 3 days & have only found answers that either use Ctrl+Shift+Enter (array formula method) or functions which either return an empty range or which raise err 1004.
I'm using 64-bit Windows 8.1 and Excel-2013 write UDFs that return variant arrays from time to time with unknown array size returns... for example 'MyFunction(args...) as Variant()'. I can see the result in immediate window or write it to file or display it with Ctrl+Shift+Enter as an array formula.
What I want to do is use MyFunction() as an argument to a sub arr2rng(Myfunction) such that arr2rng() fills a range on the activesheet starting at ActiveCell inorder to avoid the whole manual routine of using array formula method (e.g. highlight a range of some size larger, by guessing, than the returned array, then the combination Ctrl+Shift+Enter to display the array).
I've even tried the long subroutine by Nile -- see his A generic VBA Array To Range function at VBA Excel 2-Dimensional Arrays,
Public Sub ArrayToRange(rngTarget As Excel.Range, InputArray As Variant)
but at every statement in 'ArrayToRange()' where 'rngOutput.Value2 = InputArray' occurs function bombs with err.Number 1004. Just before that statement is executed both 'InputArray' and 'rngOutput.VaAlue2' elements are correctly dimensioned and filled (from Immediate or Local Window observations)... though 'rngOutput.Value2'elments are still empty as they should be. After the statement executes 'rgnOutput.value2' elements are still empty though, and err = 1004 has been raised. This occurs no matter which one of his tests in the code are executed. I've even gone so far as to invoke his sub by my function at the end of my own VBA's as:
myFunction(args....) as Variant()
[do stuff...]
ConArr = vbaTransposeVar(ConArr)
Set DestCell = ACTIVE_CELL_DESTINATION
ArrayToRange DestCell, ConArr
myFunction = ConArr
End Function
where 'ConArr' is the name of the resultant variant() array and also the return from myFunction(), where 'ACTTIVE_CELL_DESTINATION' is declared Public as Range in the Module.
What I prefer to do however is just invoke the 'sub ArrayToRange myDest, myArray' or any other sub such as a generic 'sub arr2rng(myDest as range, myArray() as Variant)' either from within some other function that invokes the sub or do it manually from the Macro window.
Can anybody help or tell me why all I get are either an empty range of cells or the 1004 error? I guess what I'm really asking is how to get around using the array formula method Ctrl+Shift+Enter. There must be a way! u
This example worked for me in testing, BUT there is no code to account for clearing any previous values which resulted from any earlier calculations. If the current run results in a smaller array then previous values will not be cleared.
You could account for this maybe by tracking the last range filled and making sure you clear it first, but depending on your exact use case that might not be viable.
Function ChangeIt(func As Range, c1 As Range, c2 As Range)
Dim arr(), r, c, rv, v
For r = 1 To c1
For c = 1 To c2
v = "Row" & r & ":Col" & c
If r = 1 And c = 1 Then
rv = v 'return this to the calling cell via myArray
Else
'all other values are written directly
func.Offset(r - 1, c - 1).Value = v
End If
Next c
Next r
ChangeIt = rv
End Function
'this is called from the worksheet
Function MyArray(arg1 As Range, arg2 As Range)
Dim v
v = arg1.Parent.Evaluate("Changeit(" & Application.Caller.Address(False, False) & "," & _
arg1.Address(False, False) & "," & _
arg2.Address(False, False) & ")")
MyArray = v 'set the top-left array value
End Function

How to improve efficiency using arrays instead of Find in VBA

I have a function that is used to find the information in a Excel worksheet knowing that:
- The Key can be in a variable column
- Variable fields can be searched
Sheets usually have less than a hundred column, but can have anything from a few hundred to 100 000 rows to search. In our biggest files, the function I'm trying to optimize can be used about a million times.
After reading
https://fastexcel.wordpress.com/2011/10/26/match-vs-find-vs-variant-array-vba-performance-shootout/
... and finding our function used Find (3 times), I tried using arrays.
This is the code I wrote
Function getInfo(Key As String, NameField As String, NameKey As String, WksName As String) As Variant
On Error GoTo Error
Dim iColumnKEY As Integer
Dim iColumnFIELD As Integer
Dim i As Integer
Dim ListFields, ListKeys As Variant
ListFields = Worksheets(WksName).Range("A1:ZZ1")
i = LBound(ListFields, 2)
'To identify which column contains the Key and which one contains the
'information we are searching for
Do While iColumnKEY=0 Or iColumnFIELD=0
If i > UBound(ListFields, 2) Then
getInfo = "//error\\"
ElseIf ListFields(1, i) = NameKey Then
iColumnKEY = i
ElseIf ListFields(1, i) = NameField Then
iColumnFIELD = i
End If
i = i + 1
Loop
Dim iROW As Integer
ListKeys = Worksheets(WksName).Columns(iColumnFIELD)
i = LBound(ListKeys, 1)
Do While iROW=0
If i > UBound(ListKeys,1) Then
getInfo = "//error\\"
ElseIf ListKeys(i,1) = Key Then
iROW = i
End If
i = i + 1
Loop
getInfo = Worksheets(WksName).Cells(iROW, iColumnFIELD)
Exit Function
Error:
getInfo = "//error\\"
End Function
The code works, but is very slow. What am I doing that is slowing things down?
It is not in the code right now, but I did try turning the screen update down, as well as automatic calculation down. I didn't see any difference in speed, which indicates me that the basic algorithm is the main issue.
Also, the article was in 2011. Are arrays still a lot faster than Match/Find?
As a side note: eventually, I'll suggest having a macro that search for a range of Keys in a batch, instead of calling the function for every single key. This would means the first Do... While loop would be done only once for a macro, and only the Do_While for Rows would be run for every key. However, this is not an option in the very short term.
Thanks. Any help or advice would be greatly appreciated.
To make sure I understood you correctly, you have a sheet that has a random column that contains unique keys.
you want to search for one of these keys and return related info (like row no, etc) many times
Approach:
Find the column in which the keys are listed.
Load that column in a dictionary(Indexed).
Use GetInfo function to return info about a specific key if it exists.
Dependencies:
Microsoft scripting runtime (Tools > refrences > Microsoft scripting runtime)
code:
Option Explicit
Private KeyDictionary As Scripting.Dictionary
Sub PopulateDictionary(ByRef WS As Worksheet, ByVal FieldName As Variant)
Dim i As Long, LastRow As Long, iColumnFIELD As Long
Dim ListKeys As Variant
iColumnFIELD = WS.Range("A1:ZZ1").Find(FieldName).Column
With WS 'Finds the last row in the sheet
If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
LastRow = .Cells.Find(What:="*", _
After:=.Range("A1"), _
LookAt:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
End If
Set KeyDictionary = New Scripting.Dictionary
For i = 1 To LastRow 'populates Dictionary with Key, Row number pair
If Not (KeyDictionary.Exists(.Cells(i, iColumnFIELD))) Then 'Make sure the key doesn't already exist(Key values should be unique)
KeyDictionary.Add .Cells(i, iColumnFIELD).Value, .Cells(i, iColumnFIELD).Row
End If
Next i
End With
End Sub
Function getInfo(ByVal key) As Variant
If KeyDictionary.Exists(key) Then
getInfo = KeyDictionary(key) 'if the key exist return row number (or whatever you want to)
Else
getInfo = "Null" 'Else return whatever you want like a msgbox "not Found" , etc
End If
End Function
usage
'populates and returns the row number of key 9500
Sub TestSearch()
PopulateDictionary ThisWorkbook.Worksheets("Sheet1"), "Key"
Debug.Print getInfo(9500)
End Sub
Notes:
-almost always Use long instead of integer , not much performance difference, but can save you from overflow pitfalls.
-you can add a reference to the range containing the key instead of the row number that would more flexible
-Passing a sheet by reference (Full Ref) is better than passing just its name and avoids a lot of possible problems like the case of multiple workbooks with the same sheet name and makes your code more reusable.
References:
Dictionary object
Edit:
I misunderstood your request , thought you wanted to know the best method available.
here's a performance comparison of the four methods:
it takes 1325 ms (Or 1.3 seconds) to populate the Dictionary with the unique key list the first time (100,000 Row)
it takes 1.79646327708265E-02 ms aka 0.02 ms To search for an item at the end of list (row 99863) using the dictionary object
it takes around 10.5 ms To search for the same item with WorksheetFunction.Match
it takes around 50 ms To search for the same item with the array method
it takes around 80 ms To search for the same item with the Range.find
Result:
Dictionary Method is faster than match -the second-best method of the Four- by over 500 Times!
The reason is that the keys are indexed inside the dictionary, unlike the other methods.
notes:
Office 2016 was used on a 2 cores (3.20 GHz) machine with 8 gigs or ram (Dictionary took about extra 8 Megabytes ram)
All of these searches were done on the same data set
(the search was done on only 1 column with 100,000 unique keys ,with the searched for value at the bottom of the list)
The break-even point on whether you should use Match or Dictionary is around 120 searches.
if the code will search for more than 120 values then it's better to use the dictionary approach.
Windows API QueryPerformanceCounter was used for High resolution timer.
Code line used to search for Values(not gonna put the full sub)
'Match
WorksheetFunction.Match(Key, ThisWorkbook.Worksheets(1).Range("CW:CW"), 0)
'Find
ThisWorkbook.Worksheets(1).Range("CW:CW").Find(Key).Row
'Array
'Loops through the column till it finds a match
In your code you never use iColumnKEY
I think this is what you are actually after:
Function getInfo(key As String, NameField As String, NameKey As String, WksName As String) As Variant
Dim keyCol As Variant, fieldCol As Variant, keyRow As Variant
Dim errMsg As String
getInfo = "//error\\"
With Worksheets(WksName)
With Intersect(.UsedRange, .Columns("A:ZZ")) ' <--| reference a range in passed worksheet cells belonging to columns "A" to "ZZ" from worksheet first used row to last used one and from worksheet first used column to last used one
MsgBox .Address
fieldCol = Application.Match(NameField, .Rows(1), 0) '<--| look for passed 'NameField' in referenced range
If IsError(fieldCol) Then
errMsg = " :field column '" & NameField & "' not found"
Else
keyCol = Application.Match(NameKey, .Rows(1), 0) '<--| look for passed 'NameKey' in referenced range
If IsError(keyCol) Then
errMsg = " :key column '" & NameKey & "' not found"
Else
MsgBox .Columns(keyCol).Address
keyRow = Application.Match(key, .Columns(keyCol)) '<--| look for passed 'key' in referenced range 'NameKey' column
If IsError(keyRow) Then
errMsg = " :key '" & key & "' not found in column '" & NameKey & "'"
Else
getInfo = .Cells(keyRow, fieldCol) '<--| get referenced range "item"
End If
End If
End If
If errMsg <> "" Then getInfo = getInfo & errMsg
End With
End With
End Function
I see that in your loop you are doing a UBound() evaluation every time. This is not needed.
The following should be faster than a Do While loop. Notice that the array returned by Range().Value has always a lower bound of one. No need to call LBound()
Also, find where the last data exists in the column and restrict the loop to that range. I do this with .End(xlUp)
Dim ListKeys() as Variant
Dim iROW As Long, nRows as Long
nRows = Worksheets(WksName).Cells(Worksheets(WksName).Rows.Count, iColumnFIELD).End(xlUp).Row
ListKeys = Worksheets(WksName).Cell(1, iColumnFIELD).Resize(nRows,1).Value
For i=1 To nRows
If ListKeys(i,1) = Key Then
iROW = i
Exit For
End If
Next i
not an answer but a radically different approach, since im from data-science background i use these methods for fast searching any data in a database which are few GB in size, in your case excel for example. this approach can be parallelized based on number of CPUs in your system. and uses python framework Pandas, which has very big community incase you need support, VB has limited community.
also read this before judging this answer https://corporatefinanceinstitute.com/resources/knowledge/other/transitioning-from-excel-to-python/
i expect criticism for this answer , OP asked this but you are giving this blah. but if you want faster development times for ever changing business needs you need something fast, and easy to maintain. python makes it easy, pandas makes it fast.
to get started read this.https://towardsdatascience.com/read-excel-files-with-python-1000x-faster-407d07ad0ed8
i will mention the pipeline here however. see very few lines of code!!! finish work faster, go home early.
import the excel file as csv
import pandas as pd
dataframe=pd.read_excel("file.xlsx")
item=dataframe[dataframe["Order ID"]==886714971] #condition based searching in excel
note "Order ID" is just any arbitary column and you can use SQL like logic here which resembles match/find in VBA.
for speed reference iterating 1,000,000 rows took 0.03 seconds, which means a transaction speed of 30 TPS. use https://modin.readthedocs.io/en/latest/ to scale up that speed linearly with number of cores in cpu.
To find out what parts of the code are the slowest, you can use Timer:
Dim t as Single
t = Timer
' part of the code
Debug.Print CDbl(Timer - t) ' CDbl to avoid scientific notation
Using .Value2 instead of .Value should help a bit:
ListFields = Worksheets(WksName).Range("A1:ZZ1").Value2
Searching for the key and field in two separate loops should be a bit faster because there will be less comparisons. Also, I am not sure if it will be a bit slower or faster, but you can iterate even multi-dimensional arrays:
Dim i As Long, v ' As Variant
i = 1
For Each v in ListFields
If v = NameKey Then
iColumnKEY = i
Exit For
End If
i = i + 1
Next

Error Initializing Array *Type Mismatch*

Attempting to utilize two arrays to iterate through a data transfer process by copy and pasting cells from one sheet into a newly created one. The code below is merely responsible for copy and pasting the correct data in the correct order from one sheet to the newly created one. I'm receiving a type mismatch when attempting to initialize the arrays. It occurs on the first array, but I haven't gotten to the second array to test that yet, so it could be wrong as well.
Things to Note: 1) firmLocationColumn is of type long. 2) All the data stored in said arrays are meant to represent column numbers. They are out of order so I needed to store them in the array in the proper order so that it's easier to iterate through them rather than writing the same information over and over again.
Let me know if I missed anything that needs to be explained and i'll edit my question:
Private Sub GetSpecificTradeDetails(ByVal masterListRow As Long, ByVal firmLocationColumn As Long, ByVal newExcelConfirmSheet As Worksheet, ByVal newExcelConfirmSheetLastRow As Long)
Dim tradesMasterListColumnIndexArray() As Long
Dim newExcelConfirmColumnIndexArray() As Long
Dim arrayIndexCounter As Long
'Sets array of columns for loop iteration through data sheet
tradesMasterListColumnIndexArray() = [1,4,firmLocationColumn,(firmLocationColumn - 1),(firmLocationColumn + 3),15,16,10,11,8,19,18,17,(firmLocationColumn + 4),9,6,2]
newExcelConfirmColumnIndexArray() = [1,2,3,4,5,7,8,9,10,11,12,13,14,15,16,17,18]
Select Case firmLocationColumn
Case 25
'Sets confirm direction to "BUY"
newExcelConfirmSheet.Cells((newExcelConfirmSheetLastRow + 1), 6) = "BUY"
Case 27
'Sets confirm direction to "SELL"
newExcelConfirmSheet.Cells((newExcelConfirmSheetLastRow + 1), 6) = "SELL"
End Select
'Transfers trade details between the masterlist and the newly created confirm sheet
With TradesMasterSheet
For arrayIndexCounter = 0 To 17
.Cells(masterListRow, tradesMasterListColumnIndexArray(arrayIndexCounter)).Copy _
Destination:=newExcelConfirmSheet.Cells((newExcelConfirmSheetLastRow + 1), newExcelConfirmColumnIndexArray(arrayIndexCounter))
Next
End With
End Sub
VBA doesn't support initialization of arrays by array literals. It does, however, have an Array() function:
Dim tradesMasterListColumnIndexArray As Variant
Dim newExcelConfirmColumnIndexArray As variant
tradesMasterListColumnIndexArray = Array(1,4,firmLocationColumn,(firmLocationColumn - 1),(firmLocationColumn + 3),15,16,10,11,8,19,18,17,(firmLocationColumn + 4),9,6,2)
newExcelConfirmColumnIndexArray = Array(1,2,3,4,5,7,8,9,10,11,12,13,14,15,16,17,18)

Resources