Ms-Access form data export to Word - vba

I really some help! Here's a link to a google drive zip of the access database that I'm struggling with.
https://drive.google.com/file/d/0BwjnhQS2X7_Qamt4clFLc1Ztb2c/view?usp=sharing
So, what I have is an access database made up of a few tables and a form and some sub forms. The database info gets inputted to the tables via a form that I've created. In the example, the form is called "Database". This form exports to a word document, fields on the database go to bookmarks on the word doc. This works great so far.
In the attachment there is a "template" folder with the original word document, when the code runs it saves the completed form to the "generated" folder - works like a charm. Its a very long form for applications for liquor licenses.
So you fill in the form in access, it saves to the tables and exports the data to the word template document.
The problem that I have is that there is a subform on tab8 of the form where "director details" are saved. There can be any number of directors per application. I've managed to access the data on the subform's table, but have no idea how to loop through the data in that table to get all the information that is applicable to that application only and not data related to other applications. There is a relationship between the director details table and the application details table(this is the main table) and I'm using an application identifier field that I've created called and "ACNumber" which is unique to each application. There is a combobox on the form that selects the application and the form and subforms bring up the correct data when you select it.
The other part of the problem is how do I output this to word? A bookmark won't work, because all the fields are being repeated. Is there a way that all the data entries can be outputted to a single bookmark mabe in a textbox with the labels?
This is how it looks on the word document form:
(First person)
Full name : generate from item 5.4(a) from database
Physical address : generate from item 5.4(b) from database
Postal code : generate from item 5.4(c) from database
Postal address : generate from item 5.4(d) from database
Postal code : generate from item 5.4(e) from database
Identity number : generate from item 5.4(f) from database
(More person’s to add if needed)
Ok, I hope that describes my problem accurately.
I've tried all sorts to get this working, but its beyond me, please help guys!!!
Below is the code that I'm using: (the loop for the subform doesn't work, but one entry from that table is exported to the bookmarks currently in place)
I've tried all sorts to get this working, but its beyond me, please help guys!!!
`Private Sub ExportToWord_Click()
'Print customer slip for current customer.
Dim appWord As Word.Application
Dim doc As Word.Document
Dim drst As Recordset
Set drst = CurrentDb.OpenRecordset(Name:="62 Other Interests", Type:=RecordsetTypeEnum.dbOpenDynaset)
'Avoid error 429, when Word isnt open.
On Error Resume Next
Err.Clear
'Set appWord object variable to running instance of Word.
Set appWord = GetObject(, "Word.Application")
If Err.Number <> 0 Then
'If Word isnt open, create a new instance of Word.
Set appWord = New Word.Application
End If
Set doc = appWord.Documents.Open("C:\forms\templates\Form 3 - Sec 36(1).docx", , True)
With doc
.Bookmarks("wAppTradingNames").Range.Text = Nz(Me!AppTradingName, "")
.Bookmarks("wAppTradingName").Range.Text = Nz(Me!AppTradingName, "")
.Bookmarks("wCompanyName").Range.Text = Nz(Me!CompanyName, "")
.Bookmarks("wCompanyNumber").Range.Text = Nz(Me!CompanyNumber, "")
.Bookmarks("wRAddress1").Range.Text = Nz(Me!RAddress1, "")
.Bookmarks("wPostalCode").Range.Text = Nz(Me!PostalCode, "")
.Bookmarks("wRPostalAddress1").Range.Text = Nz(Me!RPostalAddress1, "")
.Bookmarks("wRPostalCode").Range.Text = Nz(Me!RPostalCode, "")
.Bookmarks("wDomicilium1").Range.Text = Nz(Me!Domicilium1, "")
.Bookmarks("wDomiciliumCode").Range.Text = Nz(Me!DomiciliumCode, "")
.Bookmarks("wDomAfter1").Range.Text = Nz(Me!DomAfter1, "")
.Bookmarks("wDomAfterCode").Range.Text = Nz(Me!DomAfterCode, "")
.Bookmarks("wTelOffice").Range.Text = Nz(Me!TelOffice, "")
.Bookmarks("wTelCell").Range.Text = Nz(Me!TelCell, "")
.Bookmarks("wTelHome").Range.Text = Nz(Me!TelHome, "")
.Bookmarks("wFaxNumber").Range.Text = Nz(Me!FaxNumber, "")
.Bookmarks("wEmail").Range.Text = Nz(Me!Email, "")
.Bookmarks("wFIP").Range.Text = Nz(Me!FIP, "")
.Bookmarks("wAppLicCat").Range.Text = Nz(Me!AppLicCat, "")
.Bookmarks("wLiqourType").Range.Text = Nz(Me!LiqourType, "")
.Bookmarks("wAppTradingName").Range.Text = Nz(Me!AppTradingName, "")
.Bookmarks("wAppTradingName").Range.Text = Nz(Me!AppTradingName, "")
.Bookmarks("wLPAddress").Range.Text = Nz(Me!LPAddress, "")
.Bookmarks("wErfNumber").Range.Text = Nz(Me!ErfNumber, "")
.Bookmarks("wLPPostalCode").Range.Text = Nz(Me!LPPostalCode, "")
.Bookmarks("wLPOwnership").Range.Text = Nz(Me!LPOwnership, "")
.Bookmarks("wLPOwnersName").Range.Text = Nz(Me!LpOwnersName, "")
.Bookmarks("wLpOwnerAddress").Range.Text = Nz(Me!LpOwnerAddress, "")
.Bookmarks("wLpRightOccupation").Range.Text = Nz(Me!LpRightOccupation, "")
.Bookmarks("wLPOccDuration").Range.Text = Nz(Me!LPOccDuration, "")
.Bookmarks("wLpPremNotErected").Range.Text = Nz(Me!LpPremNotErected, "")
.Bookmarks("wLpPremAlterReq").Range.Text = Nz(Me!LpPremAlterReq, "")
.Bookmarks("wLpPremAllGood").Range.Text = Nz(Me!LpPremAllGood, "")
.Bookmarks("wLpBuildCommence").Range.Text = Nz(Me!LpBuildCommence, "")
.Bookmarks("wLpBuildDuration").Range.Text = Nz(Me!LpBuildDuration, "")
.Bookmarks("wLpTradingHours").Range.Text = Nz(Me!LpTradingHours, "")
.Bookmarks("wLpRenewal").Range.Text = Nz(Me!LpRenewal, "")
.Bookmarks("wLpJobsa").Range.Text = Nz(Me!LpJobsa, "")
.Bookmarks("wLpJobsB").Range.Text = Nz(Me!LpJobsB, "")
.Bookmarks("wLpJobsC").Range.Text = Nz(Me!LpJobsC, "")
.Bookmarks("wNNPRegName").Range.Text = Nz(Me!NNPRegName, "")
.Bookmarks("wNNPRegNumber").Range.Text = Nz(Me!NNPRegNumber, "")
.Bookmarks("wNNPRegDate").Range.Text = Nz(Me!NNPRegDate, "")
.Bookmarks("wOtherInterests").Range.Text = Nz(drst!OtherInterests, "")
.Visible = True
.Activate
End With
Dim rst As Recordset: Set rst = CurrentDb.OpenRecordset(Name:="5 Director Details", Type:=RecordsetTypeEnum.dbOpenDynaset)
'Do While Not rst.EOF
With doc
.Bookmarks("wPersonLabel").Range.Text = Nz(rst!PersonLabel, "")
.Bookmarks("wFullName").Range.Text = Nz(rst!FullName, "")
.Bookmarks("wPhAddress").Range.Text = Nz(rst!PhAddress, "")
.Bookmarks("wPhCode").Range.Text = Nz(rst!PhCode, "")
.Bookmarks("wPAddress").Range.Text = Nz(rst!PAddress, "")
.Bookmarks("wPCode").Range.Text = Nz(rst!PCode, "")
.Bookmarks("wIdNumber").Range.Text = Nz(rst!IdNumber, "")
.Visible = True
.Activate
rst.MoveNext
End With
'Loop
doc.SaveAs2 "C:\forms\generated\" & Me!ACNumber & "_Form 3 - Sec 36(1).docx"
Set doc = Nothing
Set appWord = Nothing
Exit Sub
errHandler:
MsgBox Err.Number & ": " & Err.Description
End Sub
`

