Warning for Dynamic Combobox list - ms-access

I have a combobox that builds it's list upon first usage. I know that the way I want "NotInList" to behave isn't conventional - I don't want to waste adding the item to a table separate from the needed entry, but I'd like to still warn about an item that hasn't been used yet, so that the user has to think twice before accepting the entry.
Once the user adds the item, it will automatically appear in the list next time because the data source for the combo box is as follows:
SELECT tbl_SP.PROGRAM
FROM tbl_SP
GROUP BY tbl_SP.PROGRAM
HAVING (((tbl_SP.PROGRAM) Is Not Null And (tbl_SP.PROGRAM)<>""));
I tried this:
Private Sub cmbPROGRAM_NotInList(NewData As String, Response As Integer)
If MsgBox("'" & Chr(34) & NewData & Chr(34) & " hasn't been used yet. Add to list? ", vbQuestion + vbYesNo, "Add - " & NewData & "?") = vbYes Then
Response = acDataErrAdded
End If
End Sub
but of course, Access wants the item to actually exist before it will release the error. And...if I set LimitToList to "No" then the user doesn't get a warning.
How can I achieve this behavior?
Ok, I tried this which works fine if the user selects YES, but becomes more complicated when the user selects "NO"
Public Function ReturnsRecords(strSQL As String) As Boolean
Dim d As DAO.Database
Dim arr(1 To 3) As DAO.Recordset
'Dim rs As DAO.Recordset
'assume 3 items in array above
Set d = CurrentDb
Set arr(1) = d.OpenRecordset(strSQL)
' MsgBox "Record Count is " & arr(1).RecordCount
If arr(1).RecordCount > 0 Then
ReturnsRecords = True
Else
ReturnsRecords = False
End If
Set d = Nothing
End Function
Private Sub cmbPROGRAM_BeforeUpdate(Cancel As Integer)
Dim strSQL As String
strSQL = "Select * from LU_PROGRAM where PROGRAM ='" & Me.cmbPROGRAM & "'"
If ReturnsRecords(strSQL) = False Then
If MsgBox("'" & Chr(34) & Me.cmbPROGRAM & Chr(34) & " hasn't been used yet. Add to list? ", vbQuestion + vbYesNo, "Add - " & Me.cmbPROGRAM & "?") = vbNo Then
Cancel = True
' how do I reset this? Me.cmbPROGRAM.Text = Null
End If
End If
End Sub
How do I clear the combobox if the user selects NO? If I select me.undo, that will undo all of the entries, but I just want to clear the combobox.
Incidentally, the form is totally unbound and doesn't accept an entry until the user selects "Save"

First, I'm not quite sure what you wish to achieve ...
Then, educate the users to press Escape to cancel. This is mandatory wisdom when operating an Access application.
For your code to work, you can't change the content of a control in the BeforeUpdate event. So try the AfterUpdate event with either:
Me!cmbPROGRAM.Text = ""
or:
Me!cmbPROGRAM.Value = Null

Related

Why does Access VBA dropdown method not work?

I'm using a text box to filter a combo box list in Access 2013. I put the filter code in the Form_Timer sub so as to give users time to type the entire filter string before applying the filter, and I invoke the timer from the text box Change sub. It works great except for one thing: I want the combo box list to drop down and display results, and it just won't work. However I put the exact same line of code in the GotFocus sub for the combo box, and that line works perfectly.
I also tried executing the filter code within the Change sub, just in case there was some weirdness regarding Form_Timer execution. Same result. Here is the code:
Private Sub cboCENamesMain_GotFocus()
Me.cboCENamesMain.Dropdown '<---This line works perfectly.
End Sub
Private Sub Form_Timer()
Dim strSQL As String
Me.TimerInterval = 0
Me.txtFilter.Value = Me.txtFilter.Text
Me.cboCENamesMain.SetFocus
strSQL = ""
strSQL = strSQL & "Select DISTINCT [CE ID] "
strSQL = strSQL & "From [tblMyTable] "
If Len(Me.txtFilter) > 0 Then
strSQL = strSQL & "Where [CE ID] Like ""*" & Me.txtFilter & "*"" "
End If
strSQL = strSQL & "Order By [CE ID];"
Me.cboCENamesMain.RowSource = strSQL
Me.cboCENamesMain.Dropdown '<---This line doesn't do what it's supposed to.
Me.txtFilter.SetFocus
Me.txtFilter.SelStart = Len(Me.txtFilter.Text)
Me.txtFilter.SelLength = 0
End Sub
Private Sub txtFilter_Change()
If Len(Me.txtFilter.Text) = 0 _
Or Len(Me.txtFilter.Text) > 2 Then
Me.TimerInterval = 500
End If
End Sub
I could use a list box instead of a combo box to allow users to see the results of their filter typing, but that would seriously detract from my form design. I have searched on Google and on StackOverflow, and have not found anybody else discussing this issue. Any ideas?
Here is my final code. This works just the way I want. The user types a few characters, and the timer automatically filters the combo box list down to items containing the typed string.
StackOverflow is a great place to file stuff like this - going from job to job it will save some time reinventing the wheel.
The extensive comments in the code are for the analysts who will need to maintain the applications after my contract ends. Neither of them has ever written VBA.
Private Sub cboCENamesMain_Change()
' Ordinarily the AfterUpdate event would automatically fire off the Change event.
' We set a flag to avoid that - otherwise the list would be filtered to only the selected record.
If booCancelChange = False Then
' Don't bother filtering the combo box list unless the filter text is null or longer than
' 1 character.
If Len(Me.cboCENamesMain.Text) <> 1 Then
' Set the Form_Timer event to fire off in 0.3 second to give them time to type a few characters.
Me.TimerInterval = 300
End If
Else
' Reset the flag, otherwise the Change code would stop working after the first record selection.
booCancelChange = False
End If
End Sub
Private Sub Form_Timer()
Dim strSQL As String
' Reset the timer to not fire off, so that it won't keep running without a change
' in the combo box.
Me.TimerInterval = 0
' If they have tabbed out of the combo box after selecting an item, we don't want to
' do this. It's unnecessary and it throws errors from references to the control's
' properties when focus is no longer on the control.
If Screen.ActiveControl.Name = Me.cboCENamesMain.Name Then
' Create a SQL filter for the combo box using the entered text.
strSQL = ""
strSQL = strSQL & "Select DISTINCT [CE ID] "
strSQL = strSQL & "From [Covered Entities] "
If Len(Me.cboCENamesMain.Text) > 0 Then
strSQL = strSQL & "Where [CE ID] Like ""*" & Me.cboCENamesMain.Text & "*"" "
End If
strSQL = strSQL & "Order By [CE ID];"
' Apply the filter.
Me.cboCENamesMain.RowSource = strSQL
Me.txtRowCount = Me.cboCENamesMain.ListCount
' Drop down the combo list so they can see the results of their filter text.
Me.cboCENamesMain.Dropdown
End If
End Sub

