Here’s another common request.
Say you have a contacts report and like to print each client’s individual report separately as a PDF. How can this be done exactly? A concrete example might be a billing database in which you have an Invoice report (which would be comprised of all the records) and you’d like to print each invoice separately. Or just the outstanding invoices… you get the idea.
Generating PDFs on the Hard Drive
Approach 1 – Individual Reports
The General Concept
The general concept is pretty straightforward:
- Defined a recordsource that you’re going to use to filter the report to get those records you seek
- Open the report filtered based on the above
- Print the report as a PDF
- Close the report
- Repeat for each record in 1
A Concrete Example
Enough with the blah blah, below is some sample code to inspire yourself from
Private Sub Command0_Click()
Dim rs As DAO.Recordset
Dim sFolder As String
Dim sFile As String
Const sReportName = "NameOfYourReport"
On Error GoTo Error_Handler
'The folder in which to save the PDFs
sFolder = Application.CurrentProject.Path & "\"
'Define the Records that you will use to filtered the report with
Set rs = CurrentDb.OpenRecordset("SELECT ContactID, FirstName FROM Contacts;", dbOpenSnapshot)
With rs
If .RecordCount <> 0 Then 'Make sure we have record to generate PDF with
.MoveFirst
Do While Not .EOF
'Build the PDF filename we are going to use to save the PDF with
sFile = sFolder & Nz(![FirstName], "") & ".pdf"
'Open the report filtered to the specific record or criteria we want in hidden mode
DoCmd.OpenReport sReportName, acViewPreview, , "[ContactID]=" & ![ContactID], acHidden
'Print it out as a PDF
DoCmd.OutputTo acOutputReport, sReportName, acFormatPDF, sFile, , , , acExportQualityPrint
'Close the report now that we're done with this criteria
DoCmd.Close acReport, sReportName
'If you wanted to create an e-mail and include an individual report, you would do so now
.MoveNext
Loop
End If
End With
'Open the folder housing the PDF files (Optional)
Application.FollowHyperlink sFolder
Error_Handler_Exit:
On Error Resume Next
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If
Exit Sub
Error_Handler:
If Err.Number <> 2501 Then 'Let's ignore user cancellation of this action!
MsgBox "The following error has occurred" & vbCrLf & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & _
"Error Source: Command0_Click" & vbCrLf & _
"Error Description: " & Err.Description & _
Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
, vbOKOnly + vbCritical, "An Error has Occurred!"
End If
Resume Error_Handler_Exit
End Sub
Approach 2 – A Single Filtered Report
The General Concept
The general concept is pretty straightforward:
- Defined a recordsource that you’re going to use to filter the report to get those records you seek
- Open the report to include all the records
- Apply a filter to limit the report
- Print the report as a PDF
- Repeat 3 & 4 for each record in 1
- Close the report
A Concrete Example
Private Sub Command1_Click()
Dim rs As DAO.Recordset
Dim rpt As Access.Report
Dim sFolder As String
Dim sFile As String
Const sReportName = "NameOfYourReport"
On Error GoTo Error_Handler
'The folder in which to save the PDFs
sFolder = Application.CurrentProject.Path & "\"
Set rs = CurrentDb.OpenRecordset("SELECT ContactID, FirstName FROM Contacts;", dbOpenSnapshot)
With rs
If .RecordCount <> 0 Then 'Make sure we have record to generate PDF with
'Open the Report
DoCmd.OpenReport sReportName, acViewPreview, , , acHidden
'Define a report object so we can manipulate it below
Set rpt = Reports(sReportName).Report
.MoveFirst
Do While Not .EOF
'Build the PDF filename we are going to use to save the PDF with
sFile = Nz(![firstname], "") & ".pdf"
sFile = sFolder & sFile
'filter the report to the specific record or criteria we want
rpt.Filter = "[ContactID]=" & ![ContactID]
rpt.FilterOn = True
DoEvents 'This is critical or else the filter isn't applied!!!!
'Print it out as a PDF
DoCmd.OutputTo acOutputReport, sReportName, acFormatPDF, sFile, , , , acExportQualityPrint
'If you wanted to create an e-mail and include an individual report, you would do so now
.MoveNext
Loop
'Close the report now that we're done with this criteria
DoCmd.Close acReport, sReportName
End If
End With
'Open the folder housing the PDF files (Optional)
Application.FollowHyperlink sFolder
Error_Handler_Exit:
On Error Resume Next
If Not rpt Is Nothing Then Set rpt = Nothing
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If
Exit Sub
Error_Handler:
If Err.Number <> 2501 Then 'Let's ignore user cancellation of this action!
MsgBox "The following error has occurred" & vbCrLf & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & _
"Error Source: Command1_Click" & vbCrLf & _
"Error Description: " & Err.Description & _
Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
, vbOKOnly + vbCritical, "An Error has Occurred!"
End If
Resume Error_Handler_Exit
End Sub
I haven’t had a chance to validate which approach is fastest. I suspect the 2nd approach would be best.
Some Important Things to Notes
What’s important to understand is that by controlling the line
Set rs = CurrentDb.OpenRecordset("SELECT ContactID, FirstName FROM Contacts;", dbOpenSnapshot)
You control which pages of the report you generate as PDF.
Say you wanted only the Active Contacts you might do
Set rs = CurrentDb.OpenRecordset("SELECT ContactID, FirstName FROM Contacts WHERE Active=True;", dbOpenSnapshot)
Or you wanted just the Canadian client
Set rs = CurrentDb.OpenRecordset("SELECT ContactID, FirstName FROM Contacts WHERE Country='Canada';", dbOpenSnapshot)
You get the idea.
Using This Technique With SendObject
You can adapt the above to even work with SendObject. By iterating over the recordset, we can then simply change the caption for each e-mail.
Sub CreateFilteredPDFsWithName()
On Error GoTo Error_Handler
Dim rs As DAO.Recordset
Const sReportName = "NameOfYourReport"
'Define the Records that you will use to filtered the report with
Set rs = CurrentDb.OpenRecordset("SELECT ID, FirstName, LastName FROM Contacts;", dbOpenSnapshot)
With rs
If .RecordCount <> 0 Then 'Make sure we have record to generate PDF with
.MoveFirst
Do While Not .EOF
'Open the report filtered to the specific record or criteria we want in hidden mode
DoCmd.OpenReport sReportName, acViewPreview, , "[ContactID]=" & ![ContactID], acHidden
'Change the Report's Caption (which is used as the FileName by SendObject)
Reports(sReportName).Report.Caption = Nz(![FirstName], "") & "_" & Nz(![LastName], "") & "_" & Format(Now(), "yyyymmddhhnnss")
'Generate The Email
DoCmd.SendObject acSendReport, sReportName, acFormatPDF, , , , , , True
'Close the report now that we're done with this criteria
DoCmd.Close acReport, sReportName
.MoveNext
Loop
End If
End With
Error_Handler_Exit:
On Error Resume Next
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If
Exit Sub
Error_Handler:
If Err.Number = 2501 Then
'ignore error proceed with the next record
Resume Next
'Or exit nicely
' Resume Error_Handler_Exit
Else
MsgBox "The following error has occurred" & vbCrLf & vbCrLf & _
"Error Source: CreateFilteredPDFsWithName2" & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & _
"Error Description: " & Err.Description & _
Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
, vbOKOnly + vbCritical, "An Error has Occurred!"
Resume Error_Handler_Exit
End If
End Sub
THX for the code.
I want every distinct pdf to have a specific name ex. 1.pdf , 2.pdf, etc.
The solution I came up with is
DoCmd.CopyObject , M_distinct_name, acReport, M_original_report_name.
DoCmd.OpenReport M_distinct_name, acViewPreview, , M_where
Is there an other solution?
Second question: I would like to store the pdf in a SQL-database. Anybody who has experience with this?
Kind regards,
Geert.
Hi,
codecabinett.com means to that:
open the report first (hidden) then print as pdf
Public Sub ExportFilteredReportToPDF()
Dim reportName As String
Dim fileName As String
Dim criteria As String
reportName = “rptYourReportName”
fileName = “C:\tmp\report_export_file.pdf”
criteria = “SomeTextField = ‘ABC’ AND SomeNumberField = 123”
DoCmd.OpenReport reportName, acViewPreview, , criteria, acHidden
DoCmd.OutputTo acOutputReport, reportName, acFormatPDF, fileName
DoCmd.Close acReport, reportName, acSaveNo
End Sub
mfg Klaus
You are great! I’ve resolved my problem with this script that works very well. Thank you very much!
How do I loop this code based on a single entry start and end date?
It’s kind of hard to give you an answer without context here, but you should be able to simply alter the SQL statement to include whatever WHERE clause filters your data as you need it to be.
SELECT ContactID, FirstName FROM Contacts WHERE [ActivationDate] BETWEEN #2000-01-01 00:00:00# AND #2022-08-12 14:30:00#Basically, build a normal query that gives you the results you are after and then simply use it in the function.
Daniel,
Thank you for the response, let me see if I can explain it better.
I currently have a report and in the query I have it asking for the date range criteria “Between [Start Date] And [End Date]” in the “HQ Posted” field. This works when you run the repot normally but when I plug the report into the code above and run it that way it asks for the criteria on every loop.
I’d like to set the criteria within the code instead of the query and have it run the loops retaining the single input range.
Here is the current code
Private Sub NavigationButton31_Click() Dim rs As DAO.Recordset Dim sFolder As String Dim sFile As String Const sReportName = "Rpt_FeesInvoice" On Error GoTo Error_Handler 'The folder in which to save the PDFs sFolder = Application.CurrentProject.Path & "\Invoices\" 'Define the Records that you will use to filtered the report with Set rs = CurrentDb.OpenRecordset("SELECT BranchID, BranchName FROM AllACHCC;", dbOpenSnapshot) With rs If .RecordCount 0 Then 'Make sure we have record to generate PDF with .MoveFirst Do While Not .EOF 'Build the PDF filename we are going to use to save the PDF with sFile = sFolder & Nz(![BranchName], "") & ".pdf" 'Open the report filtered to the specific record or criteria we want in hidden mode DoCmd.OpenReport sReportName, acViewPreview, , "[BranchID]=" & ![BranchID], acHidden 'Print it out as a PDF DoCmd.OutputTo acOutputReport, sReportName, acFormatPDF, sFile, , , , acExportQualityPrint 'Close the report now that we're done with this criteria DoCmd.Close acReport, sReportName 'If you wanted to create an e-mail and include an individual report, you would do so now .MoveNext Loop End If End With 'Open the folder housing the PDF files (Optional) Application.FollowHyperlink sFolder Error_Handler_Exit: On Error Resume Next If Not rs Is Nothing Then rs.Close Set rs = Nothing End If Exit Sub Error_Handler: If Err.Number 2501 Then 'Let's ignore user cancellation of this action! MsgBox "The following error has occurred" & vbCrLf & vbCrLf & _ "Error Number: " & Err.Number & vbCrLf & _ "Error Source: Command0_Click" & vbCrLf & _ "Error Description: " & Err.Description & _ Switch(Erl = 0, "", Erl 0, vbCrLf & "Line No: " & Erl) _ , vbOKOnly + vbCritical, "An Error has Occurred!" End If Resume Error_Handler_Exit End SubNow I get the issue, and I should have coded for that. The issue is explained in my article MS Access – VBA – Run Parameter Query in VBA. Implement that approach and everything should run smoothly.
Thank you.
Now to learn how to do that next, Sadly I know enough VBA to be dangerous.
Do I run that in the current sub before the rs or within it?
Try something like:
Private Sub NavigationButton31_Click() Dim db As DAO.Database Dim qdf As DAO.QueryDef Dim prm As DAO.Parameter Dim rs As DAO.Recordset Dim sFolder As String Dim sFile As String Const sReportName = "Rpt_FeesInvoice" On Error GoTo Error_Handler 'The folder in which to save the PDFs sFolder = Application.CurrentProject.Path & "Invoices" 'Define the Records that you will use to filtered the report with Set db = CurrentDb Set qdf = db.QueryDefs("AllACHCC") For Each prm In qdf.Parameters prm = Eval(prm.Name) Next prm Set rs = qdf.OpenRecordset ' Set rs = CurrentDb.OpenRecordset("SELECT BranchID, BranchName FROM AllACHCC;", dbOpenSnapshot) With rs If .RecordCount <> 0 Then 'Make sure we have record to generate PDF with .MoveFirst Do While Not .EOF 'Build the PDF filename we are going to use to save the PDF with sFile = sFolder & Nz(![BranchName], "") & ".pdf" 'Open the report filtered to the specific record or criteria we want in hidden mode DoCmd.OpenReport sReportName, acViewPreview, , "[BranchID]=" & ![BranchID], acHidden 'Print it out as a PDF DoCmd.OutputTo acOutputReport, sReportName, acFormatPDF, sFile, , , , acExportQualityPrint 'Close the report now that we're done with this criteria DoCmd.Close acReport, sReportName 'If you wanted to create an e-mail and include an individual report, you would do so now .MoveNext Loop End If End With 'Open the folder housing the PDF files (Optional) Application.FollowHyperlink sFolder Error_Handler_Exit: On Error Resume Next If Not rs Is Nothing Then rs.Close Set rs = Nothing End If If Not prm Is Nothing Then Set prm = Nothing If Not qdf Is Nothing Then Set qdf = Nothing If Not db Is Nothing Then Set db = Nothing Exit Sub Error_Handler: If Err.Number <> 2501 Then 'Let's ignore user cancellation of this action! MsgBox "The following error has occurred" & vbCrLf & vbCrLf & _ "Error Number: " & Err.Number & vbCrLf & _ "Error Source: NavigationButton31_Click" & vbCrLf & _ "Error Description: " & Err.Description & _ Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _ , vbOKOnly + vbCritical, "An Error has Occurred!" End If Resume Error_Handler_Exit End Subor, your other choice would be to alter the underlying query itself, so it use Eval() directly eliminating the need to do this in VBA.
Good luck, I’ve gotta run.
Thanks for posting that sample code. I was able to get things working in my access database with only a couple of minor tweaks.
I was initially having some troubles getting the filter to work, but when I switched from using a text value over to an integer number value for the record ID, that seem to make all the difference.
I am having a little bit of trouble still with getting the sub reports to show up in the PDF files that get pushed out to my network drive.
My main report is seven pages long, and there are sub reports on each of the individual pages that display or hide based on a variable on the main report.
It all works properly when I preview the print job in Access, but now when I’m pushing them out as individual PDF files, the sub reports don’t show up.