Passing an array between click Subs in VBA - excel

I am trying to declare and populate an array variable (coll), but wish to access the contents of the array in another Sub (UnassignButton_Click()). I keep getting the error:
Compile error: Sub or Function not defined
Below is my code:
Public Sub SearchButton_Click()
Dim RowNum As Long
Dim SearchRow As Long
Dim coll As Object
Set coll = CreateObject("System.Collections.ArrayList")
RowNum = 2
SearchRow = 2
Do Until Cells(RowNum, 1).Value = ""
If InStr(1, Cells(RowNum, 1).Value, SearchTextBox.Value, vbTextCompare) > 0 Then
Worksheets("Search_Results").Cells(SearchRow, 1).Value = Cells(RowNum, 1).Value
Worksheets("Search_Results").Cells(SearchRow, 2).Value = Cells(RowNum, 2).Value
Worksheets("Search_Results").Cells(SearchRow, 3).Value = Cells(RowNum, 3).Value
Worksheets("Search_Results").Cells(SearchRow, 4).Value = Cells(RowNum, 4).Value
Worksheets("Search_Results").Cells(SearchRow, 5).Value = Cells(RowNum, 5).Value
Worksheets("Search_Results").Cells(SearchRow, 6).Value = Cells(RowNum, 6).Value
Worksheets("Search_Results").Cells(SearchRow, 7).Value = Cells(RowNum, 7).Value
Worksheets("Search_Results").Cells(SearchRow, 8).Value = Cells(RowNum, 8).Value
Worksheets("Search_Results").Cells(SearchRow, 9).Value = Cells(RowNum, 9).Value
SearchRow = SearchRow + 1
coll.Add RowNum
End If
RowNum = RowNum + 1
Loop
If SearchRow = 2 Then
MsgBox "No products were found that match your search criteria"
Exit Sub
End If
SearchResultsListBox.RowSource = "SearchResults"
End Sub
Private Sub SearchResultsListBox_Click()
NumberIndex = SearchResultsListBox.ListIndex
End Sub
Public Sub UnassignButton_Click()
NumberIndex = SearchResultsListBox.ListIndex
MsgBox coll(NumberIndex)
End Sub
Any help to resolve this issue would be much appreciated!

See this answer for why you can't just willy-nilly tweak event handler procedure signatures to suit your needs.
Each procedure defines a scope. Local variables (whether declared with Dim, ...or not declared at all) live inside that procedure scope, and have no way out.
The next higher-up scope level is module scope. Variables declared in that scope are accessible to all procedures in the module. Use the Private keyword to declare them (Dim is also legal, but I prefer to reserve it for locals, since Private isn't legal at procedure level) - consider using a meaningful identifier name, too... "coll" doesn't say much about what it's for:
Option Explicit
Private RowNumbers As Object
And then you can initialize it in the Initialize handler:
Private Sub UserForm_Initialize()
Set RowNumbers = CreateObject("System.Collections.ArrayList")
End Sub
Probably also a good idea to explicitly wipe it out in the Terminate handler too, since that's a .NET object (FWIW this isn't a VBA array at all and has nothing to do with arrays):
Private Sub UserForm_Terminate()
Set RowNumbers = Nothing
End Sub
The next higher-up scope level is global scope, but once you understand how to pass parameters between procedures, and when to use module scope variables, you pretty much never need any globals. For completleness' sake, a global variable would be declared in a standard module, using the Public keyword (Global works too, but makes things confusing since what we call "global" is really just "project-scope", and besides Public is much more clearly antagonizing Private, so it's good for consistency).
If you added Option Explicit to the top of your modules, VBA would refuse to compile code that uses undeclared variables - and that would have highlighted coll and, likely, NumberIndex as well.
See if Rubberduck inspections flag other issues (disclaimer: I manage that open-source project).

Related

Excel VBA Command Button shows error for UserForm.Show

