How to link a subform to a combobox on the main form - ms-access

On a form in an access ADP there is an unbound combobox that displays a list of companies (the name is shown, the id is the bound field). When a company is chosen, I want to display the subscription information in a subform (the datasource for which is companySubscription view) for that company. I set the link Master Fields and links child Fields property of the subform to the companyId. Basically, I set it up like this.
In theory, I would think this would mean that when I change the value in the combobox the subform should show the subscription information for that company. It isn't working though- the subform always shows all the data in the companySubscription view, no matter what the combobox is set to.

Found the answer- had some code from another project that helped:
Private Sub cmbSub_AfterUpdate()
' Find the record that matches the control.
Dim rs As Object
Set rs = Me.Recordset.Clone
rs.FindFirst "[subID] = " & str(Nz(Me![cmbSub], 0))
If Not rs.EOF Then Me.Bookmark = rs.Bookmark
End Sub
And had to modify it for an ADP (thanks to this post!)
Private Sub ChooseCo_AfterUpdate()
' Find the record that matches the control.
Dim rs As ADODB.Recordset
Set rs = Me.Recordset.Clone
rs.Find "[companyId] = " & Str(Nz(Me![ChooseCo], 0))
If Not rs.EOF Then Me.Bookmark = rs.Bookmark
End Sub

Related

My subreport code only runs for the first record

I have a report that has two subreports in a 1:N relationship, one of which also has a subreport in a 1:N relationship. I've made the report_load functions in all the subreports public, and then have the parent report call them so that they will run when the parent report is run, as per the following instructions:
https://www.access-programmers.co.uk/forums/threads/vba-on-subreport-does-not-fire.294207/
All levels of the report are running the same public function:
Public Sub asterisks(rpt As Report, PK As String, ID As String, Date_Modified As Date)
Dim SQL As String, db As DAO.Database, rs As DAO.Recordset
SQL = "SELECT SOURCEFIELD FROM AUDIT WHERE Edit_Date > #" & Date_Modified & "# AND SOURCETABLE = """ & PK & """ AND RECORDID = """ & ID & """"
Debug.Print (SQL)
Set db = CurrentDb
Set rs = db.OpenRecordset(SQL)
If rs.RecordCount > 0 Then
rs.MoveFirst
If Not (rs.EOF And rs.BOF) Then
Do Until rs.EOF = True
Dim ctl As Control
For Each ctl In rpt
If ctl.ControlType = acTextBox Then
If rs!SourceField = ctl.Name Then
ctl.ForeColor = RGB(255, 0, 0)
End If
End If
Next ctl
Set ctl = Nothing
rs.MoveNext
Loop
End If
End If
rs.Close
Set rs = Nothing
End Sub
Which basically iterates through every textfield control in the report and colors it red if it showed up on the Audit table. (The Audit table from this solution: https://www.techrepublic.com/article/a-simple-solution-for-tracking-changes-to-access-data/)
What happens is that if a control is found in the audit table (in this case part_name), it would not only color the record that was changed, but ALL part names on the report.
How can I call the load function and make it so it runs this code for each record in the report?
Open event will run for report even when used as subreport.
However, if you want VBA to dynamically set control properties per record based on some condition, that requires code to be in Format or Print events for the section where controls are located. Those events trigger for only PrintPreview or direct to printer, not ReportView.
For conditionally setting font color, background color, text emphasis of textbox or combobox, I recommend using Conditional Formatting. A function in a general module can be called by CF rule.

Get recordset from selected records on datasheet

I have a subform with a datasheet view. On the parent form I am trying to edit records based on what is selected in the child datasheet. The forms are not linked using master/child fields.
I'm capable of getting the top row that is selected and the number of selected rows using SelTop and SelHeight like below.
Dim rs As New ADODB.Recordset
Set rs = Me.Child_Form.Form.RecordsetClone
If SelHeight > 0 Then
rs.MoveFirst
rs.Move SelectionTop - 1
For i = 1 To SelectionHeight
If Not rs.EOF Then
Debug.Print rs("ID")
rs.MoveNext
End If
Next i
End If
What I cannot do is get, say, the 10 records selected on the subform if I have filtered or sorted the form at all. The Filters and Sorts are at the form level and cannot be applied to the underlying recordset.
I've tried creating a new recordset with a query something like this
sql = "Select * from [" & Me.RecordSource & "] where " & Replace(Me.Filter, """", "'") & " order by " & Me.OrderBy
but there are multiple problems here. 1) ADO does not support the IN clause which the form filter will sometimes generate, and 2) the order order is not always the same and predictable.
How can I get a sorted, filtered recordset and find only those records which a user has selected in a datasheet view?
I am connecting to Sql Server with an ADP file.
I came up with a frustrating solution but it seems to work.
added an unbound (to my recordset) checkbox control to my subform.
named it chkSelect.
made the controlsource =IsChecked(ID)
I have this code running in the subform
Dim selectedRecords As Dictionary
Private Sub chkSelect_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
If selectedRecords.Exists(Me("Analytical_ResultID").Value) Then
selectedRecords.Remove Me("Analytical_ResultID").Value
Else
selectedRecords.Add Me("Analytical_ResultID").Value, Me("Analytical_ResultID").Value
End If
chkSelect.Requery
End Sub
Private Function IsChecked(Analysis_ResultID As Long) As Boolean
IsChecked = selectedRecords.Exists(Analysis_ResultID)
End Function
Private Sub Form_Load()
If selectedRecords Is Nothing Then
Set selectedRecords = New Dictionary
End If
End Sub
This works but it's flickery and not ideal. I would much prefer another answer.

Setting focus in MS Access

I am creating a recordset from a Qdefs and then displaying the records in a form.
When I filter the values, focus is going to the first record. But, I want the focus to point to the same record that was in focus before filtering.
This is how am creating a recordset from an existing querydefs before and after filtering
db.QueryDefs("Query_vinod").Sql = filter
Set rs_Filter_Rowsource = db.OpenRecordset("Abfr_SSCI_Check_Findings_List")
I think you can do this by using a bookmark. Set up a RecordsetClone and then find your active record by using the FindFirst method. I have some sample code that will need to be modified a little to fit your exact variables:
Dim Rs As Recordset
Dim Test As Integer
Dim varBookmark As Variant
DoCmd.OpenForm "Contracts"
Set Rs = Forms!Contracts.RecordsetClone
Rs.FindFirst ("[ID] = '" & Me![ID] & "'")
varBookmark = Rs.Bookmark
Forms!Contracts.Form.Bookmark = varBookmark
If Rs.NoMatch Then
MsgBox "That does not exist in this database."
Else
End If

Microsoft Access Sub Form Write Conflict Troubles

I have a form which contains a subform which displays editable fields linked to one my tables. For a project I'm currently working on, one of the requirements is that I have to track when the last change was made to a record and who did so.
So what I've done is for each editable textbox or combobox within the form and subform I've made it so they have events on their BeforeUpdate and AfterUpdate events.
For example my BeforeUpdate for a textbox:
Private Sub textbox_BeforeUpdate(Cancel As Integer)
If Not isValidUser Then
Cancel = True
Me.textbox.Undo
End If
End Sub
and my AfterUpdate is:
Private Sub textbox_AfterUpdate()
updateRecord Me.textbox.Value, UserNameWindows
End Sub
and updateRecord is:
Public Sub updateRecord(bucNumber As String, updater As String)
Dim Dbs As Object
Dim rst As Object
Dim fldEnumerator As Object
Dim fldColumns As Object
sqlStatement = "SELECT fName " & _
"FROM t_Staff " & _
"WHERE uName='" & updater & "';"
'Getting fullname of user via username
Set rst = CurrentDb.OpenRecordset(sqlStatement)
'Setting fullname to updater variable
updater = rst(0)
'Clean Up
Set rst = Nothing
'Opening Bucket Contents
Set Dbs = CurrentDb
Set rst = Dbs.OpenRecordset("Bucket Contents")
Set fldColumns = rst.Fields
'Scan the records from beginning to each
While Not rst.EOF
'Check the current column
For Each fldEnumerator In rst.Fields
'If the column is named Bucket No
If fldEnumerator.Name = "Bucket No" Then
'If the Bucket No of the current record is the same as bucketNumber
If fldEnumerator.Value = bucNumber Then
'Then change the updated fields by updater and todays date
rst.Edit
rst("Last Updated By").Value = updater
rst("Last Updated On").Value = Date
rst.Update
End If
End If
Next
'Move to the next record and continue the same approach
rst.MoveNext
Wend
'Clean Up
Set rst = Nothing
Set Dbs = Nothing
End Sub
Okay now is the weird thing, this works totally fine when I make a modification to a control within the Main form, however as soon as a try to alter something in the subform it throws up a write conflict.
If I opt to save record it ignores my code for updating who last modified it and when and if I opt to discard the change it runs my code and updates it that it has been changed!
Anyone know what is wrong or of a better way to do this?

