[关闭]
@Darcy 2017-08-11T03:14:03.000000Z 字数 12650 阅读 3161

第五章 图形计算器

JavaForKids

Java给你提供了一整套类库以用来开发图形应用程序。而其中两组主要的类(库)分别是AWTSwing

1.AWT和Swing

Java刚诞生时,只有AWT这一个类库可以用来开发图形界面。这个库提供了类似ButtonTextFieldLabel等一些简单的类。没多久,另一个更高级的类库Swing也发布了。同样的,它也含有按钮,文本框和其他一些窗口组件。Swing中的组件以字母J开头,例如,JButton, JTextField, JLabel等。

Swing中所有的东西都更好,更快,也更方便。但有时我们的程序需要在一些不支持Swing的旧Java虚拟机中执行,关于这个,你会在第7章看到一些用AWT开发的例子。但在本章中,我们会使用Swing来开发一个图形计算器。

此外,Eclipse中还有一组称为标准控件工具箱(Standard Widget Toolkit, SWT)的Java类, 但我们不会在这本中使用它们。

2. 包与import语句

Java中许多有用的类都是组织在包(package)中的。有些包中的类可以用来画图,另一些则可以用来与 Internet通信等等。举个例子,String类放在一个称为java.lang的包中,也因此,String类的全称是java.lang.String

Java编译器只知道到哪里去找java.lang中的类,而很多其他有用的类放在其他包中,因此你有责任告诉编译器去哪里找到这些类。例如,大部分Swing类放在下面两个包中:

  1. javax.swing
  2. javax.swing.event

如果每次都要用完整的名字类表示一个类,那将是非常烦人的一件事。为了避免这个麻烦,你可以类的声 明前使用import 语句:

  1. import javax.swing.JFrame;
  2. import javax.swing.JButton;
  3. class Calculator{
  4. JButton myButton = new JButton();
  5. JFrame myFrame = new JFrame();
  6. }

import 语句可以让你使用诸如JFrameJButton之类的短类名,同时也告诉了Java编译器去哪里找这些类。

如果你需要在同一个包中使用好几个类,你不需要用import一 一列出它们,而仅需使用通配符*。 下面这个例子中的星号*可以让包javax.swing中所有的类在你的程序中都可见。

  1. import javax.swing.*;

虽然如此,为每个类分别使用import语句仍然是更好的选择,因为你可以清楚地看到从某个包中导入某个类。 我们将在第10章讨论更多关于包的内容。

3. Swing中的主要元素

一个Swing应用程序主要包含如下几个重要的对象:

通常地,一个程序会生成一个面板(JPanel)的实例并赋予它一个布局管理器(LayoutManager)。然后,生成其他一些窗口控件并添加到这个面板实例中。最后,将这个面板添加到窗口框架(JFrame)中,设置其大小并显示它。

然而,显示窗口仅仅是工作的一半,因为控件们还需要知道如何对一系列事件作出响应。例如,按钮被点击。

本章我们会学习如何做一个漂亮的窗口,下一章我们则会告诉你如何对控件的事件作出响应。

接下来我们的目标是编写一个简单的计算器,它的功能包括计算两个数字的和,并显示计算结果。首先我们在Eclipse中新建一个工程,然后新建类SimpleCalcultor,并添加以下代码:

  1. import javax.swing.*;
  2. import java.awt.FlowLayout;
  3. public class SimpleCalculator {
  4. public static void main(String[] args) {
  5. // 创建一个面板
  6. JPanel windowContent = new JPanel();
  7. // 为这个面板设置一个布局管理器
  8. FlowLayout fl = new FlowLayout();
  9. windowContent.setLayout(fl);
  10. // 创建有关的控件
  11. JLabel label1 = new JLabel("Number 1:");
  12. JTextField field1 = new JTextField(10);
  13. JLabel label2 = new JLabel("Number 2:");
  14. JTextField field2 = new JTextField(10);
  15. JLabel label3 = new JLabel("Sum:");
  16. JTextField result = new JTextField(10);
  17. JButton go = new JButton("Add");
  18. //把这些控件添加到面板中
  19. windowContent.add(label1);
  20. windowContent.add(field1);
  21. windowContent.add(label2);
  22. windowContent.add(field2);
  23. windowContent.add(label3);
  24. windowContent.add(result);
  25. windowContent.add(go);
  26. // 创建一个窗口框架,并把面板添加到上面
  27. JFrame frame = new JFrame("My First Calculator");
  28. frame.setContentPane(windowContent);
  29. // 设置这个窗口的大小,并显示它
  30. frame.setSize(400, 100);
  31. frame.setVisible(true);
  32. }
  33. }

