I am creating a set of Access database form for entering vegetation data into a linked SQL Server data base. For one protocol, I have created a form 'frmLPI' for entering data from a vegetation monitoring method called Line-Point-Intercept. It is a form with a subform within it called 'frmLPIDetail' where individual counts of plant species get recorded. The main form has three unbound controls: [TransectOID], [DataRec], and [DataObs]. TransectOID is a unique id for each time we ran protocol. For each TransectOID, there are 30 locations where we sampled vegetation these have a hidden unique id in the subform called LPI_OID. The subform is linked to the main form by TransectOID. I want my users to be able to click the unbound [DataRec] and [DataObs] comboboxes in the main form, and have the corresponding fields in the subform autopopulate for all 30 records. I have figure out how to accomplish this for record in the subform but can't figure out how to do it for 30 records for each value of TransectOID in the Main form. Below is a screenshot of my form to help you visualize what I am after:
And here is the code I have come up with to get one record to autopopulate
Private Sub Form_Load()
Me.TransectOID = Me.OpenArgs
End Sub
Private Sub Form_Deactivate()
DoCmd.RunCommand acCmdSaveRecord
End Sub
Private Sub DataObs_AfterUpdate()
Me!frmLPIDetail.Form!Data_observer = Me!DataObs
Me.Dirty = False
End Sub
Private Sub DataRec_AfterUpdate()
Me!frmLPIDetail.Form!Data_recorder = Me!DataRec
Me.Dirty = False
End Sub
Any suggestions would be much appreciated
Since inserting multiple records at a time is desirable your question has been asked before but I couldn't find an answer that was particularly helpful so I will provide a more general answer than you asked for.
Access doesn't provide default forms for inserting multiple records. You have to code that yourself but the process is always pretty much the same.
figure out a normalized table structure for your data
figure what data you need to collect from the user for the multiple insert
add a button to the form and put the vba for the multiple insert in the click event
so here is 1 normalized table structure that might fit your data:
Since I don't know where TransectionOID is coming from we let Access provide TransectionID as the primary key and assume TransectionOID is entered on another form. All the other information of interest is in the TransectionDetails table and there is no need to write a query to gather all the variables we will need into our forms record source to finish step 2. To get a jumpstart I selected the TransactionDetails table and used the create form wizard to make a tabular style form.
To finish step 2 we put controls in the header to collect the information from the user we will need and the start editing the form for user friendliness. For instance I delete the checkbox for TransectionDetailID in the details section and replace every other control with comboboxes. I normally replace the circled record selectors with comboboxes as well but here that may be confusing so I leave the record selectors to provide some search functionality. The final form looks like:
Finally, for step 3 we add the vba for the click event
Private Sub cmdInsert_Click()
Dim db As Database
Dim rs As Recordset 'using recordset because lower error rate than using sql strings
Set db = CurrentDb
Set rs = db.OpenRecordset("TransectionDetails")
Dim L As Integer
Dim S As Integer
If Not Me.lstLocations.ListCount = 0 Then 'if no locations are selected no records can be inserted
For L = 0 To Me.lstLocations.ListCount 'simple multiselect listbox version matters for the vba code
If Me.lstLocations.Selected(L) = True Then
For S = 0 To Me.lstSpecies.ListCount
If Me.lstSpecies.Selected(S) = True Then
rs.AddNew
rs!TransectionID = Me.cmbTransectionID
rs!Data_observer = Me.cmbData_observer
rs!Data_recorder = Me.cmbData_recorder
rs!TransectLocation = Me.lstLocations.Column(0, L) 'column gives you access to values in the listbox
rs!SpeciesID = Me.lstSpecies.Column(0, S)
If Not IsNull(Me.chkDead) Then 'chkDead is a triple value checkbox, this both avoids setting Dead to null and shows how to handle when the user doesn't set all controls
rs!Dead = Me.chkDead
End If 'chkdead
rs.Update
End If 'lstspecies selected
Next S
End If
Next L
End If
Me.Detail.Visible = True 'quick and dirty bit of style (detail starts invisible)
Me.Filter = "TransectionID = " & Me.cmbTransectionID 'more quick and dirty style filter to focus on inserted records
Me.FilterOn = True
'clean up
rs.Close
Set rs = Nothing
Set db = Nothing
End Sub
Private Sub cmdSelectAllLocations_Click()
Dim i As Integer
For i = 0 To Me.lstLocations.ListCount
Me.lstLocations.Selected(i) = True
Next
End Sub
Private Sub cmdSelectAllSpecies_Click()
Dim i As Integer
For i = 0 To Me.lstSpecies.ListCount
Me.lstSpecies.Selected(i) = True
Next
End Sub
Private Sub cmdSelectNoLocations_Click()
Dim i As Integer
For i = 0 To Me.lstLocations.ListCount
Me.lstLocations.Selected(i) = False
Next
End Sub
Private Sub cmdSelectNoSpecies_Click()
Dim i As Integer
For i = 0 To Me.lstSpecies.ListCount
Me.lstSpecies.Selected(i) = False
Next
End Sub
While Mazoula's answer is far more elegant, I discovered a quick and dirty way to accomplish what I was after using a While loop. Below is my code:
Private Sub Form_Load()
Me.TransectOID = Me.OpenArgs
End Sub
Private Sub Form_Deactivate()
DoCmd.RunCommand acCmdSaveRecord
End Sub
Private Sub DataObs_AfterUpdate()
Dim rs As DAO.Recordset
Set rs = Me!frmLPIDetail.Form.RecordsetClone
rs.MoveLast
rs.MoveFirst
While Not rs.EOF
rs.Edit
rs!Data_observer.Value = Me.DataObs.Value
rs.Update
rs.MoveNext
Wend
rs.Close
Set rs = Nothing
Me.Dirty = False
End Sub
Private Sub DataRec_AfterUpdate()
Dim rs As DAO.Recordset
Set rs = Me!frmLPIDetail.Form.RecordsetClone
rs.MoveLast
rs.MoveFirst
While Not rs.EOF
rs.Edit
rs!Data_recorder.Value = Me.DataRec.Value
rs.Update
rs.MoveNext
Wend
rs.Close
Set rs = Nothing
Me.Dirty = False
End Sub
Related
Background:
I have a subform (datasheet) I update using a datasheet checkbox afterupdate event:
I clone the clicked record & Insert into the referenced table via an Insert Query
I modify the original record to differentiate from the Inserted record via an Update Query
To avoid RecordLock complaints, I have inserted: SendKeys "+{Enter}", True after each of the above to save the updates - is there a better way to do this??
Next I need to requery the subform to display the newly inserted record AND use a bookmark (?) to retain the cursor position of the original record (inserted record will be adjacent). Subform datasheet recordset is based on a query.
Question:
From within the subform's afterupdate event, using Forms![ParentForm].[SubForm].Form.Requery does not produce an error, but does open the code window with the line highlighted in Yellow.
Next attempt, from within the subform's afterupdate event, I have attempted to set focus to a control on the ParentForm BEFORE using Forms![ParentForm].[SubForm].Form.Requery, but I get a similar ~silent error~ as noted above.
What is the proper way to REQUERY a subform from within the same subform?
Thanks!
You are making it way too hard for yourself. Here is how to copy a record from a button click. No locks, no queries, and auto update of the form:
Private Sub btnCopy_Click()
Dim rstSource As DAO.Recordset
Dim rstInsert As DAO.Recordset
Dim fld As DAO.Field
If Me.NewRecord = True Then Exit Sub
Set rstInsert = Me.RecordsetClone
Set rstSource = rstInsert.Clone
With rstSource
If .RecordCount > 0 Then
' Go to the current record.
.Bookmark = Me.Bookmark
With rstInsert
.AddNew
For Each fld In rstSource.Fields
With fld
If .Attributes And dbAutoIncrField Then
' Skip Autonumber or GUID field.
ElseIf .Name = "SomeFieldToHandle" Then
rstInsert.Fields(.Name).Value = SomeSpecialValue
ElseIf .Name = "SomeOtherFieldToHandle" Then
rstInsert.Fields(.Name).Value = SomeOtherSpecialValue
ElseIf .Name = "SomeFieldToSkip" Then
' Leave empty or with default value.
ElseIf .Name = "SomeFieldToBlank" Then
rstInsert.Fields(.Name).Value = Null
Else
' Copy field content.
rstInsert.Fields(.Name).Value = .Value
End If
End With
Next
.Update
' Go to the new record and sync form.
.MoveLast
Me.Bookmark = .Bookmark
.Close
End With
End If
.Close
End With
Set rstInsert = Nothing
Set rstSource = Nothing
End Sub
If the button is placed on the parent form, replace Me with:
Me!NameOfYourSubformControl.Form
Between AddNew and Update you can modify and insert code to adjust the value of some fields that should not just be copied.
I have a main form and a subform that displays several records. When a checkbox is checked on the main form, I want all "BoxLblTime" and "Material Arrived" fields on the subform to be updated. This is the vba code that is executed when the checkbox is clicked:
Private Sub MaterialArrived_chkbx_Click()
Dim temp As Variant
Dim tempString As String
Dim ctl As Control
'If checkbox is checked
If Forms("JOBS Form").Controls("MaterialArrived_chkbx").Value < 0 Then
Dim rs As Object
'Get records of subform
Set rs = Forms("JOBS Form").[Order Form].Form.Recordset
'Loop until end
Do While Not rs.EOF
rs.Edit
'Update the two fields
rs![Material arrived] = True
rs!BoxLblTime = Now()
rs.Update
rs.MoveNext
Loop
Set rs = Nothing
End If
End Sub
There is some unusual behavior when using this code:
1) When the checkbox on the main form is checked, the two fields are updated in the subform. But if I uncheck the checkbox on the subform and then recheck the main form checkbox, the subform checkbox stays unchecked.
2) When the checkbox on the main form is checked, the two fields are updated. But if I uncheck the checkbox on the subform, move to a new set of subform records (next or back) and then check the main form checkbox, I get the error: '3021' No current record.
Why is this unusual behavior happening?
EDIT:
Here is my code using the update query approach:
Private Sub MaterialArrived_chkbx_Click()
If Forms("JOBS Form").Controls("MaterialArrived_chkbx").Value < 0 Then
With CurrentDb().QueryDefs("Update Orders")
.Parameters("[Material Arrived]").Value = True
.Parameters("[BoxLblTime]").Value = Now()
.Execute dbFailOnError
End With
Forms("JOBS Form").Form.Requery
End If
End Sub
But I'm getting "Item not found in collection" error.
I would suggest an alternative approach.
Create an update query and pass a Boolean parameter indicating the material arrived value.
'Call update query
Private Sub MaterialArrived_chkbx_Click()
With CurrentDb().QueryDefs("Update Orders")
.Parameters("[prmMaterialArrived]").Value = Me.MaterialArrived_chkbx.Value
.Parameters("[prmID]").Value = Me!ID
.Execute dbFailOnError
End With
Me.[Order Form].Form.Requery
End Sub
'SQL
PARAMETERS [prmMaterialArrived] Bit, [prmID] Long;
UPDATE T
SET T.[Material arrived] = [prmMaterialArrived], T.BoxLblTime = Now()
WHERE (((T.ID)=[prmID]));
I'm looking for help in creating the simplest method of doing the following update on a selection of items.
I have a table in SQL called Item with field ReservationID
I have a list of items in Access and what I'd like to do is select items using the record selectors in the Access form and update those items with the ReservationID using an SQL Update command.
I read somewhere that only adjacent record selectors could be selected which is fine but I don't really know where to begin.
Any pointers would be gratefully received. Thanks.
---23/02/2017---
Ok I changed it from a function to a Private Sub thus:
Private Sub cmdReserve_Click()
Dim i As Long
Dim frm As Form
Dim rs As DAO.Recordset
' Get the form and its recordset.
Set frm = Forms![F_SalesOrders_ItemsInStock]
Set rs = frm.RecordsetClone
' Move to the first record in the recordset.
rs.MoveFirst
' Move to the first selected record.
rs.Move frm.SelTop - 1
' Enumerate the list of selected records
' presenting the field contents in a message box.
For i = 1 To frm.SelHeight
MsgBox rs![ItemID]
rs.MoveNext
Next i
End Sub
but when I select records and hit the button nothing happens
Consider a linked table and walking the record set.
Function DisplaySelectedCompanyNames()
Dim i As Long
Dim frm As Form
Dim rs As DAO.Recordset
' Get the form and its recordset.
Set frm = Forms![Customers]
Set rs = frm.RecordsetClone
' Move to the first record in the recordset.
rs.MoveFirst
' Move to the first selected record.
rs.Move frm.SelTop - 1
' Enumerate the list of selected records presenting
' the CompanyName field in a message box.
For i = 1 To frm.SelHeight
MsgBox rs![CompanyName]
rs.MoveNext
Next i
End Function
I'm using too many boolean indicators and I'm sure its very inefficient/stupid...
Currently in the Access database I have numerous forms which are used to edit underlying records. Text boxes on these forms are not bound to the underlying table. I do not wish to bind the form or any of its controls directly to the underlying tables, if the data is editable by the user (less human error from users). Instead I've a Boolean for every control which contains editable information.
Users enter 'edit mode', change information (Boolean now equals true), click 'save changes', review the changes and accept and then the relevant queries are run to reflect these changes. I like this order of events however, I'm creating increasingly complex forms with 40 or so editable controls and hence 40 Boolean variables.
Anyone think of an nicer alternative? Is there a property of the controls (mainly text boxes) I can use?
CODE:
Private Sub CommentsText_AfterUpdate()
If Nz(Me.CommentsText) = "" Then
CommentsEdit = False
Else
CommentsEdit = True
End If
End Sub
'Within the 'save changes' method
If CommentsEdit Then
CommentsEdit = False
sql = "Update [General-CN] Set [Comments] = '" & Left(Me.CommentsText, 250) & "' Where [ID ( G )] = " & Me.[GeneralPK] & ";"
DoCmd.RunSQL (sql)
End If
normally data validation during data entry is one the things that normally, in my experience, before or later put the developer on his knees.
From your question, I can feel that the core is to preserve database integrity. Unfortunately there is no way to ABSOLUTELY preserve your database. If you give access to it to users you can only use some tips:
1 - If possible use combo-box with defined entries instead of using textboxes in which user is free to digit anything (e.g. think an expert system that collect data about the same problem written in many different ways!!!)
2 - Check data integrity and coherence (e.g. type) before writing it to database
3 - When the data is just a boolean (flag) you can use switches, radio button or checkboxes.
These tips help in developing a user interface more friendly and faster from the point of view of data entry.
After this I can give you another way to validate your data.
If you want to show data to user before saving you can create the mask in which you enter your data with unbounded textboxes.
Then, when he clicks the Save Button, you can show a 2nd form, opened in append mode, in which you show data with textboxes bounded to your db.
If he accepts the data, you can save otherwise you can cancel the data entry preserving your database. I post you here some lines of code with an example taken from an application of mine. It's the part in which I manage contacts
The form allows to input contact data
'------------------------------------------------------
' Temp variables to store entries before saving /cancelling
'------------------------------------------------------
Dim bolValidationErr As Boolean
Dim m_vntLastName As Variant
Dim m_vntFirstName As Variant
Dim m_vntFC As Variant
Dim m_vntVATNum As Variant
Dim m_vntAddress As Variant
Dim m_vntCity As Variant
Dim m_vntZIP As Variant
Dim m_vntCountry As Variant
Dim m_vntPhone As Variant
Dim m_vntFAX As Variant
Dim m_vntEMail As Variant
Dim m_vntNote As Variant
Dim m_vntContactType As Variant
'------------------------------------------------------
' Suppress error "Impossible to save the record...
'------------------------------------------------------
Private Sub Form_Error(DataErr As Integer, Response As Integer)
If DataErr = 2169 Then
Response = True
End If
End Sub
'------------------------------------------------------
' W/o customer last name, cancel saving
'------------------------------------------------------
Private Sub Form_Beforeupdate(Cancel As Integer)
On Error GoTo Err_Form_BeforeUpdate
Dim strType As String
Dim bolNewContact As Boolean
Dim intErrFC As Integer
Dim intErrVATNum As Integer
Dim intErrFullName As Integer
Dim strErrMsg As String
Dim intAnswer As Integer
bolValidationErr = False
'------------------------------------------------------
' If LastName is missing, cancel data saving
' Cancel = True cause the raise of the error
' "You can't save record at this time..."
' SetWarnings doesn't work. It's needed to intercept with Form_Error event
'------------------------------------------------------
If HasNoValue(Me.txtLastName) Then
strErrMsg = "Put here your error msg"
intAnswer = MsgBox(strErrMsg, vbOKOnly + vbExclamation, "Entry Error")
Cancel = True ' Cancel db update
Call BackupCurrentData ' Store data input until now
bolValidationErr = True ' Unvalid data
Exit Sub
End If
Exit_Form_BeforeUpdate:
Exit Sub
Err_Form_BeforeUpdate:
GoTo Exit_Form_BeforeUpdate
End Sub
'------------------------------------------------------
' Store the content of textboxes for restoring them in case
' of cancelling
'------------------------------------------------------
' If the record is new, if the BeforeUpdate event is cancelled,
' NULL values are restored (in fact there were no data!!!)
'------------------------------------------------------
Private Sub BackupCurrentData()
m_vntLastName = Me.txtLastName
m_vntFirstName = Me.txtFirstName
m_vntFC = Me.txtFC
m_vntVATNum = Me.txtVATNum
m_vntAddress = Me.txtAddress
m_vntCity = Me.txtCity
m_vntZIP = Me.txtZIP
m_vntCountry = Me.txtCountry
m_vntPhone = Me.txtTelNum
m_vntFAX = Me.txtFax
m_vntEMail = Me.txtEmail
m_vntNote = Me.txtNotes
m_vntContactType = Me.cmbContactType
End Sub
'------------------------------------------------------
' Restore contents of textboxes before cancelling operation
'------------------------------------------------------
Private Sub RestoreCurrentData()
Me.txtLastName = m_vntLastName
Me.txtFirstName = m_vntFirstName
Me.txtFC = m_vntFC
Me.txtVATNum = m_vntVATNum
Me.txtAddress = m_vntAddress
Me.txtCity = m_vntCity
Me.txtZIP = m_vntZIP
Me.txtCountry = m_vntCountry
Me.txtTelNum = m_vntPhone
Me.txtFax = m_vntFAX
Me.txtEmail = m_vntEMail
Me.txtNotes = m_vntNote
Me.cmbContactType = m_vntContactType
End Sub
I think that this code can be adapted to your needs.
The variable names are enough self-describing
Anyway feel free to contact me if you need further help.
Bye,
Wiz:-)
I have a function to extract and then display a recordset in a listbox.
I only get one field in my listbox.
Is there a way I can display the whole column "Caption" (several fields) in the listbox?
Function GetCaption() As String
Dim db As Database
Dim rst As DAO.Recordset
Dim SQL As String
Dim LCaption As String
Set db = CurrentDb()
SQL = "SELECT Caption FROM tblMainMenu"
Set rst = db.OpenRecordset(SQL)
If rst.EOF = False Then
LCaption = rst("Caption")
Else
LCaption = "Not found"
End If
rst.Close
Set rst = Nothing
GetCaption = LCaption
End Function
Private Sub btnGetCaption1_Click()
LstBx.RowSourceType = "Value List"
LstBx.RowSource = GetCaption
End Sub
Private Sub Form_Load()
LstBx.RowSource = ""
btnGetCaption1.Caption = DLookup("ReportID", "tblMainMenu", "ReportID = 1")
End Sub
I'm not sure how well I understand your goal. But if you want the list box to contain tblMainMenu.Caption values, one per list box row, you can use the query as its Record Source.
With the form open in Design View, open the list box's property sheet, and select the Data tab. Then choose "Table/Query" for Row Source Type. Add this SQL for the Row Source property.
SELECT [Caption] FROM tblMainMenu
Then select the Format tab, and enter 1 for the Column Count property.
Finally switch to Form View and tell us whether that gives you what you want, or how it differs from what you want.