Access 2010 here.
I have a nagging concern with two of my three subforms. The subforms are each bound to their own table with unique information; the main form is also bound to its own table. All three subform tables are in a one-to-one relationship with the main form table. My first subform acts normally in that when there is a new record, all of its bound entry fields are displayed blank. The code in Form_Current():
Private Sub Form_Current()
'Determine what should be displayed'
'Do stuff to prepare the current record for display'
If Me.NewRecord = True Then
Me.Stuff = Null
End If
End Sub
is present for all three subforms and the main form to display older records and to ready the form for data entry by resetting check boxes etc; there is no code to "reset" the entry fields themselves. Unfortunately, the other two subforms retain data from the last entered record and display it as if the user wants to re-enter the same data. The thing is, this data means nothing unless the user explicity enters the data entry field and then leaves it by any means. Simply moving on to the next record without entering the data entry fields doesn't save the phantom pre-entered data (which is expected as the record in the subform was never made as the user never entered data in the subform).
What I am looking for is either a form-specific option to open a clean record by default, or VBA code that can ensure empty data fields on new record. I have tried actively re-querying the subforms when a new record is opened in the main form without success. I have also tried pre-setting values to "" upon entering a new record, which did work to an extent, but seeing as one subform works properly, I am after a solution, not a workaround.
Go into Design View on the subform, and in the subform's properties find the setting called "Data Entry".
Changing that to "Yes" will change the subform's behavior.
From then on all the subform do is be clear and ready accept data for new records. After a record is entered, the subform is cleared and will be ready for another brand new record.
This option is available for any form.
I would update the other two subforms with this setting. Easy, and no VBA necessary!
Continuing Thoughts:
What I found with the "Data Entry" setting set static to "Yes" on a subform is that old-record browsing functionality no longer worked. New records were clean of old data, but now old records also didn't show what had been entered; because the "Data Entry" setting prepares them for new data. What had to be done was actively set the "Data Entry" setting on each subform depending on if the record was "New" or not:
Private Sub Form_Current()
'Determine what should be displayed'
'Do stuff to prepare the current record for display'
If Me.NewRecord = True Then
Me.Stuff = Null
Me!SubFormName0.Form.DataEntry = True
Me!SubFormName1.Form.DataEntry = True
Me!SubFormName2.Form.DataEntry = True
Else
Me.Stuff = DatabaseNumbers
Me!SubFormName0.Form.DataEntry = False
Me!SubFormName1.Form.DataEntry = False
Me!SubFormName2.Form.DataEntry = False
End If
End Sub
Thanks again for the help!
Related
I have an ms-access form in which the user enters assembly information (component serial numbers, measurements, print revision...etc) I have the form set as:
Data Entry = Yes
Allow Additions = Yes
Allow Deletions = No
Allow Edits = No
Cycle = Current Record
However, I still can not achieve what I'm after which is, when a user enters the data and submits it (moves from the first record entered to the second), preventing the user from cycling back to the first record that was submitted unless the form is closed and re-opened.
Is there some way to do this that I'm missing?
You can use the After Insert event of the form and force a requery of the forms data there:
Private Sub Form_AfterInsert()
Me.Requery
End Sub
That would assure that you never have the just added record in the record source and so can't move back to it.
Regarding a MS Access database.
Master Form (Clients) contains fields Case and OriginalCase
Master Form button #1 (Deactivate) copies value from Case to OriginalCase and clears Case value
Master Form button #2 (Command297) copies value from OriginalCase back to Case (like restore backup)
Master Form contains Subform (ClientDBHistory) showing multiple records per Client in Master Form (For each client in Master, it shows all related records from Subform)
Subform has linked OriginalCase fields
I have tried using similar coding for Button #1 and it replaces only 1 value of the subform's Casenum and not all related ones. Button #2 didn't restore the value of subform's Casenum.
This was as follows:
Forms!Clients!ClientDBHistory.Form!OriginalCase = Forms!Clients!ClientDBHistory.Form!Casenum
Forms!Clients!ClientDBHistory.Form!Casenum = Null
Below is my code
Private Sub Command297_Click()
If IsNull(Me.[OriginalCase]) Then
MsgBox ("Cannot use this button if the Original Case field is empty. It will result in errors.")
ElseIf Not IsNull(Me.[OriginalCase]) Then
Me.[Casenum] = Me.[OriginalCase]
End If
End Sub
Private Sub Deactivate_Click()
If IsNull(Me.[Casenum]) Then
MsgBox ("Cannot use this button if the Casenum field is empty. It will result in errors.")
ElseIf Not IsNull(Me.[Casenum]) Then
Me.[OriginalCase] = Me.[Casenum]
Me.[Casenum] = Null
End If
End Sub
I want Buttons #1 and #2 to perform the same actions to all related records in the Subform. (Not new buttons).
I.e.
Master has Casenum 123456
Subform shows all related entries from 123456 on different dates
Button #1 copies Master's Casenum to OriginalCase and clears out Casenum. The same is then supposed to happen for all related entries 123456 in Subform, from Subform's Casenum to OriginalCase and clear out Subform's Casenum.
Button #2 is to restore OriginalCase of both Master and Subform to the records (without clearing OriginalCase)
You have to look at this problem from the point of view of tables and not forms. You do not provide anything about your table structure so I can only provide generic guidance. What you need for your buttons is something like this:
DoCmd.RunSQL "UPDATE [MyTable] SET [Values I want change] WHERE [limit changes to the records displayed in the form]"
Rem Repeat if you need to UPDATE more than one table and your table relationships are not set to update
Me.Requery
Edit: This is a (IMO) simplified version of the code the OP used to solve the problem:
DoCmd.SetWarnings False
DoCmd.RunSQL "UPDATE tblClient SET Casenum=OriginalCase WHERE OriginalCase=' & Me.OriginalCase & "'"
DoCmd.RunSQL "UPDATE tblPayments SET Casenum=OriginalCase WHERE OriginalCase=' & Me.OriginalCase & "'"
Me.Requery
DoCmd.SetWarnings True
The simplification is based on running in the main form's context and that OriginalCase is a field that links form and sub-form and is therefore equal in both forms. I assume OriginalCase is a text field, otherwise the code can be simplified even further.
As an added note, it is possible to permanently disable the warnings for running action queries instead of having to disable and enable them all the time.
I am completely new to VBA. I have been told to add records to a table by using a form and a Save button and given some very basic instructions. While I have achieved this with inline query, I have been told to follow some strict methods like usage of QueryDef/QueryDefs and .Parameters.
So far I am trying a very basic project, just to grasp the concepts, but I am unable to add any record to an empty table. In case the table is not empty(I manually enter a record), whenever I click the Save button for saving newer records, the number of records added are somehow doubling with each instance. For example, when I Save for the 1st time, 1 record is added, 2nd time 2 records of the same type is added, 3rd time 4 are added and so on.
The table(tbl_test) has 2 fields --> ID(primary key), Source(Long Text) and Reg No (Number).
The query(qry_test) is made with the Append feature and I have been told to add expressions which makes the code like this -
INSERT INTO tbl_test ( Source, [Reg No] )
SELECT [strSource] AS Expr1, [lngRegNo] AS Expr2
FROM tbl_test;
The form has 2 fields for Source(txt_Source) and Reg No(txt_RegNo) which have blank Record Sources (Unbound). The Save button has the following Event Procedure -
Private Sub btn_save_Click()
Dim qdf As QueryDef
Set qdf = CurrentDb.QueryDefs("qry_test")
qdf.Parameters("strSource") = Me.txt_Source
qdf.Parameters("lngRegNo") = Me.txt_RegNo
qdf.Execute
End Sub
I have zero knowledge about VBA and would gladly accept ANY help. It would be great if I get any sort of source code that will explain to all the details about saving records from forms and editing them using these querydef, parameter and recordset stuff.
Welcome to StackOverflow!
If you are using a form to collect data for records that are stored in a table, running a saved append query by QueryDefs to do this is, in my opinion, not the best method.
Whilst an append query does add new records to an existing table, I would tend to use an append query where I've got multiple established records that I want to add to an existing table.
If I'm setting up a data entry form that is designed to collect new data for new records 1-at-a-time, an append query doesn't really suit this purpose.
Instead, there are a number of features built in to the design of MS-Access forms to help collect data and save it to a table. This is because forms are very much intended to be set up so that users can interact with records from a table in a controlled, user-friendly way rather than interact directly with the table object itself.
The first and most important feature of a form in this context is probably the form's record source property. When you create a new form, go in to design view for the form and open the form's property sheet (F4 key). In the "Data" tab of the form's property sheet you'll find the record source property:
The record source essentially connects your form with a set of records, whether those be records in a table object, a query object or an sql query string.
In your case it would be better, in my opinion, to bind your tbl_Test table to your form by referring to it in your form's record source:
It will seem like nothing has happened to your form, but if you now open the "Add Existing Fields" panel (alt + F8), you'll notice that the fields associated with your tbl_Test table are available to you:
Drag them to the detail section of your form...
Then put your form in to Form View:
Essentially what you and your users are seeing is the first blank record in your tbl_Test, but displayed on a form instead.
Entering data in these fields on the form...
...will put that data in to the table we specified in the form's record source...
So hopefully you can see that setting the form's record source property to that of your table, is much cleaner than trying to get an append query to collect data from your form and deliver it your table.
At this point you're probably asking a few questions:
When I have filled in the fields for a record on my form, how do I save that record?
More can be said about this, but for brevity, I'd recommend using a command button for running some vba to save the record; similar to what you've done, but utilising the form's Dirty property instead using an append query:
Here's the click event VBA for my save button example:
Private Sub cmdSave_Click()
If Me.Dirty Then
Me.Dirty = False
End If
End Sub
Me.Dirty is a Boolean (True or False) setting for the form; essentially it is automatically set to True when a user changes something on the form. To save those changes, the Me.Dirty setting will have to be set to False.
Me.Dirty is a bit like the swing gate on a sheep pen. When a shepherd puts a sheep (data) in the pen (form) they will open the gate to the pen. The open gate is like the form's Me.Dirty being set to True. To lock the sheep (data) in, the gate needs to be closed, or in the case of forms, the Me.Dirty property needs to be set to False. The VBA above essentially checks to see if the gate was opened and if it was to close it.
How do I move to a new record in the form once I have saved the current one?
Again, I'd give the user a command button to do this and run some VBA on its click event:
Here's the VBA for moving to a new record:
Private Sub cmdNew_Click()
DoCmd.GoToRecord , , acNewRec
End Sub
Summary
There is a lot more to consider than what I've outlined here, such as:
validating data before it is saved
checking other form events (such as close) to ensure data entry is not lost
navigating to an existing record
But hopefully what I've given you here is at least pointing you in a better direction. Best of luck!
Access is optimized for bound forms. Unbound forms are for special cases only.
Should you prefer unbound forms (could be for many reasons), Access isn't worth the trouble, and you should turn to Visual Studio and WinForms.
In either case, browse for a beginner's tutorial.
If you want to use an unbound for or unbound controls you can create the SQL string in the procedure:
Dim mysql as String
mysql = "INSERT INTO tbl_test ( [source], [reg_No]) VALUES (Me.txt_Source, Me.txt_RegNo)
DoCmd.RunSQL mysql
When copy / pasting cells from Excel into an Access Subform, when no parent record has been created, will result in an error - as expected.
The problem, is that after this error occurs access gets locked in a state where all subsequent data that is entered results in an Operation not supported in transactions error. When you open the table, you can see that the newly added data is not yet in the table - so it appears as if Access is in fact engaged in some sort of transaction.
I have tried hitting 'Save' .. 'Refresh' .. and even adding an AfterInsert event to force a commitTrans() but have had no luck - and Access claims there is no transaction underway
There is no error if the records are entered manually. The problem only seems to occur when pasting records. I'm guessing that Access is creating a transaction to handle the multiple record operations and is not properly recovering from the error.
I can remove the 'Required' flag and it will work - but then you have orphan records. I was thinking that perhaps an After Insert Data Macro could be used to add a new Batch with a default batch Name, and auto-fill the new BatchID into the Items table. I am not certain how that would be done however.
I did try to remove the 'Required' flag and trap for the error using a Before Change Data Macro - but while it cut down on the errors - it still produced the same Operation not supported in transactions error.
I have looked up the error on the Microsoft Knowledgebase, but did not find anything specific to my circumstances. I searched stackoverflow for the error message and found nothing.
I created a new database and was able to replicate the issue.
Steps to Replicate
Set up the Database
Create a new ACCDB database in Access 2010
Create a Table called 'Batches', with the following fields:
BatchID (AutoNumber) (Primary Key)
BatchName (Text)
Create a Table called 'Items', with the following fields:
RecordID (AutoNumber) (Primary Key)
BatchID (Long Integer)
Set Required attribute to True
Data - Text
Create a Relationship, linking Batches.BatchID to Items.BatchID
Include all Records from Batches, and matching records from Items
Enforce Referential Integrity
Cascade Updates / Deletes
Create a Form called 'Form'
Set the Recordsource to Batches
Add in the BatchID and Batch name Textboxes
Add in a SubForm/SubReport Control
Set Source Object to "Table.Items"
Set Link Master Fields to "BatchID"
Set Link Child Fields to "BatchID"
Set "Filter On Empty Master" = Yes
Create sample data (Using the Form)
Create a New Record in Batches.
Set BatchName = "Test"
Create a New Record in Items.
Reference the Batch Record.
Set Data = "Test"
As you can see, by hand this works fine.
Copy and Paste Data From Excel
In Excel
From A1-A10 enter one letter per cell running down: A,B,C,D,E,F,G,H,I,J
Highlight the cells A1-A10
Copy (Control+C)
In Access, using the Form:
Add a New Batch Record. It should say "(New)" in BatchID TextBox
Do NOT enter a Batch Name
In the Sub-Form, click the record selector (*) for the new record to select the entire row. Type Control+V to paste.
Click OK for "You must enter a value in the 'Data.BatchID' field. error
This error may repeat. Keep Clicking OK
If it asks "Do you want to suppress further error messages..." answer Yes
Click OK for the "Records that Microsoft Access was unable to paste have been inserted into a new table called 'Paste Errors.' notification
Fill in the Batch Name Textbox with "TestName"
Try to gracefully recover. Hit Escape. Change the Record.
At this point - you should see the BatchID entered, the Batch Name, and the New Data. Everything appears to be working as expected. If you try to refresh or navigate to another batch record - you will get the error Operation not supported in transactions. Access will continue to display this error message until we close and reopen the form. The data you pasted will not have made it into the database.
Normally someone with a bit of tech savvy will realize something isn't going well and close out of the database and re-open ... but unfortunately I have users that play "whack-a-mole" with any popup boxes and then try to continue on - so I'd like to make things as bulletproof as possible.
Desired Solution
I'd like a workaround to the problem, that won't eventually lead to other quirks with access, duplicate values, etc.
In my own experience, using VBA to 'fix-up' keys isn't reliable. Data macros seem to be a lot more reliable - but they can be tricky to set up - they aren't very mainstream yet (I'd say there should be a ms-access-data-macros tag on stackoverflow but there isn't yet)
Suggested workaround:
In the [Batches] table, set the Required property of the [BatchName] field to Yes.
Change the Tab Stop property of the [BatchID] text box to "No". That will give the [BatchName] text box the default focus when the form opens.
Have the On Current event of the form give the [BatchName] text box the focus for new records (IsNull(Me.BatchID) = True).
Make the form dirty when the [BatchName] text box loses focus.
Option Compare Database
Option Explicit
Dim DeletePending As Boolean
Private Sub Form_Load()
DeletePending = False
Me.ItemSubForm.Enabled = False ' Disable Subform by default
End Sub
Private Sub Form_Current()
If IsNull(Me.BatchID) Then
Me.BatchName.SetFocus
' Disable Subform if BatchID is NULL
Me.ItemSubForm.Enabled = False
Else
' Enable SubForm Otherwise
Me.ItemSubForm.Enabled = False
End If
End Sub
Private Sub Form_BeforeDelConfirm(Cancel As Integer, Response As Integer)
DeletePending = True
End Sub
Private Sub Form_AfterDelConfirm(Status As Integer)
DeletePending = False
End Sub
Private Sub BatchName_LostFocus()
If IsNull(Me.BatchID) And Not DeletePending Then
Me.Dirty = True
End If
End Sub
When the user clicks on the subform (and off the [BatchName] text box) they make the form dirty and BatchID gets a value. Then they can paste and they don't get the "You must enter a value..." message for [BatchID]. If they haven't entered a [BatchName] value they now get prompted for it (because it is now Required), but at least they can gracefully recover from that.
Updated 2013-11-09 18:40 UTC:
In playing around with this a bit more I discovered two quirks:
If you deleted the last parent record Access would do it and then immediately create another new one in its place.
If you navigated into the "new" parent record and then immediately backed out (e.g., via the PageDown and PageUp keys) Access would create a new record and then leave you on that record with the form dirty. Hitting Esc and then moving off the "new" record worked, and it didn't cause any errors, but it certainly could be confusing to the user.
I have updated the answer to try and address these issues. I added a bit more VBA code to track "DeletePending" status. I also added the requirement that [BatchName] be set to Required = Yes in the [Batches] table. The latter makes it slightly more intuitive (albeit slightly more annoying) if the user move into the "new" record and then immediately moves back out again.
I struggled with this for a long until I finally understood what is happening to cause this error. It would take an article of considerable length to go into the details rather than a blog response to explain it all. If anyone is interested they can leave a contact method and I will contact them and explain it in detail.
However, for those who want to figure this out, I can save you a lot of time by giving you the idea behind the issue:
When you are performing a data transaction in a bound sub-form, you cannot reference other objects. The internal code of the transaction process does not allow this. For example, if you have code in the Form_BeforeUpdate event that attempts to open another form while in the midst of a sub-form data transaction, you will get error 3246. You can have code that creates variables, set values, references the sub-form controls, etc. but you cannot go out to another object.
This makes sense if you think about it. Who knows what the user or code might do once it gets itself into another form or object. It may never come back or get involved in other errors that leave the transaction hanging. That's why the transaction must complete first.
This means that you must trap and dismiss error 2115 that is caused when a user tries to click on an area outside the sub-form while in the midst of a transaction. This usually occurs during large copy and paste where the user becomes inpatient or starts to proceed to another record while still in the midst of the sub-form transaction.
I know that this is an old storry, I was also strugling with this.
My solution was to re-desing the process so the user closes the form receiving the data in order to save the records inserted. Is nor elegant but efficient and saved me from guessing each and every event which could occure.
to avoid the undesired internal transaction is enough to
code the sub-form Error event with:
Private Sub Form_Error(DataErr As Integer, Response As Integer)
Response = acDataErrContinue
End Sub
A general approach for intercepting sub-forms events is
' parent form code
' ---------------------------------------------------
Private WithEvents subFormObj As [Form_Sottomaschera items]
Private Sub Form_Open(Cancel As Integer)
Set subFormObj = Me.Sottomaschera_items.Form
End Subcode here
' asynchronous way
Private Sub subFormObj_isInserting()
Me.batchName = vbNullString ' this resolves the new ID issue
End Sub
' synchronous way
Public Sub subFormInserting()
Me.batchName = vbNullString
End Sub
' sub-form code
' ---------------------------------------------------
Public Event isInserting() ' for asynchronous way
Dim parentFormObj As Form_Maschera1 ' for synchronous way
Private Sub Form_BeforeInsert(Cancel As Integer)
parentFormObj.subFormInserting
RaiseEvent isInserting
' Cancel = True
End Sub
Private Sub Form_Error(DataErr As Integer, Response As Integer)
Response = acDataErrContinue
End Sub
Private Sub Form_Open(Cancel As Integer)
Set parentFormObj = Me.Parent
End Sub
where [Maschera1] is the main form and [Sottomaschera items] the sub-form.
Unfortunately it doesn't resolve the paste issue.
To definitely resolve issue you need to save parent record + a SetFocus trick, either synchronous or asynchronous:
Private Sub subFormObj_isInserting()
Me.batchName = vbNullString
DoCmd.RunCommand acCmdSaveRecord
' DoEvents
Me.batchName.SetFocus
End Sub
Public Sub subFormInserting()
Me.batchName = vbNullString
DoCmd.RunCommand acCmdSaveRecord
' DoEvents
Me.batchName.SetFocus
End Sub
I don't understand what exactly do you want to achive, so this answer may be inadequate.
You can
set your subform property .Visible = False when Me.NewRecord = True to prevent entering data into it
force saving record of the main form to the table after adding Batch name by setting .Dirty = False in After Update event triggered by pressing Enter. It allows also to avoid not saving records of a subsform to a table after adding few records to a main form in some databases, at least with dynamical subform .Recordsource.
set your subform property .Visible = True
The code below works for Form View and perhaps should be extended (develop) somehow for other Views.
Set .Tag of the subform Child and all other controls you want to hide / show to "a".
Private Sub Form_Current()
If Me.CurrentView = 1 Then
If Me.NewRecord = True Then
ShowControls False
ElseIf Me![Items subform Child].Visible = False Then
ShowControls True
End If
End If
End Sub
Private Sub BatchName_Text_AfterUpdate()
Dim NewRecordx As Boolean
If Me![Items subform Child].Visible = False And Me.CurrentView = 1 Then ShowControls True
NewRecordx = Me.NewRecord
If Me.Dirty Then Me.Dirty = False 'save the new record to the table
If Me.CurrentView = 1 And NewRecordx Then Me![Items subform Child].Form.Requery
End Sub
Private Sub ShowControls(bVisible As Boolean)
Dim ctl As Control
For Each ctl In Me.Controls
If ctl.Tag = "a" Then ctl.Visible = bVisible
Next ctl
End Sub
I reported this as a bug through Microsoft Premier Support a number of years ago with a concise standalone repro case. It appears this was finally resolved in October 2021 with KB5001978.
I use a subform to show the result of a query, but at the end of record there is a *(New) for adding new records. I don't want the user to be able to add new records through this subform. How can I get rid of this?
With the subform in Design View, open its property sheet. Then select the Data tab on the property sheet, find the property named "Allow Additions" and set it to No.
The "Allow Additions" property will not display if you open the design view of the parent form and click on the subform. The subform must be opened in Design View independent of the parent form.
Had exactly the same problem.
My DB is to keep track of basketball box scores. Every new main form created a new blank subform to enter the quarter scores into. The problem was as I Entered through the fields once I hit enter on the last quarter score value a new record was created for the table that Quarter Scores was based on.
I could not use Allow Additions = no. If I did not allow additions there was no quarter score input created when a new main form (for a new game) was created.
I used the code below for a key down event of the Enter key to set the focus on another subform before a new quarter score record was created.The commented lines were to help trouble shoot when creating the code. Key code 13 is the Enter key.
Hope this helps someone, took a while to get this right.
Den
Private Sub HOT2_KeyDown(KeyCode As Integer, Shift As Integer)
If KeyCode <> 13 Then Exit Sub
'MsgBox "Enter Pressed"
KeyCode = 0
'MsgBox "KeyCode=0"
Forms!FRM_BoxScores.Scrimmage.SetFocus
Forms!FRM_BoxScores!subform_qryReturnVisitingPlayers_BosScores.Form!subform_tblPlayerPoints_BoxScores.Form!PlayerPoints.SetFocus
End Sub
Change Record-set Type to Screenshot in Data Form Properties > Data Tab. Please note user cannot change any Data in form you once you change this
in your subform design grid, open the properties.
Where is says recordset type, set it to snapshot. That will remove that line.
The recordset will NOT be updateable at that time. So if you want to edit records, you have to change it back