Excel array and collection passing between modules messing up values - vba

This is really stumping me. I put a question up yesterday regarding collections being passed between modules (see here), but it doesn't seem like I am getting anymore explanations on that one, so i am attempting to restate the problem more clearly in a generic way.
I have a module (module1) and a userform (userform1). I create a collection (or array) in userform1 and add worksheet objects to this array. I then pass control to module1, which calls a sub in userform1 called addNewFile, which is supposed to add the newly created workbook to the collection. However, each time module1 calls addNewFile i get one of two scenarios: 1) the collection has been erased and all worksheets that had been added are now gone (for a collection), 2) i get an error saying that i have a type mismatch (for an array). I don't know why this is happening, so here is the code below to illustrate better. Any help would be appreciated, even if it is just to tell me that it is not possible to store worksheet objects in arrays.
UserForm1
Dim workBooksCollection as New Collection 'can also define as an array
Private Sub CommandButton1_click()
Dim mainWorkBook as workbook
Set mainWorkBook = ActiveWorkbook
Dim testwb As Workbook
workBooksCollection.Add Item:=mainWorkBook, key:="main" 'Adds successfully
workBooksCollection.Add Item:=testwb, key:="test" 'Adds successfully
MsgBox "the size of the array is: " & usedWorkBooks.Count 'Prints off as size 2
Module1.initialize
'After running initialize, prints off as size 0, meaning collection has been erased
MsgBox "the size of the array is: " & usedWorkBooks.Count 'Prints off as size 0
End Sub
Public Sub addNewFile(filepath As String, sheetKey As String)
Dim newWorkBook As Workbook
Set newWorkBook = Workbooks.Open(filepath)
MsgBox "The name of the workbook is: " & newWorkBook.name 'Prints off name of workbook successfully
workBooksCollection.Add Item:=newWorkBook, key:=sheetKey
MsgBox "the size of the array is: " & workBooksCollection.Count 'Prints off as size 1
End Sub
Module1
Public Sub intialize()
Dim filepath as string
'The filepath is set to any path of a workbook
'This will print out that the array size is 1
UserForm1.addNewFile filePath, "secondBook"
End Sub
Sorry if i seem to be beating a dead horse here, but I really don't have any idea what is going on here. I am used to the idea of collections and lists being global and not changing when being referenced by another module. Any help on what is going on here would be great.

I would like to comment, but I can't since I had to redo my account because I got locked out of my original.
If my answer isn't helpful, I will delete in a bit, but what if you replace -
Dim workBooksCollection as collection 'can also define as an array
from the UserForm module, into Module 1 as:
Public workBooksCollection as collection 'can also define as an array
Does it help?

Related

Calling a global/public variable in moduleB whose value was defined in moduleA

