Tracking value changes when editing a record in Access - ms-access

I am trying to track value changes when editing a record. All fields on the form are unbound text box.
Below is a function that is used to insert a audit tracking record.
Public Function AuditChanges(RecordID As String, UserAction As String)
Dim DB As Database
Dim rst As Recordset
Dim clt As Control
Dim Userlogin As String
Set DB = CurrentDb
Set rst = DB.OpenRecordset("select * from tbl_audittrail", adOpenDynamic)
Userlogin = Environ("username")
Select Case UserAction
Case "Edit"
For Each clt In Screen.ActiveForm.Controls
If (clt.ControlType = acTextBox _
Or clt.ControlType = accombox) Then
If Nz(clt.Value) <> Nz(clt.OldValue) Then
With rst
.AddNew
![DateTime] = Now()
!UserName = Userlogin
!FormName = Screen.ActiveForm.Name
!Action = UserAction
!RecordID = Screen.ActiveForm.Controls(RecordID).Value
!FieldName = clt.ControlSource
!OldValue = clt.OldValue
!Newvalue = clt.Value
.Update
End With
End If
End If
Next clt
End Select
rst.Close
DB.Close
Set rst = Nothing
Set DB = Nothing
End Function
Below is how I use the function:
Private Sub btnUpdate_Click()
Set DB = CurrentDb
Set rs = DB.OpenRecordset("SELECT * FROM ASID", dbOpenDynaset, dbSeeChanges)
rs.Edit
rs!ISIN = Me.ISIN
rs!SECIDTYPE = Me.SECIDTYPE
rs!ALTSECID = Me.ALTSECID
rs.Update
Call AuditChanges("ISIN", "Edit")
End If
End Sub
The problem is when it calls AuditChanges, it goes directly from
If (clt.ControlType = acTextBox _
Or clt.ControlType = accombox)
to End If
All fields on current form are unbound text boxes and you have to press an "Add" command button to actually add a record. I think there must be something wrong with the control type but I am not sure which control type should be used. Any idea?

Start by putting Option Explicit at the top of each module.
It enforces variable declaration and reports undeclared or misspelled variables/constants at compile time.
To have this automatically in new modules, set the Require Variable Declaration option in the VBA Editor.
This is really a must have for VBA development.
Then the compiler will tell you that accombox doesn't exist, it should be acComboBox
Without Option Explicit, accombox is initialized as NULL variant, and causes your entire If condition to be NULL, and therefore never be entered.

Related

How can I get the value from a control using the name of the control source?

I am trying to write some code to audit changes made via a form. I have a function that works to do this:
Function WriteChanges()
Dim f As Form
Dim c As Control
Dim user As String
Dim db As DAO.Database
Dim cnn As ADODB.Connection
Dim MySet As ADODB.Recordset
Dim tbld As TableDef
Dim recsource As String
Set f = Screen.ActiveForm
Set db = CurrentDb
Set cnn = CurrentProject.Connection
Set MySet = New ADODB.Recordset
recsource = f.RecordSource
Set tbld = db.TableDefs(recsource)
pri_key = fFindPrimaryKeyFields(tbld)
Debug.Print "pri_key: "; pri_key
user = Environ("username")
MySet.Open "Audit", cnn, adOpenDynamic, adLockOptimistic, adCmdTable
For Each c In f.Controls
Select Case c.ControlType
Case acTextBox, acComboBox, acListBox, acOptionGroup
If c.Value <> c.OldValue Then
With MySet
.AddNew
![EditDate] = Now
![user] = user
![SourceTable] = f.RecordSource
![SourceField] = c.ControlSource
![BeforeValue] = c.OldValue
![AfterValue] = c.Value
.update
End With
End If
End Select
Next c
MySet.Close
Set MySet = Nothing
Set f = Nothing
Set db = Nothing
End Function
I use this function on the before update property of various forms and it populates the Audit table with the details of the changes to values in each of the controls. I need to also get the value from the primary key field to add to the Audit table. I can use the following code to identify the name of the primary key within the form's record source:
Function fFindPrimaryKeyFields(tdf As TableDef) As String
Dim idx As Index
On Error GoTo HandleIt
For Each idx In tdf.Indexes
If idx.Primary Then
fFindPrimaryKeyFields = Replace(idx.Fields, "+", "")
GoTo OutHere
End If
Next idx
OutHere:
Set idx = Nothing
Exit Function
HandleIt:
Select Case Err
Case 0
Resume Next
Case Else
Beep
MsgBox Err & " " & Err.Description, vbCritical + vbOKOnly, "Error"
fFindPrimaryKeyFields = vbNullString
Resume OutHere
End Select
End Function
How can I use this to get the value from the control (text box) that has the identified primary key as its control source.
Please forgive any silly errors in my code as I'm a relative novice. Thanks in advance for any help.
I'm not 100% sure what you want exactly, but if you have the name of the field, you can use the following:
Dim primaryKeyValue As Variant
Dim primaryKeyColumnName As String
primaryKeyColumnName = fFindPrimaryKeyFields(TableDefs!MyTable)
Dim f as Form
'Get the form somehow
Dim c As Control
On Error GoTo NextC 'Escape errors because lots of controls don't have a control source
For Each c In f.Controls
If c.ControlSource = primaryKeyColumnName Then
primaryKeyValue = c.Value
End If
NextC:
Next c
On Error GoTo 0
If the primary key column is part of the form record source, you can simply read it by:
Debug.Print "PK value: " & f(pri_key)
Every column of the record source is a form property at runtime, independent of whether there is a control with the same name.
Note: your whole construct will stop working if you have a form that is based on a query that joins multiple tables.

