59.java编程思想——创建窗口和程序片 Swing
当我们的工作方法在AWT 中发生了巨大的改变后(如果可以回忆起很久以前,当Java 第
一次面世时SUN 公司曾声明Java 是一种“稳定,牢固”的编程语言),可能一直有Java 还不十分的成熟的感觉。的确,现在Java 拥有一个不错的事件模型以及一个优秀的组件复用设计——JavaBeans。但GUI 组件看起来还相当的原始,笨拙以及相当的抽象。
Swing 库在Java 1.1 之后面世,因此我们可以自然而然地假设它是Java1.2 的一部分。可是,它是设计为作为一个补充在Java 1.1 版中工作的。这样,我们就不必为了享用好的UI组件库而等待我们的平台去支持Java 1.2 版了。如果Swing 库不是我们的用户的Java 1.1 版所支持的一部分,并且产生一些意外,那他就可能真正的需要去下载Swing库了。
Swing 包含所有我们缺乏的组件是一个大库,但在某些方面它为任务被设计得相应的复杂——如果任何事都是简单的,我们不必编写更多的代码但同样设法运行我们的代码逐渐地变得更加的复杂。这意味着一个容易的入口,如果我们需要它我们得到它的强大力量。
Swing 相当的深奥,不会去试图让读者理解,但会介绍它的能力和Swing 简单地使我们着手使用库。有意识的使用这一切变得简单。如果我们需要运行更多的,这时Swing 能或许能给我们所想要的,如果我们愿意深入地研究,可以从SUN 公司的在线文档中获取更多的资料。
1 S w i n g 有哪些优点
当我们开始使用Swing 库时,会注意到它在技术上向前迈出了巨大的一步。Swing 组件是Bean,因此他们可以支持Bean 的任何开发环境中使用。Swing 提供了一个完全的UI 组件集合。因为速度的关系,所有的组件都很小巧的(没有“重量级”组件被使用),Swing 为了轻便在Java 中整个被编写。最重要的是我们会希望Swing 被称为“正交使用”;一旦我们采用了这种关于库的普遍的办法我们就可以在任何地方应用它们。这主要是因为Bean 的命名规则,大多数的时候在我编写这些程序例子时我可以猜到方法名并且第一次就将它拼写正确而无需查找任何事物。这无疑是优秀库设计的品质证明。另外,我们可以广泛地插入组件到其它的组件中并且事件会正常地工作。
键盘操作是自动被支持的——我们可以使用Swing 应用程序而不需要鼠标,但我们不得不做一些额外的编程工作(老的AWT 中需要一些可怕的代码以支持键盘操作)。滚动被毫不费力地支持——我们简单地将我们的组件到一个JScrollPane 中,同样我们再增加它到我们的窗体中即可。其它的特征,例如工具提示条只需要一行单独的代码就可执行。
Swing 同样支持一些被称为“可插入外观和效果”的事物,这就是说UI 的外观可以在不同的平台和不同的操作系统上被动态地改变以符合用户的期望。它甚至可以创造我们自己的外观和效果。
2 方便的转换
如果我们长期艰苦不懈地利用Java 1.1 版构建我们的UI,我们并不需要扔掉它改变到Swing 阵营中来。幸运的是,库被设计得允许容易地修改——在很多情况下我们可以简单地放一个“J”到我们老AWT 组件的每个类名前面即可。
2.1 代码
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import javax.swing.*;
publicclass JButtonDemo extends Applet {
JButton b1 =new JButton("JButton 1"),b2 = new JButton("JButton 2");
JTextField t =new JTextField(20);
publicvoidinit() {
ActionListener al = new ActionListener() {
publicvoid 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);
}
publicstaticvoidmain(Stringargs[]){
JButtonDemo applet =new JButtonDemo();
JFrame frame =new JFrame("TextAreaNew");
frame.addWindowListener(new WindowAdapter() {
publicvoid 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 一个简单的改变所带来的好处。
3 显示框架
尽管程序片和应用程序都可以变得很重要,但如果在任何地方都使用它们就会变得混乱和毫无用处。余下部分取代它们的是一个Swing 程序例子的显示框架:
3.1 代码
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
publicclass Show {
publicstaticvoidinFrame(JPaneljp,intwidth,intheight){
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() {
publicvoid 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 产生的。
4 工具提示
几乎所有我们利用来创建我们用户接口的来自于JComponent 的类都包含一个称为setToolTipText(string)的方法。因此,几乎任何我们所需要表示的(对于一个对象jc 来说就是一些来自JComponent 的类)都可以安放在窗体中:
jc.setToolTipText("Mytip");
并且当鼠标停在JComponent 上一个超过预先设置的一个时间,一个包含我们的文字的小框就会从鼠标下弹出。
5 边框
JComponent 同样包括一个称为setBorder()的方法,该方法允许我们安放一些各种各样有趣的边框到一些可见的组件上。下面的程序例子利用一个创建JPanel 并安放边框到每个例子中的被称为showBorder()的方法,示范了一些有用的不同的边框。同样,它也使用RTTI 来找我们使用的边框名(剔除所有的路径信息),然后将边框名放到面板中间的JLable里:
5.1 代码
import java.awt.*;
importjava.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
publicclass Borders extends JPanel {
static JPanel showBorder(Borderb) {
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);
returnjp;
}
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(newBevelBorder(BevelBorder.RAISED)));
add(showBorder(newSoftBevelBorder(BevelBorder.LOWERED)));
add(showBorder(new CompoundBorder(new EtchedBorder(),new LineBorder(Color.red))));
}
publicstaticvoidmain(Stringargs[]){
Show.inFrame(new Borders(), 500, 300);
}
} /// :~
例子都使用TitledBorder,但我们可以注意到其余的边框也同样易于使用。能创建我们自己的边框并安放它们到按钮、标签等等内——任何来自JComponent 的东西。
6 按钮
Swing 增加了一些不同类型的按钮,并且它同样可以修改选择组件的结构:所有的按钮、复选框、单选钮,甚至从AbstractButton 处继承的菜单项(这是因为菜单项一般被包含在其中,它可能会被改进命名为“AbstractChooser”或者相同的什么名字)。我们会注意使用菜单项的简便,下面的例子展示了不同类型的可用的按钮:
6.1 代码
importjava.awt.*;
importjava.awt.event.*;
import javax.swing.*;
import javax.swing.plaf.basic.*;
import javax.swing.border.*;
publicclass Buttons extends JPanel {
JButton jb =new JButton("JButton");
BasicArrowButtonup = newBasicArrowButton(BasicArrowButton.NORTH),
down =new BasicArrowButton(BasicArrowButton.SOUTH),right = newBasicArrowButton(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);
}
publicstaticvoidmain(Stringargs[]){
Show.inFrame(new Buttons(), 300, 200);
}
} /// :~
JButton 看起来像AWT 按钮,但它没有更多可运行的功能(像我们后面将看到的如加入图像等)。在com.sun.java.swing.basic 里,有一个更合适的BasicArrowButton按钮,但怎样测试它呢?有两种类型的“指针”恰好请求箭头按钮使用:Spinner 修改一个中断值,并且StringSpinner 通过一个字符串数组来移动(当它到达数组底部时,甚至会自动地封装)。ActionListeners 附着在箭头按钮上展示它使用的这些相关指针:因为它们是Bean,我们将期待利用方法名,正好捕捉并设置它们的值。
当我们运行这个程序例子时,我们会发现触发按钮保持它最新状态,开或时关。但复选框和单选钮每一个动作都相同,选中或没选中(它们从JToggleButton 处继承)。
7 按钮组
如果我们想单选钮保持“异或”状态,我们必须增加它们到一个按钮组中,这几乎同老AWT 中的方法相同但更加的灵活。在下面将要证明的程序例子是,一些AbstruactButton能被增加到一个ButtonGroup 中。
为避免重复一些代码,这个程序利用映射来生不同类型的按钮组。这会在makeBPanel中看到,makeBPanel创建了一个按钮组和一个JPanel,并且为数组中的每个String 就是makeBPanel 的第二个自变量增加一个类对象,由它的第一个自变量进行声明:
7.1 代码
importjava.awt.*;
importjava.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import java.lang.reflect.*;
publicclass ButtonGroups extends JPanel {
static String[]ids= { "June","Ward","Beaver",
"Wally","Eddie","Lumpy",};
static JPanel makeBPanel(ClassbClass, 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 (inti = 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 (Exceptionex) {
System.out.println("can't create "+bClass);
}
bg.add(ab);
jp.add(ab);
}
returnjp;
}
public ButtonGroups() {
add(makeBPanel(JButton.class,ids));
add(makeBPanel(JToggleButton.class,ids));
add(makeBPanel(JCheckBox.class,ids));
add(makeBPanel(JRadioButton.class,ids));
}
publicstaticvoidmain(Stringargs[]){
Show.inFrame(new ButtonGroups(), 500,300);
}
} /// :~
边框标题由类名剔除了所有的路径信息而来。AbstractButton 初始化为一个JButton,JButtonr 的标签发生“失效”,因此如果我们忽略这个异常信息,我们会在屏幕上一直看到这个问题。getConstructor()方法产生了一个通过getConstructor()方法安放自变量数组类型到类数组的构建器对象,然后所有我们要做的就是调用newInstance(),通过它一个数组对象包含我们当前的自变量——在这种例子中,就是ids数组中的字符串。
这样增加了一些更复杂的内容到这个简单的程序中。为了使“异或”行为拥有按钮,我们创建一个按钮组并增加每个按钮到我们所需的组中。当我们运行这个程序时,我们会注意到所有的按钮除了JButton 都会向我们展示“异或”行为。
8 图标
我们可在一个JLable 或从AbstractButton 处继承的任何事物中使用一个图标(包括JButton,JCheckbox,JradioButton及不同类型的JMenuItem)。利用JLables 的图标十分的简单容易(我们会在随后的一个程序例子中看到)。下面的程序例子探索了我们可以利用按钮的图标和它们的衍生物的其它所有方法。
我们可以使用任何我们需要的GIF 文件,但在这个例子中使用的这个GIF 文件是这本书编码发行的一部分。为了打开一个文件和随之带来的图像,简单地创建一个图标并分配它文件名。从那时起,我们可以在程序中使用这个产生的图标。
8.1 代码
importjava.awt.*;
importjava.awt.event.*;
importjavax.swing.*;
publicclass Facesextends JPanel {
staticIcon[]faces = { newImageIcon("face0.gif"),newImageIcon("face1.gif"),newImageIcon("face2.gif"),
newImageIcon("face3.gif"),newImageIcon("face4.gif"), };
JButton jb =newJButton("JButton",faces[3]),jb2 =newJButton("Disable");
booleanmad = false;
publicFaces() {
jb.addActionListener(newActionListener() {
publicvoidactionPerformed(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(newActionListener() {
publicvoidactionPerformed(ActionEvent e) {
if (jb.isEnabled()){
jb.setEnabled(false);
jb2.setText("Enable");
} else {
jb.setEnabled(true);
jb2.setText("Disable");
}
}
});
add(jb2);
}
publicstatic voidmain(Stringargs[]) {
Show.inFrame(newFaces(), 300, 200);
}
} ///:~
一个图标可以在许多的构建器中使用,但我们可以使用setIcon()方法增加或更换图标。这个例子同样展示了当事件发生在JButton(或者一些AbstractButton)上时,为什么它可以设置各种各样的显示图标:当JButton被按下时,当它被失效时,或者“滚过”时(鼠标从它上面移动过但并不击它)。我们会注意到那给了按钮一种动画的感觉。
注意工具提示条也同样增加到按钮中。
9 菜单
菜单在Swing 中做了重要的改进并且更加的灵活——例如,我们可以在几乎程序中任何地方使用他们,包括在面板和程序片中。语法同它们在老的AWT 中是一样的,并且这样使出现在老AWT 的在新的Swing 也出现了:我们必须为我们的菜单艰难地编写代码,并且有一些不再作为资源支持菜单(其它事件中的一些将使它们更易转换成其它的编程语言)。另外,菜单代码相当的冗长,有时还有一些混乱。下面的方法是放置所有的关于每个菜单的信息到对象的二维数组里(这种方法可以放置我们想处理的任何事物到数组里),这种方法在解决这个问题方面领先了一步。这个二维数组被菜单所创建,因此它首先表示出菜单名,并在剩余的列中表示菜单项和它们的特性。我们会注意到数组列不必保持一致——只要我们的代码知道将发生的一切事件,每一列都可以完全不同。
9.1 代码
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
publicclass Menus extends JPanel {
staticfinalBoolean bT=newBoolean(true),bF=newBoolean(false);
// Dummy classto create type identifiers:
staticclassMType {
MType(inti) {
}
};
staticfinalMType mi=newMType(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() {
publicvoid actionPerformed(ActionEvent e) {
t.setText(((JMenuItem)e.getSource()).getText());
}
};
ActionListener a2 =new ActionListener() {
publicvoid actionPerformed(ActionEvent e) {
JMenuItem mi = (JMenuItem) e.getSource();
l.setText(mi.getText());
l.setIcon(mi.getIcon());
}
};
// Store menudata as "resources":
public Object[][]fileMenu= {
// Menu name and accelerator:
{ "File",new Character('F') },
// Name typeaccel listener enabled
{ "New",mi, new Character('N'),a1, bT }, {"Open",mi,newCharacter('O'),a1,bT},
{ "Save",mi, new Character('S'),a1, bF }, {"Save As",mi,newCharacter('A'),a1,bF}, { null}, // Separator
{ "Exit",mi, new Character('x'),a1, bT }, };
public Object[][]editMenu= {
// Menu name:
{ "Edit",new Character('E') },
// Name typeaccel listener enabled
{ "Cut",mi, new Character('t'),a1, bT }, {"Copy",mi,newCharacter('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 typeaccel listener enabled
{ "Index",mi, new Character('I'),a1, bT }, {"Using help",mi,newCharacter('U'),a1,bT}, { null}, // Separator
{ "About",mi, new Character('t'),a1, bT }, };
public Object[][]optionMenu = {
// Menu name:
{ "Options",new Character('O') },
// Name typeaccel listener enabled
{ "Option 1",cb, new Character('1'),a1, bT }, {"Option 2",cb,newCharacter('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,};
staticpublicJMenuBar createMenuBar(Object[]menuBarData) {
JMenuBar menuBar =new JMenuBar();
for (inti = 0; i < menuBarData.length; i++)
menuBar.add(createMenu((Object[][])menuBarData[i]));
returnmenuBar;
}
static ButtonGroupbgroup;
staticpublicJMenu 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 (inti = 1; i < menuData.length; i++) {
if (menuData[i][0] ==null)
menu.add(new JSeparator());
else
menu.add(createMenuItem(menuData[i]));
}
returnmenu;
}
staticpublicJMenuItem createMenuItem(Object[]data) {
JMenuItem m =null;
MType type = (MType)data[1];
if (type ==mi)
m =new JMenuItem();
elseif (type ==cb)
m =new JCheckBoxMenuItem();
elseif (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]);
returnm;
}
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);
}
publicstaticvoidmain(Stringargs[]){
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()方法分析菜单数据的每一行并且判断菜单类型和它的属性,再适当地创建菜单项。终于,像我们在菜单构建器中看到的一样,从表示createMenuBar(menuBar)的表格中创建菜单,而所有的事物都是采用递归方法处理的。
这个程序不能建立串联的菜单,但我们拥有足够的知识,如果我们需要的话,随时都能增加多级菜单进去。
10 弹出式菜单
JPopupMenu 的执行看起来有一些别扭:我们必须调用enableEvents()方法并选择鼠标事件代替利用事件接收器。它可能增加一个鼠标接收器但MouseEvent 从isPopupTrigger()处不会返回真值——它不知道将激活一个弹出菜单。另外,当我们尝试接收器方法时,它的行为令人不可思议,这或许是鼠标单击活动引起的。在下面的程序例子里一些事件产生了这种弹出行为:
10.1 代码
importjava.awt.*;
import java.awt.event.*;
import javax.swing.*;
publicclass Popup extends JPanel {
JPopupMenu popup =new JPopupMenu();
JTextField t =new JTextField(10);
public Popup() {
add(t);
ActionListener al = new ActionListener() {
publicvoid 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 PopupListenerextends MouseAdapter {
publicvoid mousePressed(MouseEvent e) {
maybeShowPopup(e);
}
publicvoid mouseReleased(MouseEvent e) {
maybeShowPopup(e);
}
privatevoid maybeShowPopup(MouseEvent e) {
if (e.isPopupTrigger()) {
popup.show(e.getComponent(),e.getX(), e.getY());
}
}
}
publicstaticvoidmain(Stringargs[]){
Show.inFrame(new Popup(), 200, 150);
}
} /// :~
相同的ActionListener 被加入每个JMenuItem 中,使其能从菜单标签中取出文字,并将文字插入JTextField。
11 列表框和组合框
列表框和组合框在Swing 中工作就像它们在老的AWT 中工作一样,但如果我们需要它,它们同样被增加功能。另外,它也更加的方便易用。例如,JList 中有一个显示String 数组的构建器(奇怪的是同样的功能在JComboBox 中无效!)。
11.1 代码
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ListCombo extendsJPanel {
publicListCombo() {
setLayout(newGridLayout(2, 1));
JListlist = new JList(ButtonGroups.ids);
add(newJScrollPane(list));
JComboBoxcombo = new JComboBox();
for(int i = 0; i < 100; i++)
combo.addItem(Integer.toString(i));
add(combo);
}
publicstatic void main(String args[]) {
Show.inFrame(newListCombo(), 200, 200);
}
} /// :~
最开始的时候,似乎有点儿古怪的一种情况是JLists 居然不能自动提供滚动特性——即使那也许正是我们一直所期望的。增加对滚动的支持变得十分容易,就像上面示范的一样——简单地将JList 封装到JScrollPane 即可,所有的细节都自动地为我们照料到了。
12 滑杆和进度指示条
滑杆用户能用一个滑块的来回移动来输入数据,在很多情况下显得很直观(如声音控制)。进程条从“空”到“满”显示相关数据的状态,因此用户得到了一个状态的透视。我最喜爱的有关这的程序例子简单地将滑动块同进程条挂接起来,所以当我们移动滑动块时,进程条也相应的改变:
12.1 代码
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;
public class Progress extendsJPanel {
JProgressBarpb = new JProgressBar();
JSlidersb = new JSlider(JSlider.HORIZONTAL, 0, 100, 60);
publicProgress() {
setLayout(newGridLayout(2, 1));
add(pb);
sb.setValue(0);
sb.setPaintTicks(true);
sb.setMajorTickSpacing(20);
sb.setMinorTickSpacing(5);
sb.setBorder(newTitledBorder("Slide Me"));
pb.setModel(sb.getModel());// Share model
add(sb);
}
publicstatic void main(String args[]) {
Show.inFrame(newProgress(), 200, 150);
}
} /// :~
JProgressBar 十分简单,但JSlider 却有许多选项,例如方法、大或小的记号标签。注意增加一个带标题的边框是多么的容易。
13 树
使用一个JTree 可以简单地像下面这样表示:
add(new JTree(
new Object[] {"this","that", "other"}));
这个程序显示了一个原始的树状物。树状物的API 是非常巨大的,可是——当然是在Swing 中的巨大。它表明我们可以做有关树状物的任何事,但更复杂的任务可能需要不少的研究和试验。幸运的是,在库中提供了一个妥协:“默认的”树状物组件,通常那是我们所需要的。因此大多数的时间我们可以利用这些组件,并且只在特殊的情况下我们需要更深入的研究和理解。
下面的例子使用了“默认”的树状物组件在一个程序片中显示一个树状物。当我们按下按钮时,一个新的子树就被增加到当前选中的结点下(如果没有结点被选中,就用根结节):
13.1 代码
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 (inti = 1; i < data.length; i++)
r.add(new DefaultMutableTreeNode(data[i]));
}
public DefaultMutableTreeNode node() {
returnr;
}
}
publicclass 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"}, };
staticinti= 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() {
publicvoid 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);
}
publicstaticvoidmain(Stringargs[]){
Show.inFrame(new Trees(), 200, 500);
}
} /// :~
最重要的类就是分支,它是一个工具,用来获取一个字符串数组并为第一个字符串建立一个
DefaultMutableTreeNode 作为根,其余在数组中的字符串作为叶。然后node()方法被调用以产生“分支”的根。树状物类包括一个来自被制造的分支的二维字符串数组,以及用来统计数组的一个静态中断i。DefaultMutableTreeNode 对象控制这个结节,但在屏幕上表示的是被JTree 和它的相关(DefaultTreeModel)模式所控制。注意当JTree 被增加到程序片时,它被封装到JScrollPane 中——这就是它全部提供的自动滚动。
JTree 通过它自己的模型来控制。当我们修改这个模型时,模型产生一个事件,导致JTree 对可以看见的树状物完成任何必要的升级。在init()中,模型由调用getModel()方法所捕捉。当按钮被按下时,一个新的分支被创建了。然后,当前选择的组件被找到(如果没有选择就是根)并且模型的insertNodeInto()方法做所有的改变树状物和导致它升级的工作。
大多数的时候,就像上面的例子一样,程序将给我们在树状物中所需要的一切。不过,树状物拥有力量去做我们能够想像到的任何事——在上面的例子中我们到处都可看到“default(默认)”字样,我们可以取代我们自己的类来获取不同的动作。但请注意:几乎所有这些类都有一个具大的接口,因此我们可以花一些时间努力去理解这些错综复杂的树状物。
14 表格
和树状物一样,表格在Swing 相当的庞大和强大。它们最初有意被设计成以Java 数据库连结为媒介的“网格”数据库接口,并且因此它们拥有的巨大的灵活性,使我们不再感到复杂。无
疑,这是足以成为成熟的电子数据表的基础条件而且可能为整本书提供很好的根据。但是,如果我们理解这个的基础条件,它同样可能创建相关的简单的Jtable。
JTable 控制数据的显示方式,但TableModel 控制它自己的数据。因此在我们创建JTable 前,应先创建一个TableModel。我们可以全部地执行TableModel 接口,但它通常从helper 类的AbstractTableModel 处简单地继承:
14.1 代码
import java.awt.*;
importjava.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.event.*;
// The TableModel controls all the data:
classDataModel extendsAbstractTableModel {
Object[][] data = { {"one", "two","three","four"}, {"five","six","seven","eight"},
{ "nine","ten", "eleven","twelve"}, };
// Prints datawhen table changes:
class TMLimplementsTableModelListener {
publicvoid tableChanged(TableModelEvent e) {
for (inti = 0; i < data.length; i++) {
for (intj = 0; j < data[0].length; j++)
System.out.print(data[i][j] +" ");
System.out.println();
}
}
}
DataModel() {
addTableModelListener(new TML());
}
publicintgetColumnCount() {
returndata[0].length;
}
publicintgetRowCount() {
returndata.length;
}
public Object getValueAt(introw,intcol){
returndata[row][col];
}
publicvoidsetValueAt(Object val,introw,intcol){
data[row][col]=val;
// Indicate the change has happened:
fireTableDataChanged();
}
publicbooleanisCellEditable(introw,intcol){
returntrue;
}
};
publicclass Table extends JPanel {
public Table() {
setLayout(new BorderLayout());
JTable table =new JTable(new DataModel());
JScrollPane scrollpane = JTable.createScrollPaneForTable(table);
add(scrollpane, BorderLayout.CENTER);
}
publicstaticvoidmain(Stringargs[]){
Show.inFrame(new Table(), 200, 200);
}
} /// :~
DateModel 包括一组数据,但我们同样能从其它的地方得到数据,例如从数据库中。构建器增加了一个TableModelListener 用来在每次表格被改变后打印数组。剩下的方法都遵循Bean的命名规则,并且当JTable 需要在DateModel 中显示信息时调用。AbstractTableModel 提供了默认的setValueAt()和isCellEditable()方法以防止修改这些数据,因此如果我们想修改这些数据,就必须过载这些方法。一旦我们拥有一个TableModel,我们只需要将它分配给JTable 构建器即可。所有有关显示,编辑和更新的详细资料将为我们处理。注意这个程序例子同样将JTable 放置在JScrollPane 中,这是因为JScrollPane 需要一个特殊的JTable 方法。
15 卡片式对话框
介绍了老式的CardLayout,并且注意到我们怎样去管理我们所有的卡片开关。有趣的是,有人现在认为这是一种不错的设计。幸运的是,Swing 用JTabbedPane 对它进行了修补,由JTabbedPane来处理这些卡片,开关和其它的任何事物。对比CardLayout 和JTabbedPane,我们会发现惊人的差异。
下面的程序例子十分的有趣,因为它利用了前面例子的设计。它们都是做为JPanel 的衍生物来构建的,因此这个程序将安放前面的每个例子到它自己在JTabbedPane的窗格中。我们会看到利用RTTI 制造的程序十分的小巧精致:
15.1 代码
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
publicclass 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(Classc) {
String title =c.getName();
title =title.substring(title.lastIndexOf('.') + 1);
JPanel sp =null;
try {
sp = (JPanel)c.newInstance();
} catch (Exceptione) {
System.out.println(e);
}
sp.setBorder(new TitledBorder(title));
returnsp;
}
public Tabbed() {
setLayout(new BorderLayout());
JTabbedPane tabbed =new JTabbedPane();
for (inti = 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);
}
publicstaticvoidmain(Stringargs[]){
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自动地将它们堆积起来。
16 S w i n g 消息框
开窗的环境通常包含一个标准的信息框集,允许我们很快传递消息给用户或者从用户那里捕捉消息。在Swing 里,这些信息窗被包含在JOptionPane 里的。我们有一些不同的可能实现的事件(有一些十分复杂),但有一点,我们必须尽可能的利用static JOptionPane.showMessageDialog()和JOptionPane.showConfirmDialog()方法,调用消息对话框和确认对话框。
17 S w i n g 更多的知识
我们能注意到通过库,我们会感觉到我们的方法何等的简单。到目前为止,我们已看到的可能足够满足我们UI 设计需要的一部分。不过,这里有许多有关Swing 额外的情况——它有意成为一全功能的UI 设计工具箱。如果我们没有发现我们所需要的,请到SUN 公司的在线文件中去查找,并搜索WEB。这个方法几乎可以完成我们能想到的任何事。
一些要点:
■更多特殊的组件,例如JColorChooser,JFileChooser,JPasswordField,JHTMLPane(完成简单的HTML 格式化和显示)以及JTextPane(一个支持格式化,字处理和图像的文字编辑器)。它们都非常易用。
■Swing 的新的事件类型。在一些方法中,它们看起来像违例:类型非常的重要,名字可以被用来表示除了它们自己之外的任何事物。
■新的布局管理:Springs & Struts 以及BoxLayout
■分裂控制:一个间隔物式的分裂条,允许我们动态地处理其它组件的位置。
■JLayeredPane 和JInternalFrame 被一起用来在当前帧中创建子帧,以产生多文件接口(MDI)应用程序。
■可插入的外观和效果,因此我们可以编写单个的程序可以像期望的那样动态地适合不同的平台和操作系统。
■自定义光标。
■JToolbar API 提供的可拖动的浮动工具条。
■双缓存和为平整屏幕重新画线的自动重画批次。
■内建“取消”支持。
■拖放支持。
18 总结
对于AWT 而言,Java 1.1 到Java1.2 最大的改变就是Java 中所有的库。Java 1.0 版的AWT 曾作为目前见过的最糟糕的一个设计被彻底地批评,并且当它允许我们在创建小巧精致的程序时,产生的GUI“在所有的平台上都同样的平庸”。它与在特殊平台上本地应用程序开发工具相比也是受到限制的,笨拙的并且也是不友好的。当Java 1.1 版纳入新的事件模型和Java Beans 时,平台被设置——现在它可以被拖放到可视化的应用程序构建工具中,创建GUI 组件。另外,事件模型的设计和Bean 无疑对轻松的编程和可维护的代码都非常的在意(这些在Java 1.0 AWT 中不那么的明显)。但直至GUI 组件-JFC/Swing 类-显示工作结束它才这样。对于Swing 组件而言,交叉平台GUI 编程可以变成一种有教育意义的经验。
现在,唯一的情况是缺乏应用程序构建工具,并且这就是真正的变革的存在之处。微软的Visual Basic 和Visual C++需要它们的应用程序构建工具,同样的是Borland 的Delphi 和C++构建器。如果我们需要应用程序构建工具变得更好,我们不得不交叉我们的指针并且希望自动授权机会给我们所需要的。Java 是一个开放的环境,因此不但考虑到同其它的应用程序构建环境竞争,而且Java 还促进它们的发展。这些工具被认真地使用,它们必须支持Java Beans。这意味着一个平等的应用领域:如果一个更好的应用程序构建工具出现,我们不需要去约束它就可以使用——我们可以采用并移动到新的工具上工作即可,这会提高我们的工作效率。这种竞争的环境对应用程序构建工具来说从未出现过,这种竞争能真正提高程序设计者的工作效率。