/** * TestPanels.java * @author Fancy */ import javax.swing.*; import java.awt.*; public class TestPanels extends JFrame { 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(); } public static void main(String[] args) { TestPanels tp = new TestPanels(); tp.show(); } } |
|
二. 框架、监听器和事件 框架 (Frame) 是 Java 图形用户界面的基础,它就是我们通常所说的窗口,是 Windows/XWindow 应用程序的典型特征。说到 Windows/XWindow,大家很容易联想到“事件 (Event) 驱动”。Java 的图形用户界面正是事件驱动的,并且由各种各样的监听器 (Listener) 负责捕捉各种事件。 如果我们需要对某一个组件的某种事件进行捕捉和处理时,就需要为其添加监听器。比如,我们要在一个窗口 (JFrame) 激活时改变它的标题,我们就需要为这个窗口 (JFrame 对象) 添加一个可以监听到“激活窗口”这一事件的监听器——WindowListener。 怎么添加监听器呢?这通常由组件类提供的一个 addXXXXXListener 的方法来完成。比如 JFrame 就提供有 addWindowListener 方法添加窗口监听器 (WindowListener)。 一个监听器常常不只监听一个事件,而是可以监听相关的多个事件。比如 WindowListener 除了监听窗口激活事件 (windowActivate) 之外,还可以监听窗口关闭事件 (windowClosing) 等。那么这些事件怎么区分呢?就靠重载监听器类 (Class) 的多个方法 (Method) 了,监听器监听到某个事件后,会自动调用相关的方法。我们只要重载这个方法,就可以处理相应的事件了。 不妨先看一个例子:
这个例子中,我们设计了一个窗口类(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 方法中显示了这窗口类的一个实例,运行得到下图所示的结果:
这个程序的运行结果就是一个什么东西都没有加的框架,也就是一个空窗口。那么,你知道显示一个窗口最主要的几句代码吗?不知道没关系,我来告诉你,显示一个窗口只需要做三件事:生成实例(对象) -> 设置大小 -> 显示,相应的,就是下面的三句代码:
也许你会说:第一句的意思我清楚,第三句的意思我也明白,为什么一定要第二句呢?其实想想也就明白了,叫你画一个没法有大小的矩形你能画出来吗?不能。同样,没有大小的窗口,怎么显示?所以我们需要用 setSize(int width, int height) 方法为其设置大小。我们还有另一种方法:用 JFrame 的 pack() 方法让它自己适配一个大小。pack() 在多数时候是令人满意的,但有时,它也会让你哭笑不得——多试试就知道了。 在 JFrame 中,我们使用 addWindowListener 方法加入一个监听器 WindowListener (addWindowListener(new WindowAdapter() ...) 去监听发生在 JFrame 上的窗口事件。WindowListener 是一个接口,在 java.awt.event 这个包中,但是上例中好象并没有使用 WindowListener,而是使用的 WindowsAdapter 吧,这是怎么回事? WindowAdapter 是 WindowsListener 接口的一个最简单的实现,也在包 java.awt.event 中。如果我们直接使用 WindowListener 产生一个匿名类,需要实现它的每一个方法 (一共 7 个)。但 WindowAdapter 作为 WindowListener 最简单的实现,已经实现了它的每一个方法为空方法 (即只包含空语句,或者说没有语句的方法)。用 WindowAdapter 就只需要重载可能用到的方法 (上例中只有 2 个) 就行了,而不需要再去实现每一个方法。优点显而易见——减少代码量。 在 JFrame 上发生的窗口事件 (WindowEvent) 包括: windowActivated(WindowEvent e) 窗口得到焦点时触发 windowClosed(WindowEvent e) 窗口关闭之后触发 windowClosing(WindowEvent e) 窗口关闭时触发 windowDeactivated(WindowEvent e) 窗口失去焦点时触发 windowDeiconified(WindowEvent e) windowIconified(WindowEvent e) windowOpened(WindowEvent e) 窗口打开之后触发 上例重载了其中两个方法。如果在上例运行产生的窗口和另外一个应用程序窗口之间来回切换 (在 Windows 操作系统中你可以使用 Alt+Tab 进行切换)……试试看,你发现了什么?有没有现我们的示例窗口标题上的数字一直在增加,这便是在 windowActivated 事件中 setTitle("Test Frame " + counter++); 的功劳。 而另一个事件处理函数 windowClosing 中的 System.exit(0) 则保证了当窗口被关闭时退出当前的 Java 应用程序。如果不作这样的处理会怎样呢?试验之后你会发现,窗口虽然关闭了,但程序并没有结束,但此时,除了使用 ^C 强行结束之外,恐怕也没有其它办法了。所以,这一点非常重要:如果你想在关闭窗口的时候退出应用程序,需要你自己写代码处理 windowClosing 事件。……也不尽然,其实还有另外一个更简单的办法,让 JFrame 自己处理这件事——你只需要如下调用 JFrame 的 setDefaultCloseOperation 即可:
我们可以在 JFrame 对象中添加 AWT 或者 Swing 组件。但是,虽然它有 add 方法,却不能直接用于添加组件,否则会抛出异常——不信就试试。造成这个现象的原因只有一个解释:JFrame 不是一个容器,它只是一个框架。那么,应该怎么添加组件呢? JFrame 有一个 Content Pane,窗口是显示的所有组件都是添加在这个 Content Pane 中。JFrame 提供了两个方法:getContentPane 和 setContentPane 就是用于获取和设置其 Content Pane 的。通常我们不需要重新设置 JFrame 的 Content Pane,只需要直接获取这个 Content Pane 来添加组件等。如:(new JFrame()).getContentPane().add(new Button("test button")); |
|
/** * TestButtons.java * @author Fancy */ import javax.swing.*; import java.awt.event.*; public class TestButtons { 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.show(); } public static void main(String[] args) { TestButtons tb = new TestButtons(); tb.show(); } } |
/** * Test.java * @author Fancy */ 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(); } } |
四. 文本输入框、密码输入框 文本输入框包括两种,单行文本输入框 (JTextField) 和多行文本输入框 (JTextArea)。密码输入框则只有一种 (JPasswordField)。JPasswordField 是 JTextField 的子类,它们的主要区别是 JPasswordField 不会显示出用户输入的东西,而只会显示出程序员设定的一个固定字符,比如 '*'。 下面的示例图和代码是 JTextField、JPasswordField 和 JTextArea 的示例:
上例中,我们构造了一个宽度为 15 个字符的单行文本框 (textField = new JTextField(15);),并使用 addCaretListener 方法添加了一个 CaretListener (textField.addCaretListener ...)。CaretListener 监听文本光标的移动事件。当用户使用键盘、鼠标等移动了文本光标在 JTextField 中的位置时触发这个事件。我们需要重载 caretUpdate(CaretEvent e) 对事件进行处理 (public void caretUpdate(CaretEvent e) ...)。这样,我们可以在这里做类似 VB 中 TextBox 的 OnChange 事件中做的事情。 JTextField 有 5 个构造方法,常用其中的四个: JTextField() JTextField(int columns),如上例 textField = new JTextField(15); JTextField(String text) JTextField(String text, int columns) 其中,参数 text 是单行文本框的初始内容,而 columns 指定了单行文本框的宽度,以字符为单位。JTextField 中的文本内容可以用 getText() 方法获得。也可以用 setText 方法指定 JTextField 中的文本内容。 JPasswordField 是 JTextField 的子类,其构造方法也是类似的。JPasswordField 提供了 setEchoChar(char ch) 方法设置为了隐藏密码而显示的字符,默认为 '*' 字符,上例中则设置为了 '#' 字符 (pwdField.setEchoChar('#');)。与 JTextField 一样,JPasswordField 也用 getText 方法和 setText 获得或者设置文本内容 (当然在用户界面上是隐藏的)。 JTextField 是单行文本框,不能显示多行文本,如果想要显示多行文本,就只好使用多行文本框 JTextArea 了。JTextArea 有六个构造方法,常用的也是四个: JTextArea() JTextArea(int rows, int columns) JTextArea(String text) JTextArea(String text, int rows, int columns) text 为 JTextArea 的初始化文本内容;rows 为 JTextArea 的高度,以行为单位;columns 为 JTextArea 的宽度,以字符为单位。如上例中就构造了一个高 5 行,宽 15 个字符的多行文本框 (textArea = new JTextArea(5, 15);)。 多行文本框默认是不会自动折行的 (不过可以输入回车符换行),我们可以使用 JTextArea 的 setLineWrap 方法设置是否允许自动折行。setLineWrap(true) 是允许自动折行,setLineWrap(false) 则是不允许自动折行。多行文本框会根据用户输入的内容自动扩展大小,不信,自己做个实验——如果不自动折行,那么多行文本框的宽度由最长的一行文字确定的;如果行数据超过了预设的行数,则多行文本框会扩展自身的高度去适应。换句话说,多行文本框不会自动产生滚动条。怎么办?后面讲到滚动窗格 (JScrollPane) 的时候,你就知道了。 多行文本框里文本内容的获得和设置,同样可以使用 getText 和 setText 两个方法来完成。 |
布局管理器 | 布局特点 |
FlowLayout | 将组件按从左到右从上到下的顺序依次排列,一行不能放完则折到下一行继续放置 |
BorderLayout | 将组件按东、南、西、北、中五个区域放置,每个方向最多只能放置一个组件 |
GridLayout | 形似一个无框线的表格,每个单元格中放一个组件 |
BoxLayout | 就像整齐放置的一行或者一列盒子,每个盒子中一个组件 |
/** * TestPanels.java * @author Fancy */ import javax.swing.*; import java.awt.*; public class TestPanels extends JFrame { 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(); } public static void main(String[] args) { TestPanels tp = new TestPanels(); tp.show(); } } |
|