VBA - Accessing a collection of values

I have two access forms. frm_Main and frm_Sub which has data conditionally displayed depending on the selections of the main form. I need to write a select all function for items displayed in frm_Sub. In VBA is there a way that I can get a list of the id's currently being displayed in frm_Sub?
for example, if I do this
me.controls("Svc_Tag_Dim_Tag_Num").value
I get the value for one of rows in the frm_Sub, is there a way to get all of the values?
Maybe I can ask a different way. I have a form that is displayed as a listview, in VBA, is there a way to get all the values from a specific column?
Another option is to write a function that concatenates the PKs of the recordset that the subform displays. Say your main form is Company and your subform lists Employees. You'd have a LinkMasterFields and LinkChildFields properties of the subform control would be CompanyID (PK of the Company table, FK of the Employees table).
Thus, to get the same set of records as is displayed in the subform when the parent is on a particular Company record:
Dim db As DAO.Database
Dim strSQL As String
Dim rst As DAO.Recordset
Dim strEmployeeIDList As String
Set db = CurrentDB
strSQL = "SELECT Employees.* FROM Employees WHERE CompanyID="
strSQL = strSQL & Me!CompanyID & ";"
Set rs = db.OpenRecordset(strSQL)
If rs.RecordCount > 0 Then
With rs
.MoveFirst
Do Until .EOF
strEmployeeIDList = strEmployeeIDList & ", " & !CompanyID
.MoveNext
Loop
End With
strEmployeeIDList = Mid(strEmployeeIDList, 3)
End If
rs.Close
Set rs = Nothing
Set db = Nothing
Now, why would you do this instead of walking through an already-opened recordset (i.e., the subform's RecordsetClone)? I don't know -- it's just that there may be cases where you don't want your lookup to be tied to a particular form/subform. You could fix that by making your function that concatenates accept a recordset, and pass it a recordset declared as I did above, or pass it the subform's RecordsetClone. In that case, you could use the concatenation function either way, without being tied to the form/subform.
If I understand your question you should be able to access the ID value in the control using Column(x) where x indicates the control's row source column starting from 0. For example, if ID is in column 0 with width 0 in. it will be hidden from view but VBA can "see" it as me.controls.["Svc_Tag_Dim_Tag_Num"].column(0).
To get at the sub-form's record source directly from outside the form's class module you could create a function something like:
Public Function test_get_sub_form_record_set() As String
Dim dbs As Database
Dim rst As Recordset
Dim xcat As String
Set dbs = CurrentDb()
Set rst = [Forms]![Your Main Form Name]![Your Sub-form Name].[Form].RecordsetClone
If rst.RecordCount > 0 Then
rst.MoveFirst
xcat = rst!ID
rst.MoveNext
Do While Not rst.EOF
xcat = xcat & ", " & rst!ID
Loop
Else
xcat = ""
End If
test_get_sub_form_record_set = xcat
rst.Close
Set dbs = Nothing
End Function
This would be included in a separate module and when called would return a concatenated, comma separated list of ID's.