check if IsNumeric using Me.Controls() - excel

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

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.

How do I solve Application.Worksheetfunction.Match error?

I searched net for answer but nothing worked (found 5-6~ different solutions but neither worked for me.
I'm getting 1004 error for Application Match. What it does in my case it should give a range from Data sheet to Column E_Menu - "menu" so I could choose one entry from a list of already created and edit it, but for some reason it won't work.
I tried defining column E_menu, changing variants for Range ("xxxxx") which originaly was Dyn_Full_Name or smth but it wouldnt work either.
Option Explicit
Private Sub CommandButton1_Click()
Dim TargetRow As Integer
```TargetRow = Application.WorksheetFunction.Match(ColumnE_Menu, Sheets("Data").Range(Cell1:="B2", Cell2:="D4"), 0)```
Sheets("Engine").Range("B5").Value = TargetRow for use later
Unload Find_Entry_UF
'''Begin retrieving data from database'''
Data_UF.Txt_Update = Sheets("Data").Range("Data_Start").Offset(TargetRow, 2).Value 'first name
Data_UF.Txt_Description = Sheets("Data").Range("Data_Start").Offset(TargetRow, 3).Value 'surname
Data_UF.Txt_Owner = Sheets("Data").Range("Data_Start").Offset(TargetRow, 4).Value 'age
Data_UF.Txt_Proc = Sheets("Data").Range("Data_Start").Offset(TargetRow, 6).Value 'gender combo box
Data_UF.Combo_Status = Sheets("Data").Range("Data_Start").Offset(TargetRow, 7).Value 'region combo box
'''End retrieving data from database'''
Data_UF.Caption = "Edit Existing" 'set caption to show that the user is editing
Data_UF.Show 'show the user form with the details loaded in
End Sub

Excel VBA To Process and Forward Emails Causing "Out of memory" Error

I hope someone can help- I'm hitting the dreaded "Out of memory or system resources" error with some code running in Excel and working with Outlook; from which the error originates.
Short description is it runs through a list of emails looking in the body/subject for a reference. If it finds it, it forwards the email item with the reference in the subject. MWE below; I'm not very experienced handling Outlook objects but I've spent nearly two hours trying different things with no luck. I can't use the GetTable() function since it doesn't include Body text data as far as I know (working off this), unless you can somehow add columns to include the body text?
If I run it in a freshly-opened Outlook session with only a dozen items it isn't a problem but I need it to work on hundreds of emails in one pop. Banging my head against a wall here. Thanks so much in advance!
Private Sub processMWE(ByVal oParent As Outlook.MAPIFolder)
Dim thisMail As Outlook.MailItem
Dim myItems As Outlook.Items
Dim emailindex As Integer
Dim folderpath As String
Dim refandType As Variant
Dim fwdItem
Set myItems = oParent.Items
folderpath = oParent.folderpath
'Starting at row 2 on the current sheet
i = 2
With myItems
'Data output to columns in Excel
For emailindex = 1 To .Count
Set thisMail = .Item(emailindex)
'i takes row value
Cells(i, 1).Value = folderpath
Cells(i, 2).Value = thisMail.Subject + " " + thisMail.Body
Cells(i, 3).Value = thisMail.SenderEmailAddress
Cells(i, 4).Value = thisMail.ReceivedTime
Cells(i, 6).Value = thisMail.Categories
'Reference from body/subject and a match type (integer)
refandType = extractInfo(Cells(i, 2))
'This is the reference
Cells(i, 5).Value = refandType(0)
'And this is the match type.
Select Case refandType(1)
Case 1, 2
'do nothing
Case Else
'For these match types, fwd the message
Set fwdItem = thisMail.Forward
fwdItem.Recipients.Add "#########"
fwdItem.Subject = Cells(i, 5) & " - " & thisMail.Subject
fwdItem.Send
'Edit original message category label
thisMail.Categories = "Forwarded"
thisMail.Save
'Note in spreadsheet
Cells(i, 7).Value = "Forwarded"
End If
End Select
i = i + 1
Next
End With
End Sub
Edit: New development: not only is it always hanging on the same line of code (thisMail.Body) it's actually doing it for specific mail items?! If I give it a batch of one of these problem messages it hangs immediately. Could it be something to do with character encoding or message length? Something that means thisMail.Body won't work that triggers a resources error?
Reason of the problem:
You are creating items without releasing them from memory -with these lines-
For emailindex = 1 To .Count
Set thisMail = .Item(emailindex)
Solution
Release the objects once you are done with them
End Select
i = i + 1
Set thisMail = Nothing
Next
End With
Common language explanation
In this scenario, think about VBA as a waiter, you are telling it that you are going to give some dishes to serve to the customers, you are giving all of them to it, but you never tell it to release them to the table, at one point, it will not be able to handle any more dishes ("Out of memory")

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