學習如何使用列印服務API

來源:互聯網
上載者:User

Java自從問世以來在各方面發展迅速,但是一直以來,列印輸出是java最弱的方面。事實上,java1.0不支援任何列印功能。Java1.1在java.awt包裡包含了一個叫做PrintJob的類,但是這個類提供的列印功能十分粗糙和不可靠。當java1.2問世,它圍繞PrinterJob設計了一個完整獨立的印表機制(叫做java2D printing API),並且在java.awt.print包裡定義了一些新的類和介面。這些使得基於PrintJob印表機制(就是AWT printing)基本荒廢,雖然PrintJob從未被抨擊而且至少在這篇文章裡仍然是一個提供技術的類。

在J2SE1.3裡當PrintJob的功能擴充到可以通過在java.awt包裡的JobAttributes 和PageAttributes兩個類設定工程和頁面的屬性時發生了一些額外的改變。隨著J2SE1.3的發布,列印功能相應的得到了完善;但是在混合使用這兩種完全不同的印表機制的時候仍然存在一些問題。比如,這兩種機制使用java.awt.Graphics這個類的一個介面來展現列印內容,意味著所有要列印的東西都必須用一張圖片表示。另外,完善的PrintJob提供了很有限的工程相關屬性的設定;這兩種機制都沒有辦法通過程式來選擇目標印表機。

Java列印最大的改變來自於J2SE的發布帶來的Java列印服務API。這個第三代Java列印支援介面突破了先前提到的使用javax.print包的PrintService和DocPrintJob介面的局限性。因為新的API就是以前兩種舊的印表機制定義的功能函數的一個父集,它是目前我們常用的方法並且是這篇文章的焦點。
更深入來說,以下的步驟包含了怎麼使用這個新的Java列印服務API:
1.定義印表機,限制那些返回到提供你要實現功能的函數的列表。列印服務實現了PrintService介面.

2.通過調用介面中定義的createPrintJob()方法建立一個列印事件,作為DocPrintJob的一個執行個體。

3.建立一個實現Doc介面的類來描述你想要列印的資料 , 你也可以建立一個PrintRequestAttributeSet的執行個體來定義你想要的列印選項。

4.通過DocPrintJob介面定義的printv()方法來初始化列印,指定你先前建立的Doc,指定PrintRequestAttributeSet或者設為空白值。

現在你可以檢查每一步並且試著完成它們。

注意
在這篇文章裡,我將交替使用印表機和列印服務,因為在大部分情況下,列印服務不亞於一台真實的印表機。 一般的列印服務反映了理論上可以發送到不僅僅是印表機的的輸出。舉例來說,列印服務也許根本不能列印東西但是可以往磁碟上的檔案寫資料。換句話說,所有的印表機可以看成是特殊的列印服務,但是並不是所有列印服務和印表機有聯絡。就像你一般把你的文本送到印表機那裡一樣,我有時候使用更為簡便的印表機這個名詞來代替技術上更精確的列印服務。

定義列印服務
你可以使用在PrintServiceLookup類中定義的三種靜態方法中的一種來定義。最簡單的一種就是lookupDefaultPrintService(),正如它的名字一樣,它返回一個你預設的印表機:

PrintService service = PrintServiceLookup.lookupDefaultPrintService(); 

雖然用這個辦法很簡單也很方便,用它來選擇你的印表機意味著使用者的印表機一直都支援你的程式所要精確傳輸的資料輸出。實際上,你真正想要的是那種可以處理你想要的資料的類型並且可以支援你要的特徵例如顏色或者兩邊列印。為了從列表中中返回你所要求的特殊功能支援的印表機,你可以使用剩下兩個方法中的lookupPrintServices() 或者lookupMultiDocPrintServices()。

lookupPrintServices()方法有兩個參數:一個DocFlavor的執行個體和實現AttributeSet介面的執行個體。
你馬上將看到,你可以使用兩者中任意一個來限制返回的印表機,但是lookupPrintServices()允許你指定這兩個參數為空白值。如果把兩者都設為空白,那麼你得到的傳回值將是任意一個可用的印表機。在這種情況下,你並不需要查看PrintService中定義的方法,其中一個getName()方法返回了一個字串,代表印表機的名字。你可以編譯下面的代碼來列出你的系統現有的印表機:

PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null);
for (int i = 0; i < services.length; i++) {
   System.out.println(services[i].getName());
}