I wrote 4 macros to do things, but it requires 2 inputs from the user to make sure the right file is being used because some of the macros switch back and between 2 workbooks. I only had access to a few of the files, but I knew that eventually I would have access to the rest of the 35 files. If I didn't have the inputs, I would have to manually change the filename in the macro code, but I don't want to do that, so I used inputs. But now that I have all the files in the right format, I am trying to a separate macro that has a list of the other files in a separate workbook, and then opens those files and does the macros, but it would require the inputs a lot. So now, I'm trying to remove that need for the inputs. But I'm unfamiliar with public variables and somewhat familiar with the calling of other subroutines.
My setup is this:
option explicit
public current as string
Sub master_macro
dim i as integer
dim path as string
dim wb as workbook
dim sht as worksheet
set wb = workbooks("name.xlsx")
set sht = wb.worksheets(1)
path = "C:\xxx\"
wb.activate
for i = 1 to 20
currun = sht.cells(i,1).value 'this takes the value from the separate workbooks that has the file names
full_currun = currun & ".xlsx"
with workbooks.open(path & full_currun)
.activate
call blanks
call lookup
call transfer
call combine
.save
.close
end with
next i
The last 2 macros switch between 2 sheets. So in those macros, the currun is generated the an inputbox, albeit a different name.
nam = inputbox("yadda yadda")
set wb = workbooks(nam & ".xlsx")
I'm trying to get the currun vaue that is defined in the master macro to macro3 and macro4.
You see the part where it says Sub master_macro? What you are doing there is declaring a procedure, which is a basically a general term to describe "a block of self-contained code that does something when it is run." Procedure declarations have three major components:
type - this is what you are doing with Sub; you are saying it is a subroutine, which is distinct from a function Function in that it does not return a value
name - this is the identifier you use to refer to the procedure elsewhere in your code. it is supposed to be descriptive since that enhances the readability. "master_macro" is not bad, but as a general rule you don't want to use underscores when naming procedures in VBA.
parameters - this is where you define the set of variable values that can be passed to the procedure when it is run. each parameter is separated by a comma and declared using the syntax [pass type] + [variable name] + [variable type]. [pass type] is either ByRef or ByVal; the basic distinction is that ByRef sends a direct reference to the variable, while ByVal sends a copy of the value.
The last part is what you are missing to solve this problem. Both macro3 and macro4 are declared (in module B) like master_macro is here. If they need to know what the currun value is then simply add (ByVal currun As String) to their declarations. When they are called from another procedure, as they are in master macro, they will expect to receive a string. Change the two lines in master macro from:
Call macro3
Call macro4
to
Call macro3(full_currun)
Call macro4(full_currun)
and macro3 and macro4 will have the value of full_currun stored in their own internal variable currun for use as they need.
Thanks guys. managed to get it to work. Here's the finished work below
sub master()
dim i as integer
dim path, currun, fullcurrun as string
dim wb as workbook
dim sht as worksheet
set wb = workbooks("Name.xlsx")
set sht = wh.worksheets(1)
path = "C:\xxx\"
wb.activate
for i = 1 to ?
currun = sht.cells(i,1).value
fullcurrun = currun & ".xlsx"
workbooks.open(path & fullcurrun)
call blank(currun)
call lookup(currun)
call transfer(currun)
activeworkbook.save
activeworkbook.close
call transfer(currun)
next i
end sub
public sub blank/lookup/transfer(byval currun as string)
blah blah blah
end sub

Why does assigning a reference in my spreadsheet sometimes work and sometimes not?

