ITEEDU

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都会向我们展示“异或”行为。