Making modular VBA function (MS Access) - ms-access

I am writing up a simple multi-condition search form.
The Access VBA function set for the first toggle button looks like this:
Private Sub ToggleQ1_Click()
Select Case ToggleQ1.Value
Case True
CondQ1 = "AND"
ToggleQ1.Caption = CondQ1
Case False
CondQ1 = "OR"
ToggleQ1.Caption = CondQ1
End Select
End Sub
ToggleQ1 = button's name
CondQ1 = variable to be used with a string to create conditional search.
It would likely be absurd to create 50 more of the same button code, differ only in its name (ex. "ToggleQ50" and "CondQ50")
Is there any way to make it modular and reusable?
Thank you very much in advance.

In the form's module create a function (not sub) like this:
Private Function SetCaption()
Dim clickedButton As Control
Dim CondQ1 As String
Set clickedButton = Me.ActiveControl
Select Case clickedButton.Value
Case True
CondQ1 = "AND"
clickedButton.Caption = CondQ1
Case False
CondQ1 = "OR"
clickedButton.Caption = CondQ1
End Select
End Function
In form designer select all 50 buttons and type in property On Click
=SetCaption()
So, you won't need to create event handler for each button.

Create another sub and send the clicked button to it. Similar to this:
Private Sub cmdTest01_Click()
SetCaption cmdTest01
End Sub
Private Sub cmdTest02_Click()
SetCaption cmdTest02
End Sub
Private Sub SetCaption(clickedButton As CommandButton)
Dim CondQ1 As String
Select Case clickedButton.Caption
Case "Test01"
CondQ1 = "AND"
clickedButton.Caption = CondQ1
Case "Test02"
CondQ1 = "OR"
clickedButton.Caption = CondQ1
End Select
End Sub
Case blocks can be simplified to
Case "Test01"
clickedButton.Caption = "AND"
Case "Test02"
clickedButton.Caption = "OR"

Use WithEvents. That takes a little code when loading and unloading the form, but zero code for each button.
A similar example with full code, which you should be able to adapt, can be found here:
Create Windows Phone Colour Palette and Selector using WithEvents
and at GitHub:
VBA.ModernTheme
Code snippet:
Private ControlCollection As Collection
Private Sub Form_Load()
' Load events for all colour value textboxes.
Dim EventProcedure As ClassTextboxSelect
Dim Control As Access.Control
Set ControlCollection = New Collection
For Each Control In Me.Controls
If Control.ControlType = acTextBox Then
Set EventProcedure = New ClassTextboxSelect
EventProcedure.Initialize Control
ControlCollection.Add EventProcedure, Control.Name
End If
Next
Set EventProcedure = Nothing
Set Control = Nothing
End Sub
Private Sub Form_Open(Cancel As Integer)
Dim Index As Integer
' Set colour palette.
For Index = 0 To 20
Me("Box" & CStr(Index + 1)).BackColor = PaletteColor(Index)
Me("Name" & CStr(Index + 1)).Value = LiteralWpThemeColor(PaletteColor(Index))
Me("Css" & CStr(Index + 1)).Value = RGBHex(PaletteColor(Index))
Me("Vba" & CStr(Index + 1)).Value = PaletteColor(Index)
Me("Hex" & CStr(Index + 1)).Value = "&H" & Hex(PaletteColor(Index))
Next
End Sub
Private Sub Form_Unload(Cancel As Integer)
' Unload events for all colour value textboxes.
Dim EventProcedure As ClassTextboxSelect
For Each EventProcedure In ControlCollection
EventProcedure.Terminate
Next
Set EventProcedure = Nothing
Set ControlCollection = Nothing
End Sub

Related

MS ACCESS COMBOBOX

