I have a form, myForm, that includes a subform, mySubform. The records in mySubform have a many to one relationship with the record source of the myForm, as expected, and There is a combobox in mySubform, myCombo, whose values is linked to one of the columns of the record source of mySubform.
I have been having difficulty to delete a record in mySubform, by erasing the current value in myCombo. I have put the code below under the OnChange event of myCombo (after trying many different alternatives).
Please not that I have simplified the delete query and the actual one works fine in the back-end (SQL Server). However, after performing the delete operation, I get this error when the execution reaches Me.Requery:
Error 3162 You tried to assign a Null value to a variable that is not a Variant data type.
Also, after having the degugger skip the Me.Requery and going back to the form, the deleted record shows "#DELETED" in every combobox and textbox for that particular record, and mySubform is not any good beyond that point.
I have looked up very many resources and tried different statements instead of Me.Requery, but none has worked so far. Help is much appreciated.
Private Sub myCombo_Change()
If IsNull(Me.myCombo.Text) Or Me.myCombo.Text = vbNullString Then
Dim strSql As String
strSql = "DELETE FROM mySubformRecordSource WHERE PrimaryKeyColumn= " & theCurrentPrimaryKeyValueForTheValueIn_MyCombo
CurrentDb.Execute strSql
Me.Requery
End If
End Sub
I solved the problem by putting Me.Dirty = False after reading this discussion:
Editing Record issues in Access / SQL (Write Conflict)
Private Sub myCombo_Change()
If IsNull(Me.myCombo.Text) Or Me.myCombo.Text = vbNullString Then
Me.Dirty = False
Dim strSql As String
strSql = "DELETE FROM mySubformRecordSource WHERE PrimaryKeyColumn= " & theCurrentPrimaryKeyValueForTheValueIn_MyCombo
CurrentDb.Execute strSql
Me.Requery
End If
End Sub
The form and subform work flawlessly now, but I still dont quite understand the complete logic behind the solution. It has something to do with Me.Dirty=False saving the changes to the content of a non-nullable (or nullable, both work fine) column before programmatically delete the entire record. How exactly though, I dont know and will appreciate input.
Not entering on the choices of used cursors etc I think that the solution could be simply "requery" the parent form called by the code of the subform.
But i also have found similar situation in past projects that i made (sorry not beeing exaustive, i'm not using VBA anymore) and the requery command was done on subform but from the main form code, something like this:
If local_procedures.deleteContratto(anagrafica_id_par, servizio_id_par) = OK Then
Me.subfrm_contratti_frm_anagrafica.Requery
...
In this case you could delegate parent form to call delete procedure and update logic (.Requery) of the child form, by mean of an event raised from subForm listen on parent for example.
For the sake of completeness i started hinting about cursors because i think it's the underlying reason for these kind of sync failures between recordsets, the parent form one and the child form one.
There are many advices about using dbSeeChanges with db.Execute on SQL Server backend and the resulting default cursors.
UPDATE
Here's a code snippet where a custom event is raised from child to be caught from parent that is delegated to execute code regarding the child.
This pattern doesn't break the parent-child/one-to-many logic, assuming parent has more decision power than child.
' in mySubform
' assuming theCurrentPrimaryKeyValueForTheValueIn_MyCombo is a Long ID
Public Event DeleteRequest(record_ID As Long)
Private Sub myCombo_Change()
If IsNull(Me.myCombo.Text) Or Me.myCombo.Text = vbNullString Then
RaiseEvent DeleteRequest(theCurrentPrimaryKeyValueForTheValueIn_MyCombo)
End If
End Sub
' in myForm
Private WithEvents frmMySubform As Form__mySubform
' you could have more instances of mySubform concerning different issues or subrecords and manage each one correctly ...
Private WithEvents frmMySubform2nd As Form__mySubform
Private Sub frmMySubform_DeleteRequest(record_ID As Long)
Dim strSql As String
strSql = "DELETE FROM mySubformRecordSource WHERE PrimaryKeyColumn= " & record_ID
CurrentDb.Execute strSql
With Me.frmMySubform.Form
.Requery
' ...
end With
End Sub
Private Sub frmMySubform2nd_DeleteRequest(record_ID As Long)
Dim strSql As String
strSql = "DELETE FROM my2ndSubformRecordSource WHERE PrimaryKeyColumn= " & record_ID
CurrentDb.Execute strSql
With Me.frmMySubform2nd.Form
.Requery
' ...
end With
End Sub
Related
I have two subforms each representing a table in my database. These subforms display all the records in those tables and they have a 1 to many relationship between each other.
When a record is deleted in the parent subform, it deletes all the children in the other one (I have cascade delete on).
I need to do some handling in the child form once the deletion is complete. I would like to use the AfterDelConfirm event but I cannot get it to fire.
I do not even get an option to confirm the delete (in the parent form). In fact I am doing it manually:
Private Sub Form_Delete(Cancel As Integer)
Dim PhaseID As Long
Dim Response As Long
Dim style As Long
style = vbYesNo + vbQuestion
Response = MsgBox("Are you sure you wish to delete this phase and all related scenarios and nodes?", style)
If Response = vbNo Then
Cancel = True
End If
End Sub
Trying to get this to run:
Private Sub Form_AfterDelConfirm(Status As Integer)
Debug.Print "trigger plz"
End Sub
Is there a way I can get this event to fire with the way I have things set up?
I am creating an asset management database with 2 tables (Assets and AssetMovements) and one form (Assets).
I need to keep records of every asset movement, so every time a new asset is added to the Assets table or the 'Location' value for an existing asset record is modified, the record should be saved to the AssetMovements table. The AssetMovements table is just there to record the transactions.
How can I achieve this? I would be grateful for any pointers I can get, or if anyone can suggest a better method of keeping the movement records.
Thank you.
Seeing you are using Access 2010, this looks like a good excuse for a Data Macro A data macros will run even when the data is updated from outside of MS Access.
I created an Assets table and an AssetMovements table, the AssetMovements table has a field ActionDate with a default value Now(), which sets the date the action occurred.
You will need two macros on the Assets table:
And that is all you need to do, as is shown below.
After adding or changing a record, the data is automatically recorded in the AssetMovements table:
You can the run a little sample VBScript ...
strCon = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=z:\docs\demo.accdb"
Set cn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
cn.Open strCon
sSQL="INSERT INTO Assets (Asset,Location) Values ('Printer',7)"
cn.Execute sSQL
sSQL="Update Assets Set Location=5 Where Asset='Printer'"
cn.Execute sSQL
To see that this also updates the AssetMovements table.
There is also a LogEvent data macro for more complete recording of changes.
The button has a click event that is fired when you click it. Use VBA to write some code that is executed when the button is clicked.
When you click the button, you would then save the record to Assets. Then copy that record to the AssetMovements table using a query. So, the code will look something like this:
Dim OldLocation As String
Private Sub CmdSave_Click()
DoCmd.RunCommand acCmdSaveRecord
End Sub
Private Sub Form_AfterUpdate()
Dim strSQL
If OldLocation <> Location.Value Then
strSQL = "INSERT INTO AssetMovements SELECT T1.* FROM Assets WHERE Assets.ID = "
strSQL = strSQL & Me.ID
CurrentDb.Execute strSQL
End If
End Sub
Private Sub Form_BeforeUpdate(Cancel As Integer)
OldLocation = Me.Location.OldValue
End Sub
Private Sub Form_Current()
OldLocation = Me.Location.Value
End Sub
This will then copy the current record of the form, using a unique ID (I guessed at AssetID), to the AssetMovement table.
I have a very complex process that involves downloading a number of files from different shares, concatenating those files into working tables, manipulating and calculating related information, and then exporting specific fields (and calculations) as reports into a number of Excel workbooks.
I have this process coded so that I can click one button and the entire process will execute end to end. I have a series of text boxes that function as 'indicators' (red - this part failed, green - this part succeeded). As you can imagine, the code for the entire process is HUGE (32 pages when copied into MSWord) and difficult to weed through when I have a problem.
I got it into my mind that I wanted to put the code into a table so that it was much more modular and easier to deal with. I have setup a combo box with the action that I want to take and a second combo box with the report/file/object that I want to work with (ie Delete - Table 2, Acquire - File 1, Export - Report 4). I have been successful at creating the SQL statement to do simple things like del * from tbl_test and execute that from the combo boxes without any issue.
What I need to know is if there is a way to put what is essentially a code snippet into the table (memo field) and then have that vba code execute when I select the matching combos.
IE the code for 'Acquire - File1' is completely VBA code; it maps a network drive, locates the file, downloads the file, and moves it to a directory.
IE the code for 'Scrub - tblMain_Part1' is a combination of vba and sql code; it checks for the existence of a file (vba), if it finds it, it deletes a portion of the main table (sql) and appends the contents of the file it finds (sql), then it updates the monitor to indicate that it is completed (vba). If the file is not found, it changes the monitor box to red and updates a command button caption (vba)
I am NOT a genius with vba, but I hold my own. The thought process I had was that if I can essentially get the code broken into managable chunks in the table, I could call the code smippets in order if I want to run the entire process, or I could just re-execute portions of the code as needed by selecting the action and report/file/object combination.
Any thoughts/ideas are appreciated.
I think it would be best to split the code into Subs. The table you loop through would have a Sub-Name field and a blnSuccess field. Your code would loop though the table running each sub and then updating blnSuccess based on any errors you receive. This would give you queryable result set when you try to see what happened.
Consider using macros. You shouldn't need a table. Also, consider moving your hard-coded SQL to queries.
I think that you shouldn't use a table, just create a module with different subs for each operation. On your button event, after the combo selections, I would do a case statement.
dim strOperation as string
strOperation = me!selectionOne
Select Case strOperation
Case "delete": deleteTable(me!selectionTwo)
Case "export": export(me!selectionTwo)
case "acquire": acquire(me!selectionTwo)
End Select
Of course, you'd have your acquire, delete, and export methods written in a module and have whatever parameters you need for each operation there.
This is just one idea of many that you could use to approach this.
I was going to edit the original answer but this seems to be off on a different tack....
I think it would be best to split the code into functions that return a string if there is an error. The table you loop through would have a strFunction,strError and strObject fields. Your code would loop though the table running each function based on the case statement while passing the strObject as a string and then updating strError based on any errors you receive. You could query the table after this process to see which records have errors in them.
If the button is called cmdRunAll here is the code for it.
Private Sub cmdRunAll_Click()
On Error GoTo ErrHandler
Dim rst As DAO.Recordset
Set rst = CurrentDb.OpenRecordset("tblCode", dbOpenDynaset, dbSeeChanges)
If Not rst.EOF Then
With rst
.MoveFirst
Do While Not .EOF
.Edit
Select Case !strFunction
Case "fExport"
!strError = fExport(!strObject)
End Select
.Update
.MoveNext
Loop
End With
End If
rst.Close
Set rst = Nothing
MsgBox "Processes complete"
Exit Sub
ErrHandler:
Debug.Print Err.Description & " cmdRunAll_Click " & Me.Name
Resume Next
End Sub
Here is a simple sample function
Public Function fExport(strTable As String) As String
On Error GoTo ErrHandler
Dim strError As String
strError = ""
DoCmd.TransferText acExportDelim, , strTable, "C:\users\IusedMyUserNameHere\" & strTable & ".txt"
fExport = strError
Exit Function
ErrHandler:
strError = Err.Description
Resume Next
End Function
Is there something wrong with the following code (in an ADP form) ?
Private Sub cmbSearchCode_AfterUpdate()
Me.Recordset.Find "usr_cde = '" & ctl & "'"`
ctl = null
end sub
It behaves erratically: the first times after opening the form, it works, then suddenly it does not seem to do anything anymore.
I replaced it by this code that seems to have no problems:
With Me.RecordsetClone
.Find "usr_cde = '" & ctl & "'"
Me.Bookmark = .Bookmark
End With
ctl = Null
Any explanation ?
The client is A2003, the server is SS 2000 (I know it's old, nothing I can do about it !)
The Form.Recordset Property is a fairly new addition to access, there are a couple of bits in the helptexts, it's how they combine that I believe is causing your issue.
If a form is based on a query, for example, referring to the
Recordset property is the equivalent of cloning a Recordset object by
using the same query. However, unlike using the RecordsetClone
property, changing which record is current in the recordset returned
by the form's Recordset property also sets the current record of the
form.
So while it doesn't look like it at first, it does actually clone the recordset and create a new copy of it. Just it keeps it synchronised.
So you have a new Recordset Object and here's the rub:
A new Recordset object is automatically added to the Recordsets
collection when you open the object, and is automatically removed when
you close it.
First time round, you clone the recordset find on usr_cde, and the form magically sets the current record to match.
Second time round, you clone the recordset find on usr_cde, but the magical record synchronisation is still stuck on the first copy of the recordset that has been persisted in the Recordsets collection.
So you just need to close the recordset, but to make sure you don't just create another copy and close that do the following:
'untested
Private Sub cmbSearchCode_AfterUpdate()
Dim rs as adodb.Recordset
Set rs = Me.Recordset
rs.Find "usr_cde = '" & ctl & "'"`
rs.Close
ctl = null
end sub
I haven't had a chance to test this, but you already have a working solution using .Bookmark. I hope this explains the unexpected behaviour.
I've got a subform (customersAnswersSub) inside of a main form (customersAnswers). Upon someone entering a new customerAnswersSub entry - I wanted it to check for duplicates first.
It has to check across 4 different fields to match first.
This is what I've got so far.
Private Sub Form_BeforeUpdate(Cancel As Integer)
Dim rsGlobals As ADODB.Recordset
Dim sql
Set rsGlobals = New ADODB.Recordset
sql = "Select * From CustomerAnswerD where subscriptionNo=" & _
Me.subscriptionNo & " AND journal=" & Me.Journal & _
" AND volume=" & Me.volume & " AND issue=" & Me.issue
rsGlobals.Open sql, CurrentProject.Connection, adOpenDynamic, adLockOptimistic, adCmdText
If Not rsGlobals.BOF And Not rsGlobals.EOF Then
MsgBox ("Already entered")
Cancel = True
Me.Undo
End If
End Sub
it doesn't do anything - just sits there. when I close the form it'll pop up a - id already exists box.
Any idea, i'm pretty unexperienced when it comes to Access VB.
thank you
it doesn't do anything - just sits there
Just checking, since you said you're inexperienced with Access ... the form update event is not triggered until the record save is attempted. That may not happen automatically as soon as the user enters data into all the fields. However, you can trigger the update by navigating to a different record in the subform, or by a method such as choosing Records->Save Record from Access' (2003) main menu.
I don't see anything wrong with your BeforeUpdate procedure. Still I would convert it use the DCount() function instead of opening an ADO recordset. (See Access' help topic for DCount)
Private Sub Form_BeforeUpdate(Cancel As Integer)
Dim strCriteria As String
strCriteria = "subscriptionNo=" & Me.subscriptionNo & " AND journal=" & Me.Journal & _
" AND volume=" & Me.volume & " AND issue=" & Me.issue
Debug.Print strCriteria
If Dcount("subscriptionNo", "CustomerAnswerD", strCriteria) > 0 Then
MsgBox ("Already entered")
Cancel = True
Me.Undo
End If
End Sub
That assumes your table's subscriptionNo, journal, volume, and issue fields are all numeric data types. If any of them are text type, you will need to enclose the values in quotes within strCriteria.
I added Debug.Print strCriteria so you can view the completed string expression in the Immediate Window. You can also troubleshoot that completed string by copying it and pasting it into SQL View of a new query as the WHERE clause.
Also, consider adding a unique index on subscriptionNo, journal, volume, and issue to your CustomerAnswerD table design. That way you can enforce uniqueness without relying solely on your form to do it. The index will also give you faster performance with the DCount function, or your original recordset SELECT statement.
If you keep your original recordset approach, close the recordset and set the object variable = Nothing before exiting the procedure.