How to explicitly save or close a recordset when a form deactivates? - forms

I apologize if this is too vague a question, but we're having the following problem:
We use a search form to find a record, then load it in a bound-control form where changes are made. Then we return to the search form and open another record. When we do that, the form's BeforeUpdate property fires a 3020 error, "Update without Add New or Edit" and stepping through the code it's referring to the FIRST opened record. This is strange because there is no explicit update call, but after much trial and error I think the error is thus:
Record #1 is opened via the form and changes are made. Record #2 is opened on that same form without closing the first recordset. Even though we now re-opened the form with the second record, Access still assumes we're editing record 1, i.e. that we're trying to edit 2 records concurrently. Same as though we had a datasheet form and we edited one row and then tried to edit a second row without saving the first. What I want to be able to do is have it automatically do an update on the first record when the form deactivates so loading a new record doesn't cause this conflict.
So the bottom line is this: **Is there a way, on say the form's Dirty or Deactivate event, that we can force the form's recordset to update and close ** before loading a second record?
I hope I made this clear enough, it's a complex problem, so any small guidance would help. Btw, you may ask, "Why are you running the same code to open the same form twice?" Good question! Hey it's an old and badly written app (the thing has GoSubs in it for Pete's sake) but I have no choice but to make the best of a bad situation.
EDIT: I was asked to post code, which is reasonable, but it's in several different places. So I have the data form, it has a "Search" button to go back to the search form for opening another record. The search button on the data entry form is:
Private Sub CommandSearch_Click()
On Error GoTo Err_CommandSearch_Click
DoCmd.OpenForm "Reference Form", acNormal 'This is the form that does the searching
Exit_CommandSearch_Click:
Exit Sub
Err_CommandSearch_Click:
MsgBox Err.Description
Resume Exit_CommandSearch_Click
End Sub
When a record is selected on that search form, then the new form is opened with this code. Now this is where it gets tricky. I'm not the original programmer, as I said I think it was written in Access 97 by someone after taking an hour to read "Access for Dummies" :). But it always looks like only one copy of the form is open, so maybe it's re-opening it?
Public Sub CommandLoadCase_Click()
Dim LoadUTUCaseNumber As String, lengthUTUCaseNumber As Integer
lengthUTUCaseNumber = InStr(Forms![Reference Form]![Reference Query SubForm]![UTU Case Number], "-") - 1
If (lengthUTUCaseNumber = 0) Then
LoadUTUCaseNumber = ""
Else
LoadUTUCaseNumber = Left$(Forms![Reference Form]![Reference Query SubForm]![UTU Case Number], lengthUTUCaseNumber)
End If
On Error Resume Next
Forms![Case Appeal_Add-On Form].UTUCaseKeyToFind = LoadUTUCaseNumber
DoCmd.OpenForm "Case Appeal_Add-On Form", acNormal, , , , , LoadUTUCaseNumber
'Case Appeal Add On Form referred to here is the data entry form I refer to above.
End Sub
Finally, the Error 3020 (Update without Add/Edit) is occurring after it executes this line on the data entry form. (I know the code is complicated which is why I didn't enter it at first).
Private Sub Form_BeforeUpdate(Cancel As Integer)
[UTU Claim Sequence Key] = [UTU Claim Alpha Sequence] & CStr([UTU Claim Numeric Sequence])
End Sub

Related

Setting focus to a subform gives the error 2449 (there is an invalid method in an expression) - VBA - Access 2013

-----------UPDATE---------
I figured out how to get the focus on my subform. If it is a subform then :
ParentFormName = frm.Parent.Name
SubFormName = frm.Name
Forms(ParentFormName).Form(SubFormName).SetFocus
This seems to work (although Andre451's post is more effective). But this resulted in the underlying problem of my subform causing the suspended state. My subform is relatively big (194 records) and when doing a MoveFirst/Last on the recordset of larger records I get the error 3021 (no current record). This probably has something to do with the entire recordset of the subform isn't loaded on the load event of the main form. Smaller recordsets can be looped faster so won't generate the error (this is just an assumption though).
The new question is: How can I avoid the 3021 error and let the subform records load entirely so I can move through them with either:
DoCmd.RunCommand acCmdRecordsGoToLast
OR
frm.RecordsetClone.MoveLast
----------------------------------------------------------------------------------
A while a go we migrated our Access data to a SQL server with still using the Access as the frontend. We did this by linking the SQL Server tables with Access and this all works (almost) fine. Recently however we encountered a problem with Access queries getting in suspended state on the SQL server (mainly happens on queries in combo boxes).
After some research we figured out that this could be solved by looping through the query records on the load of the form (so by doing a simple acCmdRecordsGoToLast and acCmdRecordsGoToFirst) and this seems to work.
So to counter this suspended state we added to each forms load an init that runs through the records of the recordsource and rowsource (the main forms recordsource, the comboxes rowsource etc.).
So on a forms load:
modFunctions.InitForm(Me)
This init is in a module and looks like this:
Dim InitFormResult As Boolean
InitFormResult = InitFormRecordSource(frm) ' Initialize the recordsource of the form
InitFormComboBoxes frm ' Initialize all the comboboxes on the form
InitFormCommandButton frm ' Initialize all the buttons on the form
The first init of the forms RecordSource (where the error happens) looks like this:
Public Function InitFormRecordSource(frm As Form) As Boolean
If frm.RecordSource <> "" Then ' if recordsource found
On Error GoTo ErrHandler
frm.SetFocus
DoCmd.RunCommand acCmdRecordsGoToLast
DoCmd.RunCommand acCmdRecordsGoToFirst
GoTo EndSuccess
ErrHandler:
MsgBox (Err.Number)
Debug.Print "ERROR during InitFormRecordSource in form : " & frm.Name
InitFormRecordSource = False
Exit Function
EndSuccess:
InitFormRecordSource = True
Else
InitFormRecordSource = True
End If
End Function
To run through the forms recordset with First and Last it needs to have the focus. This all works great for one form but it doesn't for Subforms in a main form. In subforms we want to counter the suspended state as well by also running through the subforms records, though when using the above code we keep getting the error 2449.
After some research I encountered this post: Module Function frm.setfocus runtime 2449 error which is more or less the same problem. So I figured out that a subform needs to be given it's focus in a different way, yet all my efforts to do this won't work.
What I tried so far:
If frm.RecordSource <> "" Then ' if recordsource found
On Error GoTo ErrHandler
Dim HasParent As Boolean
Dim sParentForm
HasParent = TypeName(frm.Parent.Name) = "String"
If HasParent = True Then 'The form is a subform
'sParentForm = frm.Parent
'sParentForm.frm.SetFocus
'frm.Parent.frm.SetFocus
'sParentForm!frm.SetFocus
Else
frm.SetFocus
End If
DoCmd.RunCommand acCmdRecordsGoToLast
DoCmd.RunCommand acCmdRecordsGoToFirst
GoTo EndSuccess
The tried fixes are all commented out in the above code. The HasParent does work, with this I can check if the form is a subform (it has a parent) or not and setting the focus differently.
Though I can't seem to get the focus on the subform and I don't quite know how to get it fixed!
I have had a similar problem, queries on Sql Server 2008 R2 going into a "ASYNC_NETWORK_IO" wait state, until the last record was loaded.
My solution: don't use the form itself to go to the last record, but its .RecordsetClone:
frm.RecordsetClone.MoveLast

MS Access - me.recordset not passing to sub

I have a form that loads a single record. The user does what they need to do on the form...in this case, they enter a date, and a button becomes available to click to advance the record to the next step in the process.
I have a public function that is logging the activity to tblActivity, and sets the record's new Status and Location. This Function takes 3 variables, and was working fine until today.
'I'm calling the function with this line from the button's Click event
LogActivity 15, Screen.ActiveForm, Me.Recordset
Public Function LogActivity(ByVal lSID As Long, Optional fForm As Form, Optional ByRef fRS As Recordset)
With fRS
Do Until .EOF
Debug.Print .Fields(5)
.MoveNext
Loop
End With
...
End Function
This should be printing the form's Status value, but fRS is passed in with no values. The form's recordset has values prior to being passed as the form has data. Some how it is getting lost in the pass. This was working fine, I have multiple buttons across 5 different forms that all call this same Function. Suddenly today, none of them can pass the recordset. I can think of nothing that was changed that would effect this. Most of the changes recently involved locking down fields and the appearance of buttons at the right time...nothing related to the recordset.
Naturally, this DB is supposed to go live on Monday.
Found the problem.
I had a backup from yesterday that was working fine.
One by one, I went through the changes I logged from yesterday and found that by changing some fields to .enabled = False and .locked = True is what was doing it. Apparently that was enough to clear all the values when passing.
Left the fields enabled, just locked them and it passes all values correctly.
Even though this was a failure on my part, I'll leave this up in case some one else makes the same mistake I made.
**** Update ****
I also found out that if I did a
fRS.movelast
fRS.movefirst
before anything else, it found the data. Not sure why it started happening, but these two things seem to have fixed it completely.

MS Access VBA: Closing Transaction without destroying Form's class module variables

I have an unbound form used to enter constituent data. I can't use subforms because data is spread across multiple tables and linked with foreign keys. Since I want to use the field validation rules to validate, I use a transaction to make sure new records are committed in an all-or-nothing fashion. I don't want names committed without addresses, etc.
The form is designed to iterate over a recordset, "tempImportRs", to give the user an opportunity to make sure the data looks OK and to manually correct or skip invalid entries. It works by populating the form's fields with values from tempImportRs for each entry. There's also a subform that searches for similar records and displays them to the user to decide if this is really a new person or if they already exist in the DB with somewhat different info.
I am running into a problem using this "tempImportRs" recordset, which persists for the lifetime of the form and is used by various methods in the form, alongside Access's transactions (which I don't understand very well, admittedly). If at any point I close the transaction like
WrkSp.Close
then my tempImportRs object will disappear and give me "Requires object" type errors. If I don't close the transaction, which is bad practice anyway, my form will work like I want for up to maybe a dozen records before giving me the error "Could not start transaction; too many transactions already nested."
How can I close the transaction cleanly, without destroying "tempImportRs"?
Here is a very simplified version of the VBA code for my form:
Public tempImportRs As RecordSet
Sub TryToAddRecords()
'Try to add the data from the now-populated form fields to my tables'
On Error GoTo Error_TryToAddRecords
Dim WrkSp As Workspace
Set WrkSp = DBEngine.Workspaces(0)
WrkSp.BeginTrans
'In my real code I use several recordsets to store the imported data'
'but I am simplifying here by using just one:'
Dim someOtherRs As Recordset
Set someOtherRs = CurrentDb.OpenRecordset("tblNamesAddressesEtc")
'...Do stuff using several other recordsets and '
'the form fields populated from tempImportRs'
someOtherRs.Update
WrkSp.CommitTrans
Exit_TryToAddRecords:
someOtherRs.Close
'I leave tempImportRs open for now.'
'WrkSp.Close <--This is what messes up tempImportRs'
Set someOtherRs = Nothing
Set WrkSp = Nothing
Exit Sub
Error_TryToAddRecords:
MsgBox Err.Description & vbNewLine & _
"Please fix the field and try again."
Resume Exit_TryToAddRecords
End Sub
Private Sub GoButton_click
Set tempImportRs = CurrentDb.OpenRecordset("tblTempImport")
If !tempImportRs.EOF
'<populate the form fields with the current '
'tempImportRs data here>'
TryToAddRecords
tempImportRs.MoveNext
Else
tempImportRs.close
Set tempImportRs = Nothing
MsgBox "All records imported"
End If
End Sub
This is not a problem specific to transactions and workspaces, but a more general matter of program structure. You should just make the workspace persist for the duration of the form, and commit to it multiple times. You don't need to make a new workspace for each transaction.
So, treat the workspace just like tempImportRs is treated in the code example. You can both set and close WrkSp in the GoButton_click sub. Don't set or close it in the TryToAddRecords function.
You will also have to make sure to roll back with WrkSp.Rollback if there's an error, add that line to Error_TryToAddRecords.
Public tempImportRs As RecordSet
Dim WrkSp As Workspace
'...
Private Sub GoButton_click
Set tempImportRs = CurrentDb.OpenRecordset("tblTempImport")
Set WrkSp = DBEngine.Workspaces(0) 'move this here
If !tempImportRs.EOF
'<populate the form fields with the current '
'tempImportRs data here>'
TryToAddRecords
tempImportRs.MoveNext
Else
tempImportRs.Close
Set tempImportRs = Nothing
'close the workspace here when you're done with tempImportRs
WrkSp.Close
Set WrkSp = Nothing
MsgBox "All records imported"
End If
End Sub
PS: Sorry folks for asking and answering this dumb question, I had a mental block and thought the issue was about managing workspaces and not a simpler matter of code design. Maybe this will still help someone someday.

Utilizing Access and VBA: copying information between forms

Searched around a bit and could not find an existing post with my exact question.
I am creating an Access (2003) database utilizing multiple forms and tables that need to communicate and share information with one another (i.e. - first/last name, etc.). The main form/table, "Personnel", will hold a majority of the information, yet the other forms/tables will only hold information pertinent to certain situations/circumstances, so not every entry in "Personnel" will have a concurrent entry in "Hired", for example. If a record with the same name already exists, I would like a MsgBox to tell me so and open that entry (in the form, for editing), if not a new entry would be created, auto-populated with pre-selected fields (i.e. - first/last name).
After an option is selected from a pull-down menu, the appropriate form is accessed/opened, "Hired", for example (This part works).
The copying information across forms does not work.
Dim rstPers, rstHired As DAO.recordset
Dim LastName As String
If status = "hired" Then
DoCmd.OpenForm "Hired Information"
Set rstPers Forms!Personnel.RecordsetClone
Set rstHired Forms![Hired Information].RecordsetClone
????
...
End If
I have tried to do this multiple ways but nothing seems to work. Nothing populates in the new Form or table. Am I looking at it the wrong way? Should I try a different approach?
Hope my explanation makes sense. Thanks for any help.
-Charles
You have a bad approach indeed.
Your forms are linked to a table. So you should avoid as much as possible to manipulate or retrieve a form's data directly using a recordsetclone, instead try to retrieve this data directly from the table.
So if you want to open your "hired information" form:
Dim RS As Recordset
Dim IDperson As String
IDperson = me.ID ' or whatever
Set RS = CurrentDb.OpenRecordset("SELECT ID FROM TableHired WHERE ID='" & IDperson & "'")
If RS.BOF Then
' No record exist - Open form in record creation
DoCmd.OpenForm "FormHired", acNormal, , , acFormAdd
Else
' record exists, open form and position it oon the record's ID
DoCmd.OpenForm "FormHired", acNormal, , "ID='" & RS!ID & "'", acFormEdit
End If
Of course it won't work like this as you didn't provide enough info.
Review this code and adapt it with your fields name (IDs), table name (TableHired) and FormName (FormHired) and following the situation on where and how you will trigger it. Also if your ID is not a string, you should remove the quotes in the SQL

MS Access implenting hyperlink like behavior for records to switch between forms

I'm currently working on a Database which requires the following functionality:
For example given a specific project, I have a series of structures which belong to that project, which are displayed in a datasheet view on the project form. I am attempting to allow the user to on double click to navigate to that specific structure which is displayed on another form. Currently I am using filters to implement this behavior, however, this results in the filter being left on, and when I manually turn off the filter, the form I switch to returns back to the first entry.
I am using the current code on the datasheet:
Private Sub struct_name_DblClick(Cancel As Integer)
LookupValue = Me.struct_ID
Form_frm_control.pg_structure.SetFocus
Form_frm_control.subform_structure.Form.Filter = "struct_ID = " & LookupValue
Form_frm_control.subform_structure.Form.FilterOn = True
End Sub
Any help would be greatly appreciated. Thanks in advance.
It all depends on what you need to do.
If you want to display all records and navigate to the selected record, then you can use bookmark navigation:
With Forms!MyOtherForm
.RecordsetClone.FindFirst "struct_ID = " & Me!struct_ID
If Not .RecordsetClone.NoMatch Then
If .Dirty Then
.Dirty = False
End If
.Bookmark = .RecordsetClone.Bookmark
End If
End With
This assumes that the other form is open with all the records loaded.
Another approach to this problem, which I find more useful for popup situations like this, is to just open the other form as a dialog and require it be closed before returning to the calling context. In that case, you'd do this:
DoCmd.OpenForm "MyOtherForm", , , "struct_ID = " & Me!struct_ID, , acDialog
You'd then have to close the other form to get back to the original context.
You might think that with large numbers of records this would be inefficient, but it's actually very fast, as this operation is highly optimized within Access (moreso than filtering an already open form, in fact).