编译并运行,它将会显示如下这样一个小窗口:

这也许不是最好看的计算器,但它让我们有机会去学习如何给一个窗口添加控件并显示它。下一节,在布局管理器的帮助下,我们可以让它变得更好看。

4. 布局管理器

一些过时的编程语言会要求你精确地设置窗口元素的坐标和大小,在这种情况下,如果预先知道使用这个程序的全部用户的设备屏幕参数的话,程序或许会工作的很好。而Java的布局管理器可以在不知道它们的确切位置的情况下也能帮助你很好地显示窗口控件。总之,布局管理器可以让你的控件在不同尺寸的屏幕下看起来更加和谐。

Swing提供了一下几个管理器:

为了使用这些布局管理器,程序需要去初始化它,然后把这个对象传给容器,可以参考上面的类SimpleCalculator

5. 流式布局

这个布局将组件一行一行地排列。例如,如果有空间,标签(JLable),文本框(JTextField)和按钮(JButton)会被排列在同一行。当某一行放满时,就会转到下一行,依次类推。当用户改变窗口大小时,将会触发组件的重排列。 现在,你可以试着运行这个程序,试着将鼠标放置在某个角落去改变它的大小,会随着窗口大小的改变而发生哪些变化?

在下面的代码中,关键字this表示类SimpleCalculator的当前实例。

  1. FlowLayout fl = new FlowLayout();
  2. this.setLayoutManager(fl);

好吧,FlowLayout貌似对我们的计算器来说不是一个好选择,我们现在去试试一些新的东西。

6. 网格布局

java.awt.GridLayout让你可以用一个网格的行和列来管理组件。组件将被添加到可视的网格单元 (cell)中。如果窗口变大,网格也会随之变大,但它们的相对位置不会改变。我们的计算器有7个组件 ——3个标签、3个文本框和一个按钮。我们可以用一个4行2列的网格来放置它们(一个单元留空):

  1. //该GridLayout构造方法的第一个参数是行,第二个参数是列
  2. GridLayout gr = new GridLayout(4, 2);

另外,你也可以在网格单元间放置一些水平或竖直的间隙,例如下面的例子,均为5个像素:

  1. //该GridLayout构造方法的第三个参数是水平间隙,第四个参数是竖直间隙
  2. GridLayout gr = new GridLayout(4, 2, 5, 5);

使用新的管理器更改界面后(在后面展示),我们的计算器会变得更加好看。

在工程MyCalculator新建并编译一个新的类 SimpleCalculatorGrid:

  1. import javax.swing.*;
  2. import java.awt.GridLayout;
  3. public class SimpleCalculatorGrid {
  4. public static void main(String[] args) {
  5. // 创建一个面板
  6. JPanel windowContent = new JPanel();
  7. // 为这个面板指定一个布局管理器
  8. GridLayout gl = new GridLayout(4, 2);
  9. windowContent.setLayout(gl);
  10. // 创建各种控件
  11. JLabel label1 = new JLabel("Number 1:");
  12. JTextField field1 = new JTextField(10);
  13. JLabel label2 = new JLabel("Number 2:");
  14. JTextField field2 = new JTextField(10);
  15. JLabel label3 = new JLabel("Sum:");
  16. JTextField result = new JTextField(10);
  17. JButton go = new JButton("Add");
  18. // 把控件们添加到面板中
  19. windowContent.add(label1);
  20. windowContent.add(field1);
  21. windowContent.add(label2);
  22. windowContent.add(field2);
  23. windowContent.add(label3);
  24. windowContent.add(result);
  25. windowContent.add(go);
  26. // 创建窗口框架并把面板添加到上面
  27. JFrame frame = new JFrame("My First Calculator");
  28. frame.setContentPane(windowContent);
  29. // 设置窗口的大小并显示它
  30. // frame.pack();
  31. frame.setSize(400, 100);
  32. frame.setVisible(true);
  33. }
  34. }