This is the first time I'm asking a question on Stack Overflow so please forgive me if I'm not completely 100% clear in what my problem is.
A bit of context: I'm working on an automated Accounting system and I'm trying to add a UserForm to add new transactions to the General Ledger. I already achieved a Macro in Excel that generates Invoice in PDF Format and adds the data to the general ledger. Now I want to partly automise the process for incoming invoices by means of the UserForm.
The UserForm itself looks pretty need if I might say so myself. I'll try to add a picture to give an idea of what it looks like and what I get for input. I finished the entire coding of the UserForm, including the command for the command button, the UserForm_Initialize, and all the extra button coding needed to make it work.. So I thought. When I execute the code, it gives me several errors (amongst others, error 361 "Can't load or unload file"). The debug process shows that the error occurs in the "UserForm.Show" line of the command button.
The code that I have until now is as follows:
CommandButton
Private Sub CommandButton1_Click()
Load UserFormTransactions
UserFormTransactions.Show
End Sub
UserForm Initialize:
Private Sub UserForm_Initialize()
'Clearing Input boxes
Input_Type.Clear
Reference.Value = ""
Invoice_Date.Value = ""
PaymentTerm.Value = ""
VAT_Percent.Clear
Amount.Value = ""
Comment.Value = ""
Transaction_Type.Clear
Quarter1.Value = True
'Populate ComboBox - Input_Type
Dim rng As Range
Dim ws As Worksheet
Set ws = Worksheets("Types")
For Each rng In ws.Range("Type")
Me.Input_Type.AddItem rng
Next rng
'Populate ComboBox - VAT_Percent
Set ws = Worksheets("Types")
For Each rng In ws.Range("VAT_per_cent")
Me.VAT_Percent.AddItem rng
Next rng
'Populate ComboBox - Transaction_Type
Set ws = Worksheets("Types")
For Each rng In ws.Range("List_Transaction_Types")
Me.Transaction_Type.AddItem rng
Next rng
'Set Focus on Input_Type Box
Input_Type.SetFocus
End Sub
Add Transaction Button:
Private Sub Add_Click()
Dim LR As Long
Dim NR As Long
SheetName1 = "Transactions"
Worksheets(SheetName1).Activate
LR = Range("C:C").SpecialCells(xlCellTypeLastCell).Row
NR = Cells(LR, 3).End(xlUp).Row + 1
'Transfer Information
Cells(NR, 4).Value = Input_Type.Value
Cells(NR, 5).Value = Invoice_Date.Value
Cells(NR, 6).Value = PaymentTerm.Value
Cells(NR, 7).Value = Reference.Value
Cells(NR, 8).Value = Amount.Value
Cells(NR, 9).Value = VAT_Percent.Value
Cells(NR, 10).Value = Transaction_Type.Value
Cells(NR, 19).Value = Comment.Value
If Quarter1.Value = True Then Cells(NR, 3).Value = Quarter1.Caption
If Quarter2.Value = True Then Cells(NR, 3).Value = Quarter2.Caption
If Quarter3.Value = True Then Cells(NR, 3).Value = Quarter3.Caption
If Quarter4.Value = True Then Cells(NR, 3).Value = Quarter4.Caption
End Sub
Besides I have the coding for the Cancel and Clear button, but those are trivial and I expect there is no error in those. Anyone who could help me with this problem? All help is most appreciated.
Thanks,
Maarten

Change label's caption with loop in a VBA userForm

I want my code to loop through a range of cells and every time show the content of the cell (that is a question) in a label's caption of a userForm and allow the user to choose the button of "Yes" (for knowing the answer) or "NO" (for not knowing the answer) and then the code use the user's response to perform some actions and then continue the loop for other cells.
For each iteration in for loop i need user be allowed to choose "Yes" or "No" or "Cancel" and then the userForms code continue running.
Here is some of the code:
Private Sub UserForm_Activate()
For i = 2 To n
Cells(i, 3) = Cells(i, 3) + 1
If Cells(i, 3) = 1 Then
UserForm1.Controls("question").Caption = Cells(i, 1).Value 'lable 1
UserForm1.Controls("answer").Caption = "" 'lable 2
'some codes...
elseIf Cells(i, 3) = 3 Then
UserForm1.Controls("question").Caption = Cells(i, 1).Value 'lable 1
UserForm1.Controls("answer").Caption = "" 'lable 2
next
end sub
and i need to run these codes whenever user clicks on a button on the form . then the rest of the above code be executed .
Private Sub ansYes_Click() 'If user clicks Yes show the answer and continue
UserForm1.Controls("answer").Caption = Cells(i, 2)
UserForm1.Controls("answer").Visible = True
End Sub
Private Sub ansNo_Click() 'If user clicks No show the answer and continue
UserForm1.Controls("answer").Caption = Cells(i, 2)
UserForm1.Controls("answer").Visible = True
Cells(i, 3) = 0
Cells(i, 4) = 1
End Sub
Private Sub ansCancel_Click() 'If user clicks cancel unload userform and exit the for loop
Unload UserForm1
End Sub
You haven't shown us where you show the UserForm or how it is defined within your project and you haven't given a good description of the problem. This forces assumptions that may not be accurate.
Do not name your UserForm "UserForm1", give it a useful name. Henceforth, I will refer to this as "UserResponseForm"
Do not use default instance of a UserForm, use a variable and learn the difference between Dim As and Dim As New. The difference is significant and important.
Dim myForm As UserResponseForm
Dim myForm As New UserResponseForm
Using UserResponseForm's property window, find and rename your labels "Question" and "Answer"
In UserResponseForm's code use Ctrl + H to replace:
UserForm1.Controls("answer") with Me.Answer
and
Unload UserForm1 with Me.Hide
Move all of your code from Private Sub UserForm_Activate to a normal module. Give the module and the sub a useful name. I will refer to module as "QuestionaireCodeModule" and the sub as "RunTheQuestionnaire"
The QuestionaireCodeModule should look similar to this
Option Explicit
Private Const n As Long = 20 'you didn't show how n was populated or scoped so I put it here
Sub RunTheQuestionaire()
Dim i As Long
Dim myForm As New UserResponseForm
For i = 2 To n
Cells(i, 3) = Cells(i, 3) + 1
If Cells(i, 3) = 1 Or Cells(i, 3) = 3 Then 'added or condition for clarity
myForm.Question.Caption = Cells(i, 1).Value 'lable 1
myForm.Answer.Caption = vbNullString 'lable 2
'some codes...
'next three lines removed because they are redundant
'elseIf Cells(i, 3) = 3 Then
'UserForm1.Controls("question").Caption = Cells(i, 1).Value 'lable 1
'UserForm1.Controls("answer").Caption = "" 'lable 2
End If
myForm.Show
If Not myForm.Visible Then Exit For
Next i
End Sub
That should be it but I can't say if this will work for you or not because you did not provide enough to work with and the code you provided in your example won't compile.