Class Module to trigger and handle field event & record event

I am working on a database for our company. One of the big things they want this database to do is to create reminders and emails based on changed fields and newly created records. For example, when the user puts a date in the First_Meeting field, an event should be triggered that will create 3 reminders on an Outlook Calendar. As a second example, when a new record is created in the Contract table, an event should be triggered to create 2 reminders in an Outlook Calendar and 2 Outlook emails.
I have the logic to do all of this, but I am trying to figure out the best way to handle the events. It is important that the trigger happens on whatever form the First_Meeting field is updated. If I do a form field event, I have to make sure I add the code to all forms that include that field. I am wondering if there is a way to do this with Class modules so that I could fire an event on a table field or record. I have not done any OO, but looked into it a little bit years ago, so I have a very vague understanding of how it works. I apologize that my question is somewhat non-specific, but I don’t want to spend a lot of time on the learning curve of OO & Class Modules only to find out that what I am trying to do cannot be done. On the other hand, if I could do all of this in one place and not have to worry about it going forward that would be well worth any time spent!
My question is: Can I create a class on a table field that would fire an event anytime that field is edited? And can I create a class on a table (or table record) that would fire any time there is a record inserted into the table? What is the logic to accomplish this?
I am using a table to hold all of the items that will be created based on the field that is updated, or record that is created.
I am using Access 2016. Thanks in advance for any help you can give me!!!
Kim
This is the event code I am currently using for the First_Meeting Event:
'This code calls a form to select the reminders to create
Private Sub First_Meeting_AfterUpdate()
Dim strSql As String
Dim strWhere As String
Dim strOrderBy As String
Dim intRecordCount As Integer
'Save any changes to data before selecting appointments to set
If Me.Dirty Then
Me.Dirty = False
End If
'The "Where" keyword is not included here so it can be used for the DCount function
strWhere = " [Appt Defaults].[Field Name]='First Meeting Date'"
strOrderBy = " ORDER BY [Appt Defaults].[Order for List], [Appt Defaults Child].[Date Offset]"
strSql = "SELECT Count([Appt Defaults Child].ID) AS CountOfID " & _
"FROM [Appt Defaults] INNER JOIN [Appt Defaults Child] ON [Appt Defaults].ID = [Appt Defaults Child].ReminderID"
intRecordCount = DCount("ReminderID", "qDefaultAppts", strWhere)
If intRecordCount > 0 Then
DoCmd.SetWarnings False
'Delete records from the Temp table
DoCmd.RunSQL "Delete * From TempApptToSelect"
'Add the "Where" keyword to be used in the query
strWhere = "Where " & strWhere
strSql = CurrentDb.QueryDefs("[qAddApptsToTemp-MinusCriteria]").SQL
'The ";" symbol is added to the end of the query so it needs to be stripped off
strSql = Replace(strSql, ";", "")
strSql = strSql & strWhere & strOrderBy
DoCmd.RunSQL strSql
'Flag all of the events in the Temp Table as Selected
DoCmd.RunSQL "UPDATE TempApptToSelect SET TempApptToSelect.IsSelected = -1"
DoCmd.SetWarnings True
DoCmd.OpenForm "Reminders - Select Main", , , , , , OpenArgs:=Me.Name
End If
End Sub
'This code is from the form where the reminders are selected
Private Sub cmdCreateReminders_Click()
' This Routine copies all of the selected default records from the Appt Defaults tables and copies them to the Reminder Tables
'
Dim rstReminderDefaults As Recordset
Dim rstReminders As Recordset
Dim nID As Integer
Dim dtStartDate As Date
Dim dtStartTime As Date
Dim dtEndTime As Date
Dim strProjectName As String
Dim strProjectAddress As String
Dim strApptArea As String
Dim iCount As Integer
' The calling form has the info needed to set the values for the reminders
' The form "frmCalendarReminders" is generic and will be on all forms that need to set reminders
txtCallingForm = Me.OpenArgs()
'The form recordset is a temp query created from the calling routine which determines the record filter
Set rstReminders = Forms(txtCallingForm)!frmCalendarReminders.Form.RecordsetClone
Set rstReminderDefaults = CurrentDb.OpenRecordset("qApptsToSet")
nID = Forms(txtCallingForm)!ID
strApptArea = Left(rstReminderDefaults![Appt Area], 8)
Select Case strApptArea
Case "Projects"
strProjectName = Forms(txtCallingForm)!txtProjectName
strProjectAddress = Forms(txtCallingForm)!txtProjectAddressLine & vbCrLf & Forms(txtCallingForm)!txtProjectCityLine
With rstReminderDefaults
Do While Not .EOF
'If this reminder has not already been created
If DCount("ID", "PR_Child-Reminders", "[Project ID] =" & Forms(txtCallingForm)![ID] & " And [ReminderChildID]= " & ![ReminderChildID]) = 0 Then
rstReminders.AddNew
'Initialize fields with values from defaults
rstReminders![ReminderChildID] = ![ReminderChildID]
rstReminders![Project ID] = nID
rstReminders![Reminder Type] = ![Outlook Item Type]
rstReminders![Reminder Subject] = ![Subject]
rstReminders![Reminder Text] = ![Body]
rstReminders![Invited] = ![Invite]
rstReminders![Email CC] = ![Email CC]
rstReminders!Calendar = !CalendarID
rstReminders!Color = !ColorID
Select Case ![Appt Type]
.
.
Case "First Meeting"
If Not IsNull(Forms(txtCallingForm)!dtFirstMeeting) Then
'dtStartDate will be used later to fill in Placeholder field in Subject and Body of Calendar and Email Items
dtStartDate = Forms(txtCallingForm)!dtFirstMeeting
rstReminders![Reminder Date] = dtStartDate + ![Date Offset]
Else
'Quit working on this reminder since it has invalid conditions
MsgBox "No date has been set for the " & ![Appt Type] & " so reminders cannot be created"
rstReminders.CancelUpdate
GoTo NextLoop
End If
End Select
.
rstReminders.Update
CreateOrSend (txtCallingForm)
.
NextLoop:
.MoveNext
Loop
End With
End Select
DoCmd.Close
End Sub
‘This code is used to create the reminder or email
Sub CreateOrSend(CallingForm)
Dim bError As Boolean
Dim strName As String
Dim strSubject As String
Dim strBody As String
Dim strType As String
Dim strAttendees As String
Dim strCC As String
Dim strColorCategory As String
Dim dtStartDate As Date
Dim dtEndDate As Date
Dim strReminderText As String
Dim strLocation As String
Dim decDuration As Single
With Forms(CallingForm)!frmCalendarReminders.Form
'bError will be used to determine if the calendar item is created without error
bError = False
If !cmbReminderType = "Calendar" Then
strName = !cmbCalendar.Column(2)
strSubject = !txtReminderSubject
If Not IsNull(!txtReminderNote) Then
strBody = !txtReminderNote
Else
strBody = ""
End If
If Not IsNull(!txtInvite) Then
strAttendees = !txtInvite
Else
strAttendees = ""
End If
strColorCategory = !cmbColor.Column(1)
dtStartDate = !dtStartDate & " " & !dtStartTime
dtEndDate = !dtEndDate & " " & !dtEndTime
If Not IsNull(!txtReminderNote) Then
strReminderText = !txtReminderNote
Else
strReminderText = ""
End If
strLocation = IIf(IsNull(.Parent!txtProjectAddressLine), ".", .Parent!txtProjectAddressLine & ", " & .Parent![Project City])
' Parameter Order: strName, strSubject, strBody, strAttendees, strColorCategory, dtStartDate, dtEndDate, strReminderText Optional: strLocation, decDuration
Call CreateCalendarAppt(bError, strName, strSubject, strBody, strAttendees, strColorCategory, dtStartDate, dtEndDate, strReminderText, strLocation)
If bError = False Then
!dtCreatedItem = Date
Else
MsgBox "***** YOUR APPOINTMENT FAILED ******"
End If
Else
If Not IsNull(!txtReminderNote) Then
strBody = !txtReminderNote
Else
strBody = ""
End If
strSubject = !txtReminderSubject
If Not IsNull(!txtInvite) Then
strAttendees = !txtInvite
strCC = !txtEmailCC
SendCustomHTMLMessages strAttendees, strCC, strSubject, strBody
!dtCreatedItem = Date
Else
MsgBox "There were no email addresses to send this message to"
End If
End If
End With
End Sub
Unfortunately, there is no way to accomplish what you want. Although Access has something like "Data Macros", there is no way to to run a VBA procedure from there.
But don't be afraid of using event procedures in your forms. You don't have to copy all your existing code to each and every event procedure. You can place the existing code in a standard module, and in the forms, use very short event procedures that call these procedures in the standard modules. This still makes the main routines easy to maintain.
I don't agree with Wolfgang.
Of course I would suggest using MSSQL Server as backend, but with Access and the Data-Macros you can update a timestamp field in the underlying tables that updates on every change.
In addition run a script on a server (I don't knpw what intervall would be sufficent for you) every x minutes and check if row was updated since last run of script (compare timestamp)..
If true run your tasks.
If this is not an option we can talk about intercepting form-events with a class and WithEvents but this will need more effort to implement.

Access Tab Control - Set Pages Visibilty based on Subform Records

I know how to do this, but wondering if I might be able to write a more elegant solution. I have a form with a tab control. The control has 14 pages, each one, with it's own sub form. One of the pages (pgRequirements) has a subform of requirements, with a combo control "Requirement Type". It is a continuous form, so the user can add as many requirements as they want, for the main record.
There are 9 of those requirements, which have their own tab control page / sub form. I want to set visibility of those tab control pages, based on this parent's sub form requirements. So a current main record, can have multiple sub-requirement records. If any of those match e.g. requirement type A, than page A should be visible, otherwise it should not be.
I need this code to run anytime the main form is loaded, and the detail is made visible (meaning a main record has been chosen from a find form). Also anytime a requirement record is added or removed. The below is assuming that the parent-child links on the main form to subform will limit the requirement records, to just those that are for the current main record.
Here is the simple code, that will do the job, but is probably over-written:
If Me.FKRequirementType.Column(1) = "ReqType1" Then
Me.Parent!pgReqType1.Visible = True
Else
Me.Parent!pgReqType1.Visible = False
End If
If Me.FKRequirementType.Column(1) = "ReqType2" Then
Me.Parent!pgReqType2.Visible = True
Else
Me.Parent!pgReqType2.Visible = False
End If
If Me.FKRequirementType.Column(1) = "ReqType3" Then
Me.Parent!pgReqType3.Visible = True
Else
Me.Parent!pgReqType3.Visible = False
End If
If Me.FKRequirementType.Column(1) = "ReqType4" Then
Me.Parent!pgReqType4.Visible = True
Else
Me.Parent!pgReqType4.Visible = False
End If
Thanks!
EDIT
I turned this into a public function, so I can call it from anywhere. One problem. It's not working lol (small problem). I don't get any errors, but all the tab control pages are visible. When I add a new record, most of them should be hidden. I have a tblReqType table, with all the requirement types. I added a column to this, with the exact name of it's corresponding tab control page name, so I can loop through that table, for all records where that page name is not null, and set their page visible or not, based on the current main record ID having a record-requirement (cross reference table) record for each requirement type.
This is the public function I wrote. Can anyone help me understand what I'm missing in these loops for setting the visibility true (vtrue) vs setting the visibility false (vfalse)
Public Function ShowRequirements()
Dim db As DAO.Database
Dim strRstVTrue As String
Dim rstvTrue As DAO.Recordset
Dim strRstVFalse As String
Dim rstvFalse As DAO.Recordset
Dim strFieldName As String
'Setup the recordset
Set db = CurrentDb
strRstVTrue = "SELECT tblMRecordRequirements.ID, tblMRecordRequirements.FKMC, tblReqType.txtRequirementPage " & _
"FROM tblMRecordRequirements LEFT JOIN tblReqType ON tblMRecordRequirements.FKRequirementType = tblReqType.ID " & _
"WHERE tblReqType.txtRequirementPage Is Not Null AND tblMRecordRequirements.FKMC = " & Nz(Forms!frmMRecords!ID, 0)
strRstVFalse = "SELECT tblReqType.ID, tblReqType.txtRequirementPage, tblMRecordRequirements.FKMC " & _
"FROM tblReqType LEFT JOIN tblMRecordRequirements ON tblReqType.ID = tblMRecordRequirements.FKRequirementType " & _
"WHERE tblReqType.txtRequirementPage Is Not Null AND tblMRecordRequirements.FKMC <> " & Nz(Forms!frmMRecords!ID, 0)
Set rstvTrue = CurrentDb.OpenRecordset(strRstVTrue, dbOpenDynaset, dbSeeChanges)
Set rstvFalse = CurrentDb.OpenRecordset(strRstVFalse, dbOpenDynaset, dbSeeChanges)
strFieldName = "txtRequirementPage"
Do While Not rstvTrue.EOF
Forms!frmMRecords.tbMRecordSubs.Pages(rstvTrue.Fields(strFieldName)).Visible = True
Loop
Do While Not rstvFalse.EOF
Forms!frmMRecords.tbMRecordSubs.Pages(rstvFalse.Fields(strFieldName)).Visible = False
Loop
End Function
If anyone can help me figure out my stupidity, you deserve an up vote, a check mark, and a cookie.
EDIT again
Below is updated code for the public function. I fixed the rs for the true query, and I added in the MoveNext for the loops.
Public Function ShowRequirements()
Dim db As DAO.Database
Dim strRstVTrue As String
Dim rstvTrue As DAO.Recordset
Dim strRstVFalse As String
Dim rstvFalse As DAO.Recordset
Dim strFieldName As String
'Setup the recordset
Set db = CurrentDb
strRstVTrue = "SELECT tblMRecordRequirements.ID, tblMRecordRequirements.FKMC, tblReqType.txtRequirementPage " & _
"FROM tblMRecordRequirements LEFT JOIN tblReqType ON tblMRecordRequirements.FKRequirementType = tblReqType.ID " & _
"WHERE tblReqType.txtRequirementPage Is Not Null AND tblMRecordRequirements.FKMC = " & Nz(Forms!frmMRecords!ID, 0)
strRstVFalse = "SELECT tblReqType.ID, tblReqType.txtRequirementPage, tblMRecordRequirements.FKMC " & _
"FROM tblReqType LEFT JOIN tblMRecordRequirements ON tblReqType.ID = tblMRecordRequirements.FKRequirementType " & _
"WHERE tblReqType.txtRequirementPage Is Not Null AND tblMRecordRequirements.FKMC <> Is Null"
Set rstvTrue = CurrentDb.OpenRecordset(strRstVTrue, dbOpenDynaset, dbSeeChanges)
Set rstvFalse = CurrentDb.OpenRecordset(strRstVFalse, dbOpenDynaset, dbSeeChanges)
strFieldName = "txtRequirementPage"
Do While Not rstvTrue.EOF
Forms!frmMRecords.tbMRecordSubs.Pages(rstvTrue.Fields(strFieldName)).Visible = True
rstvTrue.MoveNext
Loop
Do While Not rstvFalse.EOF
Forms!frmMRecords.tbMRecordSubs.Pages(rstvFalse.Fields(strFieldName)).Visible = False
rstvFalse.MoveNext
Loop
End Function
EDIT REDUX
I think I may have it worked out, but let me know what you all think. I really appreciate all your thoughts on this, as I know you all have a lot of experience not just in figuring out these kinds of challenges, but ensuring the code is good and not prone to issues.
Here is where I am at:
Public Function ShowRequirements()
Dim db As DAO.Database
Dim db2 As DAO.Database
Dim strRstVTrue As String
Dim rstvTrue As DAO.Recordset
Dim strRstVFalse As String
Dim rstvFalse As DAO.Recordset
Dim strFieldName As String
strFieldName = "txtRequirementPage"
Set db = CurrentDb
Set db2 = CurrentDb
strRstVTrue = "SELECT tblReqType.txtRequirementPage " & _
"FROM tblReqType LEFT JOIN tblMRecordRequirements ON tblMRecordRequirements.FKRequirementType = tblReqType.ID " & _
"WHERE tblReqType.txtRequirementPage Is Not Null AND tblMRecordRequirements.FKMC = " & MCID
strRstVFalse = "SELECT tblReqType.txtRequirementPage " & _
"FROM tblReqType LEFT JOIN tblMRecordRequirements ON tblMRecordRequirements.FKRequirementType = tblReqType.ID " & _
"WHERE tblMRecordRequirements.ID Not In (Select ID From [tblMRecordRequirements] WHERE [tblMRecordRequirements]![FKMC] = " & MCID & _
") AND tblReqType.txtRequirementPage Is Not Null;"
Set rstvTrue = db.OpenRecordset(strRstVTrue, dbOpenDynaset, dbSeeChanges)
Set rstvFalse = db2.OpenRecordset(strRstVFalse, dbOpenDynaset, dbSeeChanges)
Do While Not rstvTrue.EOF
Forms!frmMRecords.tbMRecordSubs.Pages(rstvTrue.Fields(strFieldName)).Visible = True
rstvTrue.MoveNext
Loop
Do While Not rstvFalse.EOF
Forms!frmMRecords.tbMRecordSubs.Pages(rstvFalse.Fields(strFieldName)).Visible = False
rstvFalse.MoveNext
Loop
End Function
I need this code to run anytime the main form is loaded, and the detail is made visible (meaning a main record has been chosen from a find form). Also anytime a requirement record is added.
Put the code you shared inside a sub procedure and call the sub procedure from Form_Load(), Form_Current(), Form_AfterInsert() event handler, etc.
As for elegance, I'd focus on maintainability and efficiency rather than looks, but concise code is also nice. 1) You can use a With block to reduce redundant object method calls, but that'll only work for one reference at a time. 2) Instead create another variable to temporarily hold a value/object from a series of dot property-accessors. 3) It looks like pages and column values are already numbered with a consistent naming pattern, so leverage that in a loop. 4) Comparison operations in VBA are largely Boolean operations, so they return True or False. The result of an entire Boolean expression can be assigned to another Boolean variable/property. (Boolean operations can also return Null... which is usually, but not always, treated like False. If you're certain that your data doesn't have Null values, then you can simplify the code and ignore this issue. If the data can contain null, then you need to adjust the code appropriately.)
Me.Parent!pgReqType1 is calling the default property of the Parent form which is Controls, which default property is Item. The bang operator ! passes the code text as a string into the collection Item method. In short, it is equivalent to Me.Parent.Controls.Item("pgReqType1").
Dim i as integer
Dim ctls as Controls
Dim reqValue as string
Set ctls = Me.Parent.Controls
reqValue = Me.FKRequirementType.Column(1)
For i = 1 to 4
ctls.Item("pgReqType" & i).Visible = (reqValue = "ReqType" & i)
Next i
About all I can do is translate the specific code snippet you show. I have the feeling there is probably more to it than this, because the code snippet you shared ensures that there will only be one tab visible: It is testing the same column value multiple times which could only have one value. Bug? Incomplete example?
This really goes against my better judgement of Stack Overflow principles --to not answer multi-part, continuing debugging questions-- but I really want a cookie.
How could the posted code have ever worked, since you are not moving through either recordset? There are no MoveNext calls. That means that both recordsets are empty or an error is being thrown that is being ignored somewhere (i.e. On Error Resume Next). Otherwise, it should lock up Access with infinite loops. Sometimes you can stop the code with Ctrl+Break, but not always successful in Access.
More precise table schema is required to properly interpret your data, but I'll make some assumptions. You state that tblReqType contains all requirement types. I'll assume that tblMRecordRequirements contains rows only for requirements which are "applied" (a.k.a. "on", "selected") for the ID value in tblMRecordRequirements.FKMC. Assuming the converse, if there are no rows in tblMRecordRequirements with ID in tblMRecordRequirements.FKMC for a given tblMRecordRequirements.FKRequirementType, then the requirement is not "applied" to that ID.
Does every row in tblReqType have a value in txtRequirementPage, or do some rows have null values? Also, can multiple requirements have the same page specified? Or is it a true one-to-one requirement-to-page mapping with no null values?
First of all why would the first query not be an INNER JOIN, since I assume that only records that match in both tables should be returned for the Visible = True condition? Depending on your answers above, this would probably make the condition tblReqType.txtRequirementPage Is Not Null unnecessary in the first query.
Simply reversing a LEFT JOIN will not return what you want especially if you select all other ID values ( tblMRecordRequirements.FKMC <> Nz(Forms!frmMRecords!ID, 0) ). All that does is give you the requirements for every other ID values. Not only will that be inefficient since it could return many, many irrelevant records, it could be likely that over all other ID values that every possible requirement would be applied, so that the second query will essentially cause all requirements to be invisible.
Further picky observations:
If Forms!frmMRecords!ID is null, then you might as well not even execute the queries. You should check that value for null separately and perform appropriate actions rather than letting that specific condition fall through the other code, even if the end side effect is what you desire. It makes the code harder to debug and interpret properly. In other words write code that does "If ID is null, set all pages to visible = false, then exit the sub (i.e. skip other code)"
It's more efficient to get a read-only snapshot rather than an fully-updatable Dynaset recordset: Too much overhead just for looping through without data manipulation.
Proper break points, debug output and error handling code can help identify bad code. It is worth tracing through results of recordsets manually to inspect values and proper SQL syntax.
Try this:
Public Sub ShowRequirements()
Dim db As DAO.Database
Dim iID As Long '? Assuming long integer
Dim strSQL As String
Dim rsTabs As DAO.Recordset
On Error Resume Next
iID = -1 '* Set to bogus value
If Not IsNull(Forms!frmMRecords!ID) Then
iID = Forms!frmMRecords!ID
End If
If iID = -1 Or Err.Number <> 0 Then
'* Problem accessing ID control on form (empty recordset, new record row, etc.)
'* or it is null
'Set all tab pages to Visible = False?
Exit Sub
End If
On Error GoTo Catch
'* Setup the recordset
Set db = CurrentDb
'* Use embedded query (replacable with saved query) for filtering on ID values.
'* This is critical so that the LEFT JOIN does not return or filter records
'* based on other ID values.
strSQL = _
"SELECT tblReqType.ID, tblReqType.txtRequirementPage, (IDReq.FKRequirementType Is Not Null) As ShowTab " & _
" FROM tblReqType LEFT JOIN" & _
" (SELECT MReq.FKRequirementType FROM tblMRecordRequirements AS MReq " & _
" WHERE MReq.FKMC = " & iID & ") AS IDReq" & _
" ON tblReqType.ID = IDReq.FKRequirementType" & _
" WHERE tblReqType.txtRequirementPage Is Not Null"
Set rsTabs = db.OpenRecordset(strRstVTrue, dbOpenSnapshot, dbReadOnly)
Do While Not rsTabs.EOF
Forms!frmMRecords.tbMRecordSubs.Pages(rsTabs!txtRequirementPage).Visible = rsTabs!ShowTab
rsTabs.MoveNext '* Advance recordset. (Avoid infinite loops.)
Loop
CloseAll:
On Error Resume Next
'* Best habit to explicitly close recordsets and database connections, even when not necessary (since they'll close automatically when they go out of scope)
If Not rsTabs Is Nothing Then
rsTabs.Close
Set rsTabs = Nothing
End If
Set db = Nothing
Exit Sub
Catch:
'* At least report error for development
Debug.Print "ShowRequirements(): Error: " & Err.Number & ": " & Err.Description
'* Show MsgBox or update form status control?
'* Set all tab pages to Visible = False?
'* Form state could be misleading without proper user notification and/or error handling
Resume CloseAll
End Sub

