Using Access VBA, In a form, I want to save every time a field changes. I'm guessing because I'm using some very complex queries, they don't always save immediately when they're dirty. DoCmd.RunCommand acCmdSaveRecord does the trick.
So my workaround is that every field has
Private Sub MyField_AfterUpdate()
If Me.Dirty Then Me.Dirty = False
DoCmd.RunCommand acCmdSaveRecord
End Sub
Is there a more elegant solution?
The only piece of elegance I can add is changing the first line to Me.Dirty = False.
This is based on simple logic where if Me.Dirty is false, then the assignment doesn't change anything, but if Me.Dirty is true then you want to change it to false.
You don't need to write out that code for every control. Just have one function:
Private Sub CommitChanges()
If Me.Dirty Then Me.Dirty = False
DoCmd.RunCommand acCmdSaveRecord
End Sub
Then, for each control, instead of setting the after update event to [Event Handler], set it to =CommitChanges()
Note, however, that I have a dislike for DoCmd.RunCommand. You could try:
Private Sub CommitChanges()
If Me.Dirty And Me.Recordset.Updateable Then Me.Recordset.Update
End If
Related
I have a form with some fields on it. Form was created from the table in question.
I've tried to use the button wizard to create a save button, I've tried to use the following:
Private Sub saveRecord_Click()
DoCmd.RunCommand acCmdSaveRecord
End Sub
Nothing works, any method I try to save the info doesn't work. I've made sure the form is bound, as far as I can tell it is, it was created off that table.
I have this line when the form loads to make sure the form is on a new record:
Me.Recordset.AddNew
From my limited understanding of the language, setting the form to a new record and then doing a save command should work?
RunCommand acCmdSaveRecord will only do a save if there is something to save. Simply adding a record to the form's recordset doesn't give you anything to save.
Make this change to saveRecord_Click() to confirm whether that is the explanation:
Private Sub saveRecord_Click()
If Me.Dirty = True Then
DoCmd.RunCommand acCmdSaveRecord
Else
MsgBox "nothing to save"
End If
End Sub
Some other suggestions ...
Since you want to go to the new record when the form loads, use GoToRecord instead of Me.Recordset.AddNew:
Private Sub Form_Load()
DoCmd.GoToRecord Record:=acNewRec
End Sub
The version of saveRecord_Click() I suggested earlier was only to explore the problem. But for routine use, it doesn't make sense to allow the user to click a saveRecord button and then tell them there is nothing to save. It is better to only make the button clickable when there is something to save.
So you can disable the command button in the form's On Current event and enable it in the On Dirty event (which means there is something to save).
Private Sub Form_Current()
Me.saveRecord.Enabled = False
End Sub
Private Sub Form_Dirty(Cancel As Integer)
Me.saveRecord.Enabled = True
End Sub
Also, as the last step of saveRecord_Click(), you may wish to SetFocus on another control and disable the command button.
Does anyone have an idea what could prevent either of these commands from actually saving a form? Is there a setting I'm missing that could make the form "read-only"? I'm toggling the control.enabled property on the form on load, but I can't seen to make it stick once I've closed the form.
DoCmd.Close acForm, Me.Name, acSaveYes
DoCmd.Save acForm, Me.Name
Edit:
Some psuedo code to clarify.
This function runs on form load to enable/disable certain controls and works 100% as expected.
Private Sub Form_Load()
If IsNull(TempVars.Item(Me.Name)) Then
TempVars.Add Me.Name,1
If somecondition = true then
Me.Controls.Item("somecontrol").enabled = True
Else
Me.Controls.Item("somecontrol").enabled = False
End If
End If
End Sub
This click event closes the form and opens another form. The problem is, the control.enabled settings I set on form load do not save.
Private Sub button_OnClick()
DoCmd.OpenForm "someotherform", acnormal
DoCmd.Close acForm, Me.Name, acSaveYes
End Sub
The next time I open the first form, the control properties will not be set, and I need to set the conrol.enabled property again.
I've not been able to find any documentation, but it appears that you can not save design changes that are made in Normal mode. I ended up declaring a global collection type variable. I stored concatenated strings in this collection variable of the convention "Form.Name & Control.Name". If the form had never been opened, I ran the long check on which controls should be enabled, and stored them in this collection global. If the form had been opened at least once, I looped through the controls on the form checking to see if they were in my global collection of "authorized" controls. It feels really hackish, but it works pretty well.
The code you have (Close, then Save) fails on my testing because the Me.Name is empty after executing the Close. Although I don't know what other code you may have, I was able to get the Form to close once I switched the order.
I suggest you place the code in the 'Open' event as follows:
Private Sub Form_Open(Cancel As Integer)
DoCmd.Save acForm, Me.Name
DoCmd.Close acForm, Me.Name, acSaveYes
End Sub
Do you want to change the enabled property on a condition? If you just need that control disabled IF the record ... then you'll want to move the enabling the form's Current event, which fires at every record navigation. Load and Open are too early and don't repeat at navigation.
Private Sub Form_Current()
If Me!X = True Then
Me.Controls("somecontrol").Enabled = False
Else
Me.Controls("somecontrol").Enabled = True
End If
End Sub
I have a continuous form bound to a DAO recordsource. My conditional formatting rule looks like this:
Expression Is: [DateTimeDeleted] IS NOT NULL (fill textbox backgrounds with bright pink)
And then I have a delete button that shows on each record. The code for it is:
Private Sub cmdDelete_Click()
If Me.NewRecord = False Then
If IsNull(Me!DateTimeDeleted) = True Then
Me!DateTimeDeleted = Now()
DoCmd.RunCommand acCmdSaveRecord
Else
Me!DateTimeDeleted = Null
DoCmd.RunCommand acCmdSaveRecord
End If
Else
Me.Undo
End If
End Sub
I'm well aware that there's no code here to actually delete the record and I'm OK with that. My complaint is that the conditional formatting appears to be applied only when the form loads. Clicking the button above doesn't make the boxes turn pink for the record being "deleted". Is there some misunderstanding on my part here about how conditional formatting works?
FYI, I'm using MS Access 2013 32bit on Windows 8.1. Maybe that's the source of my problems. It isn't really my choice as the MS Design and Development Action Pack Subscription doesn't allow using Office 2010 anymore.
When dealing with issues like this, I like to use the Refresh command. Your modified code would look like this.
Private Sub cmdDelete_Click()
If Me.NewRecord = False Then
If IsNull(Me!DateTimeDeleted) = True Then
Me!DateTimeDeleted = Now()
DoCmd.RunCommand acCmdSaveRecord
Else
Me!DateTimeDeleted = Null
DoCmd.RunCommand acCmdSaveRecord
End If
Me.Refresh 'This is the inserted code
Else
Me.Undo
End If
End Sub
You can also use Me.Recalc instead of Me.Refresh. Recalc does less overhead.
Making a form in Access 2010. I'm trying to make a button that moves to the next record (or the first if it's at the end), but because I want to account for other users' updates to the data set that have occurred in the meantime, I'm requerying the form before going to the next record.
I'm using the following code, adapted from this SO post:
Private Sub NextRec_Click()
Dim currentID As Long
currentID = Me.id.Value
'Here is where the requery brings the form back to the first record
Me.Requery
With Me.RecordsetClone
.FindFirst "id=" & currentID
If Not .NoMatch Then
If Me.Dirty Then
Me.Dirty = False
End If
Me.Bookmark = .Bookmark
End If
End With
If Me.CurrentRecord < Me.Recordset.RecordCount Then
DoCmd.GoToRecord , , acNext
Else
DoCmd.GoToRecord , , acFirst
End If
End Sub
It's working fine, except that .requery causes the form to briefly return to the first record before going back to the current record and then on to the next record. I don't want it to do this--is there any way I can just keep the current record showing in the form while .requery takes place, instead of showing the first record for the split second while .FindFirst is looking for the record at CurrentID?
Requery the recordset, not the form itself:
Me.Requery '<-- resets the current record to the first one in the recordset
Me.Recordset.Requery '<-- doesn't affect the current record
Sorry but I am strongly against those programmers who reinvent a NEXT button that is already provided.
If you need to do things when the user moves to the next record, use the OnCurrent event. This way you will also trap Page Down keys that users are entitled to use.
For me that code is unnecessary and a waste of resource. The recordset IS automatically updated every n seconds by Access (as specified in the refresh interval).
Is is true that deleted records stay there and appear as #deleted# and new records, might not appear unless you requery. But this is rarely a problem, and requery is really heavy and unfriendly.
Edit, after seeing #sigil's own reply:
Just an idea to reduce the number of recordset refreshes, which seem important to you: you might compare Me.Recordset.RecordCount with dCount("*", Me.RecordSource) and only requery when those differ.
So, as Remou wrote in the comments under iDevlop's answer, ODBC refresh doesn't show new records. I suspect that is the issue here because the form is getting its data from a linked table.
I'll still need to have my own Prev/Next buttons because I want to be able to loop back to the first record when I pass the last record. But I've modified the code; instead of requerying every time, I check to see if the record is the last one, and only requery when I click Next on the last record, or Prev on the first. This solves the problem adequately.
Modified code for NextRec_Click as follows:
Private Sub NextRec_Click()
Dim currentID As Long
currentID = Me.id.Value
If Me.CurrentRecord = Me.RecordsetClone.RecordCount Then
Me.Requery
With Me.RecordsetClone
.FindFirst "id=" & currentID
If Not .NoMatch Then
If Me.Dirty Then
Me.Dirty = False
End If
Me.Bookmark = .Bookmark
End If
End With
If Me.CurrentRecord < Me.Recordset.RecordCount Then
DoCmd.GoToRecord , , acNext
Else
DoCmd.GoToRecord , , acFirst
End If
Else
DoCmd.GoToRecord , , acNext
End If
End Sub
I'm doing some development in access and I'm running into an issue where I need to make sure that a form is updated in a buttons OnClick handler.
I was thinking that it might work something like this:
if me.dirty then
me.update 'This is a nonexistent form method'
end if
<rest of handler>
If such a thing exists, will I have to call the OnUpdate Event handler manually?
How about:
if me.dirty then
me.dirty=false
end if
Code as per Allen Browne, MVP:
http://allenbrowne.com/bug-01.html
I would avoid the .RunCommand version because there are cases where Me.Dirty = False will work and access to the menu commands is prevented.
On the other hand, Me.Dirty has always struck me as a property that ought to be read-only, but it's not.
I always use this code in my Save_Click handlers
If Me.Dirty Then
DoCmd.RunCommand acCmdSaveRecord
End If