# 13.17 Java 1.1用戶接口API
Java 1.1版同樣增加了一些重要的新功能,包括焦點遍歷,桌面色彩訪問,打印“沙箱內”及早期的剪貼板支持。
焦點遍歷十分的簡單,因為它顯然存在于AWT庫里的組件并且我們不必為使它工作而去做任何事。如果我們制造我們自己組件并且想使它們去處理焦點遍歷,我們重載`isFocusTraversable()`以使它返回真值。如果我們想在一個鼠標單擊上捕捉鍵盤焦點,我們可以捕捉鼠標按下事件并且調用`requestFocus()`需求焦點方法。
## 13.17.1 桌面顏色
利用桌面顏色,我們可知道當前用戶桌面都有哪些顏色選擇。這樣一來,就可在必要的時候通過自己的程序來運用那些顏色。顏色都會得以自動初始化,并置于`SystemColor`的`static`成員中,所以要做的唯一事情就是讀取自己感興趣的成員。各種名字的意義是不言而喻的:`desktop`,`activeCaption`, `activeCaptionText`,`activeCaptionBorder`, `inactiveCaption`, `inactiveCaptionText`,`inactiveCaptionBorder`, `window`, `windowBorder`, `windowText`, `menu`,`menuText`,`text`, `textText`, `textHighlight`, `textHighlightText`,`textInactiveText`,`control`, `controlText`, `controlHighlight`,`controlLtHighlight`,`controlShadow`,`controlDkShadow`, `scrollbar`, `info`(用于幫助)以及`infoText`(用于幫助文字)。
## 13.17.2 打印
非常不幸,打印時沒有多少事情是可以自動進行的。相反,為完成打印,我們必須經歷大量機械的、非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`(打印樣本)窗口正好出現,但它卻根本不能從瀏覽器中打印。
## 13.17.3 剪貼板
Java 1.1對系統剪貼板提供有限的操作支持(在`Java.awt.datatransfer package`里)。我們可以將字符串作這文字對象復制到剪貼板中,并且我們可以從剪貼板中粘貼文字到字符中對角中。當然,剪貼板被設計來容納各種類型的數據,存在于剪貼板上的數據通過程序運行剪切和粘貼進入到程序中。雖然剪切板目前只支持字符串數據,Java的剪切板API通過“特色”概念提供了良好的可擴展性。當數據從剪貼板中出來時,它擁有一個相關的特色集,這個特色集可以被修改(例如,一個圖形可以被表示成一些字符串或者一幅圖像)并且我們會注意到如果特殊的剪貼板數據支持這種特色,我們會對此十分的感興趣。
下面的程序簡單地對`TextArea`中的字符串數據進行剪切,復制,粘貼的操作做了示范。我們將注意到的是我們需要按照剪切、復制和粘貼的順序進行工作。但如果我們看見一些其它程序中的`TextField`或者`TextArea`,我們會發現它們同樣也自動地支持剪貼板的操作順序。程序中簡單地增加了剪貼板的程序化控制,如果我們想用它來捕捉剪貼板上的文字到一些非文字組件中就可以使用這種技術。
```
//: CutAndPaste.java
// Using the clipboard from Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.awt.datatransfer.*;
public class CutAndPaste extends Frame {
MenuBar mb = new MenuBar();
Menu edit = new Menu("Edit");
MenuItem
cut = new MenuItem("Cut"),
copy = new MenuItem("Copy"),
paste = new MenuItem("Paste");
TextArea text = new TextArea(20,20);
Clipboard clipbd =
getToolkit().getSystemClipboard();
public CutAndPaste() {
cut.addActionListener(new CutL());
copy.addActionListener(new CopyL());
paste.addActionListener(new PasteL());
edit.add(cut);
edit.add(copy);
edit.add(paste);
mb.add(edit);
setMenuBar(mb);
add(text, BorderLayout.CENTER);
}
class CopyL implements ActionListener {
public void actionPerformed(ActionEvent e) {
String selection = text.getSelectedText();
StringSelection clipString =
new StringSelection(selection);
clipbd.setContents(clipString, clipString);
}
}
class CutL implements ActionListener {
public void actionPerformed(ActionEvent e) {
String selection = text.getSelectedText();
StringSelection clipString =
new StringSelection(selection);
clipbd.setContents(clipString, clipString);
text.replaceRange("",
text.getSelectionStart(),
text.getSelectionEnd());
}
}
class PasteL implements ActionListener {
public void actionPerformed(ActionEvent e) {
Transferable clipData =
clipbd.getContents(CutAndPaste.this);
try {
String clipString =
(String)clipData.
getTransferData(
DataFlavor.stringFlavor);
text.replaceRange(clipString,
text.getSelectionStart(),
text.getSelectionEnd());
} catch(Exception ex) {
System.out.println("not String flavor");
}
}
}
public static void main(String[] args) {
CutAndPaste cp = new CutAndPaste();
cp.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
cp.setSize(300,200);
cp.setVisible(true);
}
} ///:~
```
創建和增加菜單及`TextArea`到如今似乎已變成一種單調的活動。這與通過工具組件創建的剪貼板字段`clipbd`有很大的區別。
所有的動作都安置在接收器中。`CopyL`和`Cupl`接收器同樣除了最后的`CutL`線以外刪除被復制的線。特殊的兩條線是`StringSelection`對象從字符串從創建并調用`StringSelection`的`setContents()`方法。說得更準確些,就是放一個字符串到剪切板上。
在`PasteL`中,數據被剪貼板利用`getContents()`進行分解。任何返回的對象都是可移動的匿名的,并且我們并不真正地知道它里面包含了什么。有一種發現的方法是調用`getTransferDateFlavors()`,返回一個`DataFlavor`對象數組,表明特殊對象支持這種特點。我們同樣能要求它通過我們感興趣的特點直接地使用`IsDataFlavorSupported()`。但是在這里使用一種大膽的方法:調用`getTransferData()`方法,假設里面的內容支持字符串特色,并且它不是個被分類在異常處理器中的難題 。
在將來,我們希望更多的數據特色能夠被支持。
- Java 編程思想
- 寫在前面的話
- 引言
- 第1章 對象入門
- 1.1 抽象的進步
- 1.2 對象的接口
- 1.3 實現方案的隱藏
- 1.4 方案的重復使用
- 1.5 繼承:重新使用接口
- 1.6 多態對象的互換使用
- 1.7 對象的創建和存在時間
- 1.8 異常控制:解決錯誤
- 1.9 多線程
- 1.10 永久性
- 1.11 Java和因特網
- 1.12 分析和設計
- 1.13 Java還是C++
- 第2章 一切都是對象
- 2.1 用引用操縱對象
- 2.2 所有對象都必須創建
- 2.3 絕對不要清除對象
- 2.4 新建數據類型:類
- 2.5 方法、參數和返回值
- 2.6 構建Java程序
- 2.7 我們的第一個Java程序
- 2.8 注釋和嵌入文檔
- 2.9 編碼樣式
- 2.10 總結
- 2.11 練習
- 第3章 控制程序流程
- 3.1 使用Java運算符
- 3.2 執行控制
- 3.3 總結
- 3.4 練習
- 第4章 初始化和清除
- 4.1 用構造器自動初始化
- 4.2 方法重載
- 4.3 清除:收尾和垃圾收集
- 4.4 成員初始化
- 4.5 數組初始化
- 4.6 總結
- 4.7 練習
- 第5章 隱藏實現過程
- 5.1 包:庫單元
- 5.2 Java訪問指示符
- 5.3 接口與實現
- 5.4 類訪問
- 5.5 總結
- 5.6 練習
- 第6章 類復用
- 6.1 組合的語法
- 6.2 繼承的語法
- 6.3 組合與繼承的結合
- 6.4 到底選擇組合還是繼承
- 6.5 protected
- 6.6 累積開發
- 6.7 向上轉換
- 6.8 final關鍵字
- 6.9 初始化和類裝載
- 6.10 總結
- 6.11 練習
- 第7章 多態性
- 7.1 向上轉換
- 7.2 深入理解
- 7.3 覆蓋與重載
- 7.4 抽象類和方法
- 7.5 接口
- 7.6 內部類
- 7.7 構造器和多態性
- 7.8 通過繼承進行設計
- 7.9 總結
- 7.10 練習
- 第8章 對象的容納
- 8.1 數組
- 8.2 集合
- 8.3 枚舉器(迭代器)
- 8.4 集合的類型
- 8.5 排序
- 8.6 通用集合庫
- 8.7 新集合
- 8.8 總結
- 8.9 練習
- 第9章 異常差錯控制
- 9.1 基本異常
- 9.2 異常的捕獲
- 9.3 標準Java異常
- 9.4 創建自己的異常
- 9.5 異常的限制
- 9.6 用finally清除
- 9.7 構造器
- 9.8 異常匹配
- 9.9 總結
- 9.10 練習
- 第10章 Java IO系統
- 10.1 輸入和輸出
- 10.2 增添屬性和有用的接口
- 10.3 本身的缺陷:RandomAccessFile
- 10.4 File類
- 10.5 IO流的典型應用
- 10.6 StreamTokenizer
- 10.7 Java 1.1的IO流
- 10.8 壓縮
- 10.9 對象序列化
- 10.10 總結
- 10.11 練習
- 第11章 運行期類型識別
- 11.1 對RTTI的需要
- 11.2 RTTI語法
- 11.3 反射:運行期類信息
- 11.4 總結
- 11.5 練習
- 第12章 傳遞和返回對象
- 12.1 傳遞引用
- 12.2 制作本地副本
- 12.3 克隆的控制
- 12.4 只讀類
- 12.5 總結
- 12.6 練習
- 第13章 創建窗口和程序片
- 13.1 為何要用AWT?
- 13.2 基本程序片
- 13.3 制作按鈕
- 13.4 捕獲事件
- 13.5 文本字段
- 13.6 文本區域
- 13.7 標簽
- 13.8 復選框
- 13.9 單選鈕
- 13.10 下拉列表
- 13.11 列表框
- 13.12 布局的控制
- 13.13 action的替代品
- 13.14 程序片的局限
- 13.15 視窗化應用
- 13.16 新型AWT
- 13.17 Java 1.1用戶接口API
- 13.18 可視編程和Beans
- 13.19 Swing入門
- 13.20 總結
- 13.21 練習
- 第14章 多線程
- 14.1 反應靈敏的用戶界面
- 14.2 共享有限的資源
- 14.3 堵塞
- 14.4 優先級
- 14.5 回顧runnable
- 14.6 總結
- 14.7 練習
- 第15章 網絡編程
- 15.1 機器的標識
- 15.2 套接字
- 15.3 服務多個客戶
- 15.4 數據報
- 15.5 一個Web應用
- 15.6 Java與CGI的溝通
- 15.7 用JDBC連接數據庫
- 15.8 遠程方法
- 15.9 總結
- 15.10 練習
- 第16章 設計模式
- 16.1 模式的概念
- 16.2 觀察器模式
- 16.3 模擬垃圾回收站
- 16.4 改進設計
- 16.5 抽象的應用
- 16.6 多重分發
- 16.7 訪問器模式
- 16.8 RTTI真的有害嗎
- 16.9 總結
- 16.10 練習
- 第17章 項目
- 17.1 文字處理
- 17.2 方法查找工具
- 17.3 復雜性理論
- 17.4 總結
- 17.5 練習
- 附錄A 使用非JAVA代碼
- 附錄B 對比C++和Java
- 附錄C Java編程規則
- 附錄D 性能
- 附錄E 關于垃圾收集的一些話
- 附錄F 推薦讀物