This will point you to the right direction. You need to make a couple of changes though to fit your needs e.g. insert all your bookmarks, update the SQL strings and recordset fields.
You also need to make a few changes to your Word document though:
1) Add a table to hold the manager data (loop). Hide the borders if needed.
2) Save the document as Word Template (.dotx)
Public Sub ExportToWord()
On Error GoTo ErrorTrap
Const TemplatePath As String = "C:\forms\templates\Form 3 - Sec 36(1).dotx"
'Data
Dim rs As DAO.Recordset
Set rs = CurrentDb().OpenRecordset("SELECT * FROM TableName WHERE [Criteria]", dbOpenSnapshot)
'SaveAs
Dim name_ As String
name_ = "C:\forms\generated\" & rs![FieldName] & "_Form 3 - Sec 36(1).docx"
'Word
Dim oWord As Word.Application
Set oWord = New Word.Application
oWord.Visible = False
Dim oDoc As Word.Document
Set oDoc = oWord.Documents.Add(TemplatePath)
With oDoc
.Bookmarks("Bookmark_1").Range.Text = rs![FieldName_1]
.Bookmarks("Bookmark_2").Range.Text = rs![FieldName_2]
.Bookmarks("Bookmark_3").Range.Text = rs![FieldName_3]
'...
End With
rs.Close
Set rs = Nothing
'Loop data
Set rs = CurrentDb().OpenRecordset("SELECT * FROM TableName WHERE [Criteria]", dbOpenSnapshot)
With rs
If Not .EOF Then
.MoveLast
.MoveFirst
End If
End With
Dim idx As Integer
For idx = 1 To rs.RecordCount
With oDoc.Tables(1)
.Cell(idx, 1).Range.Text = rs![FieldName_1] '1st Column
.Cell(idx, 2).Range.Text = rs![FieldName_2] '2nd Column
.Cell(idx, 3).Range.Text = rs![FieldName_1] '3rd Column
'...
'add extra rows if required
If rs.AbsolutePosition <> rs.RecordCount - 1 Then .Columns(1).Cells.Add
End With
rs.MoveNext
Next idx
'Save
With oDoc
.SaveAs FileName:=name_, FileFormat:=Word.WdSaveFormat.wdFormatXMLDocument
.Close SaveChanges:=wdDoNotSaveChanges
End With
Leave:
On Error Resume Next
rs.Close
Set rs = Nothing
oWord.Quit
Set oWord = Nothing
On Error GoTo 0
Exit Sub
ErrorTrap:
MsgBox Err.Description, vbCritical, "ExportToWord()"
Resume Leave
End Sub