MSAccess.EXE remains open in the background after quitting: VBA/Form object issue

I have a really weird issue with a VBA function running in Access. When this function is called, something happens to Access that keeps it from truly quitting in the Task Manager. If this function does not run, Access will quit normally. I feel like it has something to do with passing a form object as a parameter, but I can't understand why this is happening.
The Call to the function looks like this:
...
With Forms!frmbuytool
'...setting visible properties of form objects
SetColumnOrder (!sfmReordersView.Form)
End with
...
The function looks like this:
Public Sub SetColumnOrder(frm As Form)
Dim db As Database
Dim rs As Recordset
Dim Username As String
Dim DataSheetID As Integer
Set db = CurrentDb
Username = Environ("USERNAME")
Set rs = db.OpenRecordset("SELECT DatasheetID FROM sysDataSheets WHERE DataSheetName = """ & frm.Name & """")
DataSheetID = rs!DataSheetID
'load up user settings
Set rs = db.OpenRecordset("SELECT * FROM sysUserSettings WHERE Username = """ & Username & """ AND DatasheetID = " & DataSheetID)
'if no settings are found for the user, use the defaults
If rs.EOF Then
If IsRowUser Then
Set rs = db.OpenRecordset("SELECT * FROM sysUserSettings WHERE Username = ""ROW_Default"" AND DatasheetID = " & DataSheetID)
Else
Set rs = db.OpenRecordset("SELECT * FROM sysUserSettings WHERE Username = ""CAN_Default"" AND DatasheetID = " & DataSheetID)
End If
End If
'Apply settings
Do While Not rs.EOF
With frm.Controls(rs!ColumnName)
.ColumnOrder = rs!ColumnOrder
.ColumnWidth = rs!ColumnWidth
.ColumnHidden = rs!ColumnHidden
End With
rs.MoveNext
Loop
frm.Refresh
Set frm = Nothing
Set rs = Nothing
Set db = Nothing
End Sub
I added the "Set frm = Nothing" thinking that somehow the form object is not being released, but it didn't help.
Thanks for any insights you can provide!
The call with extra parentheses
SetColumnOrder (!sfmReordersView.Form)
was the problem. You are evaluating the form object instead of just passing it as reference. Use
Call SetColumnOrder(!sfmReordersView.Form)
or
SetColumnOrder !sfmReordersView.Form

Error 3251 on .oldValue control property

I am currently working on adding an audit trail to a MS-Access 2010 database and I am struggling with
"error 3251 : operation is not supported for this type object"
Here is the code of my audit trail module, mostly arranged code coming from web :
Public Function auditChanges(RecordID As String, userAction As String, cForm As Form)
Dim db As DAO.Database
Dim rst As DAO.Recordset
Dim ctl As Control
Dim userLogin As String
Set db = CurrentDb
Set rst = db.OpenRecordset("SELECT * FROM T_AUDIT")
userLogin = getCurrentUser
Select Case userAction
Case "New"
With rst
.AddNew
![Date] = Now()
![utilisateur] = userLogin
![nomFormulaire] = cForm.Name
![Action] = userAction
![RecordID] = cForm.Controls(RecordID).Value
.Update
End With
Case "Delete"
With rst
.AddNew
![Date] = Now()
![utilisateur] = userLogin
![nomFormulaire] = cForm.Name
![Action] = userAction
![RecordID] = cForm.Controls(RecordID).Value
.Update
End With
Case "Edit"
For Each ctl In cForm.Controls
If (ctl.ControlType = acTextBox) Or (ctl.ControlType = acComboBox) Or (ctl.ControlType = acCheckBox) Then
If (Nz(ctl.Value, "") <> Nz(ctl.OldValue, "")) Then
With rst
.AddNew
![Date] = Now()
![utilisateur] = userLogin
![nomFormulaire] = cForm.Name
![Action] = userAction
![RecordID] = cForm.Controls(RecordID).Value
![champs] = ctl.ControlSource
![ancienneValeur] = ctl.OldValue
![nouvelleValeur] = ctl.Value
.Update
End With
End If
End If
Next ctl
End Select
rst.Close
db.Close
Set rst = Nothing
Set db = Nothing
End Function
This function is called in beforeUpdate event of the forms I want to track.
The error is fired when I try to edit a bound textbox. And the line If (Nz(ctl.Value, "") <> Nz(ctl.OldValue, "")) Then is the line provoking the error
The form is based on 2 tables linked with a One-To-Many relationship. The function is working when I edit fields from the table which are sided to the "One" part of the relationship but it throws the error when I want to edit fields from the "Many" side.
I hope I am clear enough, thank you
Edit : More details
My form is based on that request :
SELECT T_REVISION.ID_revision, T_REVISION.fk_ID_proposition, T_REVISION.numero, T_REVISION.fk_etat_revision, T_REVISION.EOTP, T_PROPOSITION.reference_simple, T_PROPOSITION.libelle, T_REVISION.description_localisation
FROM T_PROPOSITION INNER JOIN T_REVISION ON T_PROPOSITION.ID_proposition = T_REVISION.fk_ID_proposition
ORDER BY T_REVISION.numero DESC;
The error is fired from T_PROPOSITION.reference_simple control.
The error 3251 occurs when : I try to edit T_REVISION.EOTP, T_REVISION.description_localisation fields. The error 3251 does not occur when I edit T_PROPOSITION.reference_simple, T_PROPOSITION.libelle !
So : I'am able to edit values coming from the "One" side of the relation but when I want to edit the "Many" side it seems I can't access the oldValue property
How can I solve this ?
Not exactly an answer, but the comment area is not suitable...
If would add 2 lines BEFORE the If (Nz(ctl.Value, "") <> Nz(ctl.OldValue, "")) Then
:
debug.print ctl.Name, ctl.value
debug.print ctl.name, ctl.oldvalue
This will allow you to see if the error is linked to a specific control and to a specific property, and narrow your search.
Edit: After you edited your OP indicating that the issue arises on the "many" side of you join, I think you should change your form to a "main form - subform" architecture. This will allow you to track updates to each TABLE correctly.
It's hard to tell exactly what's causing the error when you don't specify the line that's popping up the error, but there is one obvious possibility.
You're trapping for Null in your control loop
If (Nz(ctl.Value, "") <> Nz(ctl.OldValue, "")) Then
And more importantly you're missing a front parentheses as " ("
But then you don't trap for that possibility when assigning the control's value and old value to your recordset.
![ancienneValeur] = ctl.OldValue
![nouvelleValeur] = ctl.Value
Do your fields ancienneValeur and nouvelleValeur allow for null or zero length fields to be assigned to them?
In any case you should be consistent and make sure you trap for null values before assigning them to your table's fields.
![ancienneValeur] = NZ(ctl.OldValue,"")
![nouvelleValeur] = NZ(ctl.Value,"")

MS Access 2013 saved append query not updating all fields

I have a saved query, qryInsertLog which is as follows:
PARAMETERS UserIDPar Long, UnitIDPar Long, LogEntryPar LongText, FNotesPar LongText;
INSERT INTO tblLogBook ( UserID, UnitID, LogEntry, FNotes )
SELECT [UserIDPar] AS Expr1, [UnitIDPar] AS Expr2, [LogEntryPar] AS Expr3, [FNotesPar] AS Expr4;
I'm trying to run this query when a save button is clicked on an unbound form, where the parameters are gathered from the form controls. My VBA code for the save button is:
Private Sub cmdSave_Click()
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim okToSave As Boolean
If Me.cboUser.Value = 0 Or IsNull(Me.cboUser.Value) Then
MsgBox "You must choose a user. Record not saved."
okToSave = False
ElseIf Me.cboUnit.Value = 0 Or IsNull(Me.cboUnit.Value) Then
MsgBox "You must choose a unit. Record not saved."
okToSave = False
ElseIf Me.txtLogEntry.Value = "" Or IsNull(Me.txtLogEntry.Value) Then
MsgBox "You must have somtehing to log. Record not saved."
okToSave = False
Else
okToSave = True
End If
Set db = CurrentDb
Set qdf = db.QueryDefs("qryInsertLog")
qdf.Parameters("UserIDPar").Value = Me.cboUser.Value
qdf.Parameters("UnitIDPar").Value = Me.cboUnit.Value
qdf.Parameters("LogEntryPar").Value = Me.txtLogEntry.Value
qdf.Parameters("FNotesPar").Value = IIf(IsNull(Me.txtFNotes.Value), "", Me.txtFNotes.Value)
If okToSave Then
qdf.Execute
End If
qdf.Close
Set qdf = Nothing
End Sub
When this code is run, the FNotes field of the table isn't updated. The other three fields update as expected. FNotes is the only field which isn't required. I hardcoded a string for FNotes paramater like so:
qdf.Parameters("FNotesPar").Value = "why doesn't this work"
rather than using the form control value, and got the same result: that field just doesn't update. When I run this query from the Access Objects window and supply parameter values from the prompts, it works just fine. When I create form that's bound to the table, it also seems to work just fine.
I can't figure out why there's no trouble updating the LogEntry field but the FNotes field fails to update.
Add the new record via a DAO.Recordset instead of a DAO.QueryDef.
First, include this declaration ...
Dim rs As DAO.Recordset
Then use this after Set db = CurrentDb ....
Set rs = db.OpenRecordset("tblLogBook")
With rs
If okToSave Then
.AddNew
!UserID = Me.cboUser.Value
!UnitID = Me.cboUnit.Value
!LogEntry = Me.txtLogEntry.Value
!FNotes = Nz(Me.txtFNotes.Value, "")
.Update
End If
.Close
End With
Note Nz(Me.txtFNotes.Value, "") gives you the same thing as IIf(IsNull(Me.txtFNotes.Value), "", Me.txtFNotes.Value), but more concisely.

vba access run time error 3265

I am new to programming in VBA. I am trying to copy Form data from an existing form when I click on the Copy Record button. This is supposed to copy the current form data as a new record with a new master_id (that is autonumbered) and have Brand as blank field for them to fill in. I get a:
Run Time Error 3265 "Item not found in this collection"
at the new_master_id that i created. I am not sure how to fix this problem. Any help is appreciated.
Private Sub Copy_Record_Click()
Dim RS As DAO.Recordset, C As Control
Dim FillFields As String, FillAllFields As Integer
Dim New_MASTER_ID As Integer
New_MASTER_ID = (DMax("[MASTER_ID]", "tbl_Drug_Master") + 1)
Dim BRAND As String
BRAND = ""
Set RS = CurrentDb.OpenRecordset(Name:="tbl_Drug_Master", Type:=RecordsetTypeEnum.dbOpenDynaset)
With RS
.AddNew
![MASTER_ID] = ![New_MASTER_ID] <--this is where the problem is...
![MASTER_KEY] = Me![MASTER_KEY]
![PRODUCT_CATEGORY] = Me![PRODUCT_CATEGORY]
![BRAND] = Me![BRAND]
![GENERIC] = Me![GENERIC]
![STUDY_NAME] = Me![STUDY_NAME]
![MANUFACTURER] = Me![MANUFACTURER]
![MASTER_COMMENTS] = Me![MASTER_COMMENTS]
.Update
End With
End Sub
ok so firstly, im not sure why the following are required:
dim c as control
Dim FillFields As String, FillAllFields As Integer
New_MASTER_ID = (DMax("[MASTER_ID]", "tbl_Drug_Master") + 1)
Dim BRAND As String
BRAND = ""
therefore I am leaving them out as part of this question because they appear unnecessary. Brand is not required because you are creating a new record and putting nothing in the brand field so it will remain blank.
I am also not too sure why you have 2 tables both that are the same? I think what should happen is that you simply copy the data to a new record in the same table.
You will see I have put a save record command in to the routine. other additions such as error handling is also recommended.
Private Sub Copy_Record_Click()
docmd.runcommand accmdsaverecord
Dim RS As Recordset
Set RS = CurrentDb.OpenRecordset(Name:="tbl_Drug_Master", Type:=RecordsetTypeEnum.dbOpenDynaset)
With RS
.AddNew
![MASTER_KEY] = Me.MASTER_KEY.value
![PRODUCT_CATEGORY] = Me.PRODUCT_CATEGORY.value
![GENERIC] = Me.GENERIC.value
![STUDY_NAME] = Me.STUDY_NAME.value
![MANUFACTURER] = Me.MANUFACTURER.value
![MASTER_COMMENTS] = Me.MASTER_COMMENTS.value
.Update
End With
Set RS = Nothing
End Sub
I was mistaken with my comment rs.close it would be db.close but you are using the currentdb and no reason to close it. This procedure will remain on the original record, if you want to go to the new record you will have to add a command like docmd.gotorecord acdataform, , aclast before the end of the routine.