check if IsNumeric using Me.Controls()

I have a userform with dozens of text boxes where the user should input only numbers from 1 to 6. When the form is completed the user clicks Save and the numbers are added to an Excel table.
As the text boxes are quite a lot I used the following code to read their value:
' Text boxes name are: txt_a01, txt_a02, txt_a03...
For i = 1 To 5
ws.Cells(iRow, i).Value = Me.Controls("txt_a0" & i).Value
Next
' Here text boxes name are: txt_b01, txt_b02, txt_b03...
For i = 6 To 10
ws.Cells(iRow, i).Value = Me.Controls("txt_b0" & i - 5).Value
Next
etc.
rather than write dozens of times:
ws.Cells(iRow, 1).Value = Me.txt_a01.Value
ws.Cells(iRow, 2).Value = Me.txt_a02.Value
ws.Cells(iRow, 3).Value = Me.txt_a03.Value
ws.Cells(iRow, 4).Value = Me.txt_a04.Value
ws.Cells(iRow, 5).Value = Me.txt_a05.Value
Now I would like to check - during input - if the user has really added a number and if its between 1 and 6. Normally I would do it like this:
Private Sub txt_a01_AfterUpdate()
If Not IsNumeric(txt_a01.Value) Then
MsgBox ("Only numbers accepted")
End If
End Sub
But as I use a FOR - LOOP and Me.Controls() to read the text box values I don't understand how to write down the _AfterUpdate Sub.
You could attach dynamically an event handler on form load or you could create a custom control with an event.
Personally I prefer use custom control and KeyPressEventHandler to refuse all not allowed chars.
You may create a class to handle the event :
Private WithEvents m_oTextBox as TextBox
Public Property Set TextBox(ByVal oTextBox as TextBox)
Set m_oTextBox = oTextBox
End Property
Private Sub m_oTextBox_Change()
' Do something
End Sub
then, for each control:
Dim myEventHandler As TextBoxEventHandler
Set myEventHandler = New TextBoxEventHandler
Set myEventHandler.TextBox = oControl
m_oCollectionOfEventHandlers.Add myEventHandler

VBA Excel - Mantaining Public Variables With Button Press