Related

MS-Access - MailMerge specific record from a form

I am creating an Access 2019 database for small family business (dog breeding) so I setup some tables containing all details on the dogs and the owners. Just to give an idea (simplistic description of the situation):
Dogs
Name
Birth
Microchip
Etc…
Owners
Name
Address
Etc…
I was now trying to create a "Contract composer" for when we sell the dogs. So I made a new table "Contract" and a related form
Contract
Seller ->linked to Owners table
Buyer ->linked to Owners table
Dog ->linked to Dogs table
Price
And made a query to pull all relevant information from the related tables so that I can have
ContractQuery
Seller!Name
Seller!Address
Buyer!Name
Buyer!Address
Dog!Name
Dog!Birthdate
Dog!Microchip
Contract!Price
Everything so far is working perfectly fine.
Now I need to convert the ContractQuery fields in a form of "human readable" contract. I think the best way to do so is the MailMerge to a specific Word document, and I've already setup one. My problem is: how can I set a button into the Contract form so that the "contract.doc" is populated with the specific record I'm seeing now in the form?
I had made some researches and the most relevant information I've found is this
https://www.access-programmers.co.uk/forums/threads/run-mail-merge-from-vba.158126/
and this https://www.tek-tips.com/faqs.cfm?fid=3237
But they are related to old MS-Access so when I tried to apply it I had errors all around. Unluckily my VBA knowledge is far from being proficient and I was not able to make it work.
Can anyone help me, or address me to a solution?
Thanks in advance for any advice
OK I got it working thanks to Kostas K, pointing me in the fight direction. This is my final code, it might need some cleanup and tweaking (for example, the loop within the resulst is now redundant as I only have one result), but it is working :)
The solution is based on this post, should anyone need please have a look at it as reference for the template docx etc
Generating completed PDF forms using word docs and ms access
Option Explicit
Private Sub cmdMergeIt_Click()
On Error GoTo Trap
' **** defining project path as string to make this portable
Dim CurPath As String
CurPath = CurrentProject.path & "\"
' MsgBox (CurPath) 'debug
Dim TEMPLATE_PATH As String
TEMPLATE_PATH = CurPath & "Contratto.dotx"
Dim wApp As Word.Application
Dim wDoc As Word.Document
Dim rs As DAO.Recordset
Dim idx As Long
' *** intercepting the contract ID field from the launching form
Dim checkID As String
checkID = ID.Value
'MsgBox (checkID) 'debug
' **** defining a SQL query on my Access query
Dim strSQL As String
strSQL = "Select * from qContratto where ID =" & checkID & ""
' MsgBox (strSQL) 'debug
Set wApp = New Word.Application
wApp.Visible = False
' ***** changed the OpenRecordset to call my strSQL query insetad than reading the whole Access query
Set rs = CurrentDb.OpenRecordset(strSQL, dbOpenSnapshot)
If rs.EOF Then GoTo Leave
With rs
.MoveLast
.MoveFirst
End With
For idx = 1 To rs.RecordCount
Set wDoc = wApp.Documents.Add(TEMPLATE_PATH)
With wDoc
.Bookmarks("Prezzo").Range.Text = Nz(rs!Prezzo, vbNullString)
.Bookmarks("Venditore").Range.Text = Nz(rs!Venditore, vbNullString)
.Bookmarks("Acquirente").Range.Text = Nz(rs!Acquirente, vbNullString)
.Bookmarks("Cessione").Range.Text = Nz(rs!Cessione, vbNullString)
.Bookmarks("NomeCane").Range.Text = Nz(rs!NomeCane, vbNullString)
.Bookmarks("Riproduzione").Range.Text = Nz(rs!Riproduzione, vbNullString)
.Bookmarks("Sesso").Range.Text = Nz(rs!Sesso, vbNullString)
.ExportAsFixedFormat CurPath & rs!Acquirente & ".pdf", wdExportFormatPDF, False, wdExportOptimizeForOnScreen
.Close wdDoNotSaveChanges
' in the ExportAsFixedFormat here above called one of the SQL query values to make a unique and distinctive name. Also please note use of CurPath for portability
End With
Set wDoc = Nothing
rs.MoveNext
Next
Leave:
On Error Resume Next
If Not rs Is Nothing Then rs.Close
If Not wDoc Is Nothing Then wDoc.Close wdDoNotSaveChanges
If Not wApp Is Nothing Then wApp.Quit wdDoNotSaveChanges
On Error GoTo 0
Exit Sub
Trap:
MsgBox Err.Description, vbCritical
Resume Leave
End Sub

Save Outlook attachment in MS Access using VBA