I have a few cells in my excel workbook which are available for a client to put his own values. I wanted the workbook to initialize those cells with default values. In order to do so I have a worksheet "Arkusz do makr", where I store the values.
In a module "GM" I declare a variable to reference my worksheet easier like this:
Public M As Worksheet
Then I initialize this variable and set my default values like this (in ThisWorkbook):
Private Sub Workbook_Open()
Set M = Worksheets("Arkusz do makr")
Worksheets("Values").Range("Value1") = M.Range("Value1")
Worksheets("Values").Range("Value2") = M.Range("Value2")
Worksheets("Values").Range("Value3") = M.Range("Value3") `etc
End Sub
Now sometimes this works like a charm, and sometimes, when I open the workbook I get a
Run-time error '91': Object variable or With block variable not set.
Could someone please explain this behaviour to me? Additionally I would like to ask if my approach makes sense, since I have a hard time grasping the order of events in excel as well as the range of its objects.
EDIT: Additionally I should mention that the Debug function highlights the first Worksheets... line in my code. In specific worksheets I reference the M object as well, though I thought it changes anything here...
Try to change the code of this Sub like below.
I have added a simple error handling - if there is no worksheet "Arkusze do makr" or "Values" in your workbook, warning message is displayed and default values are not copied.
You can find more comments in code.
Private Sub Workbook_Open()
Dim macrosSheet As Excel.Worksheet
Dim valuesSheet As Excel.Worksheet
'------------------------------------------------------------------
With ThisWorkbook
'This command is added to prevent VBA from throwing
'error if worksheet is not found. In such case variable
'will have Nothing as its value. Later on, we check
'the values assigned to those variables and only if both
'of them are different than Nothing the code will continue.
On Error Resume Next
Set macrosSheet = .Worksheets("Arkusz do makr")
Set valuesSheet = .Worksheets("Values")
On Error GoTo 0 'Restore default error behaviour.
End With
'Check if sheets [Values] and [Arkusz do makr] have been found.
'If any of them has not been found, a proper error message is shown.
'In such case default values are not set.
If valuesSheet Is Nothing Then
Call VBA.MsgBox("Sheet [Values] not found")
ElseIf macrosSheet Is Nothing Then
Call VBA.MsgBox("Sheet [Arkusz do makr] not found")
Else
'If both sheets are found, default values are copied
'from [Arkusz do makr] to [Values].
'Note that if there is no Range named "Value1" (or "Value2" etc.)
'in any of this worksheet, another error will be thrown.
'You can add error-handling for this case, similarly as above.
With valuesSheet
.Range("Value1") = macrosSheet.Range("Value1")
.Range("Value2") = macrosSheet.Range("Value2")
.Range("Value3") = macrosSheet.Range("Value3")
End With
End If
End Sub

Excel Crashes when workbook is opened

I cannot figure out why my excel workbook file keeps on crashing everytime I open the file.
I have this event handler which I'm sure the one causing the problem.
Option Explicit
Private Sub Workbook_Open()
On Error Resume Next
CurrEntities = Array("Curr1,Ent1", "Curr2,Ent2", "Curr3,Ent3")
End Sub
CurrEntities is declared as public in separate Module.
Public CurrEntities() As Variant
When I try to comment out the line - CurrEntities = Array("Curr1,Ent1", "Curr2,Ent2", "Curr3,Ent3"), the file can be opened without a problem.
So strange because it doesn't give any run time error, it will just prompt a message "Microsoft Excel has stopped working" and then the Excel closes.
Is there something I missed or violated an array variable declaration?
Public CurrEntities() As Variant
means: declare an array of Variant.
Replace with:
Public CurrEntities As Variant
and everything should be OK ;)
Unless... you want to use an array:
Public CurrEntities() As Variant
Sub Test()
Dim i As Integer, j As Integer
Dim curent As Variant
CurrEntities = Array(Array("a", "b"), Array("c", "d"))
For i = LBound(CurrEntities()) To UBound(CurrEntities)
curent = CurrEntities(i)
Debug.Print "---=== " & i & " ===---"
For j = LBound(curent) To UBound(curent)
Debug.Print curent(j)
Next
Next
End Sub
Cheers,Maciej
The declaration above and the usage has no problem. I found out and rectified the problem when I tried to create another file and use only the necessary variable for testing. The problem was caused by another variable which is declared incorrectly:
Public CoCodes("00123", "00456", "00789") As String
I removed this line from my code and the above code worked perfectly.

Dynamically Create Dynamic Arrays in VBA

My objective is to use an array of names to create dynamic variables in VBA, heres the code:
Sub mymacro()
Dim names()
names = Array("cat_code()", "dog_code()", "eagle_code()")
For Each c In names
Dim c As Integer
Next
end sub
And of course my real name array has hundreds of animals so it would be rather boring doing Dim for each and every one of them. The error I'm getting is Compile Error: Duplicate declaration in current scope
What is the best feasible solution to my objective?
The compile error you are getting is caused by a duplicate declaration in the current scope.
In other words: this means you are declaring more than one variable with the same name.
Adding an Option Explicit statement on top of you modules requires you to declare each variable you use. It's very helpful when you receive this error because you can quickly scan your code for duplicate declaration of the highlighted line Dim <variable_name>
This is a sample demonstrating why you are getting the error:
Option Explicit
Sub Main()
Dim c As Worksheet
For Each c In Sheets
Dim c As Long ' you are going to get an error in here because
' a variable named: c, is already declared within the sub
' you can't have two variables named: c.
For c = 1 To ws.Range("A" & Rows.Count).End(xlUp).Row
' some code
Next c
Next
End Sub
There is no easy work around your problem. We would have been able to provide a better solution to your problem if you better explain what you are trying to achieve.
There is a workaround to achieve what you want but I wouldn't recommend doing it this way if you are unsure of you are actually doing ;). The below code will create a new module in your current VBA project. While iterating over the array with the animal names it will be writing new lines to Module2 so after the execution your module two will be
In order for this code to work you have to add references to Microsoft Visual Basic for Applications Extensibility 5.3". You can do that by selectingTools>>References` in the VBE window.
Also, this requires you to Trust Access to VBA Project Object Model. Go to Excel Settings >> Trust Centre >> Macros >> tick Trust Access To VBA Project Object Model.
Run the sample code.
Option Explicit
' this VBA project requires
' 1 - references to Microsoft Visual Basic For Applications Extensibility 5.3
' add it via Tools > References
'
' 2 - trust access to VBA project object model
' In spreadsheet view go to Excel(application options) >> Trust Centre >> Macro Settings
' tick the Trust Access to VBA project object model
Sub mymacro()
Dim names
names = Array("cat_code", "dog_code", "eagle_code")
Dim c As Variant
AddAModule
For Each c In names
' dynamically create arrays
WriteToModule CStr(c)
Next
CloseModule
End Sub
Private Sub AddAModule()
Dim VBProj As VBIDE.VBProject
Dim VBComp As VBIDE.vbComponent
Dim CodeMod As VBIDE.CodeModule
Set VBProj = ThisWorkbook.VBProject
Set VBComp = VBProj.VBComponents.Add(vbext_ct_StdModule)
Set CodeMod = VBComp.CodeModule
With CodeMod
.DeleteLines 1, .CountOfLines
.InsertLines 1, "Public Sub DynamicallyCreatedArrays()"
.InsertLines 2, " ' code for the sub"
End With
End Sub
Private Sub WriteToModule(arrayName As String)
With ActiveWorkbook.VBProject.VBComponents("Module2").CodeModule
.InsertLines .CountOfLines + 2, " Dim " & arrayName & " as Variant"
End With
End Sub
Private Sub CloseModule()
With ActiveWorkbook.VBProject.VBComponents("Module2").CodeModule
.InsertLines .CountOfLines + 2, "End Sub"
End With
End Sub
VBA can't really do what you're trying to do without getting into a horrible world of complications.
How about using a VBA Collection object instead? You'll need to create a simple class to hold the number, because VBA collections work with references, not values.
So I created a Class and set its name to "AnimalCounter", with this content:
Public Counter As Integer
Then your macro becomes something like this:
Sub mymacro()
Dim coll As New Collection
Dim c As Variant
Dim ac As AnimalCounter
For Each c In Array("cat", "dog", "eagle")
Set ac = New AnimalCounter
coll.Add ac, c
Next
Debug.Print coll("cat").Counter ' what's in "cat"?
coll("dog").Counter = coll("dog").Counter + 1 ' update "dog" by one
Debug.Print coll("dog").Counter ' "dog" should now be one more
End Sub
If you wanted arrays, put an array in to the class. Or another Collection, maybe?
Mike Woodhouse has the right idea of using a Collection with the keys of the animals. I add two notes:
First, I would recommend using a Dictionary instead. It is faster than a Collection, and allows explicit access to the Keys and Items collections. With a Collection, there is actually no way to fetch the keys, since the basic purpose is an ordered list of items rather than a order-agnostic hash as with a Dictionary.
For early-bound use of the Dictionary type, add a reference to Microsoft Scripting Runtime.
Second, do not use an array for the individual animals!. The reason is because arrays in VBA use by-value semantics ( see Collections in VBA – Overview, Values and References in VBA, Array Assignment Rules for more information). In short, every time you fetch an instance of an array from the containing Collection or Dictionary, you will be getting a new copy of the entire array. Thus any changes you make to the content of that array will not affect the actual array in the Dictionary or Collection. To get around this, use a Collection instead. This will use by-reference semantics and makes it much easier to append new items.
So here's what you'd want to do:
Sub ReadCodes()
Dim ws As Worksheet
Dim strAnimalName As String
Dim dctAnimalCodes As New Dictionary
Dim colAnimalCodes As Collection
Dim lngAnimalCode As Long
Set ws = Worksheets("Animal Code Data")
For iRow = 1 To ws.UsedRange.Rows.Count
strAnimalName = ws.Cells(iRow, 1)
lngAnimalCode = ws.Cells(iRow, 2)
' Easy to check if key exists
If Not dctAnimalCodes.Exists(strAnimalName) Then
Set dctAnimalCodes(strAnimalName) = New Collection
End If
' Getting the collection for this animal
Set colAnimalCodes = dctAnimalCodes(strAnimalName)
' Easy appending of new code
colAnimalCodes.Add lngAnimalCode
Next
End Sub