How can I determine the difference between typing into a combo box and selecting from a drop down in Access VBA?

This question was asked in the topic with a similar name earlier, but the answer provided didn't really indicate HOW those events would help determine whether somebody was typing in the combo box or selecting an item in the list. I think that it really answered the other question about how to determine when somebody was done typing, but without seeing the event handlers, I can't be sure.
Unfortunately, I'm new here and don't have enough reputation to post a comment asking for clarification, so I have to start a new question. Here's what I'm trying to do:
I have a form with a combo box in the Header and, as I type in the combo box, I want the characters that I've typed to be used as a filter on the Details part of the form. Both the combo box control source and the form's record source use the same query string.
I've tried numerous iterations of the code below, but I can't get it to work correctly.
Private Sub cmbAppName_Change()
Dim strApp As String
Dim nSelStart As Integer
Dim nSelLen As Integer
Dim nSelected As Integer
Dim strMsg As String
On Error GoTo ERR_SUB
strMsg = ""
Me.cmbAppName.SetFocus
' Get current selection details
nSelStart = Me.cmbAppName.SelStart
nSelLen = Me.cmbAppName.SelLength
nSelected = Me.cmbAppName.ListIndex
Me.cmbAppName.SetFocus
strApp = Nz(Me.cmbAppName.Text, "")
Debug.Print "Index = " & nSelected & "; SelStart = " & nSelStart & "; SelLen = " & nSelLen
If nSelected = -1 Then
Debug.Print "Change by typing: " & strApp
Else
Debug.Print "Change by list selection: " & strApp
End If
' Get the part of the text that the user has typed
If nSelStart > 0 Then
strApp = Left(strApp, nSelStart)
Debug.Print "App piece = '" & strApp & "'"
End If
' If there is text, set a filter (MatchAppName = InStr(strApp, datbase_column_value)
If strApp <> "" Then
Me.Filter = "MatchAppName('" & strApp & "', " & DCApplications_Application_Col & ") > 0"
Me.FilterOn = True
' Me.txtApplication.SetFocus
' Call DoCmd.FindRecord(strApp, acStart, False, acSearchAll, False, acCurrent, True)
' Me.cmbAppName.SetFocus
Else
Me.Filter = ""
Me.FilterOn = False
End If
EXIT_SUB:
' Restore the selection in the combo box's text box
Me.cmbAppName.SetFocus
Me.cmbAppName.SelStart = nSelStart
Me.cmbAppName.SelLength = nSelLen
Exit Sub
ERR_SUB:
If ERR.Number = 2185 Then
strApp = Nz(Me.cmbAppName.Value, "")
Me.cmbAppName.SetFocus
Debug.Print "Using " & strApp
Resume Next
End If
Me.Filter = ""
Me.FilterOn = False
Debug.Print ErrorMessage(ERR.Description, "cmbAppName_Change", ERR.Number, "Value = '" & Me.cmbAppName.Value & "'", False)
Resume EXIT_SUB
End Sub ' cmbAppName_Change
As you can see from the error handling code, I'd often get an error 2185 telling me that my control didn't have focus when using the Text property despite having a SetFocus call right before it.
If somebody selects from the list (either by clicking or moving the selection), I'd like to go to that record, but I at least need the above piece working first.
After searching the Web, I found out that a Details section with zero records causes the 2185 error. Apparently, filtering like that causes problems when all records are filtered out.
The solutions on the Web said that you can set the Allow Additions property of the form to True, but that always displays one row in the Details section. This can be especially confusing if the rows in the Details section contain controls, which will be displayed in the "addition" row. Also, I would still get an error typing additional characters after the one that caused the Details section to have zero records.
Eventually, I replaced the combo box with a simple text control to filter the Details section. When the Details section has rows, I turn Allow Additions off and make the controls visible; when it doesn't have rows, I turn Allow Additions on and hide the controls.
Here's the code that I used:
Private Sub txtApplicationFilter_Change()
Dim strApp As String
Dim nSelStart As Integer
Dim nSelLen As Integer
Dim strFilter As String
Dim strQuery As String
Dim strWhere As String
Dim nRecs As Integer
On Error GoTo ERR_SUB
' Save text selection
nSelStart = Me.txtApplicationFilter.SelStart
nSelLen = Me.txtApplicationFilter.SelLength
' Get application name typed and selection information
strApp = Nz(Me.txtApplicationFilter.Text, "")
strFilter = "[" & DCApplications_Application_Col & "] LIKE '*" & EscapeQuotes(strApp) & "*'"
nRecs = DCount("[" & DCApplications_Application_Col & "]", LocalTableName(DCApplications_Tab), strFilter)
' Kludge code to prevent various errors (like 2185) when no records are returned in the form
Call UpdateList(nRecs)
' Update the record source to reflect the filtered list of apps
strWhere = " WHERE APPS." & strFilter
strQuery = strSelect & strFrom & strWhere & strOrderBy
Me.RecordSource = strQuery
' 20200423 SHM: Restore or update filter to avoid issues with Delete and Backspace and applications with spaces in their names
Me.txtApplicationFilter.SetFocus
Me.txtApplicationFilter = strApp
Me.txtApplicationFilter.SelStart = nSelStart
Me.txtApplicationFilter.SelLength = nSelLen
EXIT_SUB:
Me.btnAddNew.enabled = (Nz(Me.txtApplicationFilter, "") <> "")
Exit Sub
ERR_SUB:
' NOTE: ErrorMessage is a helper function that basically displays a form displaying the error
Call ErrorMessage(ERR.Description, "txtApplicationFilter_Change", ERR.Number, "Filter = " & strApp & " Records = " & nRecs)
Resume EXIT_SUB
Resume Next
End Sub ' txtApplicationFilter_Change
Private Sub UpdateList(nRecs As Integer)
Dim bShowControls As Boolean
On Error GoTo ERR_SUB
bShowControls = (nRecs > 0)
' Kludge code to turn off checkbox control source
If bShowControls Then
strSelect = strSelectStart & ", (" & strAppUser & ") AS " & strCtrlSource
Me.chkTestedByMe.ControlSource = strCtrlSource
Else
strSelect = strSelectStart
Me.chkTestedByMe.ControlSource = ""
End If
' Kludge code to prevent various errors (like 2185) when no records are returned in the form
' Turning on AllowAdditions prevents errors when no records are returned.
' However, that puts an empty row in the form, but the controls are showing, so we have to hide them to prevent confusing the user.
Me.AllowAdditions = Not bShowControls
Me.btnAddExisting.visible = bShowControls
Me.chkTestedByMe.visible = bShowControls
EXIT_SUB:
Exit Sub
ERR_SUB:
Call ErrorMessage(ERR.Description, "UpdateList", ERR.Number, " Records = " & nRecs)
Resume EXIT_SUB
Resume Next
End Sub ' UpdateList
I would use a work around to settle this issue
A simple code bellow demonstrate the work around using Tag property of Combo Box and keypress event along with change event, I hope it can be applied in your code
Private Sub Combo2_Change()
If Combo2.Tag = 1 Then
Text4 = "change - from key"
Else
Text4 = "change - from select"
End If
Combo2.Tag = 0
End Sub
Private Sub Combo2_KeyPress(KeyAscii As Integer)
Combo2.Tag = 1
End Sub
Don't forget to set Tag property of Combo Box to 0 on design view to avoid error at comparing empty Tag with number

