I'm trying to lock a separate combobox on a form when another one is filled. Below is the code, however it's not doing anything and I'm unsure why.
Private Sub fieldApplicationType_AfterUpdate()
If Me.fieldApplicationType.Value Like "Application" Then
Me.fieldPrimaryBusinessFunction.Enabled = True
Else:
Me.fieldPrimaryBusinessFunction.Enabled = False
End Sub
The value being displayed in the form is the "name" field on the table. Basically if the name contains "Application" I want to disable the other combobox so it can't be used.
Bonus Points on how do I get this to combobox to stay disabled if the form already has the fieldApplicationType selected as NOT Application?
The Update does not seem to be called and maybe it's due to the following?
There is a Form_Current() which calls a module. I didn't design this code but guessing that Module is taking precident over the form Events?
Private Sub Form_Current()
' Temporarily turn off screen refreshes while we update widget states.
' For some reason, if this isn't done MS Access doesn't consistently repaint all controls
application.Echo False
Call UpdateManagementFormReadonlyState(Me, _
Me.lblReadOnlyStatusIndicator, _
hasEditPermissionsForManagementFields(Me.recordOwnerId.Value))
' Re-enable normal GUI updates
application.Echo True
End Sub
Related
I have a Project to be done in MS Access 2016, and stubled across an Issue, that should be easy to resolve, however, I have no clue on how to do it.
The Database I am developing is based on a huge, unfiltered datasheet exported by another database. I have a main form headview in which I placed two subforms listview an detailview. The listviewis sorted by a combobox.
Now to what "should" happen: If you click on an entry of said listview, the detailview shows additional information of the clicked entry.
Both subforms are based on the same datasheet. So I went ahead and tried to match them via primary key entries. However, that didnt work. The detailview subform is still empty. I also tried to write a vba macro for the listview with listview.click() that didnt work either.
Is there a way to connect those two subforms within a main form? If so, how do I do that?
I am greatfull for any response,
Have a nice day
-Ninsa
Ok, finally I got the reason for error 2455, it's a timing problem.
When the procedure Form_Current of the listview form is called the first time, the detail subform is not yet bound to the detail subform control, which causes the error.
Possible solutions
Option1: Ignore error 2455
Either add On Error Resume Next in the top of the Form_Current procedure or rewrite it to handle that specific error 2455:
Private Sub Form_Current()
On Error GoTo Catch
With Me.Parent.DetailSubformControl.Form
.Filter = "[ID] = '" & Me.ID.Value & "'"
.FilterOn = True
End With
Finally:
Exit Sub
Catch:
If Err.Number = 2455 Then Resume Finally
MsgBox Err.Number & "(" & Err.Description & ") occured.", vbExclamation, "Attention"
Resume Finally
End Sub
Option2: Control the source objects of the subform controls
Clear the Source Object property of the subform controls in the head form and set them explicit when loading the head form.
That prevents the unlucky timing at all.
So in the head forms load event procedure add this:
Private Sub Form_Load()
Me.DetailSubformControl.SourceObject = "Table1Detail"
Me.DatasheetSubformControl.SourceObject = "Table1Datasheet"
End Sub
Option3: Use Link Master/Child Fields
You could use the properties Link Master Fields and Link Child Fields of the detail subform control.
Therefor you have to create a textbox control named ID on the head form and for cosmetic aspects hide it by setting its property Visible to False.
This new control will be bound to the detail subform control:
Set the property Link Master Fields and Link Child Fields of the detail subform control on the head form both to ID.
The Form_Current procedure of the listview form then only contains this:
Private Sub Form_Current()
' Set the value of the hidden control 'ID' on the head form,
' which is bound to the detail subform control, to the selected ID.
Me.Parent.ID.Value = Me.ID.Value
End Sub
You should handle filtering of the detailview on the Listview_Current event. That event fires as soon as Listview changes records.
You can either set up an event handler for the Listview_Current event on the listview's form module, or use WithEvents in the parent form to listen to that specific event.
If you choose the latter, note that it's required that Listview has a form module, else the events won't fire.
Given that your ID field of the datasource is ID and the detail subform control is named DetailSubformControl, this example works.
Place this code to the Form_Current event of the listview subform (which is fired on every record you move to):
Private Sub Form_Current()
' Set a reference to the detail subform control
With Me.Parent.DetailSubformControl
' Set the filter of its contained form to the current ID of the listview.
' The "'" are only necessary if it is a text and not a numeric field.
.Form.Filter = "[ID] = '" & Me.ID.Value & "'"
.Form.FilterOn = True
End With
End Sub
What I'm trying to do is, whenever a user opens a form (and the sub-form that opens by default), I want to search through all the columns (controls?) on the form, check to see if they are currently set to aggregate (sum, count etc.) with Access' built-in Totals row, and if so, set them to not aggregate.
The reason for this is there are several millions records that are stored, so when someone queries it down to 3-4 and turns on Sum, then closes it, when the next person opens it, it tries to sum millions of numbers and freezes up. The form displays the queried results from a table which is populated via SQL (I think, if that sentence makes sense). Here's what I have so far:
Private Sub Form_Load()
'this form_load is in the UserApxSub sub-form, for reference
Call De_Aggregate
End Sub
Private Sub De_Aggregate()
Dim frm As Form, con As Control
Set frm = Forms!UserAPX!UserApxSub.Form!
For Each con In frm.Controls
If con.ControlType = acTextBox Then
If con.Properties("AggregateType").Value <> -1 Then
'crashes on following line
con.Properties("AggregateType").Value = -1
End If
End If
Next con
End Sub
I have not so much experience in Access VBA (usually work in Excel VBA) so please forgive me if I'm entirely off the mark here. The command con.Properties("AggregateType").Value = -1 doesn't throw an error, but Access just straight-up crashes when reaching that line specifically.
I've tried a number of variations in the syntax with no success, and I've also tried looping through other elements of the file (tabledefs, querydefs, recordsets, etc.) as well to see if I'm trying to change the wrong value, but the controls on this subform are the only things in the entire .mdb file that results when I search for elements with the AggregateType property.
I switched out the line that errors with Debug.Print con.Name & " - " & con.Properties("AggregateType").Value and I can check, have nothing return anything other than -1, turn on aggregation in some column manually, and have it return the correct result (0 for sum for example), so I think I'm looking in the right place, just missing some key factor.
I've been working on this for a couple weeks with no success. Any way to fix what I have or point me toward the right direction would be greatly appreciated!
This is not necessarily the answer but I don't have enough reputation
to give a "comment"...
I tried your scenario and verified can change the property value as you are however I did not iterate through all controls and simply used an onDoubleClick event on a column to simulate.
I would suggest trying to fire your sub with Form_Open or Form_Current to see if the property is getting reset after your code has been called for some reason.
UPDATE:
You are referencing the "Subform" Object of your main Form:
Set frm = Forms!UserAPX!UserApxSub.Form!
Try referencing the actual UserApxSub FORM explicitly.
Something like Set frm = Forms!UserApxSub! (assuming UserApxSub is the name of the form)
then stick in the Form_Open of your main form:
Private Sub Form_Open(Cancel As Integer)
'// the following would set a single control only. You can add your loop through all controls
Me!{your control name}.Properties("AggregateType").Value = -1 '// set control in your main form
Form_UserApxSub!{your control name}.Properties("AggregateType").Value = -1 '// set control in your "sub" form
End Sub
We have an Access 2010 database that acts as a front-end to a MS SQL database. When we edit data in the form there is a procedure that needs to run in order to properly save certain data back to SQL.
Our DB programmer added a "Save Button" to do this. But that causes another problem - there are multiple ways in Access by which to save a form -
Navigate to the next record
Click on the Confirmation bar on the left
Create a new record
Search for a new record
Use commands in the ribbon
Is there any way to attach a procedure the actual save action so that no matter how a person moves to a next form that the procedure gets run?
[update]
Here is the code behind the scenes: the first sub is attached to the "Save" Button. Of course, the second is attached to the form BeforeUpdate.
Private Sub SaveRecord_Click()
'From NAME form
Form_BeforeUpdate False
End Sub
Private Sub Form_BeforeUpdate(Cancel As Integer)
'used by NAME form
[Last_Update] = Now
'*********************
Save_Record
'*********************
MName_ID = Me.Name_ID
Me.Undo
Cancel = True
If Not IsNull(MName_ID) Then
Jump_to_Name_ID MName_ID, True
Else
End If
End Sub
I guess I just don't understand what the button is for.
So I installed an MS Access 2010 trial and finally managed to figure out a way to solve your problem. It includes data macros and a hidden gem that took me quite a while to find.
Here's how you run VBA when a table changes:
Create an ordinary module (haven't tried class modules) with public functions:
Module name: EventHandlers
Public Function InsertEvent(ByVal id As Integer)
MsgBox "inserted: " + CStr(id)
End Function
Open the table that, when modified, should run VBA and go to "Table" in the ribbon.
Click on "After Insert"
In the "Add New Action"-select box, choose SetLocalVar (or select it from the Action Catalog).
In the Name-field, insert the name of the module (in this case, EventHandlers, as we created earlier)
In the Expression-field, write the name of the function: InsertEvent([id]) (where [id] is an actual column in the table you're adding a data macro for)
Save and close
Whenever something is inserted to the table, a messagebox will be shown with the id.
You could do the same with the update event. The function could be something like this:
Public Function UpdateEvent(ByVal oldValue As String, ByVal newValue As String)
MsgBox oldValue + " changed to: " + newValue
End Function
and the data macro would be
Action: SetLocalVar
Name: EventHandlers
Expression: UpdateEvent([Old].[your_column_name];[your_column_name])
Note: Executing DoCmd.RunSQL with update, insert or delete will execute data macros and THEN ask the user if he or she actually WANTS to update/insert/delete the row. If the user clicks cancel, nothing is changed but your data macro executed anyway. If you haven't already, you should probably disable this check before implementing data macros.
Well, I was not able to use Mr. Sommer's solution because it was not possible to add an event handler to a linked table on account of their being read-only. But, I did work out a simple procedure that seems to work well enough.
So, I was actually already using the BeforeUpdate event, so I'm catching the right event here - this is the event that traps the save, whether it be on change of navigation or the save-record bar on the left. However, there were a few issues that resulted from using Application.Echo False to keep Access from posting back the old data to the control whilst the Me.Undo takes place.
So we use cancel=true to prevent the BeforeUpdate event from doing its normal processing, and we use Me.Undo to prevent Access from trying to save data to the linked tables.
Private Sub Form_BeforeUpdate(Cancel As Integer)
Cancel = True
[Last_Update] = Now
'*********************
Save_Record '-->This will save the data back to SQL
'*********************
MName_ID = Me.Name_ID
Application.Echo False 'don't show the undo in the controls
Me.Undo
If Not IsNull(MName_ID) Then 'used for the navigation buttons
Jump_to_Name_ID MName_ID, True
Else
End If
Application.Echo True 'turn the control refresh back on
Me.Repaint
End Sub
I am using Microsoft Access 2013 to create a database where I have a form that the default view is read-only to prevent accidental editing. I am currently trying to include a button to enable editing for the current record only. I tried using DoCmd.OpenForm to open the record in editable mode (since I am using that command elsewhere to open specific records), but it seems like it can not open a record within the same form with that command.
I thank you in advance for any advice on how to solve this issue.
You cannot edit anything else than the current record.
Use this code line:
Me.AllowEditions = True
But it doesn't make much sense as you could just open the form this way.
I learned this from the first VBA book I ever read (Access 97 VBA for Dummies, I think), and I have never seen anyone use the Tag property since, which has no other purpose than for you to find something to use it for. Enter "Lockable" in the tag property for any control (text box, combobox etc.) that you want to protect. Add a button named btnEdit to your form.
When you enter a record, the Form Current event will unlock the controls if it is a new record, and lock the controls if it is an existing record.
The button will then unlock to allow edits, and re-lock once the record is exited. I've found this to be very effective in preventing inadvertent edits.
Private Sub Form_Current()
Dim ctlCurr As Control
'Lock the record if it is not new. Prevents inadvertent edits.
If Me.NewRecord = False Then
For Each ctlCurr In Me.Controls
If ctlCurr.Tag = "Lockable" Then
ctlCurr.Locked = True
End If
btnEdit.Enabled = True
Next
End If
'Unlock a new record for editing.
If Me.NewRecord = True Then
For Each ctlCurr In Me.Controls
If ctlCurr.Tag = "Lockable" Then
ctlCurr.Locked = False
End If
Next
btnEdit.Enabled = False
End If
End Sub
Private Sub btnEdit_Click()
Dim ctlCurr As Control
'Unlocks a record for editing. Requires the operator to make the decision to edit.
If Me.NewRecord = False Then
For Each ctlCurr In Me.Controls
If ctlCurr.Tag = "Lockable" Then
ctlCurr.Locked = False
End If
Next
End If
End Sub
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.