前言:為客戶(hù)的項目編寫(xiě)(將WORD文檔轉換成PDF格式)
需求分析:客戶(hù)的項目以B/S結構為主,提供一個(gè)WORD文件在后臺自動(dòng)轉換成PDF,經(jīng)過(guò)實(shí)際測試,如果該篇WORD文檔有100多頁(yè)的話(huà),轉換需要20分鐘左右的時(shí)間(環(huán)境:CPU是奔騰M 1.6G,512M內存),整個(gè)CPU的占用率近乎95%~100%,此結果告訴客戶(hù)以后,客戶(hù)提議:到客戶(hù)下班后,自動(dòng)轉換PDF,同時(shí)如果使用人確認要查看該PDF文檔,如果沒(méi)有轉換,提供給客戶(hù)選擇,是現在轉換成PDF,還是由服務(wù)器在客戶(hù)下班后,自動(dòng)轉換。
項目功能:按需求分析要寫(xiě)兩個(gè)功能
第一為:B/S結構后臺轉換,要提交給客戶(hù)選擇
第二為:Windows服務(wù)自動(dòng)轉換WORD文檔到PDF
這兩個(gè)分類(lèi):核心的轉換程序都是采用線(xiàn)程的方式執行,只不過(guò)第一個(gè)功能是針對一個(gè)WORD文件,第二個(gè)功能針對所有未轉換的WORD文檔.
分析到現在:我們開(kāi)始實(shí)戰轉換了!
一:必備工具
安裝必須的工具M(jìn)S VS.Net2003,MS Office2003,Adobe Acrobat 7.0 Professional,postscript.exe,gs811w32.exe
MS VS.Net2003的安裝不說(shuō)明
MS Office2003的安裝不說(shuō)明
Adobe Acrobat 7.0 Professional安裝說(shuō)明
運行setup.exe文件,出現輸入序列號,就運行注冊機,用鼠標在第一行刷下就可以看見(jiàn)序列號,復制粘貼到Adobe Acrobat 7.0 Professional安裝程序對話(huà)框,安裝到最后出現注冊時(shí),點(diǎn)擊PHONE...將安裝程序中顯示的第二行序列號(第一行是剛才注冊機生成的序列號)復制粘貼到注冊機的第二行,點(diǎn)擊右邊的按鈕,再用鼠標刷第三行授權號就出來(lái)了,將其復制粘貼到安裝程序的最后一行,完成安裝注冊!
postscript.exe默認安裝就可以了,它是一個(gè)PDF轉換時(shí)所需要的腳本
gs811w32.exe默認安裝就可以,它其實(shí)是個(gè)PDF虛擬打印機的驅動(dòng)
二:配置虛擬打印機
進(jìn)入Windows的控制面板,進(jìn)入打印機,點(diǎn)擊"添加打印機"圖標.在安裝對話(huà)框上"按一步",出現選擇打印機時(shí),在制造商一欄中選擇"Generic",在打印機一欄中,選擇"MS Publisher Color Printer",然后一路按下一步,知道安裝結束.
三:開(kāi)始寫(xiě)第一個(gè)程序(腳本程序)
為什么要使用腳本程序進(jìn)行轉換呢,其實(shí)實(shí)際測試過(guò)程中,使用PDF Distiller的對象引用到C#后,轉換成功,但整個(gè)PDF Distiller對象不能釋放,第二次再轉換時(shí),就發(fā)生了錯誤,故此處使用腳本程序實(shí)現轉換.這樣我們只要在C#的程序中調用腳本程序就可以實(shí)現WORD到PDF的轉換。
宿主腳本文件名:ConvertDoc2PDF.js
腳本文件內容:
var files = WScript.Arguments;
var fso = new ActiveXObject("Scripting.FileSystemObject");
var word = new ActiveXObject("Word.Application");
var PDF = new ActiveXObject("PDFDistiller.PDFDistiller.1");
word.ActivePrinter = "MS Publisher Color Printer";
//files(0) 為WORD文檔文件名
//files(1) 為,轉換后需要保存的路徑
//調用fso.GetBaseName(files(0))后,為無(wú)路徑,無(wú)擴展名,的文件名
//files.length為文件參數的個(gè)數,使用循環(huán)可以支持多個(gè)WORD文檔的轉換
var docfile = files(0);
var psfile = files(1) + fso.GetBaseName(files(0)) + ".ps";
var pdffile = files(1) + fso.GetBaseName(files(0)) + ".pdf";
var logfile = files(1) + fso.GetBaseName(files(0)) + ".log";
try{
var doc = word.Documents.Open(docfile);
//WORD文件轉成PS文件;
word.PrintOut(false, false, 0, psfile);
doc.Close(0);
//PS文件轉成PDF文件;
PDF.FileToPDF(psfile,pdffile,"");
fso.GetFile(psfile).Delete();//刪除PS腳本文件
fso.GetFile(logfile).Delete();//刪除轉換的日志文件
word.Quit();
WScript.Echo("isuccess");//成功
WScript.Quit(0);
}
catch(x)
{
word.Quit();
WScript.Echo("isfail");//失敗
WScript.Quit(0);
}
然后測試該腳本程序
啟動(dòng)MS-DOS,輸入如下命令:
c:\>cscript //nologo c:\ConvertDoc2PDF.js c:\test.doc c:\
說(shuō)明:
運行成功后將看到test.pdf文檔了
c:\test.doc參數對應的是腳本程序中的files(0)
c:\參數對應的是腳本程序中的files(1)
你可以安照該腳本改寫(xiě)成,支持多個(gè)參數,使用FOR循環(huán),一次轉換多個(gè)WORD文檔,此處沒(méi)有使用多個(gè)文件轉換功能,是考慮到,該段腳本放在C#的線(xiàn)程中執行,這樣一來(lái)也可以轉換多個(gè)WORD文檔.
四:使用C#調用ConvertDoc2PDF.js腳本
新建一個(gè)C#的WINDOWS應用程序,添加一個(gè)按鈕button1
添加一個(gè)函數,函數名StartConvertPDF
public void StartConvertPDF()
{
Process proc = new Process();
proc.StartInfo.FileName = "cmd.exe";
proc.StartInfo.WorkingDirectory = @"c:\";
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardInput = true; //輸入重定向
proc.Start();
proc.StandardInput.WriteLine(@"cscript //nologo c:\ConvertDoc2PDF.js c:\test.doc c:\");
proc.StandardInput.WriteLine("exit");
proc.WaitForExit();
}
然后在按鈕的CLICK事件中添加調用線(xiàn)程的代碼
private void button1_Click(object sender, System.EventArgs e)
{
//定義線(xiàn)程序
Thread thConvert = new Thread(new ThreadStart(StartConvertData));
thConvert.Start();
}
注意:在測試上面的C#程序時(shí),必須添加如下命名空間
using System.Diagnostics;
using System.Threading;
五:健壯的C#調用代碼(實(shí)際考慮,可放在B/S系統中)
完成第4步的C#測試后,細心的讀者,可能看到一點(diǎn)問(wèn)題,那就是如何得到腳本運行后輸出的結果,如何給線(xiàn)程中調用的StartConvertData方法傳遞參數
1:傳遞參數,此話(huà)說(shuō)來(lái)也可用一篇教程告訴大家線(xiàn)程中方法如何來(lái)傳遞參數,現在就講一個(gè)方案,此種方案很多,我采用一個(gè)類(lèi),初始化這個(gè)類(lèi),然后調用該類(lèi)的方法作為線(xiàn)程執行的方法
2:得到腳本的輸出結果,使用Process對象的輸出重定向,就是說(shuō)改變輸出方向,使腳本不輸出到控制臺(MS-DOS窗口),而是重定向輸出到C#程序中,并采用線(xiàn)程的異步回調方法,顯示腳本運行結果。
添加一個(gè)新類(lèi),類(lèi)名為T(mén)oPdf
using System;
using System.Diagnostics;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
namespace Doc2Pdf
{
public class ToPdf
{
private string strWord = "";//此處的WORD文件不含路徑
private string sPath = "";
public string sExecResult = "";
public bool bSuccess = false;
public ToPdf(string sParamWord,string sParamPath)
{
strWord = sParamWord;
sPath = sParamPath;
}
public void StartConvertPDF()
{
Process proc = new Process();
proc.StartInfo.FileName = "cmd.exe";
proc.StartInfo.WorkingDirectory = sPath;
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardInput = true;//標準輸入重定向
proc.StartInfo.RedirectStandardOutput = true;//標準輸出重定向
proc.Start();
proc.StandardInput.WriteLine("cscript //nologo "+sPath+"ConvertDoc2PDF.js "+sPath+strWord+ " "+sPath);
proc.StandardInput.WriteLine("exit");
sExecResult = proc.StandardOutput.ReadToEnd();//返回腳本執行的結果
proc.WaitForExit();
proc.Close();
}
public void EndConvertPDF(System.IAsyncResult ar)//ar參數必須寫(xiě),是線(xiàn)程執行完成后的回調函數
{
if(sExecResult.IndexOf("isuccess")!=-1)bSuccess=true;
else if(sExecResult.IndexOf("isfail")!=-1)bSuccess=false;
//如果放在B/S系統,你可以在此處寫(xiě)數據庫,是成功還是失敗,并用一個(gè)WEBService程序不斷檢查數據庫,此WEBService程序不放在該回調用函數中
//如果放在C/S系統,回調函數可以不放在類(lèi)中,以便在窗體程序中調用結果
}
}
}
改寫(xiě)原來(lái)的button1_Click事件中的代碼
private void button1_Click(object sender, System.EventArgs e)
{
ToPdf my2Pdf = new ToPdf("test.doc","c:\\");
ThreadStart thStartConvert = new ThreadStart(my2Pdf.StartConvertPDF); //開(kāi)始異步調用線(xiàn)程
thStartConvert.BeginInvoke(new AsyncCallback(my2Pdf.EndConvertPDF),null);//設置異步線(xiàn)程的回調函數
//如果需要轉換多個(gè)WORD,你可以用循環(huán)
//如果是B/S系統,可以將本段代碼放在A(yíng)SPX中,并結合客戶(hù)端的無(wú)刷新顯示數據的技術(shù),不斷訪(fǎng)問(wèn)WEBService程序,以確定PDF是否轉換成功或失敗
}
六:編寫(xiě)更加健壯的C#調用代碼(實(shí)際考慮,可放在WINDOWS的服務(wù)程序中)
實(shí)際使用時(shí),由于轉化PDF時(shí)CPU的占用率很高,考慮只在同一時(shí)間轉換一篇WORD文檔,放棄異步線(xiàn)程的回調函數的使用,考慮一個(gè)WINDOWS的服務(wù)程序。
寫(xiě)一個(gè)函數CheckData2Convert(),不斷的檢查沒(méi)有轉換的WORD文檔,并使用循環(huán)調用ToPdf類(lèi)中執行轉換方法StartConvertPDF
//以下給出,泛代碼,用戶(hù)按照自己的需求,填寫(xiě)完整即可
//bool bStart為全局變量,控制循環(huán)的進(jìn)入與退出
//例:18:30開(kāi)始檢查并轉換,那么18:30時(shí),bStart=true;并啟動(dòng)轉換線(xiàn)程
//6:30停止轉換線(xiàn)程,bStart=fasle;
private void CheckData2Convert()
{
//檢查指定目錄下的沒(méi)有轉換的WORD文檔,你同樣可以檢查數據庫中記錄的沒(méi)有轉換的WORD文檔
string sPath = System.Threading.Thread.GetDomain().BaseDirectory; //當前的路徑
while(bStart)
{
int iFileCount = CheckWord(); //CheckWord為一個(gè)方法,檢查當前沒(méi)有轉換的WORD文檔,返回沒(méi)有轉換的文件數,該方法的代碼由讀者自己編寫(xiě)
for(int i=0;i<iFileCount;i++)
{
string sWord = GetWordFileName(i) //GetWordFileName為一個(gè)方法,返回一個(gè)不帶路徑的WORD文件名,該方法的代碼由讀者自己編寫(xiě)
//ToPdf類(lèi)中的StartConvertPDF()方法使用的是不帶路徑的WORD文件名
ToPdf my2Pdf = new ToPdf(sWord ,sPath);
my2Pdf.StartConvertPDF();
if(my2Pdf.sExecResult.IndexOf("isuccess")!=-1)
{
//成功,寫(xiě)日志,或回寫(xiě)數據庫
}
else if(my2Pdf.sExecResult.IndexOf("isfail")!=-1)
{
//失敗,寫(xiě)日志,或回寫(xiě)數據庫
}
}
if(!bStart)break;
Thread.Sleep(1000);
}
}
然后在服務(wù)的開(kāi)始事件中,啟動(dòng)線(xiàn)程
protected override void OnStart(string[] args)
{
//可以使用一個(gè)開(kāi)始定時(shí)器,檢查是否到開(kāi)始時(shí)間,時(shí)間一到,就開(kāi)始執行線(xiàn)程,此處的開(kāi)始執行線(xiàn)程可以放在開(kāi)始定時(shí)事件中
//可以使用一個(gè)結束定時(shí)器,檢查是否到結束時(shí)間,時(shí)間一到,就結束線(xiàn)程,結束線(xiàn)程的代碼可以放在結束定時(shí)事件中
//注意:應該使用組件中的定時(shí)器,而不是Windows的FORMS中的定時(shí)器
//該定時(shí)器的類(lèi)名為System.Timers.Timer,千萬(wàn)別搞錯,不然執行不會(huì )正常的
bStart = true;
Thread thConvert = new Thread(new ThreadStart(StartConvertData));
thConvert.Start();
}
然后在服務(wù)的結束事件中,設置停止線(xiàn)程的標識bStart= false
protected override void OnStop()
{
bStart = false;
//為何次處不停止線(xiàn)程呢,因為考慮到,現在線(xiàn)程正在轉換WORD文檔,但沒(méi)有結束,所以只設置停止標識,轉換完成后,線(xiàn)程也執行結束了.
}
結束語(yǔ):
Adobe Acrobat 7.0 Professional,postscript.exe,gs811w32.exe這三個(gè)文件可以在itbaby.jss.cn下載,都包含在同一個(gè)RAR的壓縮文件中了。