2012年11月9日

Excel VBA直接列印PDF (2012/11/08)

科技始終來自於人性.......的懶散

這幾天的專案,必須將上市櫃公司的財務分析報告從Excel轉成PDF呈現。只有幾家時還好辦,但是,一口氣要產生一千多家公司的PDF,這就有點...不好辦。

Excel VBA雖然可以指定PDF Printer列印,但這些PDF Printer (CutePDF, pdfCreator, etc.)都會詢問儲存的檔名,顯然不是我們想要的"科技"的方法。怎樣可以用VBA控制PDF Printer呢?

照例,"行動之前要有規劃,規劃之前要有拜神"。根據大神的隱喻
1. 免費的選項,只有pdfCreator
2. pdfCreator的官網有寫"Use the COM object to control PDFCreator from your application"。
3. Ken puls, 唯一找到的作者寫出如何用VBA控制pdfCreator的程式碼,也有許多人引用。

這麼充分的資料,想必手到擒來。可是.......

狀況1 - 怎麼列印出來的PDF內容是空白呢?(狀況排除,二天)
大神的神喻說,好像印Chart時會有Error...但,財報分析能不有圖嗎?
大神的神喻又說,pdfCreator較新的版本,出現當檔名中有中文,就會列印出空白頁的問題。
see TOOL - PDFCREATOR 輸出 PDF 檔案內容為空白
所以,要改回採用1.2.3版本,現在都1.5版了,哪兒去找呢?
大神又說了:Sourceforg

狀況2 - 怎麼列印出來的PDF比我想要的頁數還多呢?(狀況排除,二天)
看到Excel VBA火速的印出所要的PDF檔,眼淚都快流出來,真是痛快。
等等,我只要印20頁啊,怎麼印了30頁?
我看著程式碼,程式碼也看著我,明明頁數就是設定20頁。
想說,是不是pdfCreator看到超過某個頁數,就會情不自禁的全部列印。
難道,又是個Bug?這不是莊孝維嗎?
趕緊再拜大神,這次...是笑杯 (就是沒有神喻)
好吧,一頁一頁加加看。
就在增加到某個頁數時,果然開始狂印很多頁出來。。。哈哈哈,果然是個Bug。這這這不是白搭了?
我再看著程式碼,程式碼也在看著我,這個神奇的頁數突然雷擊了我,原來,它是代表"工作表數",而非"頁數" ...
大神的笑杯,真是有道理。

狀況3 - 怎麼列印出來的PDF的各頁順序與Excel工作表順序不同呢?(狀況排除,一天)
看著Excel VBA源源不絕的吐出各家公司的財報分析圖表PDF,真是做夢時都會笑醒。(科技始終來自於人性.......的懶散,記得嗎??)
次日早上,收割看看。
嗯...很好,頁數正確。嗯...很好...嗯,等勒,順序怎麼不一樣。而且每個PDF的頁順序還很隨機呢?
這次莊孝維大了,我要一個隨機出頁的PDF要幹麻?
趕緊再拜大神。
"pdfCreator 順序" - 笑杯
"pdfCreator 頁次" - 笑杯
"pdfCreator 次序亂掉" - 笑杯
"pdfCreator 混亂" - 笑杯
"pdfCreator 亂了" - 笑杯
"pdfCreator 隨機" - 笑杯
........

看來,跟大神溝通,要換說英文
"pdfCreator order" - Bingo,Ken Puls

我再看著程式碼,程式碼也在看著我,它很滿意的跑著。

天,也亮了。


[分享 - 經修改後的VBA程式碼,保留原作者相關資訊]


Sub PrintToPDF_MultiSheetToOne_Late(sPDFPath, sPDFName, lTtlSheets)
   ' ================
   ' Stephen修改重點
   ' 1. 允許傳入路徑(路徑最後要加"\")、檔名(檔名就好,不用加".pdf")、列印工作表數量(注意,非頁數)
   ' 2. 補充讓PDF能按照原來順序列印 (Ken Puls在論壇的回覆)
   ' ================
   'Author       : Ken Puls (www.excelguru.ca)
   'Macro Purpose: Print to PDF file using PDFCreator
   '   (Download from http://sourceforge.net/projects/pdfcreator/)
   '   Designed for late bind, no references req'd
    Dim pdfjob As Object
    'Dim sPDFName As String
    'Dim sPDFPath As String
    Dim lSheet As Long
    'Dim lTtlSheets As Long

    '/// Change the output file name here!  ///
    Set pdfjob = CreateObject("PDFCreator.clsPDFCreator")

    'Make sure the PDF printer can start
    If pdfjob.cStart("/NoProcessingAtStartup") = False Then
        MsgBox "Can't initialize PDFCreator.", vbCritical + _
            vbOKOnly, "Error!"
        Exit Sub
    End If

    'Set all defaults
    With pdfjob
        .cOption("UseAutosave") = 1
        .cOption("UseAutosaveDirectory") = 1
        .cOption("AutosaveDirectory") = sPDFPath
        .cOption("AutosaveFilename") = sPDFName
        .cOption("AutosaveFormat") = 0    ' 0 = PDF
        .cClearCache
    End With
 
    'Delete the PDF if it already exists
    If Dir(sPDFPath & sPDFName & ".pdf") = sPDFName & ".pdf" Then Kill (sPDFPath & sPDFName & ".pdf")

    'Print the document to PDF
    For lSheet = 1 To lTtlSheets
        On Error Resume Next 'To deal with chart sheets
        If Not IsEmpty(Application.Sheets(lSheet).UsedRange) Then
            Application.Sheets(lSheet).PrintOut copies:=1, ActivePrinter:="PDFCreator"
            '
            ' Stephen - 此段是讓PDF列印能按照順序的重點
            '
            '<-- added="added" before="before" check="check" code="code" entered="entered" has="has" if="if" moving="moving" on="on" p="p" queue="queue" sheet="sheet" to="to">            'Wait until job has entered PDF queue
            Do Until pdfjob.cCountOfPrintjobs = lSheet
                DoEvents
            Loop
            '-- Code modification ends -->
        Else
            lTtlSheets = lTtlSheets - 1
        End If
        On Error GoTo 0
    Next lSheet

    'Wait until all print jobs have entered the print queue
    Do Until pdfjob.cCountOfPrintjobs = lTtlSheets
        DoEvents
    Loop

    'Combine all PDFs into a single file and start the printer
    With pdfjob
        .cCombineAll
        .cPrinterStop = False
    End With

    'Wait until the file shows up before closing PDF Creator
    Do
        DoEvents
    Loop Until Dir(sPDFPath & sPDFName & ".pdf") = sPDFName + ".pdf"
    pdfjob.cClose
    Set pdfjob = Nothing
End Sub







沒有留言: