非常不幸,打印時沒有多少事情是可以自動進行的。相反,為完成打印,我們必須經歷大量機械的、非OO(面向對象)的步驟。但打印一個圖形化的組件時,可能多少有點兒自動化的意思:默認情況下,print()方法會調用paint()來完成自己的工作。大多數時候這都已經足夠了,但假如還想做一些特別的事情,就必須知道頁面的幾何尺寸。
下面這個例子同時演示了文字和圖形的打印,以及打印圖形時可以采取的不同方法。此外,它也對打印支持進行了測試:
//: PrintDemo.java // Printing with Java 1.1 import java.awt.*; import java.awt.event.*; public class PrintDemo extends Frame { Button printText = new Button("Print Text"), printGraphics = new Button("Print Graphics"); TextField ringNum = new TextField(3); Choice faces = new Choice(); Graphics g = null; Plot plot = new Plot3(); // Try different plots Toolkit tk = Toolkit.getDefaultToolkit(); public PrintDemo() { ringNum.setText("3"); ringNum.addTextListener(new RingL()); Panel p = new Panel(); p.setLayout(new FlowLayout()); printText.addActionListener(new TBL()); p.add(printText); p.add(new Label("Font:")); p.add(faces); printGraphics.addActionListener(new GBL()); p.add(printGraphics); p.add(new Label("Rings:")); p.add(ringNum); setLayout(new BorderLayout()); add(p, BorderLayout.NORTH); add(plot, BorderLayout.CENTER); String[] fontList = tk.getFontList(); for(int i = 0; i < fontList.length; i++) faces.add(fontList[i]); faces.select("Serif"); } class PrintData { public PrintJob pj; public int pageWidth, pageHeight; PrintData(String jobName) { pj = getToolkit().getPrintJob( PrintDemo.this, jobName, null); if(pj != null) { pageWidth = pj.getPageDimension().width; pageHeight= pj.getPageDimension().height; g = pj.getGraphics(); } } void end() { pj.end(); } } class ChangeFont { private int stringHeight; ChangeFont(String face, int style,int point){ if(g != null) { g.setFont(new Font(face, style, point)); stringHeight = g.getFontMetrics().getHeight(); } } int stringWidth(String s) { return g.getFontMetrics().stringWidth(s); } int stringHeight() { return stringHeight; } } class TBL implements ActionListener { public void actionPerformed(ActionEvent e) { PrintData pd = new PrintData("Print Text Test"); // Null means print job canceled: if(pd == null) return; String s = "PrintDemo"; ChangeFont cf = new ChangeFont( faces.getSelectedItem(), Font.ITALIC,72); g.drawString(s, (pd.pageWidth - cf.stringWidth(s)) / 2, (pd.pageHeight - cf.stringHeight()) / 3); s = "A smaller point size"; cf = new ChangeFont( faces.getSelectedItem(), Font.BOLD, 48); g.drawString(s, (pd.pageWidth - cf.stringWidth(s)) / 2, (int)((pd.pageHeight - cf.stringHeight())/1.5)); g.dispose(); pd.end(); } } class GBL implements ActionListener { public void actionPerformed(ActionEvent e) { PrintData pd = new PrintData("Print Graphics Test"); if(pd == null) return; plot.print(g); g.dispose(); pd.end(); } } class RingL implements TextListener { public void textValueChanged(TextEvent e) { int i = 1; try { i = Integer.parseInt(ringNum.getText()); } catch(NumberFormatException ex) { i = 1; } plot.rings = i; plot.repaint(); } } public static void main(String[] args) { Frame pdemo = new PrintDemo(); pdemo.setTitle("Print Demo"); pdemo.addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); pdemo.setSize(500, 500); pdemo.setVisible(true); } } class Plot extends Canvas { public int rings = 3; } class Plot1 extends Plot { // Default print() calls paint(): public void paint(Graphics g) { int w = getSize().width; int h = getSize().height; int xc = w / 2; int yc = w / 2; int x = 0, y = 0; for(int i = 0; i < rings; i++) { if(x < xc && y < yc) { g.drawOval(x, y, w, h); x += 10; y += 10; w -= 20; h -= 20; } } } } class Plot2 extends Plot { // To fit the picture to the page, you must // know whether you're printing or painting: public void paint(Graphics g) { int w, h; if(g instanceof PrintGraphics) { PrintJob pj = ((PrintGraphics)g).getPrintJob(); w = pj.getPageDimension().width; h = pj.getPageDimension().height; } else { w = getSize().width; h = getSize().height; } int xc = w / 2; int yc = w / 2; int x = 0, y = 0; for(int i = 0; i < rings; i++) { if(x < xc && y < yc) { g.drawOval(x, y, w, h); x += 10; y += 10; w -= 20; h -= 20; } } } } class Plot3 extends Plot { // Somewhat better. Separate // printing from painting: public void print(Graphics g) { // Assume it's a PrintGraphics object: PrintJob pj = ((PrintGraphics)g).getPrintJob(); int w = pj.getPageDimension().width; int h = pj.getPageDimension().height; doGraphics(g, w, h); } public void paint(Graphics g) { int w = getSize().width; int h = getSize().height; doGraphics(g, w, h); } private void doGraphics( Graphics g, int w, int h) { int xc = w / 2; int yc = w / 2; int x = 0, y = 0; for(int i = 0; i < rings; i++) { if(x < xc && y < yc) { g.drawOval(x, y, w, h); x += 10; y += 10; w -= 20; h -= 20; } } } } ///:~
這個程序允許我們從一個選擇列表框中選擇字體(並且我們會注意到很多有用的字體在Java 1.1版中一直受到嚴格的限制,我們沒有任何可以利用的優秀字體安裝在我們的機器上)。它使用這些字體去打出粗體,斜體和不同大小的文字。另外,一個新型組件調用過的繪圖被創建,以用來示范圖形。當打印圖形時,繪圖擁有的ring將顯示在屏幕上和打印在紙上,並且這三個衍生類Plot1,Plot2,Plot3用不同的方法去完成任務以便我們可以看到我們選擇的事物。同樣,我們也能在一個繪圖中改變一些ring——這很有趣,因為它證明了Java 1.1版中打印的脆弱。在我的系統裡,當ring計數顯示“too high”(究竟這是什麼意思?)時,打印機給出錯誤信息並且不能正確地工作,而當計數給出“low enough”信息時,打印機又能工作得很好。我們也會注意到,當打印到看起來實際大小不相符的紙時頁面的大小便產生了。這些特點可能被裝入到將來發行的Java中,我們可以使用這個程序來測試它。
這個程序為促進重復使用,不論何時都可以封裝功能到內部類中。例如,不論何時我想開始打印工作(不論圖形或文字),我必須創建一個PrintJob打印工作對象,該對象擁有它自己的連同頁面寬度和高度的圖形對象。創建的PrintJob打印工作對象和提取的頁面尺寸一起被封裝進PrintData class打印類中。
1. 打印文字
打印文字的概念簡單明了:我們選擇一種字體和大小,決定字符串在頁面上存在的位置,並且使用Graphics.drawSrting()方法在頁面上畫出字符串就行了。這意味著,不管怎樣我們必須精確地計算每行字符串在頁面上存在的位置並確定字符串不會超出頁面底部或者同其它行沖突。如果我們想進行字處理,我們將進行的工作與我們很相配。ChangeFont封裝進少量從一種字體到其它的字體的變更方法並自動地創建一個新字體對象和我們想要的字體,款式(粗體和斜體——目前還不支持下劃線、空心等)以及點陣大小。它同樣會簡單地計算字符串的寬度和高度。當我們按下“Print text”按鈕時,TBL接收器被激活。我們可以注意到它通過反復創建ChangeFont對象和調用drawString()來在計算出的位置打印出字符串。注意是否這些計算產生預期的結果。(我使用的版本沒有出錯。)
2. 打印圖形
按下“Print graphics”按鈕時,GBL接收器會被激活。我們需要打印時,創建的PrintData對象初始化,然後我們簡單地為這個組件調用print()打印方法。為強制打印,我們必須為圖形對象調用dispose()處理方法,並且為PrintData對象調用end()結束方法(或改變為為PrintJob調用end()結束方法。)
這種工作在繪圖對象中繼續。我們可以看到基礎類繪圖是很簡單的——它擴展畫布並且包括一個中斷調用ring來指明多少個集中的ring需要畫在這個特殊的畫布上。這三個衍生類展示了可達到一個目的的不同的方法:畫在屏幕上和打印的頁面上。
Plot1采用最簡單的編程方法:忽略繪畫和打印的不同,並且過載paint()繪畫方法。使用這種工作方法的原因是默認的print()打印方法簡單地改變工作方法轉而調用Paint()。但是,我們會注意到輸出的尺寸依賴於屏幕上畫布的大小,因為寬度和高度都是在調用Canvas.getSize()方法時決定是,所以這是合理的。如果我們圖像的尺寸一值都是固定不變的,其它的情況都可接受。當畫出的外觀的大小如此的重要時,我們必須深入了解的尺寸大小的重要性。不湊巧的是,就像我們將在Plot2中看到的一樣,這種方法變得很棘手。因為一些我們不知道的好的理由,我們不能簡單地要求圖形對象以它自己的大小畫出外觀。這將使整個的處理工作變得十分的優良。相反,如果我們打印而不是繪畫,我們必須利用RTTI instanceof關鍵字(在本書11章中有相應描述)來測試PrintGrapics,然後下溯造型並調用這獨特的PrintGraphics方法:getPrintJob()方法。現在我們擁有PrintJob的句柄並且我們可以發現紙張的高度和寬度。這是一種hacky的方法,但也許這對它來說是合理的理由。(在其它方面,到如今我們看到一些其它的庫設計,因此,我們可能會得到設計者們的想法。)
我們可以注意到Plot2中的paint()繪畫方法對打印和繪圖的可能性進行審查。但是因為當打印時Print()方法將被調用,那麼為什麼不使用那種方法呢?這種方法同樣也在Plot3中也被使用,並且它消除了對instanceof使用的需求,因為在Print()方法中我們可以假設我們能對一個PrintGraphics對象造型。這樣也不壞。這種情況被放置公共繪畫代碼到一個分離的doGraphics()方法的辦法所改進。
2. 在程序片內運行幀
如果我們想在一個程序片中打印會怎以樣呢?很好,為了打印任何事物我們必須通過工具組件對象的getPrintJob()方法擁有一個PrintJob對象,設置唯一的一個幀對象而不是一個程序片對象。於是它似乎可能從一個應用程序中打印,而不是從一個程序片中打印。但是,它變為我們可以從一個程序片中創建一個幀(相反的到目前為止,我在程序片或應用程序例子中所做的,都可以生成程序片並安放幀。)。這是一個很有用的技術,因為它允許我們在程序片中使用一些應用程序(只要它們不妨礙程序片的安全)。但是,當應用程序窗口在程序片中出現時,我們會注意到WEB浏覽器插入一些警告在它上面,其中一些產生“Warning:Applet Window.(警告:程序片窗口)”的字樣。
我們會看到這種技術十分直接的安放一個幀到程序片中。唯一的事是當用戶關閉它時我們必須增加幀的代碼(代替調用System.exit()):
//: PrintDemoApplet.java // Creating a Frame from within an Applet import java.applet.*; import java.awt.*; import java.awt.event.*; public class PrintDemoApplet extends Applet { public void init() { Button b = new Button("Run PrintDemo"); b.addActionListener(new PDL()); add(b); } class PDL implements ActionListener { public void actionPerformed(ActionEvent e) { final PrintDemo pd = new PrintDemo(); pd.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e){ pd.dispose(); } }); pd.setSize(500, 500); pd.show(); } } } ///:~
伴隨Java 1.1版的打印支持功能而來的是一些混亂。一些宣傳似乎聲明我們能在一個程序片中打印。但Java的安全系統包含了一個特點,可停止一個正在初始化打印工作的程序片,初始化程序片需要通過一個Web浏覽器或程序片浏覽器來進行。在寫作這本書時,這看起來像留下了一個未定的爭議。當我在WEB浏覽器中運行這個程序時,printdemo(打印樣本)窗口正好出現,但它卻根本不能從浏覽器中打印。