作为一个 Java 程序员,从论坛上感受到使用 Java 开发程序的人越来多,心中不免欣慰。但是,同样是从论坛中,看到多数人提到 Java 就以为是网络开发——不是这样的,Java 也可以开发应用程序,而且可以开发出漂亮的图形用户界面的应用程序,也就是 Windows/XWindow 应用程序。因此,我写下这篇文章,希望能带你进入 Java 图形用户界面设计之门。
- /*
- * AwtSwing.java
- * @author Fancy
- */
- import java.awt.BorderLayout;
- import java.awt.Button;
- import javax.swing.JButton;
- import javax.swing.JDesktopPane;
- import javax.swing.JFrame;
- import javax.swing.JInternalFrame;
- import javax.swing.JPanel;
- public final class AwtSwing ...{
- public static void main(String[] args) ...{
- AwtSwing as = new AwtSwing();
- as.show();
- }
- JFrame frame = new JFrame("Test AWT and SWING");
- JDesktopPane jdp = new JDesktopPane();
- JInternalFrame jif1 = new JInternalFrame("controls");
- JInternalFrame jif2 = new JInternalFrame("cover");
- public AwtSwing() ...{
- frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- frame.getContentPane().add(jdp);
- jif1.setContentPane(new JPanel());
- jif2.setContentPane(new JPanel());
- jif1.getContentPane().setLayout(new BorderLayout());
- jif1.getContentPane().add(new Button("AWT Button"), BorderLayout.WEST);
- jif1.getContentPane().add(new JButton("Swing Button"),
- BorderLayout.EAST);
- jif1.setSize(200, 100);
- jif2.setSize(200, 100);
- jdp.add(jif1);
- jdp.add(jif2);
- frame.setSize(240, 140);
- }
- public void show() ...{
- frame.setVisible(true);
- jif1.setVisible(true);
- jif2.setVisible(true);
- }
- }
运行这个程序,并用鼠标拖动那个名为“cover”的子窗口,我们会发现一个非常有趣的现象,如图:
显然 cover 子窗口是在 controls 子窗口之上的,但是它只罩盖住了 Swing Button,没有罩盖住 AWT Button。再看一会儿,你是不是有这样一种感觉:Swing Button 是“画”上去的,而 AWT Button 则是“贴”上去的。这就是二者混用造成层次错乱的一个例子。
- /**
- * @(#) TestFrame.java
- * @author James
- */
- import javax.swing.*;
- import java.awt.event.*;
- public class TestFrame extends JFrame ...{
- private int counter = 0;
- public TestFrame() ...{
- /**//* 使用匿名类添加一个窗口监听器 */
- addWindowListener(new WindowAdapter() ...{
- public void windowClosing(WindowEvent e) ...{
- System.out.println(
- "Exit when Closed event");
- //退出应用程序
- System.exit(0);
- }
- public void windowActivated(WindowEvent e) ...{
- // 改变窗口标题
- setTitle("Test Frame " + counter++);
- }
- });
- // 设置窗口为固定大小
- setResizable(false);
- setSize(200, 150);
- }
- public static void main(String[] args) ...{
- TestFrame tf = new TestFrame();
- tf.show();
- }
- }
这个例子中,我们设计了一个窗口类(public class TestFrame extends JFrame { ... }),并且为这个窗口添加了一个窗口监听器 (addWindowListener(new WindowAdapter() ...)。而我们添加的这个窗口监听器主要监听了两个事件:窗口关闭 (public void windowClosing(WindowEvent e) ...) 和窗口激活 (public void windowActivated(WindowEvent e) ...)。在窗口关闭事件中我们退出了整个应用程序(System.exit(0);),而在窗口激活事件中,我们改变了窗口的标题 (setTitle("Test Frame " + counter++);)。最后,我们在 main 方法中显示了这窗口类的一个实例,运行得到下图所示的结果:
这个程序的运行结果就是一个什么东西都没有加的框架,也就是一个空窗口。那么,你知道显示一个窗口最主要的几句代码吗?不知道没关系,我来告诉你,显示一个窗口只需要做三件事:生成实例(对象)→设置大小→显示,相应的,就是下面的三句代码:
- JFrame frame = new JFrame("Frame's Title");
- frame.setSize(400, 300);
- frame.setVisible(true);
也许你会说:第一句的意思我清楚,第三句的意思我也明白,为什么一定要第二句呢?其实想想也就明白了,叫你画一个没法有大小的矩形你能画出来吗?不能。同样,没有大小的窗口,怎么显示?所以我们需要用 setSize(int width, int height) 方法为其设置大小。我们还有另一种方法:用 JFrame 的 pack() 方法让它自己适配一个大小。pack() 在多数时候是令人满意的,但有时,它也会让你哭笑不得——多试试就知道了。
上例重载了其中两个方法。如果在上例运行产生的窗口和另外一个应用程序窗口之间来回切换 (在 Windows 操作系统中你可以使用 Alt+Tab 进行切换)……试试看,你发现了什么?有没有现我们的示例窗口标题上的数字一直在增加,这便是在 windowActivated 事件中 setTitle("Test Frame " + counter++) 的功劳。
上图中,从上到下,依次就是按钮、切换按钮、复选按钮和单选按钮。图示的窗口,就是下面这个例子的运行结果:
- /*
- * TestButtons.java
- * @author Fancy
- */
- import java.awt.event.ActionEvent;
- import java.awt.event.ActionListener;
- import java.awt.event.ItemEvent;
- import java.awt.event.ItemListener;
- import javax.swing.ButtonGroup;
- import javax.swing.JButton;
- import javax.swing.JCheckBox;
- import javax.swing.JFrame;
- import javax.swing.JLabel;
- import javax.swing.JRadioButton;
- import javax.swing.JToggleButton;
- public final class TestButtons ...{
- public static void main(String[] args) ...{
- TestButtons tb = new TestButtons();
- tb.show();
- }
- JFrame frame = new JFrame("Test Buttons");
- JButton jButton = new JButton("JButton"); // 按钮
- JToggleButton toggle = new JToggleButton("Toggle Button"); // 切换按钮
- JCheckBox checkBox = new JCheckBox("Check Box"); // 复选按钮
- JRadioButton radio1 = new JRadioButton("Radio Button 1"); // 单选按钮
- JRadioButton radio2 = new JRadioButton("Radio Button 2");
- JRadioButton radio3 = new JRadioButton("Radio Button 3");
- JLabel label = new JLabel("Here is Status, look here."); // 不是按钮,是静态文本
- public TestButtons() ...{
- frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- frame.getContentPane().setLayout(new java.awt.FlowLayout());
- // 为一般按钮添加动作监听器
- jButton.addActionListener(new ActionListener() ...{
- public void actionPerformed(ActionEvent ae) ...{
- label.setText("You clicked jButton");
- }
- });
- // 为切换按钮添加动作监听器
- toggle.addActionListener(new ActionListener() ...{
- public void actionPerformed(ActionEvent ae) ...{
- JToggleButton toggle = (JToggleButton) ae.getSource();
- if (toggle.isSelected()) ...{
- label.setText("You selected Toggle Button");
- } else ...{
- label.setText("You deselected Toggle Button");
- }
- }
- });
- // 为复选按钮添加条目监听器
- checkBox.addItemListener(new ItemListener() ...{
- public void itemStateChanged(ItemEvent e) ...{
- JCheckBox cb = (JCheckBox) e.getSource();
- label.setText("Selected Check Box is " + cb.isSelected());
- }
- });
- // 用一个按钮组对象包容一组单选按钮
- ButtonGroup group = new ButtonGroup();
- // 生成一个新的动作监听器对象,备用
- ActionListener al = new ActionListener() ...{
- public void actionPerformed(ActionEvent ae) ...{
- JRadioButton radio = (JRadioButton) ae.getSource();
- if (radio == radio1) ...{
- label.setText("You selected Radio Button 1");
- } else if (radio == radio2) ...{
- label.setText("You selected Radio Button 2");
- } else ...{
- label.setText("You selected Radio Button 3");
- }
- }
- };
- // 为各单选按钮添加动作监听器
- radio1.addActionListener(al);
- radio2.addActionListener(al);
- radio3.addActionListener(al);
- // 将单选按钮添加到按钮组中
- group.add(radio1);
- group.add(radio2);
- group.add(radio3);
- frame.getContentPane().add(jButton);
- frame.getContentPane().add(toggle);
- frame.getContentPane().add(checkBox);
- frame.getContentPane().add(radio1);
- frame.getContentPane().add(radio2);
- frame.getContentPane().add(radio3);
- frame.getContentPane().add(label);
- frame.setSize(200, 250);
- }
- public void show() ...{
- frame.setVisible(true);
- }
- }
除一般按钮外,其余三种按钮都有两种状态,即选择 (按下) 状态和未选择 (弹起) 状态。那么我们又该如何判断呢?切换按钮 (JToggleButton) 提供了一个 isSelected() 方法用来判断当前所处的状态,返回值为真 (true) 时表示它处于选择状态,返回值为假 (false) 时表示它处于未选择状态。而复选按钮 (JCheckBox) 和单选按钮 (JRadioButton) 都是从 JToggleButton 继承的,所以也具有 isSelected() 方法。如上例中 if (toggle.isSelected()) { ... } 等。
- /**
- * @(#) Test.java
- * @author James
- */
- import javax.swing.*;
- import java.awt.event.*;
- public class Test ...{
- JButton b;
- JRadioButton rb;
- public Test() ...{
- JFrame f = new JFrame("Test");
- f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- f.getContentPane().setLayout(
- new java.awt.FlowLayout());
- b = new JButton("JButton");
- rb = new JRadioButton("RadioButton");
- ActionListener a = new ActionListener() ...{
- public void actionPerformed(ActionEvent ae) ...{
- if (ae.getSource() == b) ...{
- System.out.println(
- "You clicked the JButton");
- } else ...{
- System.out.println(
- "You clicked the RadioButton");
- }
- }
- };
- b.addActionListener(a);
- rb.addActionListener(a);
- f.getContentPane().add(b);
- f.getContentPane().add(rb);
- f.pack();
- f.show();
- }
- public static void main(String[] args) ...{
- new Test();
- }
- }
运行程序后,分别单击两个按钮,相应的,在控制台能分别得到如下输出:
- /*
- * TestTexts.java
- * @author Fancy
- */
- import javax.swing.JFrame;
- import javax.swing.JLabel;
- import javax.swing.JPasswordField;
- import javax.swing.JTextArea;
- import javax.swing.JTextField;
- import javax.swing.event.CaretEvent;
- import javax.swing.event.CaretListener;
- public final class TestTexts extends JFrame ...{
- public static void main(String[] args) ...{
- TestTexts tt = new TestTexts();
- tt.setVisible(true);
- }
- private JLabel label = new JLabel("Status");
- private JTextField textField;
- private JPasswordField pwdField;
- private JTextArea textArea;
- public TestTexts() ...{
- super("Test Texts");
- setDefaultCloseOperation(EXIT_ON_CLOSE);
- getContentPane().setLayout(new java.awt.FlowLayout());
- textField = new JTextField(15);
- /**//* 监听文本光标移动事件 */
- textField.addCaretListener(new CaretListener() ...{
- public void caretUpdate(CaretEvent e) ...{
- // 如果改变了内容,就可以即时更新 label 显示的内容
- label.setText(textField.getText());
- }
- });
- pwdField = new JPasswordField(15);
- pwdField.setEchoChar('#');
- textArea = new JTextArea(5, 15);
- textArea.setLineWrap(true);
- getContentPane().add(textField);
- getContentPane().add(pwdField);
- getContentPane().add(textArea);
- getContentPane().add(label);
- setSize(200, 200);
- }
- }
上例中,我们构造了一个宽度为 15 个字符的单行文本框 (textField = new JTextField(15);),并使用 addCaretListener 方法添加了一个 CaretListener (textField.addCaretListener ...)。CaretListener 监听文本光标的移动事件。当用户使用键盘、鼠标等移动了文本光标在 JTextField 中的位置时触发这个事件。我们需要重载 caretUpdate(CaretEvent e) 对事件进行处理 (public void caretUpdate(CaretEvent e) ...)。这样,我们可以在这里做类似 VB 中 TextBox 的 OnChange 事件中做的事情。
就上述的编辑器为例,如果选用 FlowLayout,那么两个按钮和一个多行文本框就会排列在一行——当然这是窗口足够宽的情况;如果窗口稍窄一些,则可能分两行排列,第一行有两个按钮,而第二行是多行文本框——这是最理想的情况;如果窗口再窄一些,就可能分三行排列,第一行和第二行分别放置一个按钮,第三行放置多行文本框。因此,如果窗口大小可以改变,那么三个组件的位置关系也可能随着窗口大小的变化而变化。其实上面所举的例程中,大部分都是用的 FlowLayout,那是因为我们没有要求组件的布局。
刚才已经提到了使用 JPanel。JPanel 作为一个容器,可以包容一些组件,然后将这个 JPanel 对象作为一个组件添加到另一个容器 (称作父容器) 中。这个功能有什么好处呢?
- /*
- * TestPanels.java
- * @author Fancy
- */
- import java.awt.BorderLayout;
- import javax.swing.JButton;
- import javax.swing.JFrame;
- import javax.swing.JPanel;
- import javax.swing.JScrollPane;
- import javax.swing.JTextArea;
- public final class TestPanels extends JFrame ...{
- public static void main(String[] args) ...{
- TestPanels tp = new TestPanels();
- tp.setVisible(true);
- }
- public TestPanels() ...{
- setDefaultCloseOperation(EXIT_ON_CLOSE);
- JPanel panel = new JPanel();
- for (int i = 0; i < 2; i++) ...{
- panel.add(new JButton("Button 00" + i));
- }
- JTextArea textArea = new JTextArea(5, 15);
- textArea.setLineWrap(true);
- JScrollPane scrollPane = new JScrollPane(textArea);
- getContentPane().add(panel, BorderLayout.NORTH);
- getContentPane().add(scrollPane, BorderLayout.CENTER);
- pack();
- }
- }
这个例子的运行结果如下图,正是我们想要的结果——上面两个按钮,下面是一个可以滚动的多行文本框:
上例中首先产生了一个 JPanel 对象 (JPanel panel = new JPanel();),然后将两个按钮置于其中 (panel.add ...);然后产生了一个多行文本框 (JTextArea textArea = new JTextArea(5, 15);),并使用一个滚动窗格将它包裹起来 (JScrollPane scrollPane = new JScrollPane(textArea);),使之成为可以滚动的多行文本框。最后将两个容器 (JPanel 对象和 JScrollPane 对象) 分别添加到了窗口的北部 (getContentPane().add(panel, BorderLayout.NORTH);) 和中部 (也就是剩余部分,getContentPane().add(scrollPane, BorderLayout.CENTER);)。