Listbox records deselecting when running a DoCmd.Requery in Access VBA

In my app, users are selecting records from a listbox that they want to edit. Before they're able to edit, however, there is an additional query to make sure that info they want to change is correct. Below is the flow:
User inputs an Item_Number and clicks a button to query the DB for all Location(s) that contain that Item_Number. These populate into a listbox.
User selects the location(s) from the listbox that he/she wishes to edit
User inputs a new Last_Cost and/or new Vendor_ID for the selected items and clicks to confirm edit
In VBA, there is a query against another DB to ensure the new Vendor_ID is valid
The flow continues
The issue is that when the VBA code for the requery
DoCmd.Requery (VendorNumbers)
executes, it deselects all of the selected locations in the listbox. I've been able to narrow the issue down to this exact section of the VBA code.
I'm wondering if there is a way to ensure that the selected records stay selected, or if there is a way to store which rows were selected so they can be referenced again after the requery code is ran?
Entirety of the code. It may look slightly confusing because I don't have anything noted with "Me."
Private Sub EditInLocationsButton_Click()
EditVendorIDFixed = Null
EditVendorIDFixed = " " & EditVendorIDInput
DoCmd.Requery (VendorNumbers)
If EditVendorIDFixed = " " Or DCount("*", "VendorNumbers") > 0 Then
EditResponse = MsgBox("Are you sure you want to edit the selected items?", vbYesNo, "Edit Selected Items?")
If EditResponse = vbYes Then
'EditItemNumberResultsList.RowSource = ""
Dim i As Integer
For i = 0 To EditItemNumberResultsList.ListCount - 1
If EditItemNumberResultsList.Selected(i) Then
LastCost = EditItemNumberResultsList.Column(4, i)
EditID = EditItemNumberResultsList.Column(5, i)
VendorID = EditItemNumberResultsList.Column(6, i)
'MsgBox for verification purposes
MsgBox (LastCost & "-" & EditID & "-" & VendorID)
If EditLastCostInput = Null Then
LastCostforEditQuery = LastCost
Else
LastCostforEditQuery = EditLastCostInput
End If
If EditVendorIDInput = Null Then
VendorIDforEditQuery = VendorID
Else
VendorIDforEditQuery = EditVendorIDFixed
End If
End If
txtEditID = EditID
With DoCmd
.SetWarnings False
.OpenQuery "EditIminvlocRecords"
.SetWarnings True
End With
Next i
MsgBox ("The selected plants have been updated.")
End If
Else
MsgBox ("Error: The requested Vendor ID does not exist.")
End If
End Sub
Below is the VendorNumbers query:
SELECT dbo_apvenfil_sql.vend_no
FROM dbo_apvenfil_sql
WHERE (((dbo_apvenfil_sql.vend_no)=[Forms]![EditItemsInExistingPlants]![EditVendorIDFixed]))
ORDER BY dbo_apvenfil_sql.vend_no;
Again, this is just to verify that the Vendor_ID they input is valid, i.e. there are existing rows.
I think that this may be the best option because I don't know if you will ever be able to stop the requery from causing a refresh. I would be lying to say that I tested out this SQL but this is the basic idea for how I would perform what you are looking for. Instead of requerying VendorNumbers you just basically make it and run it right in VBA. This is also assuming that EditItemsInExistingPlants is the form from where you are running this code, if not then just adjust appropriately.
Private Sub EditInLocationsButton_Click()
EditVendorIDFixed = Null
EditVendorIDFixed = " " & EditVendorIDInput
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim strSQLQuery As String
strSQLQuery = "SELECT dbo_apvenfil_sql.vend_no " & _
"FROM dbo_apvenfil_sql " & _
"WHERE (((dbo_apvenfil_sql.vend_no)= " & Me.EditVendorIDFixed & ")) " & _
"ORDER BY dbo_apvenfil_sql.vend_no"
Set db = CurrentDb
Set rs = db.OpenRecordset(strSQLQuery, dbOpenDynaset)
If EditVendorIDFixed = " " Or rs.Count > 0 Then
EditResponse = MsgBox("Are you sure you want to edit the selected items?", vbYesNo, "Edit Selected Items?")
If EditResponse = vbYes Then
'EditItemNumberResultsList.RowSource = ""
Dim i As Integer
For i = 0 To EditItemNumberResultsList.ListCount - 1
If EditItemNumberResultsList.Selected(i) Then
LastCost = EditItemNumberResultsList.Column(4, i)
EditID = EditItemNumberResultsList.Column(5, i)
VendorID = EditItemNumberResultsList.Column(6, i)
'MsgBox for verification purposes
MsgBox (LastCost & "-" & EditID & "-" & VendorID)
If EditLastCostInput = Null Then
LastCostforEditQuery = LastCost
Else
LastCostforEditQuery = EditLastCostInput
End If
If EditVendorIDInput = Null Then
VendorIDforEditQuery = VendorID
Else
VendorIDforEditQuery = EditVendorIDFixed
End If
End If
txtEditID = EditID
With DoCmd
.SetWarnings False
.OpenQuery "EditIminvlocRecords"
.SetWarnings True
End With
Next i
MsgBox ("The selected plants have been updated.")
End If
Else
MsgBox ("Error: The requested Vendor ID does not exist.")
End If
End Sub