I am running MS Access 2010. Using VBA I am trying to pull attachments out of MS Exchange 2013 and insert them into the Access table "TBL_APPT_ATTACHMENT".
The table "TBL_APPT_ATTACHMENT" looks like this:
Attachment_title Memo
Attachment_filename Memo
Attachment_blob OLE Object
Everything seems to work correctly except I can not figure out how to save the actual file into the column ATTACHMENT_BLOB. Here is my VBA function that I am calling (See question marks below).
Private Function createRecord(fItem As Outlook.AppointmentItem)
Set rsAtt = CurrentDb.OpenRecordset("TBL_APPT_ATTACHMENT")
rsAtt.OpenRecordset
For Each Attachment In fItem.Attachments
Call MsgBox("FileName: " & Attachment.FileName, vbOKOnly, "Error")
Call MsgBox("DisplayName: " & Attachment.DisplayName, vbOKOnly, "Error")
Call MsgBox("Index: " & Attachment.Index, vbOKOnly, "Error")
rsAtt.AddNew
rsAtt!APPT_ITEM_ID = aID
rsAtt!APPT_FIELD_id = rsOl!ID
rsAtt!ATTACHMENT_TITLE = Attachment.DisplayName
rsAtt!ATTACHMENT_FILENAME = Attachment.FileName
rsAttID = rsAtt!ID
rsAtt.Update
'Save file to harddrive.
filePath = "c:\temp\" + Attachment.FileName
Attachment.SaveAsFile (filePath)
Set rsParent = CurrentDb.OpenRecordset("SELECT ID, ATTACHMENT_BLOB FROM TBL_APPT_ATTACHMENT WHERE ID = " & rsAttID)
rsParent.OpenRecordset
Do While Not rsParent.EOF
rsParent.Edit
'Load file into Database.
'??? This next statement gives me a "Type Mismatch" error. Why?????
Set rsChild = rsParent.Fields("ATTACHMENT_BLOB").Value
rsChild.AddNew
rsChild.Fields("FileData").LoadFromFile (filePath)
rsChild.Update
rsParent.Update
rsParent.MoveNext
Loop
Next
End Function
Thanks!!
Remember that the attachment is really a file (whether its an OLE object or not). While it may be possible to perform a copy-paste of the object from Outlook into Access, my recommendation is to save the attachment as a file:
dim filepath as String
dim filename as String
filepath = "C:\appropriatefolder\"
filename = Attachment.FileName
Attachment.SaveAsFile filepath & filename
Now you're in a position to save the attachment in Access, but I seriously don't recommend using the Attachment field type. It can be rather tricky to use. So my solution to the same problem was to create a field of type Hyperlink. Then your statement in your macro will simply be:
rsAtt!ATTACHMENT_LINK = filename & "#" & filepath & filename
The hyperlink definition is important and uses the format:
displayString # fullPathToFile [ # optionalPositionInsideFile ]
EDIT: Using the Attachment Field Type in Access
The Attachment field type in an Access table can be understood if you consider it an embedded recordset within that single record. Therefore, every time you add a new record (or read an existing record), you have to handle the Attachment field a bit differently. In fact, the .Value of the Attachment field is the recordset itself.
Option Compare Database
Option Explicit
Sub test()
AddAttachment "C:\Temp\DepTree.txt"
End Sub
Sub AddAttachment(filename As String)
Dim tblAppointments As DAO.Recordset
Dim attachmentField As DAO.Recordset
Dim tblField As Field
Set tblAppointments = CurrentDb.OpenRecordset("TBL_APPT_ATTACHMENT", dbOpenDynaset)
tblAppointments.AddNew
tblAppointments![APPT_ITEM_ID] = "new item id"
tblAppointments![APPT_FIELD_ID] = "new field id"
tblAppointments![ATTACHMENT_TITLE] = "new attachment"
tblAppointments![ATTACHMENT_FILENAME] = filename
'--- the attachment field itself is a recordset, because you can add multiple
' attachments to this single record. so connect to the recordset using the
' .Value of the parent record field, then use it like a recordset
Set attachmentField = tblAppointments![ATTACHMENT_BLOB].Value
attachmentField.AddNew
attachmentField.Fields("FileData").LoadFromFile filename
attachmentField.Update
tblAppointments.Update
tblAppointments.Close
Set tblAppointments = Nothing
End Sub
Here is what I ended up doing.
Private Function createRecord(fItem As Outlook.AppointmentItem)
Set rsAtt = CurrentDb.OpenRecordset("TBL_APPT_ATTACHMENT")
rsAtt.OpenRecordset
For Each Attachment In fItem.Attachments
'Save file to harddrive.
filePath = "c:\temp\" + Attachment.FileName
Attachment.SaveAsFile (filePath)
rsAtt.AddNew
rsAtt!APPT_ITEM_ID = aID
rsAtt!APPT_FIELD_id = rsOl!ID
rsAtt!ATTACHMENT_TITLE = Attachment.DisplayName
rsAtt!ATTACHMENT_FILENAME = Attachment.FileName
Call FileToBlob(filePath, rsAtt!ATTACHMENT_BLOB)
rsAttID = rsAtt!ID
rsAtt.Update
Next
End Function
Public Function FileToBlob(strFile As String, ByRef Field As Object)
On Error GoTo FileToBlobError
If Len(Dir(strFile)) > 0 Then
Dim nFileNum As Integer
Dim byteData() As Byte
nFileNum = FreeFile()
Open strFile For Binary Access Read As nFileNum
If LOF(nFileNum) > 0 Then
ReDim byteData(1 To LOF(nFileNum))
Get #nFileNum, , byteData
Field = byteData
End If
Else
MsgBox "Error: File not found", vbCritical, _
"Error reading file in FileToBlob"
End If
FileToBlobExit:
If nFileNum > 0 Then Close nFileNum
Exit Function
FileToBlobError:
MsgBox "Error " & Err.Number & ": " & Err.Description, vbCritical, _
"Error reading file in FileToBlob"
Resume FileToBlobExit
End Function