运行程序 SimpleCalculatorGrid 后,你将会会看到:

试着更改窗口大小,控件也将随之变化,但他们的相对位置始终不变:

关于网格布局还有一件事情要记住的: 所有的网格单元都拥有同样的宽度和高度。

7. 边框布局

java.awt.BorderLayout将窗口分为东(East)、西(West)、南(South)、北(North)、中 (Center) 五个区域。North 区始终位于窗口的上方,South则位于底部,West在左边,East在右边。Center区域会随着窗口大小的变化而充满空间。

例如,在下面展示的计算器中,用来显示数字的文本框就是放在 North 区域。

你可以像下面这样创建一个 BorderLayout,并添加一个文本框:

  1. BorderLayout bl = new BorderLayout();
  2. this.setLayoutManager(bl);
  3. JTextField txtDisplay = new JTextField(20);
  4. this.add("North", txtDisplay);

你并不需要一次使用全部5个区域。如果你只使用North,CenterSouth区域,那么Center区域会尽可能地占用EastWest区域,因为你并没有用到它们。

稍后些,我们会在下一个版本的计算器Calculator.java中使用BorderLayout

8. 组合布局管理器

你觉得网格布局GridLayout可否让你写出像微软的Windows所提供的计算器那样的界面呢?

很遗憾,它不能。因为这个计算器中的网格单元有着不同的大小--文本框比按钮大的多。不过,你可以组合使用多个面板,让它们各自拥有不同的布局管理器。

为了在上面的计算器中组合使用布局管理器,需要做一下几个步骤:

  1. 给内容面板设置一个边框布局(BorderLayout)。
  2. 添加一个 JTextFieldNorth 区域用以显示数字。
  3. 创建面板p1,使用GridLayout布局管理器,给它添加20个按钮后把p1放置于内容面板的Center区域。
  4. 同上,给它添加4个按钮并将其放到内容面板的West区域。

让我们从一个像下面这样的简单些的计算器开始:

创建一个新的类Calculator并运行程序。仔细读读下面程序中的注释以理解它是如何工作的。

  1. import javax.swing.*;
  2. import java.awt.GridLayout;
  3. import java.awt.BorderLayout;
  4. public class Calculator {
  5. // 声明所有的计算器组件
  6. JPanel windowContent;
  7. JTextField displayField;
  8. JButton button0;
  9. JButton button1;
  10. JButton button2;
  11. JButton button3;
  12. JButton button4;
  13. JButton button5;
  14. JButton button6;
  15. JButton button7;
  16. JButton button8;
  17. JButton button9;
  18. JButton buttonPoint;
  19. JButton buttonEqual;
  20. JPanel p1;
  21. // 在构造方法里面创建各种组件,
  22. // 组合使用边框布局(Borderlayout)和网格布局(Gridlayout)来把这些组件添加到窗口中
  23. Calculator() {
  24. windowContent = new JPanel();
  25. // 为面板设置边框布局
  26. BorderLayout bl = new BorderLayout();
  27. windowContent.setLayout(bl);
  28. // 创建显示文本框,并把它放到窗口的North区域。
  29. displayField = new JTextField(30);
  30. windowContent.add("North", displayField);
  31. // 用带文本参数的JButton的构造方法来创建各种按钮
  32. button0 = new JButton("0");
  33. button1 = new JButton("1");
  34. button2 = new JButton("2");
  35. button3 = new JButton("3");
  36. button4 = new JButton("4");
  37. button5 = new JButton("5");
  38. button6 = new JButton("6");
  39. button7 = new JButton("7");
  40. button8 = new JButton("8");
  41. button9 = new JButton("9");
  42. buttonPoint = new JButton(".");
  43. buttonEqual = new JButton("=");
  44. // 创建带网格布局的面板,其中可以放置12个按钮:10个数字,一个小数点和一个等号。
  45. p1 = new JPanel();
  46. GridLayout gl = new GridLayout(4, 3);
  47. p1.setLayout(gl);
  48. // 把这些控件都添加到面板p1上
  49. p1.add(button1);
  50. p1.add(button2);
  51. p1.add(button3);
  52. p1.add(button4);
  53. p1.add(button5);
  54. p1.add(button6);
  55. p1.add(button7);
  56. p1.add(button8);
  57. p1.add(button9);
  58. p1.add(button0);
  59. p1.add(buttonPoint);
  60. p1.add(buttonEqual);
  61. // 把面板p1添加到窗口的Center区域中
  62. windowContent.add("Center", p1);
  63. // 创建窗口框架,并设置它的内容面板
  64. JFrame frame = new JFrame("Calculator");
  65. frame.setContentPane(windowContent);
  66. // 设置自适应窗口的大小
  67. frame.pack();
  68. // 最后,显示这个窗口框架
  69. frame.setVisible(true);
  70. }
  71. public static void main(String[] args) {
  72. Calculator calc = new Calculator();
  73. }
  74. }

9. 盒式布局

java.awt.BoxLayout允许水平(沿x轴)或竖直(沿y轴)放置多个控件。与流式布局不同,当拥有BoxLayout的窗口大小改变时,它里面的控件不会跟着变化。在盒式布局里,控件们可以有不同的尺寸(网格布局里却不能 这么做)。

下面的两行代码将JPanel设置为竖直对齐的盒式布局:

  1. JPanel p1 = new JPanel();
  2. p1.setLayout(new BoxLayout(p1, BoxLayout.Y_AXIS));

为了让代码不至于太冗长,我并没有声明一个用来存放BoxLayout实例的变量,而是实例化一个对象后立即将它作为参数传递给setLayout()

阅读下面的代码示例,理解盒式布局是怎么工作的:

  1. import java.awt.Component;
  2. import java.awt.Container;
  3. import javax.swing.BoxLayout;
  4. import javax.swing.JButton;
  5. import javax.swing.JFrame;
  6. public class BoxLayoutDemo {
  7. public static void addComponentsToPane(Container pane) {
  8. //为面板设置竖直对齐的盒式布局
  9. pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));
  10. addAButton("Button 1", pane);
  11. addAButton("Button 2", pane);
  12. addAButton("Button 3", pane);
  13. addAButton("Long-Named Button 4", pane);
  14. addAButton("5", pane);
  15. }
  16. /**
  17. * 添加一个文本为text的按钮到内容面板container上
  18. */
  19. private static void addAButton(String text, Container container) {
  20. JButton button = new JButton(text);
  21. button.setAlignmentX(Component.CENTER_ALIGNMENT);
  22. container.add(button);
  23. }
  24. public static void main(String[] args) {
  25. JFrame frame = new JFrame("BoxLayoutDemo");
  26. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  27. addComponentsToPane(frame.getContentPane());
  28. frame.pack();
  29. frame.setVisible(true);
  30. }
  31. }

运行的结果显示:

10. 栅格式布局

在这一节里,我会展示另一种创建计算器窗口的方式,它使用java.awt.GridBagLayout而不是组合使用布局和面板。