例如你的系統名為PrintServer,下面有Alpha, Beta, 和Gamma 印表機,用以上代碼可以得到以下輸出:
//PrintServer/Alpha
//PrintServer/Beta
//PrintServer/Gamma

現在查看那些你可以傳給lookupPrintServices()方法的參數來看看如何返回擁有特殊功能的印表機。

DocFlavor
第一個你可以指定的參數是一個DocFlavor類的執行個體,它描述了將要列印的資料的類型和資料如何儲存。在大部分情況下,並不需要去建立一個新的執行個體因為Java包含了很多預先定義的執行個體,使得你可以用它們來傳給lookupPrintServices()。然而,我們還是來看一下DocFlavor的結構和方法來探討列印服務如何使用這個執行個體。

建立DocFlavor執行個體需要的兩個參數都是字串,一個是MIME (Multipurpose Internet Mail Extensions)類型另一個是類的名字。MIME類型被用於描述資料類型。例如,你要列印一個gif檔案,你需要使用MIME類型是image/gif的DocFlavor。相類似,如果你要列印HTML檔案裡的文本資訊要使用MIME類型似text/plain或者text/html。

表現類
MIME類型描述將要列印的資料的類型,表現的類則表示如何讓列印服務得到這些資料。DocFlavor包含了幾個靜態內部類,每一個相對應一個表現類和如何裝載要列印得資料。

表1中列出了上面提到的內部類和表現類。注意在SERVICE_FORMATTED(一會我會更詳細地解釋)旁邊,每一個和"binary"或者 "character"相對應。事實上,這些差別是人為的,因為"character"資料類型本身就是一種特殊的binary類型。這種情況下,我們說的二進位(binary)資料包括人們可以看懂的字元和一些格式化的字元比如tabs,換行斷行符號等。當然,這些差別很重要,反映出面向字元的表現類並不適合儲存位元據。

例如,你不會用字元隊列或者字串來儲存一個gif檔案,你也不能通過Reader介面來訪問它。另一方面,因為字元也是一種特殊的位元據,它完全適合儲存文本資訊到位元組數組裡或者通過InputStream或者一個URL來訪問它。

Table

1. DocFlavor的表現類

上面定義的任何一個靜態內部類相對應一個表現類,但是請記住我說過每一個DocFlavor的執行個體通過一個表現類和一個MIME來確認要列印的資料的類型。
要訪問這樣一個執行個體,你要通過表1總列出的內部類。例如,我們假設你要列印一個在網上通過URL訪問的gif檔案,這樣的話,就選擇表現類是javav.net.url,對應的在DocFlavor中的靜態類就是URL類。如果你開啟那個內部類的文檔,你會發現其實它定義了一系列靜態內部類,每一個對應一種印表機支援的MIME類型。表2描述了在DocFlavor.URL裡的內部類和MIME

Table 2. The DocFlavor.URL inner classes

因為要通過URL列印gif圖片,你可以用一下代碼來獲得執行個體

DocFlavor flavor = DocFlavor.URL.GIF; 

這個代碼建立了一個DocFlavor執行個體,代表類是java.net.URL,MIME是image/gif。
表2列出的了DocFlavor.URL的類,那麼其他六個內部類呢?我們等下來討論一下SERVICE_FORMATTED,這之前,看看與位元據聯絡的所有三種類型(BYTE_ARRAY, INPUT_STREAM, and URL)相關的內部類。例如,如果你把gif儲存到了一個位元組數組裡,那麼你可以用以下代碼:

DocFlavor flavor = DocFlavor.BYTE_ARRAY.GIF; 

正如有三個與二進位類型關聯的內部類一樣,與字元類型相關的另外三個類列在表3裡

Table 3. CHAR_ARRAY, READER, and STRING

所以,如果你想列印儲存在字串中的文本資料,用以下代碼:

DocFlavor flavor = DocFlavor.STRING.TEXT_PLAIN;

類似,如果文本來自於網頁上的HTML文檔,用以下代碼:

DocFlavor flavor = DocFlavor.STRING.TEXT_HTML;

選擇正確的印表機
還記得我們在開始關於討論DocFlavor之前關於印表機的那個精確支援你想要列印的資料類型的假設嗎?這似乎看起來沒有必要。實際上,你會對給你的印表機所支援的文件類型感到吃驚。例如,剛提到文本類型看起來似乎是最容易支援的,所以,如果你的程式要列印一個普通文本或者HTML文本,你可以隨便選擇一個列印服務並把它送到印表機那去。然而大部分印表機不支援基於文本的表現類,如果你試圖向印表機發送它不支援的DocFlavor,會產生下面的異常:

Exception in thread "main" sun.print.PrintJobFlavorException: invalid flavor
   at sun.print.Win32PrintJob.print(Win32PrintJob.java:290)
   at PrintTest.main(PrintTest.java:11)

現在你已經知道了如何得到一個DocFlavor的引用而且我們也討論了選擇支援這個flavor的印表機重要性,接下來我來告訴你如何確定你使用的印表機支援它。我先前說過lookupPrintServices()允許你指定一個DocFlavor作為第一個參數,如果你指定的參數非空,那麼方法會返回相應支援這個的印表機的執行個體。例如以下代碼將返回可以通過URL來列印gif檔案的印表機的列表:

DocFlavor flavor = DocFlavor.URL.GIF;
PrintService[] services = PrintServiceLookup.lookupPrintServices(flavor, null);

另外,如果你的程式已經獲得了列印服務的執行個體,而你想知道它是否支援另一種特定的flavor,你可以調用isDocFlavorSupported()方法。在下面的代碼裡,將得到一個預設印表機的引用,如果不能列印gif就會出現錯誤資訊:

PrintService service = PrintServiceLookup.lookupDefaultPrintService();
DocFlavor flavor = DocFlavor.URL.GIF;
if (!service.isDocFlavorSupported(flavor)) {
   System.err.println("The printer does not support the appropriate DocFlavor");
}

AttributeSet
正如你看到的,DocFlavor描述列印資料而且可以用來確定列印服務是否支援這種資料。然而,你的程式需要選擇一個基於那些支援的元素的印表機。例如,你要列印圖片用不同的顏色來描述不同的資訊,你想知道提供的服務是否支援彩色列印,如果不,那麼要麼禁止它使用或者要求提供一個黑白圖片。

類似彩色列印,兩邊列印或者使用不同的定位取決於印表機本身的屬性,而javax.print.attribute包包含了許多你可以用於描述這些屬性的包和介面。其中一個介面是前面提到的lookupPrintServices()中第二個參數AttributeSet。正如你願,它返回屬性的集合,在調用lookupPrintServices()指定一個不為空白的值將返回支援這些屬性的列印服務。換句話說,如果DocFlavor和 AttributeSet都不為空白,那麼方法將返回那些這兩種屬性都支援的印表機

Attribute
AttributeSet 是屬性的集合,一個顯而易見的問題是如何指定屬性的值呢? javax.print.attribute包裡同時含有一個叫Attribute的介面,你馬上可以看到通過調用add方法來給AttributeSet建立一個Attribute執行個體來獲得這個集合。在javax.print.attribute.standard包裡定義了大量你將要用到的介面。在之前,你可以查看javax.print.attribute這個包裡的其他介面。

屬性模組
目前為止,我們把屬性描述成列印服務的功能,而實際上在java支援的屬性中算很簡單的。對應每個屬性,java都有相應的模組。只有遵循這些模組屬性才有效。在不同的java列印服務位置使用不同的屬性,而不是所有的屬性在任何地方都適用。
為了更好的理解這個,來看一下javax.print.attribute.standard 包裡定義的
OrientationRequested和 ColorSupported介面。建立一個新的列印文檔時可以指定OrientationRequested屬性和用於列印的定位。ColorSupported在你調用PrintService介面的getAttributes方法時返回。OrientationRequested是一個你用來傳給印表機的屬性,而ColorSupported是列印服務用來提供給你關於印表機能力資訊的工具。你可以在建立列印文檔時把ColorSupported作為屬性指定,因為印表機是否支援彩色列印是你的程式不能控制的。

介面和繼承
你第一次查看javax.print.attribute包裡的介面和類時你也許會感到選擇那些列表裡的介面和類很麻煩。除了Attribute 和AttributeSet和繼承AttributeSet的HashAttributeSet,javax.print.attribute包裡有4個子介面和類,列出在表4和圖1中。

Table 4. javax.print.attribute 裡定義的介面和類

Figure 1. javax.print.attribute 包的一部分類的階層.

那麼有了Attribute, AttributeSet, 和 HashAttributeSet為什麼需要使用這些不同的介面和繼承類呢?是因為這些特殊的類是為那些特殊的屬性量身定做的。比方說,我提到過當你建立列印文檔的時候有個地方可以使用的屬性例如ColorSupported在那裡不能使用。當建立這樣的文檔,你可以使用DocAttributeSet介面(或者更專業一點,HashDocAttributeSet這個繼承的類),這個繼承類只允許你添加繼承DocAttribute這個介面的屬性。這四種不同的模組如下:
&#61623; Doc: 在建立文檔時指&#61623; 定如何列印文檔
&#61623; PrintJob: 列印任務的屬性描述任務的狀態
&#61623; PrintRequest: 初始化列印時傳給任務的請求
&#61623; PrintService:由列印服&#61623; 務返回來描述印表機的功能