Write Conflict error in access even after a requery on the main form

I've done a ton of research on this topic. I've tried every plausible solution under the internet's sun. Maybe I'm not thinking about this logically since I've been trying to figure out a solution for this for a week. After an actual change to the data and a save from the user I get the write conflict error message "Save Change, Copy to Clipboard, Drop Changes" this random little nuisance is becoming a bigger headache than it's worth.
Just for refrence my main form is called frmCNSUpdates which on a button press opens the subform frmContactInfo
What happens is after a user changes data on this subform and clicks the save button, then closes the subform and returns to the main form then proceeds to move away from the record that has been changed in the subform, saves it from the mainform, add it from the mainform, or even close out of the form I get the write conflict error. I have tried refreshing the parent form and the subform, and do a sql injection update instead of a record set, I have even tried just suppressing the error but I fear I'm putting my solutions in the wrong place. Here is the code for the save button on the subform:
Private Sub cmdSave_Click()
On Error GoTo Err_cmdSave_Click
SaveRecord
Exit_cmdSave_Click:
Exit Sub
Err_cmdSave_Click:
MsgBox Err.Description
Resume Exit_cmdSave_Click
End Sub
Private Sub SaveRecord()
On Error GoTo Err_SaveRecord
Dim db As DAO.Database
Dim rst As DAO.Recordset
Dim intRow As Integer
Dim varItem As Variant
Dim strSQL As String
DoCmd.Hourglass True
strSQL = "select * from tblConstructionUpdates where [ProjectNo] =" & Forms![frmCNSUpdates]!ProjectNo
Set db = CurrentDb
Set rst = db.OpenRecordset(strSQL, dbOpenDynaset)
rst.Edit
'rst("ProjectNo") = Me.txtID
'rst("ContractNo") = Me.txtContractNo
rst("EstimatedCompletionDate") = Me.EstimatedCompletionDate
rst("PercentComplete") = Me.txtPercentComplete
rst("SupervisorFirstName") = Me.txtSupervisorFirstName
rst("SupervisorLastName") = Me.txtSupervisorLastName
rst("SupervisorOfficePhoneNumber") = Me.txtSupOfficePhone
rst("SupervisorMobilPhoneNumber") = Me.txtSupMobilPhone
rst("ResidentFirstName") = Me.txtResidentFirstName
rst("ResidentLastName") = Me.txtResidentLastName
rst("ResidentOfficePhoneNumber") = Me.txtResOffice
rst("TelephoneNo") = Me.txtResMobil
rst("ResidentHomePhoneNumber") = Me.txtResHome
rst("ConsultantFirstName") = Me.txtConsultantFirstName
rst("ConsultantLastName") = Me.txtConsultantLastName
rst("ConsultantOfficePhoneNumber") = Me.txtConOffice
rst("ConsultantMobilPhoneNumber") = Me.txtConMobil
rst("ConsultantHomePhoneNumber") = Me.txtConHome
rst("Contractor") = Me.txtGenContractor
rst("GeneralOfficePhoneNumber") = Me.txtGenOfficePhone
rst("GeneralMobilPhoneNumber") = Me.txtGenMobilPhone
rst("GeneralHomePhoneNumber") = Me.txtGenHomePhone
rst("ContractorRep1Name") = Me.cboRep1
rst("ContractorRep1OfficePhone") = Me.txtOffice
rst("ContractorRep1MobilPhone") = Me.txtMobil
rst("ContractorRep1HomePhone") = Me.txtHome
rst("ContractorRep2Name") = Me.cboRep2
rst("ContractorRep2OfficePhone") = Me.txtOffice2
rst("ContractorRep2MobilPhone") = Me.txtMobil2
rst("ContractorRep2HomePhone") = Me.txtHome2
rst("TrafficControlCompanyName") = Me.cboTrafficName
rst("TrafficControlContactName") = Me.txtContact
rst("TrafficControlPhoneNumber") = Me.txtTrafficPhone
rst("ElectricalContractor") = Me.txtContractor
rst("ElectricalPhoneNumber") = Me.txt24hrElecPhone
rst("StateRep") = Me.txtStateRep
rst("StateSen") = Me.txtStateSen
rst.Update
blnChangeMade = False
rst.Close
LoadScreen
MsgBox "The record has been saved.", vbOKOnly, "Save Complete"
Exit_SaveRecord:
DoCmd.Hourglass False
Exit Sub
Err_SaveRecord:
LogErrorDAO Err.Description, CStr(Err.Number), Application.CurrentObjectName, _
"SaveRecord"
Resume Exit_SaveRecord
End Sub
Change all the rst("XXXX") to rst![XXXX] = me.etc , or do it like:
with rst
.edit
![XXX] = me.XXX
![AAA] = me.AAA
...
![something] = me.something
.update
end with
That's a start. (Note that the [] are actually only needed in cases of spaces.)