I'm trying to Create a document that has a bunch o constant strings.
I've declared then Public in a Module like this:
Public Abc As String
In "ThisWorkbook" I run the following code to initialize de variable
Private Sub Workbook_Open()
Abc = "C5"
End Sub
I have Buttons coded to change some values like:
If Range(Abc) = "" Then
Range(Abc) = 1
Else
Range(Abc) = Range(Abc) + 1
End If
When I run a button with this code:
Sub BotaoNovoDia()
i = 3
While i <= 33
If Cells(i, 11) = "" Then
Cells(i, 11) = Range(Apresentacao)
Cells(i, 12) = Range(Aceitacao)
Cells(i, 13) = Range(Aceitou)
Cells(i, 31) = Range("D41")
Cells(i, 11).Interior.Color = Range(Apresentacao).Interior.Color
Cells(i, 12).Interior.Color = Range(Aceitacao).Interior.Color
If Range("K34") < 0.65 Then
Range("K34").Interior.Color = vbRed
Else
Range("K34").Interior.Color = vbGreen
End If
If Range("L34") < 0.45 Then
Range("L34").Interior.Color = vbRed
Else
Range("L34").Interior.Color = vbGreen
End If
Range(Aceitou) = 0
Range(Rejeitou) = 0
Range(NaoApres) = 0
End
Else
i = i + 1
End If
Wend
End Sub
And i try to run the first button again I get and error saying: "Run-Time error '1004': Method 'Range' of object '_Global' failed"
the debug button takes me to the fisrt line that tries to access to the public variables value. What can i do to mantain the values in the Public variables?
When you call End (By itself, not as part of End If, etc) you clear your Globals.
Don't use End.
Using a named range is a great idea; but there's no reason why your public declaration shouldn't work - except for that pesky End statement. (I missed that first read through...)
However, your scope isn't clear in each of your functions, e.g. which worksheet is the Range referring to, so if one function works on another worksheet, pushing the button that fires "Change" could refer to a different place that doesn't like that reference.
e.g. your ranges should be something like SomeWorkbook.TheWorksheet.Range(<range>) and when you're changing the cell value, you should use .Value to ensure there's no ambiguity - you'll know from searching through here that error 1004 is the least descriptive error code...

Run sub procedure in excel after values passed from MS Access record

I have an Access database which successfully passes the values in my current record to an Excel sheet. To calculate my Excel sheet I then need to call a subroutine.
I think my problem is the order in which things are happening.
I have tried to call the routine (within Excel) from the MyWorkbook with the following code
Private Sub Workbook_Activate()
On Error Resume Next
With Application
.DisplayFullScreen = True
.CommandBars("Worksheet Menu Bar").Enabled = True
.ScreenUpdating = False
End With
Call Rategenerator
Sheets("home").Select
End Sub
The routine Rategenerator is, I think, being called before the values from the Access record are populating the designated cells in my sheet "home."
Is there a way to force the sub routine Rategenerator to execute after the values are populated on the sheet?
The code from the current Access record to populate the Excel sheet looks like this:
Dim objXLApp As Object
Dim objXLBook As Object
Dim r As Object
'check whether Excel is open before opening another copy.
Set objXLApp = CreateObject("Excel.Application")
Set objXLBook = objXLApp.Workbooks.Open("e:\!!!Access SHare Folder\Ogden v7.1.1.5 final.xls")
objXLApp.Application.Visible = True
'Specify Sheet
With objXLBook.Sheets("home")
.unProtect Password:="xxxxxxxx"
.Cells(15, 6).Value = Me.DateofAcc
.Cells(16, 6).Value = Me.DOB
.Cells(17, 6).Value = Me.todaysDate
.Cells(18, 6).Value = Me.Gender
.Cells(19, 6).Value = Me.RetireAge
.Cells(22, 6).Value = Me.DeferredAge
.Cells(28, 6).Value = Me.ContEmpPre
.Cells(29, 6).Value = Me.ContDisPre
.Cells(30, 6).Value = Me.txtContOveridePre 'taken from the txtbox not the checkbox
.Cells(31, 6).Value = Me.ContOverideValPre
.Cells(28, 7).Value = Me.ContEmpPost
.Cells(29, 7).Value = Me.ContDisPost
.Cells(30, 7).Value = Me.txtContOveridePost 'taken from the txtbox not the checkbox
.Cells(31, 7).Value = Me.ContOverideValPost
End With
With objXLBook.Sheets("LOETblCalx")
.Cells(19, 17).Value = Me.SalaryNet1
.Cells(20, 17).Value = Me.Residual1
End With
''Tidy up
Set objXLBook = Nothing
Set objXLApp = Nothing
End Sub
As I say all the field values from the Access record populate without issue. Parts of the Excel workbook which do not rely on the sub routine auto calculate as expected with Auto calculation in the workbook turned on.
Its just the triggering of the sub routine after the cells have been populated which is causing me some difficulty.
Eventually I want to try and get the calculated results back into some additional fields in the Access record but having never attempted anything like that before I'm careful not to get ahead of myself.
Thanks for looking
Instead of using Workbook_Activate(), try running the Excel macro directly from your Access code after it has finished populating. This would be done by putting code similar to this at the end (before the tidy up part) of your Access macro: objXLBook.Run "Rategenerator"
I'm not sure if that exact piece of code will work for you, but see here for more details on how to do it.

Resources