我们的计算器有多个行和列,在网格布局中,所有的组件只能是相同的尺寸。这对于位于顶部,宽度相当于三个数字按钮的文本框来说是不合适的。

GridBagLayout是一个更加高级的布局管理器,同时它也是Java布局管理器中最复杂的一个,它可以让你赋予网格单元不同的尺寸。GridBagLayout与另一个称为GridBagConstraints的类一起工作。GridBagConstraints用来设置网格单元的一些属性,你应该为每个单元分别去设置它的相关属性。另外,我们必须在向某个特定的单元放入组件前设置它的限制条件。举个例子,有个称为gridwidth的限制条件,它可以让某个单元拥有若干倍于其他单元的宽度。

在使用GridBagLayout时,你要先创建一个GridBagConstraints对象并对它的一些属性进行设置。完成后,你便可以将组件添加到容器中。

  1. JPanel pane = new JPanel(new GridBagLayout());
  2. GridBagConstraints c = new GridBagConstraints();
  3. /**
  4. 设置GridBagConstraints的各种条件
  5. ...
  6. */
  7. pane.add(theComponent, c);

我们先来看一下GridBagConstraints中的相关属性吧:

注意: 以下我们讨论关于GridBagLayout的有关内容,都是假设我们的组件容器是从左到右(left-to-right)方向的类型的。

下面的代码示例可以帮助你理解这个布局的工作原理:

  1. JButton button;
  2. //设置面板为GridBagLayout布局
  3. pane.setLayout(new GridBagLayout());
  4. GridBagConstraints c = new GridBagConstraints();
  5. button = new JButton("Button 1");
  6. c.weightx =1;
  7. c.fill = GridBagConstraints.HORIZONTAL;
  8. c.gridx = 0;
  9. c.gridy = 0;
  10. pane.add(button, c);
  11. button = new JButton("Button 2");
  12. c.fill = GridBagConstraints.HORIZONTAL;
  13. c.weightx = 1;
  14. c.gridx = 1;
  15. c.gridy = 0;
  16. pane.add(button, c);
  17. button = new JButton("Button 3");
  18. c.fill = GridBagConstraints.HORIZONTAL;
  19. c.weightx = 1;
  20. c.gridx = 2;
  21. c.gridy = 0;
  22. pane.add(button, c);
  23. button = new JButton("Long-Named Button 4");
  24. c.fill = GridBagConstraints.HORIZONTAL;
  25. c.ipady = 40; // 让这个按钮的上下加上40个像素
  26. c.weightx = 0.0;
  27. c.gridwidth = 3;
  28. c.gridx = 0;
  29. c.gridy = 1;
  30. pane.add(button, c);
  31. button = new JButton("5");
  32. c.fill = GridBagConstraints.HORIZONTAL;//向水平方向铺满
  33. c.ipady = 0; // 重新设置回默认值
  34. c.weighty = 1.0; // 当窗口变高时,分给它更多的空间
  35. c.anchor = GridBagConstraints.FIRST_LINE_START; // 位置在显示区域的右上角处
  36. c.insets = new Insets(10, 0, 0, 0); // 顶部的间距为10个像素
  37. c.gridx = 1; // 和Button2对齐
  38. c.gridwidth = 2; // 占两列宽
  39. c.gridy = 2; // 在第三行
  40. pane.add(button, c);

看完上面的代码,你能想到它的样子了吗?

红色的框就是每个组件所占用的显示区域大小了。按钮1、2、3的weightx值都是1,所以在窗口变宽时,它们的宽度始终是相等的。

11. 卡片布局

想象一下一副叠放在一起的牌,你所能看到的,仅仅是最上面的那张。如果你需要创建一个折叠的tab, 那么你需要的就是 java.awt.CardLayout 了。

当你点击一个tab时,窗口的内容也会随之改变。事实上,程序所需的所有面板都预先加载并相互堆 叠。当用户点击某一个tab时,程序便会将某张“牌”拿到最上面,并且使得其他的“牌”不可见。