good day!
i am using ms access and i want to know what will i do if i have 5 comboboxes in my form then 10 choices of name which connect in one table, how can make that if ever i select one of the name in list then the selected list will not show on the 2nd combobox list. there are 5 comboboxes in my list. look like this
Consider switching to value list and add the items the boxes using a query in VBA. Then delete and add items from other boxes when a change is made.
Option Explicit
' Have to use global variables because combobox.oldValue is not reliable
Dim strOld1 As String
Dim strOld2 As String
Dim strOld3 As String
Dim strOld4 As String
Dim strOld5 As String
Private Sub frmMain_Load()
Dim rsNames as Recordset
' Get names
Set rsNames = CurrentDB.OpenRecordset( _
"SELECT [Names] " & _
"FROM tblPerson")
' Setup recordset
If rsNames.RecordCount > 0 Then
rsNames.MoveLast
rsNames.MoveFirst
' Add names to all boxes
Do While Not rsNames.EOF
cboNames1.addItem rsNames.Field("Name")
cboNames2.addItem rsNames.Field("Name")
cboNames3.addItem rsNames.Field("Name")
cboNames4.addItem rsNames.Field("Name")
cboNames5.addItem rsNames.Field("Name")
rsNames.MoveNext
End If
' Dispose recordset asap
Set rsNames = Nothing
End Sub
Private Sub addRemoveItem(ByRef thisCombo As Variant, ByRef oldValue As String)
Dim arrCombos(1 To 5) As ComboBox
Dim otherCombo As Variant
Dim intIndex As Integer
' Get a list of all combo boxes
Set arrCombos(1) = Me.cboNames1
Set arrCombos(2) = Me.cboNames2
Set arrCombos(3) = Me.cboNames3
Set arrCombos(4) = Me.cboNames4
Set arrCombos(5) = Me.cboNames5
' Check for comboboxes that are not the one changed
For Each otherCombo in arrCombos
If otherCombo.ControlName <> thisCombo.ControlName Then
' Search for exisitng item
IntIndex = 0
Do While otherCombo.itemData(intIndex) <> thisCombo.Value _
And intIndex < otherCombo.ListCount
intIndex = intIndex + 1
Loop
' Remove the found item
otherCombo.removeItem intIndex
' Add unselected value back
If oldValue <> "" Then
otherCombo.addItem oldValue
End if
Next
' Change the old value to the new one
oldValue = thisCombo.Value
End Sub
Private Sub cboName1_Change()
RemoveAddItem Me.cboName1, strOld1
End Sub
Private Sub cboName2_Change()
RemoveAddItem Me.cboName2, strOld2
End Sub
Private Sub cboName3_Change()
RemoveAddItem Me.cboName3, strOld3
End Sub
Private Sub cboName4_Change()
RemoveAddItem Me.cboName4, strOld4
End Sub
Private Sub cboName5_Change()
RemoveAddItem Me.cboName5, strOld5
End Sub
Sorry, I did this on a phone...

Color field in access 2003

On MS-Access 2003 i've a mask that shows results of a query. For example result of query is:
Column1Column2
1 Y
2 N
3 N
4 Y
It shows in the mask ad a table.
I need to color background field of column2 if the value is Y. To do that i've use the code:
Private Sub Form_Current()
if (Column2) = "Y" Then
Stato.BackColor = vbGreen
End If
End Sub
But it colored all background. So i've tried a workaround:
For Each ctl In Me.Section(acDetail).Controls
If (ctl) = Column2 Then
If (Me.Column2) = "Y" Then
ctl.BackColor = QBColor(2)
End If
End If
But this also colored all bg. Some suggestion?
You can add conditional formatting in code using something like this. This function is based on some code I've used and you may need to tweek it to fit your specific requirements.
Dim fcd As FormatCondition
Dim ctl As control
Dim frm As Form
Dim txt As TextBox
Dim strCond As String
For Each ctl In frm.Controls
If TypeOf ctl Is Access.TextBox Then
If ctl.Visible = True Then
Set txt = ctl
If txt.Name = "Column2" Then
strCond = "=Y"
Set fcd = txt.FormatConditions.Add(acExpression, acEqual, strCond)
fcd.BackColor = QBColor(2)
End If
End If
End If
Next

MS Access 2007 VBA - Reusable list box code