MS Project 2007 VBA to retrieve tasks and custom fields

I have a simple MS Project file (2007) with just couple of tasks.
I have also created a custom field called VBATest and assigned values to this custom field against the two project tasks.
I would like to retrieve a list of project tasks and the value assigned to the custom field like this;
ProjectTask | VBATest <--Custom field
------------|--------
Task1 | vba1
Task2 | vba2
I'm doing this from Access 2007 VBA as this is where the final code will end up.
I can get most of it working, but I can't seem to read the custom field value from the Assignments object. Do you have Any ideas?
Thanks
Here is what I have done so far.
Sub LoadProjectFile()
Dim pjApp As MSProject.Application
Dim FileToOpen
Dim Proj As MSProject.Project
Dim Project_Task As Task
Dim fd As FileDialog
Set pjApp = New MSProject.Application
If pjApp Is Nothing Then
MsgBox "Project is not installed"
End
End If
pjApp.Visible = True
AppActivate "Microsoft Project"
Set fd = Application.FileDialog(msoFileDialogFilePicker)
fd.Filters.Clear
fd.Filters.Add "Microsoft Project Files", "*.mpp"
fd.AllowMultiSelect = False
fd.Show
If (fd.SelectedItems.Count = 0) Then
'Application.GetOpenFilename("Microsoft Project Files (*.mpp), *.mpp")
pjApp.Quit
Set pjApp = Nothing
Exit Sub
End If
pjApp.FileOpen fd.SelectedItems(1)
Debug.Print "Project_Task_Name~CustomField"
Dim ass As Assignment
For Each Project_Task In pjApp.ActiveProject.Tasks
If Not Project_Task Is Nothing Then
For Each ass In Project_Task.Assignments
assignCFVal = assignCFVal & "," & ass.VBATestField '<<PROBLEM Line
Next ass
Debug.Print Project_Task.Name & "~" & assignCFVal
assignCFVal = ""
End If
Next Project_Task
pjApp.FileClose pjDoNotSave
pjApp.Quit
Set pjApp = Nothing
End Sub
It turns out I don't need to use the Assignments object for this. The SetField method would return what I need as below;
For Each Project_Task In pjApp.ActiveProject.Tasks
If Not Project_Task Is Nothing Then
assignCFVal = Project_Task.SetField(FieldNameToFieldConstant("VBATestField"))
Debug.Print Project_Task.Name & "~" & assignCFVal
End If
Next Project_Task

Importing/Exporting Relationships

