# 13.19 Swing入門(注釋⑦)
通過這一章的學習,當我們的工作方法在AWT中發生了巨大的改變后(如果可以回憶起很久以前,當Java第一次面世時SUN公司曾聲明Java是一種“穩定,牢固”的編程語言),可能一直有Java還不十分的成熟的感覺。的確,現在Java擁有一個不錯的事件模型以及一個優秀的組件復用設計——JavaBeans。但GUI組件看起來還相當的原始,笨拙以及相當的抽象。
⑦:寫作本節時,Swing庫顯然已被Sun“固定”下來了,所以只要你下載并安裝了Swing庫,就應該能正確地編譯和運行這里的代碼,不會出現任何問題(應該能編譯Sun配套提供的演示程序,以檢測安裝是否正確)。若遇到任何麻煩,請訪問`http://www.BruceEckel.com`,了解最近的更新情況。
而這就是Swing將要占領的領域。Swing庫在Java 1.1之后面世,因此我們可以自然而然地假設它是Java 1.2的一部分。可是,它是設計為作為一個補充在Java 1.1版中工作的。這樣,我們就不必為了享用好的UI組件庫而等待我們的平臺去支持Java 1.2版了。如果Swing庫不是我們的用戶的Java 1.1版所支持的一部分,并且產生一些意外,那他就可能真正的需要去下載Swing庫了。
Swing包含所有我們缺乏的組件,在整個本章余下的部分中:我們期望領會現代化的UI,來自按鈕的任何事件包括到樹狀和網格結構中的圖片。它是一個大庫,但在某些方面它為任務被設計得相應的復雜——如果任何事都是簡單的,我們不必編寫更多的代碼但同樣設法運行我們的代碼逐漸地變得更加的復雜。這意味著一個容易的入口,如果我們需要它我們得到它的強大力量。
Swing相當的深奧,這一節不會去試圖讓讀者理解,但會介紹它的能力和Swing簡單地使我們著手使用庫。請注意我們有意識的使用這一切變得簡單。如果我們需要運行更多的,這時Swing能或許能給我們所想要的,如果我們愿意深入地研究,可以從SUN公司的在線文檔中獲取更多的資料。
## 13.19.1 Swing有哪些優點
當我們開始使用Swing庫時,會注意到它在技術上向前邁出了巨大的一步。Swing組件是Bean,因此他們可以支持Bean的任何開發環境中使用。Swing提供了一個完全的UI組件集合。因為速度的關系,所有的組件都很小巧的(沒有“重量級”組件被使用),Swing為了輕便在Java中整個被編寫。
最重要的是我們會希望Swing被稱為“正交使用”;一旦我們采用了這種關于庫的普遍的辦法我們就可以在任何地方應用它們。這主要是因為Bean的命名規則,大多數的時候在我編寫這些程序例子時我可以猜到方法名并且第一次就將它拼寫正確而無需查找任何事物。這無疑是優秀庫設計的品質證明。另外,我們可以廣泛地插入組件到其它的組件中并且事件會正常地工作。
鍵盤操作是自動被支持的——我們可以使用Swing應用程序而不需要鼠標,但我們不得不做一些額外的編程工作(老的AWT中需要一些可怕的代碼以支持鍵盤操作)。滾動被毫不費力地支持——我們簡單地將我們的組件到一個`JScrollPane`中,同樣我們再增加它到我們的窗體中即可。其它的特征,例如工具提示條只需要一行單獨的代碼就可執行。
Swing同樣支持一些被稱為“可插入外觀和效果”的事物,這就是說UI的外觀可以在不同的平臺和不同的操作系統上被動態地改變以符合用戶的期望。它甚至可以創造我們自己的外觀和效果。
## 13.19.2 方便的轉換
如果我們長期艱苦不懈地利用Java 1.1版構建我們的UI,我們并不需要扔掉它改變到Swing陣營中來。幸運的是,庫被設計得允許容易地修改——在很多情況下我們可以簡單地放一個`J`到我們老AWT組件的每個類名前面即可。下面這個例子擁有我們所熟悉的特色:
```
//: JButtonDemo.java
// Looks like Java 1.1 but with J's added
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import javax.swing.*;
public class JButtonDemo extends Applet {
JButton
b1 = new JButton("JButton 1"),
b2 = new JButton("JButton 2");
JTextField t = new JTextField(20);
public void init() {
ActionListener al = new ActionListener() {
public void actionPerformed(ActionEvent e){
String name =
((JButton)e.getSource()).getText();
t.setText(name + " Pressed");
}
};
b1.addActionListener(al);
add(b1);
b2.addActionListener(al);
add(b2);
add(t);
}
public static void main(String args[]) {
JButtonDemo applet = new JButtonDemo();
JFrame frame = new JFrame("TextAreaNew");
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
frame.getContentPane().add(
applet, BorderLayout.CENTER);
frame.setSize(300,100);
applet.init();
applet.start();
frame.setVisible(true);
}
} ///:~
```
這是一個新的輸入語句,但此外任何事物除了增加了一些`J`外,看起都像這Java 1.1版的AWT。同樣,我們不恰當的用`add()`方法增加到Swing `JFrame`中,除此之外我們必須像上面看到的一樣先準備一些“content pane”。我們可以容易地得到Swing一個簡單的改變所帶來的好處。
因為程序中的封裝語句,我們不得不調用像下面所寫的一樣調用這個程序:
```
java c13.swing.JbuttonDemo
```
在這一節里出現的所有的程序都將需要一個相同的窗體來運行它們。
## 13.19.3 顯示框架
盡管程序片和應用程序都可以變得很重要,但如果在任何地方都使用它們就會變得混亂和毫無用處。這一節余下部分取代它們的是一個Swing程序例子的顯示框架:
```
//: Show.java
// Tool for displaying Swing demos
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Show {
public static void
inFrame(JPanel jp, int width, int height) {
String title = jp.getClass().toString();
// Remove the word "class":
if(title.indexOf("class") != -1)
title = title.substring(6);
JFrame frame = new JFrame(title);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
frame.getContentPane().add(
jp, BorderLayout.CENTER);
frame.setSize(width, height);
frame.setVisible(true);
}
} ///:~
```
那些想顯示它們自己的類將從`JPanel`處繼承并且隨后為它們自己增加一些可視化的組件。最后,它們創建一個包含下面這一行程序的`main()`:
```
Show.inFrame(new MyClass(), 500, 300);
```
最后的兩個參數是顯示的寬度和高度。
注意`JFrame`的標題是用RTTI產生的。
## 13.19.4 工具提示
幾乎所有我們利用來創建我們用戶接口的來自于`JComponent`的類都包含一個稱為`setToolTipText(string)`的方法。因此,幾乎任何我們所需要表示的(對于一個對象`jc`來說就是一些來自`JComponent`的類)都可以安放在窗體中:
```
jc.setToolTipText("My tip");
```
并且當鼠標停在`JComponent`上一個超過預先設置的一個時間,一個包含我們的文字的小框就會從鼠標下彈出。
## 13.19.5 邊框
`JComponent`同樣包括一個稱為`setBorder()`的方法,該方法允許我們安放一些各種各樣有趣的邊框到一些可見的組件上。下面的程序例子利用一個創建`JPanel`并安放邊框到每個例子中的被稱為`showBorder()`的方法,示范了一些有用的不同的邊框。同樣,它也使用RTTI來找我們使用的邊框名(剔除所有的路徑信息),然后將邊框名放到面板中間的`JLable`里:
```
//: Borders.java
// Different Swing borders
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
public class Borders extends JPanel {
static JPanel showBorder(Border b) {
JPanel jp = new JPanel();
jp.setLayout(new BorderLayout());
String nm = b.getClass().toString();
nm = nm.substring(nm.lastIndexOf('.') + 1);
jp.add(new JLabel(nm, JLabel.CENTER),
BorderLayout.CENTER);
jp.setBorder(b);
return jp;
}
public Borders() {
setLayout(new GridLayout(2,4));
add(showBorder(new TitledBorder("Title")));
add(showBorder(new EtchedBorder()));
add(showBorder(new LineBorder(Color.blue)));
add(showBorder(
new MatteBorder(5,5,30,30,Color.green)));
add(showBorder(
new BevelBorder(BevelBorder.RAISED)));
add(showBorder(
new SoftBevelBorder(BevelBorder.LOWERED)));
add(showBorder(new CompoundBorder(
new EtchedBorder(),
new LineBorder(Color.red))));
}
public static void main(String args[]) {
Show.inFrame(new Borders(), 500, 300);
}
} ///:~
```
這一節中大多數程序例子都使用`TitledBorder`,但我們可以注意到其余的邊框也同樣易于使用。能創建我們自己的邊框并安放它們到按鈕、標簽等等內——任何來自`JComponent`的東西。
## 13.19.6 按鈕
Swing增加了一些不同類型的按鈕,并且它同樣可以修改選擇組件的結構:所有的按鈕、復選框、單選鈕,甚至從`AbstractButton`處繼承的菜單項(這是因為菜單項一般被包含在其中,它可能會被改進命名為`AbstractChooser`或者相同的什么名字)。我們會注意使用菜單項的簡便,下面的例子展示了不同類型的可用的按鈕:
```
//: Buttons.java
// Various Swing buttons
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.plaf.basic.*;
import javax.swing.border.*;
public class Buttons extends JPanel {
JButton jb = new JButton("JButton");
BasicArrowButton
up = new BasicArrowButton(
BasicArrowButton.NORTH),
down = new BasicArrowButton(
BasicArrowButton.SOUTH),
right = new BasicArrowButton(
BasicArrowButton.EAST),
left = new BasicArrowButton(
BasicArrowButton.WEST);
public Buttons() {
add(jb);
add(new JToggleButton("JToggleButton"));
add(new JCheckBox("JCheckBox"));
add(new JRadioButton("JRadioButton"));
JPanel jp = new JPanel();
jp.setBorder(new TitledBorder("Directions"));
jp.add(up);
jp.add(down);
jp.add(left);
jp.add(right);
add(jp);
}
public static void main(String args[]) {
Show.inFrame(new Buttons(), 300, 200);
}
} ///:~
```
`JButton`看起來像AWT按鈕,但它沒有更多可運行的功能(像我們后面將看到的如加入圖像等)。在`com.sun.java.swing.basic`里,有一個更合適的`BasicArrowButton`按鈕,但怎樣測試它呢?有兩種類型的“指針”恰好請求箭頭按鈕使用:`Spinner`修改一個中斷值,并且`StringSpinner`通過一個字符串數組來移動(當它到達數組底部時,甚至會自動地封裝)。`ActionListeners`附著在箭頭按鈕上展示它使用的這些相關指針:因為它們是Bean,我們將期待利用方法名,正好捕捉并設置它們的值。
當我們運行這個程序例子時,我們會發現觸發按鈕保持它最新狀態,開或時關。但復選框和單選鈕每一個動作都相同,選中或沒選中(它們從`JToggleButton`處繼承)。
## 13.19.7 按鈕組
如果我們想單選鈕保持“異或”狀態,我們必須增加它們到一個按鈕組中,這幾乎同老AWT中的方法相同但更加的靈活。在下面將要證明的程序例子是,一些`AbstruactButton`能被增加到一個`ButtonGroup`中。
為避免重復一些代碼,這個程序利用映射來生不同類型的按鈕組。這會在`makeBPanel`中看到,`makeBPanel`創建了一個按鈕組和一個`JPanel`,并且為數組中的每個`String`就是`makeBPanel`的第二個參數增加一個類對象,由它的第一個參數進行聲明:
```
//: ButtonGroups.java
// Uses reflection to create groups of different
// types of AbstractButton.
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import java.lang.reflect.*;
public class ButtonGroups extends JPanel {
static String[] ids = {
"June", "Ward", "Beaver",
"Wally", "Eddie", "Lumpy",
};
static JPanel
makeBPanel(Class bClass, String[] ids) {
ButtonGroup bg = new ButtonGroup();
JPanel jp = new JPanel();
String title = bClass.getName();
title = title.substring(
title.lastIndexOf('.') + 1);
jp.setBorder(new TitledBorder(title));
for(int i = 0; i < ids.length; i++) {
AbstractButton ab = new JButton("failed");
try {
// Get the dynamic constructor method
// that takes a String argument:
Constructor ctor = bClass.getConstructor(
new Class[] { String.class });
// Create a new object:
ab = (AbstractButton)ctor.newInstance(
new Object[]{ids[i]});
} catch(Exception ex) {
System.out.println("can't create " +
bClass);
}
bg.add(ab);
jp.add(ab);
}
return jp;
}
public ButtonGroups() {
add(makeBPanel(JButton.class, ids));
add(makeBPanel(JToggleButton.class, ids));
add(makeBPanel(JCheckBox.class, ids));
add(makeBPanel(JRadioButton.class, ids));
}
public static void main(String args[]) {
Show.inFrame(new ButtonGroups(), 500, 300);
}
} ///:~
```
邊框標題由類名剔除了所有的路徑信息而來。`AbstractButton`初始化為一個`JButton`,`JButtonr`的標簽發生“失效”,因此如果我們忽略這個異常信息,我們會在屏幕上一直看到這個問題。`getConstructor()`方法產生了一個通過`getConstructor()`方法安放參數數組類型到類數組的構造器對象,然后所有我們要做的就是調用`newInstance()`,通過它一個數組對象包含我們當前的參數——在這種例子中,就是`ids`數組中的字符串。
這樣增加了一些更復雜的內容到這個簡單的程序中。為了使“異或”行為擁有按鈕,我們創建一個按鈕組并增加每個按鈕到我們所需的組中。當我們運行這個程序時,我們會注意到所有的按鈕除了`JButton`都會向我們展示“異或”行為。
## 13.19.8 圖標
我們可在一個`JLable`或從`AbstractButton`處繼承的任何事物中使用一個圖標(包括`JButton`,`JCheckbox`,`JradioButton`及不同類型的`JMenuItem`)。利用`JLables`的圖標十分的簡單容易(我們會在隨后的一個程序例子中看到)。下面的程序例子探索了我們可以利用按鈕的圖標和它們的派生物的其它所有方法。
我們可以使用任何我們需要的GIF文件,但在這個例子中使用的這個GIF文件是這本書編碼發行的一部分,可以在`www.BruceEckel.com`處下載來使用。為了打開一個文件和隨之帶來的圖像,簡單地創建一個圖標并分配它文件名。從那時起,我們可以在程序中使用這個產生的圖標。
```
//: Faces.java
// Icon behavior in JButtons
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Faces extends JPanel {
static Icon[] faces = {
new ImageIcon("face0.gif"),
new ImageIcon("face1.gif"),
new ImageIcon("face2.gif"),
new ImageIcon("face3.gif"),
new ImageIcon("face4.gif"),
};
JButton
jb = new JButton("JButton", faces[3]),
jb2 = new JButton("Disable");
boolean mad = false;
public Faces() {
jb.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e){
if(mad) {
jb.setIcon(faces[3]);
mad = false;
} else {
jb.setIcon(faces[0]);
mad = true;
}
jb.setVerticalAlignment(JButton.TOP);
jb.setHorizontalAlignment(JButton.LEFT);
}
});
jb.setRolloverEnabled(true);
jb.setRolloverIcon(faces[1]);
jb.setPressedIcon(faces[2]);
jb.setDisabledIcon(faces[4]);
jb.setToolTipText("Yow!");
add(jb);
jb2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e){
if(jb.isEnabled()) {
jb.setEnabled(false);
jb2.setText("Enable");
} else {
jb.setEnabled(true);
jb2.setText("Disable");
}
}
});
add(jb2);
}
public static void main(String args[]) {
Show.inFrame(new Faces(), 300, 200);
}
} ///:~
```
一個圖標可以在許多的構造器中使用,但我們可以使用`setIcon()`方法增加或更換圖標。這個例子同樣展示了當事件發生在`JButton`(或者一些`AbstractButton`)上時,為什么它可以設置各種各樣的顯示圖標:當`JButton`被按下時,當它被失效時,或者“滾過”時(鼠標從它上面移動過但并不擊它)。我們會注意到那給了按鈕一種動畫的感覺。
注意工具提示條也同樣增加到按鈕中。
## 13.19.9 菜單
菜單在Swing中做了重要的改進并且更加的靈活——例如,我們可以在幾乎程序中任何地方使用他們,包括在面板和程序片中。語法同它們在老的AWT中是一樣的,并且這樣使出現在老AWT的在新的Swing也出現了:我們必須為我們的菜單艱難地編寫代碼,并且有一些不再作為資源支持菜單(其它事件中的一些將使它們更易轉換成其它的編程語言)。另外,菜單代碼相當的冗長,有時還有一些混亂。下面的方法是放置所有的關于每個菜單的信息到對象的二維數組里(這種方法可以放置我們想處理的任何事物到數組里),這種方法在解決這個問題方面領先了一步。這個二維數組被菜單所創建,因此它首先表示出菜單名,并在剩余的列中表示菜單項和它們的特性。我們會注意到數組列不必保持一致——只要我們的代碼知道將發生的一切事件,每一列都可以完全不同。
```
//: Menus.java
// A menu-building system; also demonstrates
// icons in labels and menu items.
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Menus extends JPanel {
static final Boolean
bT = new Boolean(true),
bF = new Boolean(false);
// Dummy class to create type identifiers:
static class MType { MType(int i) {} };
static final MType
mi = new MType(1), // Normal menu item
cb = new MType(2), // Checkbox menu item
rb = new MType(3); // Radio button menu item
JTextField t = new JTextField(10);
JLabel l = new JLabel("Icon Selected",
Faces.faces[0], JLabel.CENTER);
ActionListener a1 = new ActionListener() {
public void actionPerformed(ActionEvent e) {
t.setText(
((JMenuItem)e.getSource()).getText());
}
};
ActionListener a2 = new ActionListener() {
public void actionPerformed(ActionEvent e) {
JMenuItem mi = (JMenuItem)e.getSource();
l.setText(mi.getText());
l.setIcon(mi.getIcon());
}
};
// Store menu data as "resources":
public Object[][] fileMenu = {
// Menu name and accelerator:
{ "File", new Character('F') },
// Name type accel listener enabled
{ "New", mi, new Character('N'), a1, bT },
{ "Open", mi, new Character('O'), a1, bT },
{ "Save", mi, new Character('S'), a1, bF },
{ "Save As", mi, new Character('A'), a1, bF},
{ null }, // Separator
{ "Exit", mi, new Character('x'), a1, bT },
};
public Object[][] editMenu = {
// Menu name:
{ "Edit", new Character('E') },
// Name type accel listener enabled
{ "Cut", mi, new Character('t'), a1, bT },
{ "Copy", mi, new Character('C'), a1, bT },
{ "Paste", mi, new Character('P'), a1, bT },
{ null }, // Separator
{ "Select All", mi,new Character('l'),a1,bT},
};
public Object[][] helpMenu = {
// Menu name:
{ "Help", new Character('H') },
// Name type accel listener enabled
{ "Index", mi, new Character('I'), a1, bT },
{ "Using help", mi,new Character('U'),a1,bT},
{ null }, // Separator
{ "About", mi, new Character('t'), a1, bT },
};
public Object[][] optionMenu = {
// Menu name:
{ "Options", new Character('O') },
// Name type accel listener enabled
{ "Option 1", cb, new Character('1'), a1,bT},
{ "Option 2", cb, new Character('2'), a1,bT},
};
public Object[][] faceMenu = {
// Menu name:
{ "Faces", new Character('a') },
// Optinal last element is icon
{ "Face 0", rb, new Character('0'), a2, bT,
Faces.faces[0] },
{ "Face 1", rb, new Character('1'), a2, bT,
Faces.faces[1] },
{ "Face 2", rb, new Character('2'), a2, bT,
Faces.faces[2] },
{ "Face 3", rb, new Character('3'), a2, bT,
Faces.faces[3] },
{ "Face 4", rb, new Character('4'), a2, bT,
Faces.faces[4] },
};
public Object[] menuBar = {
fileMenu, editMenu, faceMenu,
optionMenu, helpMenu,
};
static public JMenuBar
createMenuBar(Object[] menuBarData) {
JMenuBar menuBar = new JMenuBar();
for(int i = 0; i < menuBarData.length; i++)
menuBar.add(
createMenu((Object[][])menuBarData[i]));
return menuBar;
}
static ButtonGroup bgroup;
static public JMenu
createMenu(Object[][] menuData) {
JMenu menu = new JMenu();
menu.setText((String)menuData[0][0]);
menu.setMnemonic(
((Character)menuData[0][1]).charValue());
// Create redundantly, in case there are
// any radio buttons:
bgroup = new ButtonGroup();
for(int i = 1; i < menuData.length; i++) {
if(menuData[i][0] == null)
menu.add(new JSeparator());
else
menu.add(createMenuItem(menuData[i]));
}
return menu;
}
static public JMenuItem
createMenuItem(Object[] data) {
JMenuItem m = null;
MType type = (MType)data[1];
if(type == mi)
m = new JMenuItem();
else if(type == cb)
m = new JCheckBoxMenuItem();
else if(type == rb) {
m = new JRadioButtonMenuItem();
bgroup.add(m);
}
m.setText((String)data[0]);
m.setMnemonic(
((Character)data[2]).charValue());
m.addActionListener(
(ActionListener)data[3]);
m.setEnabled(
((Boolean)data[4]).booleanValue());
if(data.length == 6)
m.setIcon((Icon)data[5]);
return m;
}
Menus() {
setLayout(new BorderLayout());
add(createMenuBar(menuBar),
BorderLayout.NORTH);
JPanel p = new JPanel();
p.setLayout(new BorderLayout());
p.add(t, BorderLayout.NORTH);
p.add(l, BorderLayout.CENTER);
add(p, BorderLayout.CENTER);
}
public static void main(String args[]) {
Show.inFrame(new Menus(), 300, 200);
}
} ///:~
```
這個程序的目的是允許程序設計者簡單地創建表格來描述每個菜單,而不是輸入代碼行來建立菜單。每個菜單都產生一個菜單,表格中的第一列包含菜單名和鍵盤快捷鍵。其余的列包含每個菜單項的數據:字符串存在在菜單項中的位置,菜單的類型,它的快捷鍵,當菜單項被選中時被激活的動作接收器及菜單是否被激活等信息。如果列開始處是空的,它將被作為一個分隔符來處理。
為了預防浪費和冗長的多個`Boolean`創建的對象和類型標志,以下的這些在類開始時就作為`static final`被創建:`bT`和`bF`描述`Booleans`和啞類`MType`的不同對象描述標準的菜單項(`mi`),復選框菜單項(`cb`),和單選鈕菜單項(`rb`)。請記住一組`Object`可以擁有單一的`Object`引用,并且不再是原來的值。
這個程序例子同樣展示了`JLables`和`JMenuItems`(和它們的派生事物)如何處理圖標的。一個圖標經由它的構造器置放進`JLable`中并當對應的菜單項被選中時被改變。
菜單條數組控制處理所有在文件菜單清單中列出的,我們想顯示在菜單條上的文件菜單。我們通過這個數組去使用`createMenuBar()`,將數組分類成單獨的菜單數據數組,再通過每個單獨的數組去創建菜單。這種方法依次使用菜單數據的每一行并以該數據創建`JMenu`,然后為菜單數據中剩下的每一行調用`createMenuItem()`方法。最后,`createMenuItem()`方法分析菜單數據的每一行并且判斷菜單類型和它的屬性,再適當地創建菜單項。終于,像我們在菜單構造器中看到的一樣,從表示c`reateMenuBar(menuBar)`的表格中創建菜單,而所有的事物都是采用遞歸方法處理的。
這個程序不能建立串聯的菜單,但我們擁有足夠的知識,如果我們需要的話,隨時都能增加多級菜單進去。
## 13.19.10 彈出式菜單
`JPopupMenu`的執行看起來有一些別扭:我們必須調用`enableEvents()方`法并選擇鼠標事件代替利用事件接收器。它可能增加一個鼠標接收器但`MouseEvent`從`isPopupTrigger()`處不會返回真值——它不知道將激活一個彈出菜單。另外,當我們嘗試接收器方法時,它的行為令人不可思議,這或許是鼠標單擊活動引起的。在下面的程序例子里一些事件產生了這種彈出行為:
```
//: Popup.java
// Creating popup menus with Swing
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Popup extends JPanel {
JPopupMenu popup = new JPopupMenu();
JTextField t = new JTextField(10);
public Popup() {
add(t);
ActionListener al = new ActionListener() {
public void actionPerformed(ActionEvent e){
t.setText(
((JMenuItem)e.getSource()).getText());
}
};
JMenuItem m = new JMenuItem("Hither");
m.addActionListener(al);
popup.add(m);
m = new JMenuItem("Yon");
m.addActionListener(al);
popup.add(m);
m = new JMenuItem("Afar");
m.addActionListener(al);
popup.add(m);
popup.addSeparator();
m = new JMenuItem("Stay Here");
m.addActionListener(al);
popup.add(m);
PopupListener pl = new PopupListener();
addMouseListener(pl);
t.addMouseListener(pl);
}
class PopupListener extends MouseAdapter {
public void mousePressed(MouseEvent e) {
maybeShowPopup(e);
}
public void mouseReleased(MouseEvent e) {
maybeShowPopup(e);
}
private void maybeShowPopup(MouseEvent e) {
if(e.isPopupTrigger()) {
popup.show(
e.getComponent(), e.getX(), e.getY());
}
}
}
public static void main(String args[]) {
Show.inFrame(new Popup(),200,150);
}
} ///:~
```
相同的`ActionListener`被加入每個`JMenuItem`中,使其能從菜單標簽中取出文字,并將文字插入`JTextField`。
## 13.19.11 列表框和組合框
列表框和組合框在Swing中工作就像它們在老的AWT中工作一樣,但如果我們需要它,它們同樣被增加功能。另外,它也更加的方便易用。例如,`JList`中有一個顯示`String`數組的構造器(奇怪的是同樣的功能在`JComboBox`中無效!)。下面的例子顯示了它們基本的用法。
```
//: ListCombo.java
// List boxes & Combo boxes
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ListCombo extends JPanel {
public ListCombo() {
setLayout(new GridLayout(2,1));
JList list = new JList(ButtonGroups.ids);
add(new JScrollPane(list));
JComboBox combo = new JComboBox();
for(int i = 0; i < 100; i++)
combo.addItem(Integer.toString(i));
add(combo);
}
public static void main(String args[]) {
Show.inFrame(new ListCombo(),200,200);
}
} ///:~
```
最開始的時候,似乎有點兒古怪的一種情況是`JLists`居然不能自動提供滾動特性——即使那也許正是我們一直所期望的。增加對滾動的支持變得十分容易,就像上面示范的一樣——簡單地將`JList`封裝到`JScrollPane`即可,所有的細節都自動地為我們照料到了。
## 13.19.12 滑桿和進度指示條
滑桿用戶能用一個滑塊的來回移動來輸入數據,在很多情況下顯得很直觀(如聲音控制)。進程條從“空”到“滿”顯示相關數據的狀態,因此用戶得到了一個狀態的透視。我最喜愛的有關這的程序例子簡單地將滑動塊同進程條掛接起來,所以當我們移動滑動塊時,進程條也相應的改變:
```
//: Progress.java
// Using progress bars and sliders
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;
public class Progress extends JPanel {
JProgressBar pb = new JProgressBar();
JSlider sb =
new JSlider(JSlider.HORIZONTAL, 0, 100, 60);
public Progress() {
setLayout(new GridLayout(2,1));
add(pb);
sb.setValue(0);
sb.setPaintTicks(true);
sb.setMajorTickSpacing(20);
sb.setMinorTickSpacing(5);
sb.setBorder(new TitledBorder("Slide Me"));
pb.setModel(sb.getModel()); // Share model
add(sb);
}
public static void main(String args[]) {
Show.inFrame(new Progress(),200,150);
}
} ///:~
```
`JProgressBar`十分簡單,但`JSlider`卻有許多選項,例如方法、大或小的記號標簽。注意增加一個帶標題的邊框是多么的容易。
## 13.19.13 樹
使用一個`JTree`可以簡單地像下面這樣表示:
```
add(new JTree(
new Object[] {"this", "that", "other"}));
```
這個程序顯示了一個原始的樹狀物。樹狀物的API是非常巨大的,可是——當然是在Swing中的巨大。它表明我們可以做有關樹狀物的任何事,但更復雜的任務可能需要不少的研究和試驗。幸運的是,在庫中提供了一個妥協:“默認的”樹狀物組件,通常那是我們所需要的。因此大多數的時間我們可以利用這些組件,并且只在特殊的情況下我們需要更深入的研究和理解。
下面的例子使用了“默認”的樹狀物組件在一個程序片中顯示一個樹狀物。當我們按下按鈕時,一個新的子樹就被增加到當前選中的結點下(如果沒有結點被選中,就用根結節):
```
//: Trees.java
// Simple Swing tree example. Trees can be made
// vastly more complex than this.
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.*;
// Takes an array of Strings and makes the first
// element a node and the rest leaves:
class Branch {
DefaultMutableTreeNode r;
public Branch(String[] data) {
r = new DefaultMutableTreeNode(data[0]);
for(int i = 1; i < data.length; i++)
r.add(new DefaultMutableTreeNode(data[i]));
}
public DefaultMutableTreeNode node() {
return r;
}
}
public class Trees extends JPanel {
String[][] data = {
{ "Colors", "Red", "Blue", "Green" },
{ "Flavors", "Tart", "Sweet", "Bland" },
{ "Length", "Short", "Medium", "Long" },
{ "Volume", "High", "Medium", "Low" },
{ "Temperature", "High", "Medium", "Low" },
{ "Intensity", "High", "Medium", "Low" },
};
static int i = 0;
DefaultMutableTreeNode root, child, chosen;
JTree tree;
DefaultTreeModel model;
public Trees() {
setLayout(new BorderLayout());
root = new DefaultMutableTreeNode("root");
tree = new JTree(root);
// Add it and make it take care of scrolling:
add(new JScrollPane(tree),
BorderLayout.CENTER);
// Capture the tree's model:
model =(DefaultTreeModel)tree.getModel();
JButton test = new JButton("Press me");
test.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e){
if(i < data.length) {
child = new Branch(data[i++]).node();
// What's the last one you clicked?
chosen = (DefaultMutableTreeNode)
tree.getLastSelectedPathComponent();
if(chosen == null) chosen = root;
// The model will create the
// appropriate event. In response, the
// tree will update itself:
model.insertNodeInto(child, chosen, 0);
// This puts the new node on the
// currently chosen node.
}
}
});
// Change the button's colors:
test.setBackground(Color.blue);
test.setForeground(Color.white);
JPanel p = new JPanel();
p.add(test);
add(p, BorderLayout.SOUTH);
}
public static void main(String args[]) {
Show.inFrame(new Trees(),200,500);
}
} ///:~
```
最重要的類就是分支,它是一個工具,用來獲取一個字符串數組并為第一個字符串建立一個`DefaultMutableTreeNode`作為根,其余在數組中的字符串作為葉。然后`node()`方法被調用以產生“分支”的根。樹狀物類包括一個來自被制造的分支的二維字符串數組,以及用來統計數組的一個靜態中斷`i`。`DefaultMutableTreeNode`對象控制這個結節,但在屏幕上表示的是被`JTree`和它的相關(`DefaultTreeModel`)模式所控制。注意當`JTree`被增加到程序片時,它被封裝到`JScrollPane`中——這就是它全部提供的自動滾動。
`JTree`通過它自己的模型來控制。當我們修改這個模型時,模型產生一個事件,導致`JTree`對可以看見的樹狀物完成任何必要的升級。在`init()`中,模型由調用`getModel()`方法所捕捉。當按鈕被按下時,一個新的分支被創建了。然后,當前選擇的組件被找到(如果沒有選擇就是根)并且模型的`insertNodeInto()`方法做所有的改變樹狀物和導致它升級的工作。
大多數的時候,就像上面的例子一樣,程序將給我們在樹狀物中所需要的一切。不過,樹狀物擁有力量去做我們能夠想像到的任何事——在上面的例子中我們到處都可看到“`default`(默認)”字樣,我們可以取代我們自己的類來獲取不同的動作。但請注意:幾乎所有這些類都有一個具大的接口,因此我們可以花一些時間努力去理解這些錯綜復雜的樹狀物。
## 13.19.14 表格
和樹狀物一樣,表格在Swing相當的龐大和強大。它們最初有意被設計成以Java數據庫連結(JDBC,在15章有介紹)為媒介的“網格”數據庫接口,并且因此它們擁有的巨大的靈活性,使我們不再感到復雜。無疑,這是足以成為成熟的電子數據表的基礎條件而且可能為整本書提供很好的根據。但是,如果我們理解這個的基礎條件,它同樣可能創建相關的簡單的`Jtable`。
`JTable`控制數據的顯示方式,但`TableModel`控制它自己的數據。因此在我們創建`JTable`前,應先創建一個`TableModel`。我們可以全部地執行`TableModel`接口,但它通常從`helper`類的`AbstractTableModel`處簡單地繼承:
```
//: Table.java
// Simple demonstration of JTable
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.event.*;
// The TableModel controls all the data:
class DataModel extends AbstractTableModel {
Object[][] data = {
{"one", "two", "three", "four"},
{"five", "six", "seven", "eight"},
{"nine", "ten", "eleven", "twelve"},
};
// Prints data when table changes:
class TML implements TableModelListener {
public void tableChanged(TableModelEvent e) {
for(int i = 0; i < data.length; i++) {
for(int j = 0; j < data[0].length; j++)
System.out.print(data[i][j] + " ");
System.out.println();
}
}
}
DataModel() {
addTableModelListener(new TML());
}
public int getColumnCount() {
return data[0].length;
}
public int getRowCount() {
return data.length;
}
public Object getValueAt(int row, int col) {
return data[row][col];
}
public void
setValueAt(Object val, int row, int col) {
data[row][col] = val;
// Indicate the change has happened:
fireTableDataChanged();
}
public boolean
isCellEditable(int row, int col) {
return true;
}
};
public class Table extends JPanel {
public Table() {
setLayout(new BorderLayout());
JTable table = new JTable(new DataModel());
JScrollPane scrollpane =
JTable.createScrollPaneForTable(table);
add(scrollpane, BorderLayout.CENTER);
}
public static void main(String args[]) {
Show.inFrame(new Table(),200,200);
}
} ///:~
```
`DateModel`包括一組數據,但我們同樣能從其它的地方得到數據,例如從數據庫中。構造器增加了一個`TableModelListener`用來在每次表格被改變后打印數組。剩下的方法都遵循Bean的命名規則,并且當`JTable`需要在`DateModel`中顯示信息時調用。`AbstractTableModel`提供了默認的`setValueAt()`和`isCellEditable()`方法以防止修改這些數據,因此如果我們想修改這些數據,就必須重載這些方法。
一旦我們擁有一個`TableModel`,我們只需要將它分配給`JTable`構造器即可。所有有關顯示,編輯和更新的詳細資料將為我們處理。注意這個程序例子同樣將`JTable`放置在`JScrollPane`中,這是因為`JScrollPane`需要一個特殊的`JTable`方法。
## 13.19.15 卡片式對話框
在本章的前部,向我們介紹了老式的`CardLayout`,并且注意到我們怎樣去管理我們所有的卡片開關。有趣的是,有人現在認為這是一種不錯的設計。幸運的是,Swing用`JTabbedPane`對它進行了修補,由`JTabbedPane`來處理這些卡片,開關和其它的任何事物。對比`CardLayout`和`JTabbedPane`,我們會發現驚人的差異。
下面的程序例子十分的有趣,因為它利用了前面例子的設計。它們都是做為`JPanel`的派生物來構建的,因此這個程序將安放前面的每個例子到它自己在`JTabbedPane`的窗格中。我們會看到利用RTTI制造的程序十分的小巧精致:
```
//: Tabbed.java
// Using tabbed panes
package c13.swing;
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
public class Tabbed extends JPanel {
static Object[][] q = {
{ "Felix", Borders.class },
{ "The Professor", Buttons.class },
{ "Rock Bottom", ButtonGroups.class },
{ "Theodore", Faces.class },
{ "Simon", Menus.class },
{ "Alvin", Popup.class },
{ "Tom", ListCombo.class },
{ "Jerry", Progress.class },
{ "Bugs", Trees.class },
{ "Daffy", Table.class },
};
static JPanel makePanel(Class c) {
String title = c.getName();
title = title.substring(
title.lastIndexOf('.') + 1);
JPanel sp = null;
try {
sp = (JPanel)c.newInstance();
} catch(Exception e) {
System.out.println(e);
}
sp.setBorder(new TitledBorder(title));
return sp;
}
public Tabbed() {
setLayout(new BorderLayout());
JTabbedPane tabbed = new JTabbedPane();
for(int i = 0; i < q.length; i++)
tabbed.addTab((String)q[i][0],
makePanel((Class)q[i][1]));
add(tabbed, BorderLayout.CENTER);
tabbed.setSelectedIndex(q.length/2);
}
public static void main(String args[]) {
Show.inFrame(new Tabbed(),460,350);
}
} ///:~
```
再者,我們可以注意到使用的數組構造式樣:第一個元素是被置放在卡片上的`String`,第二個元素是將被顯示在對應窗格上`JPanel`類。在`Tabbed()`構造器里,我們可以看到兩個重要的`JTabbedPane`方法被使用:`addTab()`插入一個新的窗格,`setSelectedIndex()`選擇一個窗格并從它開始。(一個在中間被選中的窗格證明我們不必從第一個窗格開始)。
當我們調用`addTab()`方法時,我們為它提供卡片的`String`和一些組件(也就是說,一個AWT組件,而不是一個來自AWT的`JComponent`)。這個組件會被顯示在窗格中。一旦我們這樣做了,自然而然的就不需要更多管理了——`JTabbedPane`會為我們處理其它的任何事。
`makePanel()`方法獲取我們想創建的類`Class`對象和用`newInstance()`去創建并轉換為`JPanel`(當然,假定那些類是必須從`JPanel`繼承才能增加的類,除非在這一節中為程序例子的結構所使用)。它增加了一個包括類名并返回結果的`TitledBorder`,以作為一個`JPanel`在`addTab()`被使用。
當我們運行程序時,我們會發現如果卡片太多,填滿了一行,`JTabbedPane`自動地將它們堆積起來。
## 13.19.16 Swing消息框
開窗的環境通常包含一個標準的信息框集,允許我們很快傳遞消息給用戶或者從用戶那里捕捉消息。在Swing里,這些信息窗被包含在`JOptionPane`里的。我們有一些不同的可能實現的事件(有一些十分復雜),但有一點,我們必須盡可能的利用`static JOptionPane.showMessageDialog()`和` JOptionPane.showConfirmDialog()`方法,調用消息對話框和確認對話框。
## 13.19.17 Swing更多的知識
這一節意味著唯一向我們介紹的是Swing的強大力量和我們的著手處,因此我們能注意到通過庫,我們會感覺到我們的方法何等的簡單。到目前為止,我們已看到的可能足夠滿足我們UI設計需要的一部分。不過,這里有許多有關Swing額外的情況——它有意成為一全功能的UI設計工具箱。如果我們沒有發現我們所需要的,請到SUN公司的在線文件中去查找,并搜索WEB。這個方法幾乎可以完成我們能想到的任何事。
本節中沒有涉及的一些要點:
+ 更多特殊的組件,例如`JColorChooser`,`JFileChooser`,`JPasswordField`,`JHTMLPane`(完成簡單的HTML格式化和顯示)以及`JTextPane`(一個支持格式化,字處理和圖像的文字編輯器)。它們都非常易用。
+ Swing的新的事件類型。在一些方法中,它們看起來像異常:類型非常的重要,名字可以被用來表示除了它們自己之外的任何事物。
+ 新的布局管理:Springs & Struts以及`BoxLayout`
+ 分裂控制:一個間隔物式的分裂條,允許我們動態地處理其它組件的位置。
+ `JLayeredPane`和`JInternalFrame`被一起用來在當前幀中創建子幀,以產生多文件接口(MDI)應用程序。
+ 可插入的外觀和效果,因此我們可以編寫單個的程序可以像期望的那樣動態地適合不同的平臺和操作系統。
+ 自定義光標。
+ `JToolbar` API提供的可拖動的浮動工具條。
+ 雙緩存和為平整屏幕重新畫線的自動重畫批次。
+ 內建“取消”支持。
+ 拖放支持。
- 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 推薦讀物