How to add a new spreadsheet with VBA-Code, using VBA

I am creating a macro and part of the macros function is to make VBA create a new spreadsheet. Because of the nature of distribution the name will change. I need to add code to this spreadsheet. Is there anyway I can do this?
Jook has already explained how it works. I will take it a step further.
The syntax of adding a worksheet is
expression.Add(Before, After, Count, Type)
If you check inbuilt Excel's help then you can see what Before, After, Count, Type stands for
FROM EXCEL"S HELP
Parameters (All 4 parameters are Optional)
Before - An object that specifies the sheet before which the new sheet is added.
After - An object that specifies the sheet after which the new sheet is added.
Count - The number of sheets to be added. The default value is one.
Type - Specifies the sheet type. Can be one of the following XlSheetType constants: xlWorksheet, xlChart, xlExcel4MacroSheet, or xlExcel4IntlMacroSheet. If you are inserting a sheet based on an existing template, specify the path to the template. The default value is xlWorksheet.
Once the sheet is created then you need to use .insertlines to create the relevant procedure and to also embed the code that you want to run.
NOTE - IMP: If you want the code to embed code in the VBA project, you need to ensure that you have "Trust Access to the VBA Project Object Model" selected. See snapshot.
Here is an example where I am creating a sheet and then embedding a Worksheet_SelectionChange Code which will display a message "Hello World"
CODE - TRIED AND TESTED
Option Explicit
Sub Sample()
Dim ws As Worksheet
Dim nLines As Long
Dim VBP As Object, VBC As Object, CM As Object
Dim strProcName As String
Set ws = Worksheets.Add
Set VBP = ThisWorkbook.VBProject
Set VBC = VBP.VBComponents(ws.Name)
Set CM = VBC.CodeModule
strProcName = "Worksheet_SelectionChange"
With ThisWorkbook.VBProject.VBComponents( _
ThisWorkbook.Worksheets(ws.Name).CodeName).CodeModule
.InsertLines Line:=.CreateEventProc("SelectionChange", "Worksheet") + 1, _
String:=vbCrLf & _
" Msgbox ""Hello World!"""
End With
End Sub
This is how the new sheet code area looks once you run the above code.
the following code will add you a spreadsheet.
Public Sub Workbook_Add()
Dim wks As Worksheet
Set wks = ThisWorkbook.Worksheets.Add(, , 1, xlWorksheet)
With wks
'set codename of wks
ThisWorkbook.VBProject.VBComponents(.CodeName).Name = "tblWhatever"
'set tablename of wks
.Name = "whatever"
'add code (untested demo)
'ThisWorkbook.VBProject.VBComponents(.CodeName).CodeModule.InsertLines 1, "Option Explicit"
'add code (as of example from excel-help)
'Application.VBE.CodePanes(1).CodeModule.InsertLines 1, "Option Explicit"
End With
End Sub
If you need to add VBA-Code to this specific spreadsheet, you should further inspect the VBProject object - look for CodeModule and then i.e. InsertLines.
A further hint for you - I would try to use the CodeNames of your tables. It is less likely to be changed - BUT it might be not that comfortable to use in your code at first. I had to get used to it, but for me it has many advantages against using a tables name.
Hope this helps ;)
The default .Add method adds a sheet at the start of the list. Often you want to add it at the end before adding the code lines, as explained by Siddarth Rout. To do that anywhere you can use:
ActiveWorkbook.Worksheets.ADD After:=ActiveWorkbook.Sheets(ActiveWorkbook.Worksheets.Count)
It is easier to read if you have defined and set WB:
Dim WB as Excel.workbook
Set WB = ActiveWorkbook
WB.Sheets.ADD After:=WB.Sheets(WB.Sheets.Count)
Set VBC = ActiveSheet 'If using in Siddarth Rout's code above
Sheets and Worksheets are interchangeable, as illustrated.