I have a MS Access 2007 application which has several forms where I've used the same list box design. I have two list boxes, one of which gets values from a table with a query like:
SELECT id, value FROM table
And the second which is initially empty. In between these two list boxes are add and remove buttons, which are disabled by default. Clicking a value in the first list box enables the add button, and clicking a value in the second list box enables the remove button. Clicking the add button adds the selected item to the second list, and clicking the remove button removes an item for the second list.
The code I have for the add button is as follows ("ALLLIST" refers to the list with the query values, "SELECTEDLIST" is the one that's initially empty) :
Dim selectedId, selectedValue, safeValue As String
Dim existing As Boolean
Dim index As Integer
existing = False
selectedId = Me.ALLLIST.Value
index = Me.ALLLIST.ListIndex
selectedValue = Me.ALLLIST.Column(1,index)
'Loop through the list of selected values and see if this one has already been added to the list
For i = 0 To (Me.SELECTEDLIST.ListCount)
If (Me.SELECTEDLIST.Column(0, i) = selectedId) Then
existing = True
End If
Next i
'Only add the value if it's not already on the list
If (existing) Then
MsgBox "This list can't contain duplicate values", vbOKOnly + vbInformation, "Error"
Else
safeValue = Replace(selectedValue & "", "'", "''")
Me.SELECTEDLIST.AddItem (selectedId & ";'" & safeValue & "'")
Me.SELECTEDLIST.Value = Null
Me.REMOVEBUTTON.Enabled = False
End If
And the code for the remove button is:
Dim numItems, index As Integer
index = Me.SELECTEDLIST.ListIndex
'Remove the selected item and move to the top of the list
Me.SELECTEDLIST.RemoveItem (index)
Me.SELECTEDLIST.Selected(0) = True
numItems = Me.SELECTEDLIST.ListCount
'Cosmetic feature, select the row above the one we're removing
If (index > 0) Then
Me.SELECTEDLIST.Selected(index - 1) = True
Else
Me.SELECTEDLIST.Selected(0) = True
End If
'If the list is empty now, disable the remove button
If (numItems = 0) Then
Me.ALLLIST.SetFocus
Me.REMOVEBUTTON.Enabled = False
Me.SELECTEDLIST.Selected(-1) = True
End If
What I would like to do is, rather than copy and paste this code all over the place, have this template stored somewhere and then in my form code write something like:
hookUpLists(allListName, selectedListName, addButtonName, removeButtonName)
How can I do this? Can I write a module to do this some how? I'm also open to any improvements I can make on the above code.
Thanks
Thanks for the hint on using class modules, HK1, I'd never used them before.
To solve the original question, I created a new class module "MultiSelectListBox", which has the following code:
Option Compare Database
Private WithEvents allList As ListBox
Private WithEvents selectedList As ListBox
Private WithEvents addBtn As CommandButton
Private WithEvents removeBtn As CommandButton
Private numColumns As Integer
Public Sub hookUpLists(numberOfColumns As Integer, allValuesList As ListBox, selectedValuesList As ListBox, addButton As CommandButton, _
removeButton As CommandButton)
'Grab all the controls passed in
Set allList = allValuesList
Set selectedList = selectedValuesList
Set addBtn = addButton
Set removeBtn = removeButton
numColumns = numberOfColumns
'Tell Access we want to handle the click events for the controls here
allList.OnClick = "[Event Procedure]"
selectedList.OnClick = "[Event Procedure]"
addBtn.OnClick = "[Event Procedure]"
removeBtn.OnClick = "[Event Procedure]"
End Sub
Private Sub allList_Click()
addBtn.Enabled = True
End Sub
Private Sub selectedList_Click()
removeBtn.Enabled = True
End Sub
Private Sub addBtn_Click()
Dim selectedId As String
Dim existing As Boolean
Dim index As Integer
existing = False
selectedId = allList.Value
index = allList.ListIndex
'Loop through the list of selected values and see if this one has already been added to the list
For i = 0 To (selectedList.ListCount)
If (selectedList.Column(0, i) = selectedId) Then
existing = True
End If
Next i
'Only add the value if it's not already on the list
If (existing) Then
MsgBox "This list can't contain duplicate values", vbOKOnly + vbInformation, "Error"
Exit Sub
End If
Dim item As String
item = selectedId & ";"
'Loop over all of the columns and add them to the second list box
For i = 1 To numColumns - 1
item = item & "'" & Replace(allList.Column(i, index) & "", "'", "''") & "'"
'Don't add a trailing semicolon
If (i <> numColumns - 1) Then
item = item & ";"
End If
Next i
selectedList.AddItem (item)
selectedList.Value = Null
removeBtn.Enabled = False
'Select the next row
If (index <> allList.ListCount - 1) Then
allList.Selected(index + 1) = True
allList.Value = allList.Column(0, index + 1)
End If
End Sub
Private Sub removeBtn_Click()
Dim numItems, index As Integer
index = selectedList.ListIndex
'Remove the selected item and move to the top of the list
selectedList.RemoveItem (index)
selectedList.Selected(0) = True
numItems = selectedList.ListCount
'Cosmetic feature, select the row above the one we're removing
If (index > 0) Then
selectedList.Selected(index - 1) = True
Else
selectedList.Selected(0) = True
End If
'If the list is empty now, disable the remove button
If (numItems = 0) Then
allList.SetFocus
removeBtn.Enabled = False
selectedList.Selected(-1) = True
End If
End Sub
Most of the above is identical to what I was already using, one important thing to note for anyone stumbling across this is the use of "WithEvents" when declaring the variables. This tells Access to look in the class module for event handlers. Finally, from my form I can do the following:
Private contactList As MultiSelectListBox
Private Sub Form_Open(Cancel As Integer)
Set contactList = New MultiSelectListBox
contactList.hookUpLists 3, Me.allContactsList, Me.selectedContactsList, Me.addContactBtn, Me.removeContactBtn
End Sub

VBA code that changes the background color of a form after update

I need some code that when a check box is unchecked it will change the background color of my form and return it back to its original color when checked. The code i have for the check box currently locks a combo box when a value is chosen. Example below
Private Sub AccessKeyNo_AfterUpdate()
If MsgBox("Do you want to assign Access Key " & Me.AccessKeyNo & "?", _
vbYesNo) = vbYes Then
Me.GuestAccessKeyID = Me.AccessKeyNo
If Me.Dirty Then Me.Dirty = False
Me.AccessKeyNo.Requery
Me.AccessKeyNo = Null
Me.MyCheckBox = IsNull(Me.GuestAccessKeyID)
End If
End Sub
In a standard module (not the form module -- the scope of the constants would be limited to form, thus you wouldn't be able to reuse them):
Public Const colorBlue_Cornflower = "15714765"
Public Const colorTan_Encarnacion = "11398133"
Now in the module for the form:
Dim colorThis as String, booWhatever as Boolean
booWhatever = Me.MyCheckBox ''Use of the variable can prevent problems
If booWhatever Then
colorThis = colorBlue_Cornflower
Else
colorThis = colorTan_Encarnacion
End If
subFrm.Form.Section(acDetail).BackColor = colorThis
subFrm.Form.Section(acHeader).BackColor = colorThis
subFrm.Form.Repaint

Can I wrap an Access form with a transaction?

I want to make a form the essentially creates an invoice, but using some other related data as inputs or limits; In the process of adding items to the invoice, I need to reduce the items in another table. Since the user will enter several items at a time, I'd like to issue a "START TRANSACTION" when the form loads, and then do a "COMMIT" when the form updates. Thus, if they cancel the form, the other related tables (shown via subforms) would roll back to the previous values.
Can't be done using bound forms. You could use temporary tables to store the data and then update the main tables. A bit of a kludge but I've done that in the past.
See the TempTables.MDB page at my website which illustrates how to use a temporary MDB in your app.
Yes it can be done, to take control over the transaction in the form you need use this code:
Private Sub Form_Open(Cancel As Integer)
Set Me.Recordset = CurrentDb.OpenRecordset("NAME_OF_YOUR_TABLE_OR_QUERY")
End Sub
After that, you can use DBEngine to control the transaction.
It work for me (Im using Access 2007)
Note: If you insert a new record using the form interface it is visible when the Form_AfterInsert event is raised, therefore you can use DbEngine.Rollback in that event to undo the changes.
I have figured it out its possible to have it on bound forms. Everything you need to assign variable that contain an ID number on change event of any of the parent control. Than you need to transmit that ID value into the subform connected field and perform transaction on both forms the primary and the subform. Here is the example of how I did it.
Primary Form VBA
Option Compare Database
Option Explicit
Private boolFrmDirty As Boolean
Private boolFrmSaved As Boolean
Private Sub EmpolyeesID_Change()
Dim ordID As Integer
Dim subFormOrdID As Object
Set subFormOrdID = Forms!Order.OrderInstallation.Form!OrderID
ordID = Me.Form!OrderID
subFormOrdID.DefaultValue = ordID
End Sub
Private Sub Form_AfterDelConfirm(Status As Integer)
If Me.Saved = False Then Me.Saved = (Status = acDeleteOK)
End Sub
Private Sub Form_AfterUpdate()
Me.Saved = True
End Sub
Private Sub Form_Delete(Cancel As Integer)
If Me.Dirtied = False Then DBEngine.BeginTrans
Me.Dirtied = True
End Sub
'Check if form has got new values in it
Private Sub Form_Dirty(Cancel As Integer)
If Me.Dirtied = False Then DBEngine.BeginTrans
Me.Dirtied = True
End Sub
'Open Form as a Record Set and set the variables for it
Private Sub Form_Open(Cancel As Integer)
Dim db As DAO.Database
Dim rs As DAO.Recordset
Set db = CurrentDb
Set rs = db.OpenRecordset("SELECT * FROM Orders", dbOpenDynaset, dbAppendOnly)
Set Me.Recordset = rs
End Sub
Private Sub Form_Unload(Cancel As Integer)
Dim msg As Integer
If Me.Saved Then
msg = MsgBox("Do you want to commit all changes?", vbYesNoCancel)
Select Case msg
Case vbYes
DBEngine.CommitTrans
Case vbNo
DBEngine.Rollback
Case vbCancel
Cancel = True
End Select
Else
If Me.Dirtied Then DBEngine.Rollback
End If
End Sub
Public Property Get Dirtied() As Boolean
Dirtied = boolFrmDirty
End Property
Public Property Let Dirtied(boolFrmDirtyIn As Boolean)
boolFrmDirty = boolFrmDirtyIn
End Property
Public Property Get Saved() As Boolean
Saved = boolFrmSaved
End Property
Public Property Let Saved(boolFrmSavedIn As Boolean)
boolFrmSaved = boolFrmSavedIn
End Property
Private Sub ProductID_AfterUpdate()
'Calculations of VAT and Floor Price
Dim clcVAT As Integer
Dim sqlQry As String
Dim instID As Integer
instID = Me.Form!ProductID.Value
sqlQry = "SELECT Products.Price FROM Products WHERE Products.ProductID =" & instID & ""
Me.flPrice.RowSource = sqlQry
End Sub
Sub Form VBA
Option Compare Database
Option Explicit
'Transaction for sub-form
Private Sub Form_Open(Cancel As Integer)
Dim db As DAO.Database
Dim rs As DAO.Recordset
Set db = CurrentDb
Set rs = db.OpenRecordset("SELECT * FROM OrderInstallation")
Set Me.Recordset = rs
End Sub
Private Sub Form_AfterUpdate()
Dim emplID As Object
Dim cstmID As Object
Dim prdcID As Object
Dim DataArray As Variant
Dim RqrdFieldErorr As String
Dim qry As String
Set emplID = Me.Parent!EmpolyeesID
Set cstmID = Me.Parent!CustomerID
Set prdcID = Me.Parent!ProductID
If IsNull(emplID.Value) Or IsNull(cstmID.Value) Or IsNull(prdcID.Value) Then
MsgBox ("Please enter select required fields first")
Else
End If
End Sub
'Restrict updates of Installation subform if Employee, Customer and Product is not selected
Private Sub InstallationID_AfterUpdate()
Dim instID As Integer
Dim instPrice As Integer
Dim strQry As String
' Create query based on InstallationID value
instID = InstallationID.Value
strQry = "SELECT Installation.Price, Installation.InstallationID FROM Installation WHERE Installation.InstallationID =" & instID & ""
Me.Price.RowSource = strQry
End Sub