In the VBA code of an Access database (the "CurrentDB"), I open another database (by Application.DBEngine.OpenDatabase(...)).
and I'd like to update some library references within this second database. But as the References object is a property of the Application object (and not of the Database object as it should be in my opinion).
I only can list and change the references of the CurrentDB, not of the second one.
Any idea how to access the References of the second database?
Andre, thank you for your first answer, it really brought me a big piece further! Now I face the next problem: I open the second database, delete all broken references (they really disappear), close the second database - and when I open it again, the broken references are back. Here's my code:
Set appAccess = CreateObject("Access.Application")
With appAccess
Call .OpenCurrentDatabase(sPathMasterDb, False)
' It makes no difference if I use the next two statements or not:
.Visible = True
.UserControl = True
Call deleteBrokenReferences(appAccess)
.CloseCurrentDatabase
End With
Set appAccess = Nothing
Private Sub deleteBrokenReferences(app As Application)
Dim aRef As Access.Reference
If app.BrokenReference Then
For Each aRef In app.References
If aRef.IsBroken Then
app.References.Remove aRef
End If
Next aRef
End If
End Sub
After the call of "deleteBrokenReferences", I open the second database in a new instance of MS Access, and the broken references are gone. But after the execution of ".CloseCurrentDatabase" they are back again.
Is there a possibility to save the changes before closing the second database?
The reason for my problem was that none of the three mutually dependent databases could be opened exclusively. I solved the problem by creating a new database which opens each of the three databases separately and performs the updates of code references and table links. But once more: Thank you Andre for your valuable input!
Let me start by saying I am not at all familiar with Access or VBA. However, I am the IT guy and at some point someone created an MS Access database that does a thing and now I have to support it.
We have a database that upon opening deletes any old data and re-runs the external query that makes this application work. Occasionally whatever state the program exited out in that table already does not exist. This causes MS Access to hang on the delete line and I have to run the debugger, comment out the DoCmd.DeleteObject line, re run, and then un-comment the line to let the user continue with their day.
I want to add in some sort of conditional statement, but anything I've been able to google in terms of If...Then statements or 'TableExist' type functions always causes an error to be thrown that I haven't defined the function. My best guess is I'm nesting this incorrectly or I'm not calling some sort of external function correctly, but as I said my VBA knowledge is extremely limited.
This code executes on startup:
Public Function Startup() As Integer
DoCmd.Close acForm, "soLookup"
DoCmd.DeleteObject acTable, "sales_order"
DoCmd.RunSavedImportExport "Import-sales_order"
DoCmd.OpenForm "soLookup"
End Function
Its the
DoCmd.DeleteObject acTable, "sales_order"
Line that causes things to fail.
I've attempted to restructure this several times based on several examples I had found, but I'll only bother with one below
Public Function Startup() As Integer
DoCmd.Close acForm, "soLookup"
If TableExists("sales_orders") Then
DoCmd.DeleteObject acTable, "sales_orders"
Else
'Do nothing
End If
DoCmd.RunSavedImportExport "Import-sales_order"
DoCmd.OpenForm "soLookup"
End Function
Nothing I seem to try seems to give me any result other than an error of some sort. All I want to do is add a conditional statement to this 'Startup' bit that checks if the "sales_order" table even if exists, and if it doesn't, then to just move on to the next comment and forget the DoCmd.DeleteObject. How can I achieve this functionality?! Thanks!
The TableExists function is not a standard function in Access. You need to define it yourself.
There are two main ways to define such a function, by trying and error trapping, or by iterating through all tables and checking names.
Way 1 (error trapping):
Public Function TableExists(TableName As String) As Boolean
On Error Resume Next
TableExists = CurrentDb.TableDefs(TableName).Name = TableName
End Function
Way 2 (iterating collection):
Public Function TableExists(TableName As String) As Boolean
Dim td As DAO.TableDef
For Each td In CurrentDb.TableDefs
If td.Name = TableName Then
TableExists = True
Exit Function
End If
Next
End Function
Define either of these functions in a public module, and your last approach should work
I will instead modify the query to import the source data into a new table (sales_orders), which will overwrite existing data when the query runs and so I don't have to delete the table and have additional check for TableExists
Hope someone can help with my following problem.
I want to make sure a specific workbook is closed before It gets an update.
for that I was planning to start with.
Private Sub Command1_Click()
workbooks("workbookname.XLSM").close Savechanges = False
End Sub
I also tried
workbooks("workbookname.Xlsm").close
Both i have run separately and both give me Error code 9.
The specific file will always be on the desktop of the user.
I thought it might have something to do with my references so I checked and have the following references turned on. (some for other specific reasons).
Visual Basic For Applications
Microsoft Access 16.0 Object Library
OLE Automation
Microsoft DAO 3.6 Object Library
Microsoft Excel 16.0
Object Library Microsoft Scripting Runtime.
Really hope someone can help me with this. I've been surfing quite a large number of forums but no where i could find the same problem or solution.
w.k.r. Drac.
You have to check to see if it's open, and reference the open object properly. The function below (which I picked up off SO somewhere), returns a true/false value about the file in particular:
You would use it as such:
Private Sub Command1_Click()
Dim xls As Object
If IsWorkBookOpen("C:/Users/Desktop/Workbook.xlsm") = True Then
Set xls = GetObject("C:/Users/Desktop/Workbook.xlsm")
xls.Close True
Else
'do stuff if closed
End If
End Sub
Function:
Function IsWorkBookOpen(FileName As String)
Dim ff As Long, ErrNo As Long
On Error Resume Next
ff = FreeFile()
Open FileName For Input Lock Read As #ff
Close ff
ErrNo = Err
On Error GoTo 0
Select Case ErrNo
Case 0: IsWorkBookOpen = False
Case 70: IsWorkBookOpen = True
Case Else: Error ErrNo
End Select
End Function
I've searched and can't find any help with this behavior. (I'm working in Access 2010, but the database is in Access 2000 file format.) I'm working with a form in datasheet view. I wrote the code below to copy Inventory Location from the record above if the Down Arrow key is pressed. The code works fine if I use the Down Arrow key to move vertically down from one record to the next, but not if I use the Tab or Enter keys to move from one field to the next.
Private Sub InventoryLocation_KeyDown(KeyCode As Integer, Shift As Integer)
' Variables are defined as Public at head of module.
If KeyCode = vbKeyDown Then
If Me.CurrentRecord = intPreviousRecordNumber + 1 Then
If IsNull(Me.InventoryLocation.Value) Then
Me.InventoryLocation.Value = varPreviousInventoryLocation
DoCmd.CancelEvent
End If
End If
End If
End Sub
Variable are being populated from the previous record here:
Private Sub InventoryLocation_LostFocus()
' Variables are defined as Public at head of module.
varPreviousInventoryLocation = Me.InventoryLocation.Value
intPreviousRecordNumber = Me.CurrentRecord
End Sub
This is my first post, but I find most of my answers here at StackOverflow.com. Any help will be greatly appreciated! Thanks!
My mistake apparently is that I thought my variable intPreviousRecordNumber could be used for more than one field in the record. (I also have a field called OverstockLocation whose LostFocus event was setting the value of intPreviousRecordNumber. I assumed this was fine because the value would be the same for both fields in the same record.)
Apparently Access was objecting to both InventoryLocation and OverstockLocation trying to use the same variable. I created a new variable called intPreviousRecordNumberOverstock, for use by the OverstockLocation code, and now both work as I expected.
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.