其实,大多数人并不会直接去使用这个布局,因为在Swing库里,还有一个更好用来管理tab的组件,它叫 JTabbedPanel

12. 我可以创建一个窗口而不使用布局管理器吗?

答案是肯定的!在添加控件时,你可以为它们设置一个屏幕坐标。在这种情况下,你的类必须显式地表明不会使用布局管理器。Java有一个特殊的关键字 null,它可以用来表示“还没有值”。以后 我们还会经常遇到这个关键字。下面的例子表示不使用任何布局管理器:

  1. windowContent.setLayout(null);

如果你使用了这个设置,你必须为每个窗口控件显式设置左上角的坐标、宽度和高度。下面的例子在窗口左 上角往下200像素、往右100像素的地方放置一个宽高分别为40和20像素的按钮。

  1. JButton myButton = new JButton("New Game");
  2. myButton.setBounds(100, 200, 40, 20);

13. 窗口组件

我并不打算在这本书解释所有的Swing组件。如果有兴趣,你可以在扩展阅读一节找到一些有关Swing 的在线教程。教程里有所有Swing组件的详细参考。我们的计算器只用了JButtonJLabelJTextField,下面是可用的组件:

此外,你还可以创建菜单(JMenuJPopupMenu),弹出式窗口(popup window),在框架里创建子框架 (JInternalFrame),使用一些具有标准外观的窗口(JFileChooser, JColorChooserJOptionPane)。

Java还有很多非常棒的关于Swing组件的示例,它们放在Java安装路径里的 demo/jfc/SwingSet2[1]。打开文SwingSet.html后,你便会看到类似于下面图片的窗口。

试着点击工具条上的任意图片,看看他们会有什么反应。你通过选择Source Code这个便签页从而找到程序的源代码。举个例子,如果你点击了从左数的第四个图标(所谓的combobox),你会看到一个像 下面这样的窗口:

Swing有许多不同的组件可以让你创建漂亮的图形用户界面!

在这一章里,我们通过简单地编写代码来创建图形程序而不是某些特别的工具。但是,存在这样一些工具,通过它们,你可以从工具条选择一个组件并将起拖放到窗口中。它们会为控件们自动生成合适的代 码。其中一个免费的用于SwingSWT组件的图形用户界面设计工具是CloudGarden所创作的 jigllo , 你可以在扩展阅读材料一节中找到关于它的更多信息。

接下来的一章,我们将会学习如何让窗口根据用户的动作作出响应。

14. 扩展阅读

  1. Swing 官网教程
    http://docs.oracle.com/javase/tutorial/uiswing/

  2. 类 JFormattedTextField:
    https://docs.oracle.com/javase/tutorial/uiswing/components/formattedtextfield.html

  3. SWT 开发者指导:
    http://wiki.eclipse.org/SWT/Developer_Guide

  4. Jigloo GUI builder:
    http://www.cloudgarden.org/jigloo/

15. 练习

  1. 修改类Calculator.java,为它添加 +-*/这几个按钮。先将它们添加到一个面板中,再将 这个面板放到内容面板的East区域。

  2. 查看JFormattedTextField的在线文档,然后用它代替Calculator.java中的JTextField。使用它的目的是创建一个和真实计算器一样文字右对齐的文本框。

16. 进一步的练习

修改类 Calculator.java,让它像下面这样将10个数字按钮放到一个数组里:

  1. Button[] numButtons = new Button[10];

把从下面这行代码开始的那十行用它替换掉:

  1. button0 = new JButton("0");

然后用一个循环来创建这些对象并放到数组中。
提示:看看第七章中Tic-Tac-Toe中的代码。


創用 CC 授權條款
本著作係採用創用 CC 姓名標示-非商業性-禁止改作 2.5 中國大陸 授權條款授權.


[1] 译者注:现在已从JDK分离,示例程序需要独立下载,相对路径不变, 你可以从这里下载:http://t.cn/8sUuTbt
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注