要知道如何工作,我們來建立一個DocAttributeSet的執行個體然後為AttributeSet設定DocAttributeSet和OrientationRequested屬性。HashDocAttributeSet定義了很好的結構,所有你可以很簡便的如下建立執行個體:

DocAttributeSet attrs = new HashDocAttributeSet(); 

現在你已經建立了AttributeSet,你可以調用add方法並把它傳給Attribute的繼承執行個體去。如果你看了OrientationRequested這個類的文檔,你會發現它包含了一系列靜態OrientationRequest執行個體,每一個對應一種文檔定位方式。要指定你想要的類型,你所要做的只是按下面的方法傳給add方法一個靜態執行個體的引用:

DocAttributeSet attrs = new HashDocAttributeSet();
attrs.add(OrientationRequested.PORTRAIT);

ColorSupported類有一點不同但一樣很簡單,它定義了兩種靜態執行個體:一個表示支援彩色列印另一個不是。你可以試著增加一個ColorSupported屬性到DocAttributeSet去,代碼如下:

DocAttributeSet attrs = new HashDocAttributeSet();
attrs.add(OrientationRequested.PORTRAIT);
attrs.add(ColorSupported.SUPPORTED);

早先提過,去指定是否支援彩色列印不恰當因為這不是程式所能控制的內容。換句話說,ColorSupported這個屬性放到一系列文件屬性中並不合適,所以,運行先前的代碼當添加ColorSupported屬性時會拋出一個ClassCastException異常。

要學習怎麼運行,記住每一個AttributeSet子介面都有一個相應Attribute子介面和繼承子類。當添加一個屬性時,繼承的子類試圖把Attribute作為參數給相應的子介面,這樣來確保只有當前適當的屬性會成功添加。

這樣的話,HashDocAttributeSet 的add方法第一次和OrientationRequested的一個執行個體一起調用,並成功的把它作為一個object傳給DocAttribute。因為2所示,OrientationRequested繼承了那個介面。與之相對應,傳ColorSupported執行個體的時候因為沒有繼承DocAttribute所以失敗了。


Figure 2. javax.print.attribute 包的一部分類的階層

這個例子舉例說明,表4裡的四個介面和類組來保證使用正確的屬性。注意模組和不同的屬性之間有大量的互動,所以很多屬性與不止一個模組關聯。例如,許多屬性繼承了PrintJobAttribute 和 PrintRequestAttribute因為大部分是通過一個相關的列印任務獲得提供給你的。你可以在初始化時指定它們。舉個例子,你可以把它加到PrintRequestAttributeSet中去來指定任務名,並且在列印的時候通過PrintJobAttributeSet來返回它。因此,JobName屬性類同時繼承
PrintRequestAttribute 和 PrintJobAttribute。
AttributeSet and HashAttributeSet

你已經知道了為什麼會有四個子類,但是AttributeSet介面和HashAttributeSet父類又是什麼呢?AttributeSet/HashAttributeSet在你不能確定要儲存在這個集合中的那些僅僅和一個模組相關的屬性時使用。記得我以前提到的lookupPrintServices()方法允許你指定AttributeSet參數來限制返回的列印服務。表面上看來最好指定PrintServiceAttributeSet的執行個體,但是很多你可能用到的屬性並不繼承PrintServiceAttribute。

我們假設你想要讓lookupPrintServices()方法返回支援彩色列印和風景畫列印的印表機。這些屬性與ColorSupported和OrientationRequested屬性關聯,但是請注意這些類並不共用模組,前者是一個PrintServiceAttribute而OrientationRequested與另外三個模組(Doc, PrintRequest,和 PrintJob)關聯。這意味著不存在單個的AttributeSet介面或類來同時包含ColorSupported和Sides屬性。

建立AttributeSet的方法使用一個HashAttributeSet執行個體同時包含一個OrientationRequested 和 ColorSupported太簡單了。不像它的子類,它並不限制你往上加特殊的屬性,所以可以用以下代碼成功執行:

AttributeSet attrs = new HashAttributeSet();
attrs.add(ColorSupported.SUPPORTED);
attrs.add(OrientationRequested.LANDSCAPE);
PrintService[] services = PrintServiceLookup.lookupPrintServices(null, attrs);

通過使用者介面的印表機選擇
就此觀點而言,我認為使用的印表機應該由應用程式計劃選擇。但操作過程中,列印輸出內容時往往會顯示一個對話方塊讓使用者選擇。幸運的是,Java通過使用ServiceUI類(在javax.print包中定義)中的靜態printDialog()方法使得這些操作簡單化。
在顯示的對話方塊旁邊,僅在調用printDialog()時必須指定的參數值如下:
使用者可選用的PrintService執行個體數組。
預設的PrintService。
PrintRequestAttributeSet執行個體。這用來彈出顯示的對話方塊,並在對話方塊消失之前返回使用者所作的任何更改。
要解釋這些如何運作,可使用下列簡單的程式碼片段來顯示列印對話:

PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null);
PrintService svc = PrintServiceLookup.lookupDefaultPrintService();
PrintRequestAttributeSet attrs = new HashPrintRequestAttributeSet();
PrintService selection = ServiceUI.printDialog(
   null, 100, 100, services, svc, null, attrs);

運行時,代碼產生例3中所示的對話方塊


Figure 3. The printer dialog

隨著代碼的說明,從printDialog()方法返回的值是一個PrintService執行個體,識別使用者所選的印表機,或在使用者取消印表機對話時識別為空白。此外,PrintRequestAttributeSet已更新到可通過對話方塊來反映使用者作出的更改,比如要列印的份數。
通過使用printDialog()方法,可讓使用者選擇其輸出要發往的印表機,提供使用者對於專業應用程式的期望功能。

建立列印任務
這是列印中的一個簡單步驟;因為一旦獲得PrintService的一個參考,你需要做的就是調用createPrintJob()方法,如:

PrintService service;
.
.
.
DocPrintJob job = service.createPrintJob();

代碼中顯示,從createPrintJob()返回的值是一個DocPrintJob執行個體,可讓您控制並監控列印操作的狀態。要啟動列印,您會調用DocPrintJob對象的print()方法,但是,這之前,您需要定義待列印的文檔或選用PrintRequestAttributeSe。您已經知道如何構造並彈出AttributeSet,這個步驟不再重複,接下來,您將瞭解定義待列印的文檔。

定義要列印的文檔
接下來這一步是定義要列印的文檔,用一個在javax.print包裡的Doc的介面執行個體來建立。每一個Doc的執行個體有兩個必須定義的屬性和一個可選擇的屬性:
&#61623; Object 代表要列印的內容
&#61623; DocFlavor的一個執行個體描述資料類型
&#61623; 可選的DocAttributeSet 包含列印時的屬性

複習Doc介面的文檔可以看出javax.print包裡包含了一個叫SimpleDoc 的介面的繼承,它的建構函式包含了上面三個參數。要知道如何構建SimpleDoc 的執行個體,我們假設你要列印兩份存在http://www.apress.com/ApressCorporate/supplement/1/421/bcm.gif的gif檔案拷貝。
我們要做的就是構建一個SimpleDoc執行個體來描述這個文檔建立了一個URL來指向圖片,並且引用了DocFlavor,並把這兩個傳給SimpleDoc建構函式:

URL url = new URL(
   "http://www.apress.com/ApressCorporate/supplement/1/421/bcm.gif");
DocFlavor flavor = DocFlavor.URL.GIF;
SimpleDoc doc = new SimpleDoc(url, flavor, null);

啟動列印
列印的最後一個步驟就是調用DocPrintJob的 print()方法,傳遞待列印資料的Doc對象,或選用PrintRequestAttributeSet執行個體。為簡單起見,假設預設印表機支援你所需要的flavor和屬性,在此情況下要使用下列代碼將上一個例子提及的gif檔案列印兩份:

PrintService service = PrintServiceLookup.lookupDefaultPrintService();
DocPrintJob job = service.createPrintJob();
URL url = new URL(
   "http://www.apress.com/ApressCorporate/supplement/1/421/bcm.gif ");
DocFlavor flavor = DocFlavor.URL.GIF;
Doc doc = new SimpleDoc(url, flavor, null);
PrintRequestAttributeSet attrs = new HashPrintRequestAttributeSet();
attrs.add(new Copies(2));
job.print(doc, attrs)

;
注意,一些情況下,列印不同步執行,這可能會在實際列印完成之前返回對print()的調用。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.