I have a couple of mdb files with the exact table structure. I have to change the primary key of the main table from autonumber to number in all of them, which means I have to:
Drop the all the relationships the main table has
Change the main table
Create the relationships again,... for all the tables.
Is there any way to export the relationships from one file and importing them to all the rest?
I am sure this can be done with some macro/vb code. Does anyone has an example I could use?
Thanks.
Not a complete solution, but this may get you going...
The following function will print out the metadata for all relationships. Change this to save to a file in whatever format you prefer (CSV, tab delimited, XML, etc.):
Function PrintRelationships()
For Each rel In CurrentDb.Relations
With rel
Debug.Print "Name: " & .Name
Debug.Print "Attributes: " & .Attributes
Debug.Print "Table: " & .Table
Debug.Print "ForeignTable: " & .ForeignTable
Debug.Print "Fields:"
For Each fld In .Fields
Debug.Print "Field: " & fld.Name
Next
End With
Next
End Function
This function will drop all the relationships in the database:
Function DropRelationships()
With CurrentDb
For Each rel In .Relations
.Relations.Delete Name:=rel.Name
Next
End With
End Function
This function will create a relationship. You'll have to iterate over the file of saved relationship data.
Function CreateRelationships()
With CurrentDb
Set rel = .CreateRelation(Name:="[rel.Name]", Table:="[rel.Table]", ForeignTable:="[rel.FireignTable]", Attributes:=[rel.Attributes])
rel.Fields.Append rel.CreateField("[fld.Name for relation]")
rel.Fields("[fld.Name for relation]").ForeignName = "[fld.Name for relation]"
.Relations.Append rel
End With
End Function
Error handling and IO omitted due to time constraints (gotta put the kids to bed).
Hope this helps.
Based on #Patrick Cuff's answer, I have created a pair of scripts: one exporting into xml, other reading this xml and parsing it into the database
VBScript for exporting relationships from MsAccess into XML
'supply the Access Application object into this function and path to file to which the output should be written
Function ExportRelationships(oApplication, sExportpath)
Dim relDoc, myObj
Set relDoc = CreateObject("Microsoft.XMLDOM")
relDoc.appendChild relDoc.createElement("Relations") 'create root xml element
'loop though all the relations
For Each myObj In oApplication.CurrentDb.Relations
If Not Left(myObj.Name, 4) = "MSys" Then 'exclude system relations
Dim relName, relAttrib, relTable, relFoTable, fld
relDoc.childNodes(0).appendChild relDoc.createElement("Relation")
Set relName = relDoc.createElement("Name")
relName.Text = myObj.Name
relDoc.childNodes(0).lastChild.appendChild relName
Set relAttrib = relDoc.createElement("Attributes")
relAttrib.Text = myObj.Attributes
relDoc.childNodes(0).lastChild.appendChild relAttrib
Set relTable = relDoc.createElement("Table")
relTable.Text = myObj.Table
relDoc.childNodes(0).lastChild.appendChild relTable
Set relFoTable = relDoc.createElement("ForeignTable")
relFoTable.Text = myObj.ForeignTable
relDoc.childNodes(0).lastChild.appendChild relFoTable
'in case the relationship works with more fields
For Each fld In myObj.Fields
Dim lf, ff
relDoc.childNodes(0).lastChild.appendChild relDoc.createElement("Field")
Set lf = relDoc.createElement("Name")
lf.Text = fld.Name
relDoc.childNodes(0).lastChild.lastChild.appendChild lf
Set ff = relDoc.createElement("ForeignName")
ff.Text = fld.ForeignName
relDoc.childNodes(0).lastChild.lastChild.appendChild ff
Next
End If
Next
relDoc.insertBefore relDoc.createProcessingInstruction("xml","version='1.0'"), relDoc.childNodes(0)
relDoc.Save sExportpath
End Function
VBScript for importing relationships into MsAccess from XML
'supply the Access Application object into this function and path to file from which the input should be read
Function ImportRelationships(oApplication, sImportpath)
Dim relDoc, myObj
Set relDoc = CreateObject("Microsoft.XMLDOM")
relDoc.Load(sImportpath)
Dim xmlRel, xmlField, accessRel, relTable, relName, relFTable, relAttr, i
'loop through every Relation node inside .xml file
For Each xmlRel in relDoc.selectNodes("/Relations/Relation")
relName = xmlRel.selectSingleNode("Name").Text
relTable = xmlRel.selectSingleNode("Table").Text
relFTable = xmlRel.selectSingleNode("ForeignTable").Text
relAttr = xmlRel.selectSingleNode("Attributes").Text
'remove any possible conflicting relations or indexes
On Error Resume next
oApplication.CurrentDb.Relations.Delete (relName)
oApplication.CurrentDb.TableDefs(relTable).Indexes.Delete(relName)
oApplication.CurrentDb.TableDefs(relFTable).Indexes.Delete(relName)
On Error Goto 0
'create the relationship object
Set accessRel = oApplication.CurrentDb.CreateRelation(relName, relTable, relFTable, relAttr)
'in case the relationship works with more fields
For Each xmlField In xmlRel.selectNodes("Field")
accessRel.Fields.Append accessRel.CreateField(xmlField.selectSingleNode("Name").Text)
accessRel.Fields(xmlField.selectSingleNode("Name").Text).ForeignName = xmlField.selectSingleNode("ForeignName").Text
Next
'and finally append the newly created relationship to the database
oApplication.CurrentDb.Relations.Append accessRel
Next
End Function
Notes
Just to clarify what is expected to be passed into oApplication parameter
Set oApplication = CreateObject("Access.Application")
oApplication.NewCurrentDatabase path 'new database
oApplication.OpenCurrentDatabase path 'existing database
In case you are running this from VBA instead of VBScript, you can delete the parameter and just the regular Application object everywhere in the code where oApplication is being used.
I got started to work on this code as I needed to implement a Version Control on a very complicated MsAccess project. This post got me moving, there are also some good advices on how to export/import other parts of the MsAccess project.
It occurs to me that you can use a backup of the file made before any changes to restore the indexes and relations. Here are some notes.
Sub RunExamples()
Dim strCopyMDB As String
Dim fs As FileSystemObject
Dim blnFound As Boolean
Dim i
' This code is not intended for general users, it is sample code built '
' around the OP '
'You will need a reference to the Microsoft DAO 3.x Object Library '
'This line causes an error, but it will run '
'It is not suitable for anything other than saving a little time '
'when setting up a new database '
Application.References.AddFromFile ("C:\Program Files\Common Files\Microsoft Shared\DAO\dao360.dll")
'You must first create a back-up copy '
Set fs = CreateObject("Scripting.FileSystemObject")
strCopyMDB = CurrentProject.Path & "\c.mdb"
blnFound = fs.FileExists(strCopyMDB)
i = 0
Do While blnFound
strCopyMDB = CurrentProject.Path & "\c" & i & ".mdb"
blnFound = fs.FileExists(strCopyMDB)
Loop
fs.CopyFile CurrentProject.FullName, strCopyMDB
ChangeTables
AddIndexesFromBU strCopyMDB
AddRelationsFromBU strCopyMDB
End Sub
Sub ChangeTables()
Dim db As Database
Dim tdf As DAO.TableDef
Dim rel As DAO.Relation
Dim fld As DAO.Field
Dim ndx As DAO.Index
Dim i
Set db = CurrentDb
'In order to programmatically change an autonumber, '
'it is necessary to delete any relationships that '
'depend on it. '
'When deleting from a collection, it is best '
'to iterate backwards. '
For i = db.Relations.Count - 1 To 0 Step -1
db.Relations.Delete db.Relations(i).Name
Next
'The indexes must also be deleted or the '
'number cannot be changed. '
For Each tdf In db.TableDefs
If Left(tdf.Name, 4) <> "Msys" Then
For i = tdf.Indexes.Count - 1 To 0 Step -1
tdf.Indexes.Delete tdf.Indexes(i).Name
Next
tdf.Indexes.Refresh
For Each fld In tdf.Fields
'If the field is an autonumber, '
'use code supplied by MS to change the type '
If (fld.Attributes And dbAutoIncrField) Then
AlterFieldType tdf.Name, fld.Name, "Long"
End If
Next
End If
Next
End Sub
Sub AddIndexesFromBU(MDBBU)
Dim db As Database
Dim dbBU As Database
Dim tdf As DAO.TableDef
Dim tdfBU As DAO.TableDef
Dim ndx As DAO.Index
Dim ndxBU As DAO.Index
Dim i
Set db = CurrentDb
'This is the back-up made before starting '
Set dbBU = OpenDatabase(MDBBU)
For Each tdfBU In dbBU.TableDefs
'Skip system tables '
If Left(tdfBU.Name, 4) <> "Msys" Then
For i = tdfBU.Indexes.Count - 1 To 0 Step -1
'Get each index from the back-up '
Set ndxBU = tdfBU.Indexes(i)
Set tdf = db.TableDefs(tdfBU.Name)
Set ndx = tdf.CreateIndex(ndxBU.Name)
ndx.Fields = ndxBU.Fields
ndx.IgnoreNulls = ndxBU.IgnoreNulls
ndx.Primary = ndxBU.Primary
ndx.Required = ndxBU.Required
ndx.Unique = ndxBU.Unique
' and add it to the current db '
tdf.Indexes.Append ndx
Next
tdf.Indexes.Refresh
End If
Next
End Sub
Sub AddRelationsFromBU(MDBBU)
Dim db As Database
Dim dbBU As Database
Dim rel As DAO.Relation
Dim fld As DAO.Field
Dim relBU As DAO.Relation
Dim i, j, f
On Error GoTo ErrTrap
Set db = CurrentDb
'The back-up again '
Set dbBU = OpenDatabase(MDBBU)
For i = dbBU.Relations.Count - 1 To 0 Step -1
'Get each relationship from bu '
Set relBU = dbBU.Relations(i)
Debug.Print relBU.Name
Set rel = db.CreateRelation(relBU.Name, relBU.Table, relBU.ForeignTable, relBU.Attributes)
For j = 0 To relBU.Fields.Count - 1
f = relBU.Fields(j).Name
rel.Fields.Append rel.CreateField(f)
rel.Fields(f).ForeignName = relBU.Fields(j).ForeignName
Next
'For some relationships, I am getting error'
'3284 Index already exists, which I will try'
'and track down tomorrow, I hope'
'EDIT: Apparently this is due to Access creating hidden indexes
'and tracking these down would take quite a bit of effort
'more information can be found in this link:
'http://groups.google.ie/group/microsoft.public.access/browse_thread/thread/ca58ce291bdc62df?hl=en&ie=UTF-8&q=create+relation+3284+Index+already+exists
'It is an occasional problem, so I've added an error trap
'Add the relationship to the current db'
db.Relations.Append rel
Next
ExitHere:
Exit Sub
ErrTrap:
If Err.Number = 3284 Then
Debug.Print relBU.Name, relBU.Table, relBU.ForeignTable, relBU.Attributes
Resume Next
Else
'this is not a user sub, so may as well ... '
Stop
End If
End Sub
Sub AlterFieldType(TblName As String, FieldName As String, _
NewDataType As String)
'http://support.microsoft.com/kb/128016'
Dim db As Database
Dim qdf As QueryDef
Set db = CurrentDb()
' Create a dummy QueryDef object.'
Set qdf = db.CreateQueryDef("", "Select * from PROD1")
' Add a temporary field to the table.'
qdf.SQL = "ALTER TABLE [" & TblName & "] ADD COLUMN AlterTempField " & NewDataType
qdf.Execute
' Copy the data from old field into the new field.'
qdf.SQL = "UPDATE DISTINCTROW [" & TblName _
& "] SET AlterTempField = [" & FieldName & "]"
qdf.Execute
' Delete the old field.'
qdf.SQL = "ALTER TABLE [" & TblName & "] DROP COLUMN [" _
& FieldName & "]"
qdf.Execute
' Rename the temporary field to the old field's name.'
db.TableDefs("[" & TblName & "]").Fields("AlterTempField").Name = FieldName
End Sub
Thanks for code snippet.
to get rid of your 3284 error I have changed a few things.
If you copy all indexes from sample mdb and then try to put relationships it throws an exception as it expects no idexes for relationshisps when you put relationships it puts its own indexes.
Steps I followed are (assume target.mdb and source.mdb):
Run this code in target.mdb remove all indexes and relationsships
frmo target.mdb by calling ChangeTables
Call AddIndexesFromBU source.mdb and use condition
If ndxBU.Unique Then tdf.Indexes.Append ndx End If this willput just Unique index
call AddRelationsFromBU source.mdb and put all relationsships
Call again AddIndexesFromBU source.mdb and change condition to If
not ndxBU.Unique Then
I have also added error trap same as AddRelationsFromBU in AddIndexesFromBU and resume next for if ans else
This worked for me.