您的位置:寻梦网首页编程乐园Java天地Core Java>输入、界面和网络
Java 天地
Java 的输入、界面和网络

在“Java 对多媒体的支持”一文中, 介绍了如何在Java的Applet中绘制图形、动画和加入声音。 这一章要介绍如何设计人机交互界面、如何接收用户的输入以及如何处理这些输入。

5.1 接收鼠标、键盘的输入

Java中的事件(Event)处理是AWT(Abstract WindowingToolkit)的一部分。当某些情?况发生时, 例如鼠标的移动、键盘的按键等, 会触发相应的事件。而通过这些事件, AWT构件与用户或AWT构件之间就可以进行某种通讯。AWT对事件的处理分为两种情况, 一种是由AWT或浏览器负责处理, 例如paint()方法, 还有一种是不作处理, 例如鼠标的移动。 在这一节里, 我们要讨论的是对于这些AWT不作处理的方法, 如何自己编写并覆盖这些事件处理方法, 例如接收鼠标、键盘的输入。 关于AWT如何处理这些事件, 在本节的最后一部分将专门讨论。为了接收鼠标、键盘的输入, 可把它们相应的事件分成三类:鼠标的按键;鼠标的移动或拖动;键盘的输入。

5.1.1 鼠标的按键

对于鼠标的按键, AWT会产生两种事件。当鼠标左键按下时产生mouseDown事件, 当鼠标左键弹起时产生mouseUp事件。对于这两种事件, 系统会分别调用相应的处理方法mouseDown()和mouseUp()。 所以为了在程序中接收这两种事件, 需要在程序中覆盖这两个事件的处理方法。例如对mouseDown()方法, 其调用方式为:

 public boolean mouseDown(Event evt, int x, int y) {………}

其中X, Y是事件发生时鼠标的位置, evt参数是由系统产生的Event类的一个实例, 它包含了关于这个事件的一些信息。如果将程序改写为:

 public boolean mouseDown(Event evt, int x, int y) {
   System.out.println("A mouse click happened");
   return true;
 }

那么在每次鼠标按下时, 都会输出这句话。mouseUp()的使用与mouseDown()是相同的。 有一点需要说明的是, 这个方法必须返回一个布尔值。 返回什么样的布尔值, 取决于方法对事件的处理。如果返回true, 则表明方法已完成对事件的处理;如果返回false, 则表明需要其它AWT构件来处理。在大多数情况下, 都是返回true。下面是使用mouseDown()方法的一个较完整的例子。

1:  import java.awt.Graphics;
2:  import java.awt.Color;
3:  import java.awt.Event;
4:
5:  public class triangle extends java.applet.Applet {
6:    int xspots = new int[3];
7:    int yspots = new int[3];
8:    int currspots = 0;
9:
10:   public void init() {
11:     setBackground(Color.white);
12: }
13:
14:   public boolean mouseDown(Event evt, int x, int y) {
15:     if (currspots <3) { 
16:       xspots[currspots]="x;" 
17:       yspots[currspots]="y;" 
18:       currspots ++; 
19:       repaint(); 
20:     } 
21:     return true; 
22:   } 
23: 
24:   public void paint(Graphics g) { 
25:     int i; 
26:     g.setColor (Color.green); 
27:     for (i="0;i" < 3; i++) { 
28:       g.fillOval(xspots[i] 2,yspots[i] 2,4,4); 
29:     } 
30:     if (currspots="=" 3) { 
31:       g.setColor(Color.red); 
32:       for (i="1;" i<3; i++) { 
33:         g.drawLine(xspots[i-1],yspots[i-1],xspots[i],yspots[i]); 
34:       } 
35:       g.drawLine(xspots[i-1], yspots[i-1], xspots[0], yspots[0]); 
36:     } 
37:   } 
38:  } 

这个程序的功能是用鼠标在屏幕上选定三个点, 然后将这三个点连成一个三角形。 现在我们来解释一下这个程序。这个程序中使用了三个AWT类:Graphics, Color和Event 类, 这在程序的1-3行已标出。也可只用一句 import java.awt.*; 6-8行中, 程序中使用了两个数组xspots和yspots来储存三个点的坐标值(在paint()方 法中需要使用), 另外设置一个变量currspots来记录已画点的个数。 14-22行是mouseDown方法。每次鼠标按下后, 都调用方法mouseDown()。它首先判断是否 已画满三个点。如果未满, 则在数组中加入该点的坐标值, 并调用repaint()方法;如果 已满, 则不做处理。 24-37行是paint方法。 在上章中讲到, repaint()方法的功能是刷新背景, 并调用paint() 方法。 因此在paint()方法中, 首先将已有的点重新标出(原有点已被刷新), 然后判断 如果已有三个点, 那么将三个点连成一个三角形。

5.1.2 鼠标的移动

鼠标的移动有两种情况, 鼠标的移动和鼠标的拖动, 同样也对应了两个方法mouseDrag和mouseMove。其调用方式和使用方法与mouseDown等相同:

public boolean mouseDrag( Event evt, int x, int y){………}

下面是一个使用mouseDrag方法的例子。

1: import java.awt.*;
2:
3: public class drawcircle extends java.applet.Applet {
4:   Point start,end;
5:
6:   public void init() {
7:     setBackground(Color.white);
8:   }
9:
10:  public boolean mouseDown(Event evt, int x, int y) {
11:    start = new Point(x,y);
12:    return true;
13:  }
14:
15:  public boolean mouseDrag(Event evt, int x, int y) {
16:    end = new Point(x,y);
17:    repaint();
18:    return true;
19:  }
20:
21:  public void paint(Graphics g) {
22:    g.setColor(Color.blue);
23:    if (start.x <= end.x) 
24:      if (start.y <="end.y)" 
25:        g.drawOval(start.x, start.y, end.x-start.x, end.y-start.y); 
26:      else g.drawOval(start.x, end.y, end.x-start.x, start.y-end.y); 
27:    else 
28:   if (start.y <="end.y)" 
29:      g.drawOval(end.x, start.y, start.x-end.x, end.y-start.y); 
30:    else g.drawOval(end.x, end.y, start.x-end.x, start.y-end.y); 
31:  } 
32:}

这个程序是用来模拟Windows中Paintbrush的画圆处理。它的处理方法是鼠标按下的 位置是起始点, 然后拖动鼠标, 松开鼠标时的位置为结束点, 在这两点形成的长方形中画 一个内切椭圆。这个程序中第4行, 使用了两个Point类型的变量start和end来存放起始点 和结束点的坐标。

10-13行是mouseDown方法, 它的功能是在每次鼠标按下时给起始点赋值。

15-19行是mouseDrag方法。因为在鼠标拖动时, 结束点的位置尚未确定, 所以, 在每次 调用mouseDrag时, 都要设置新的结束点坐标, 并调用repaint方法。一但鼠标松开, 就以 最后一次调用mouseDrag时确定的坐标为结束点坐标。

21-31行是paint方法, 这里考虑到起始点与结束点的位置关系, 所以用了双重判断, 并 调用画圆方法。

这个程序执行的结果是只能在屏幕上画出一个圆, 这是因为程序中只有一 对变量来储存起始点和结束点的坐标。每次画新圆时, 已画的圆就被刷新。如果象上小节 的例子中, 设置两个数组, 则可以把画出的圆保留下来。但这个方法也有缺点, 能显示的 圆的数量受数组大小的限制。最好的解决办法是用上章中提到的双重缓冲的思想, 使用一 个缓冲区来保存屏幕上的图像。

除了mouseDrag和mouseMove以外, Java中还提供两个相似的方法mouseEnter和 mouseExit。这两个方法是在鼠标进入和离开一个Applet画面时被调用。它们的调用方式 与前面的几个相同。

  public boolean mouseEnter (Event evt, int x, int y) { ……… } 

5.1.3 键盘的输入

当用户按下或松开键盘的按键时, 会产生键盘事件。相应的处理方法是keyDown和keyUp。它们的调用方式与上面的略有不同。

  public boolean keyDown (Event evt, int key) {………}

这里的key参数是一个整数变量, 是所按下的键的ASCII值。 可以用类型转换(char)key将其转成字符类型。keyUp的调用方式相同。这两种方法的区别是使用场合不同。 如果将按住键不放解释为一连串相同的输入时, 用keyDown来处理;而将按住键不放解释为一个字符输入时, 用keyUp来处理。下一个问题是对于某些特殊键, 如Home、End、PageUp、PageDown和方向键等, 如何处理。 最容易想到的是根据ASCII值来判断, 但有谁记得住它们的ASCII值呢?Java中提供了较为简单的方法, 它将这些特殊ASCII值定义成Event类的变量。例如判断是否Home键, 可以写成:

  if (key = = Event.HOME) {………}

这样在编写程序时, 就方便多了。表一是这些特殊键所对应的常量。 后面三个键是组合键, Shift, Ctrl, Alt键, 这些键与其它键一起使用往往有特殊的含义。 在Java的Event类还另外提供三个方法shiftDown(), metaDown(), controlDown() 来判断Shift、Alt、Ctrl键是否按下。使用方法如下:

  public boolean keyDown (Event evt, int key) {
    if (evt.shiftDown())
      ………//显示大写字母
    else
    ………//显示小写字母
}

5.1.4 事件处理器(Event Handler)

在Java的AWT中有一个事件处理器(Event Handler), 事件处理器负责接收发生的事件, 并对事件作出处理。上面介绍的这些常用的事件处理方法实际上都是由事件处理器来调用的。事件处理器的调用方式是 public boolean handleEvent(Event evt) {………}在事件处理器中根据Event类参数evt的id值来判断发生事件的种类, 针对用户界面(UI)构件的事件(下一节将会介绍)、键盘的按键、鼠标的操作、窗口的创建、移动、撤销等等。如果手头有Java API reference的话, 可以自己查阅一下。在某中情况下如果需要覆盖handleEvent方法, 应相当慎重。因为对于原来的这些事件处理方法, 除非在覆盖后的方法中加以指出, 否则是不会被调用的。比较可靠的覆盖方法是:

  public boolean handleEvent (Event evt) {
    if (evt.id == Event.KEY_PRESS ) {
      ………//处理键盘操作
      return true;
    }
    else {
      return super.handleEvent(evt);
    }
  }

这里是将余下的事件处理交给handleEvent的父类处理, 从而保证对事件的正常处理。

5.2 用户界面设计

用户界面设计在程序设计中占有较大的比重, 在较为复杂的Java应用程序中, 用户界面设计是不可缺少的。Java AWT(Abstract windowingToolkit)——抽象窗口工具箱就是专门用来进行用户界面设计的。

5.2.1 抽象窗口工具箱(AWT)概述

AWT是Java语言提供的用户界面设计工具。AWT的基本思想是将一个窗口看作一系列嵌套的构件。最外层可以是窗口、菜单条、按钮或包容器等, 而包容器又可以包含其它的构件或包容器。这样由表及里, 所有的构件构成了一棵嵌套树。一个构件在这棵树中的位置决定了它显示在屏幕上的位置以及事件的传递途径。从这个思想出发, AWT是将Java用户界面的基本元素分成四类:包容器(Container)、基本构件(UI component)、画布(Canvas)和窗口构造构件(Windows construction component)。

1. 包容器是可以包含其它构件(包括包容器)的AWT构件。嵌板(panel)是使用最多的包容器, 它可以显示在屏幕上。我们一直在使用的Applet是panel的一种, 因为从类的继 承性来看, Applet类是Panel类的子类。

2. 画布是AWT提供的专门用来绘画的构件, 尽管在panel上也可绘画。

3. 基本构件是不可以包含其它构件的AWT构件。它包括按钮、列表、弹出式菜单、 单选按钮等用户界面的基本元素。

图5-1 AWT类的类层次

4. 窗口构造构件包括框架、菜单条、对话框等。其中框架可以包含其它构件, 但它也 是一个构成窗口的重要构件, 所以将它们归入这一类。这一类构件在Applet中使用较少, 但编写独立的Application应用程序界面时, 这些是必不可少的。如果从AWT类的层次角度 来看, 更容易了解它们之间的关系。Component类是大多数AWT类的根。它下面包括Canvas 类和Button类、Label类等UI基本元素。 TextComponent类包括TextField类和TextArea类。 Container类包括Panel类和Windows类, 分别对应于Applet和Application两种情况。它们 下面又包括Applet类、Frame类等。所以, 在JavaApplet中我们不需要定义Container或 Panel, 就可以加入UI的基本元素。

在本节的第二小节, 将逐一介绍这些类的用法。第三小节将介绍版面的设置。另外大多数的AWT构件还需要定义相应的操作, 即这个构件被激活时应完成的功能, 第四小节将作专门介绍。最后一个小节将介绍窗口构造构件。

5.2.2 UI基本元素

1.标识(Label)

UI基本元素中最简单的就是标识。所谓标识实质上就是一段文字, 但它与文字不同的是它是AWT构件。所以在每次repaint时, 不用重新添加。大多数AWT构件都有多种构造方法, Label类中定义了三种。

    Label()构造一个空的标识。
    Label(String)构造建立一个以String为内容的标识。
    Label(String,int)定义一个String标识。

这里int参数代表一个给定的对齐方式。可以是靠左、靠右和居中, 缺省设置是居中。 为了便于记忆, 在Label类中定义了相应的常量, Label.LEFT、Label.CENTER和Label.RIGHT。 因此要定义一个居中的Name标识, 可以写成,

    Label l1 = new Label(*Name*, Label.CENTER);
或  Label l1 = new Label(*Name*);

上面是Label的定义方法, 在定义之后, 我们要用add方法将它添加到屏幕上, 否则它还不会显示在屏幕上。需要强调的是, 所有的AWT构件的使用过程都是这样的。

  add(l1);或者直接写成add(new Label(*Name*);

这两种方法的区别在于, 如果这个标识不再需要修改, 可以直接添加, 如果还需要修改, 应当先定义一个Label类, 再添加。

2.按钮(Button)

按钮是用户界面中常用的元素。Button类有两种构造方法:

    Button()方法构造一个没有标识的按钮。
    Button(String)方法构造一个以String为标识的按钮。

如果要定义一个“OK”按钮, 定义方法如下:

    Button b = new Button(*OK*);
    add(b);
  或
    add ( new Button(*OK*));


3.画布(Canvas)

画布是专门用来绘画的构件, 它不能包含其它的构件。Canvas类的使用也相当简单。

    Canvas canv = new Canvas();
    add (canv);

Canvas类中的方法也只有一个paint方法, 这在上一章已介绍过了, 这里就不重复了。

4.复选框(Checkbox)

Checkbox类一般不需要定义相应的操作, 它只是用来让用户设置某些选项。Checkbox类可以有两种使用方式。一种是一次可以选择多项, 即复选框。另一种是一次只能选择一项, 即单选按钮。这里介绍的是复选框, 单选按钮另外介绍。

Checkbox类的构造方法有三种。

    Checkbox();  //构造一个空的复选框条目, 未被选中。
    Checkbox(String);  //构造一个以String为标识的复选框条目, 未被选中。
    Checkbox(String, CheckboxGroup, boolean);
                   //构造一个以String为标识的复选框条目。

这里的CheckboxGroup参数是指出这个条目所属的条目组, 只有单选按钮才需要条目组, 所以在这里可以用null, boolean参数是设置这个条目是否预先被选中, true是选中, false是未选中。所以如果需要将某个条目设置成预先选中的话, 必须用这个构造方法。

下面是个建立复选框的例子。效果如图5-2。

    add(new Label("What are you like: "));
    add(new Checkbox("Apple "));
    add(new Checkbox("orange "));
    add(new Checkbox("Strawberry "));
    add(new Checkbox("Peach "));

图5-2复选框

5.单选按钮(Radio Buttons)

单选按钮的使用方法与复选框使用方法基本相同, 所不同的是所有条目必须属于一个条目组, 在这个条目组中, 一次只能选择一个条目。 CheckboxGroup类的构造方法是: CheckboxGroup()构造一个条目组。 在构造完一个条目组, 就可以把条目加入到这个条目组中。在加入条目时, 不要忘记只有一个条目能被预先选中。下面是一个例子, 图5-3 是这段程序的运行结果。

    add(new Label("Sex: "));
    CheckboxGroup cbg = new CheckboxGroup();
    add(new Checkbox("Male ", cbg, true));
    add(new Checkbox("Female ", cbg, false));

图5-3 单选按钮


6.选择菜单(Choice Menu)

选择菜单是指弹出式菜单, 用户可以在菜单的条目中进行选择。选择菜单在Java中是由Choice类实现的。Choice类的构造方法是:

    Choice();  //构造一个选择菜单。

构造完之后, 再使用Choice类中的addItem方法加入菜单的条目。条目在菜单中的位 置由条目添加的顺序决定, Choice类建立一个整数索引以便于检索。下面是一个较为完整的例子。

    add(new Label("How much do you eat them per week: "));
    Choice c = new Choice();
    c.addItem("less than 1kg ");
    c.addItem("1kg to 3kg");
    c.addItem("more than 3kg");
    add(c);

再加入条目之后, 不要忘记所做的这些只是定义了一个选择菜单, 还需要用add方法把选择菜单添加到屏幕上。 此外, 在菜单被添加到屏幕上, 仍可以加入新的条目。这些语句的执行结果如下。

图5-4 选择菜单


7.列表框(Scrolling List)

列表框的功能与弹出式菜单相似, 也是让用户在几个条目中作出选择, 但又有一些区别。列表框是由List类实现的, List类的构造方法有两种。

    List();             //构造一个空的列表框。
    List(int, boolean); // 构造一个指定行数的列表框。
                        // int类型参数为指定的行数, 
                        // boolean类型参数确定这个列表是多选还是单选。
                        // true表示多选, 
                        // false表示单选。

与Choice类相同, 在构造一个List类后, 也要用addItem方法添加列表中的条目。 在添加条目的同时, 也会建立一个整数索引。让我们来看一个例子。

    add(new Label("What are you like: "));
    List list = new List(5,true);
    list.addItem("Apple");
    list.addItem("Banana");
    list.addItem("Grape");
    list.addItem("Orange");
    list.addItem("Peach");
    list.addItem("Pear");
    list.addItem("Strawberry");
    add(list);

这个例子和上面复选框的例子内容相同, 只是换了一种实现方法。图5-5 是执行后的结果。


图5-5 列表框

通过这个例子, 可以看出列表框与选择菜单的区别。首先, 它不是弹出式的菜单, 而是列表, 如果条目的数目超过列表的大小, 会自动出现滚动条。其次, 列表框可以是单选的, 也可以是多选的。List类中可以调用的方法有:


8.单行文本输入框(TextField)

在许多情况下, 用户可能还需要输入一些文字, 这时就需要文本输入框。单行文本输入框是由TextField类实现的。TextField类的构造方法有四种。

    TextField();  // 构造一个新的单行文本输入框、
    TextField(int); //构造一个指定长度的单行文本输入框。
    TextField(String); //构造一个指定初始内容的单行文本输入框。
    TextField(String,int); // 构造一个指定长度、指定初始内容的单行文本输入框。

下面是一个使用单行文本输入框的例子。图5-6是执行后的结果。
    add(new Label("Your name: "));
    add(new TextField(30));

图5-6 单行文本输入框

在某种情况下, 用户可能希望自己的输入不被别人看到, 这时可以用TextField类中setEchoCharacter方法设置回显字符, 使用户的输入全部以某个特殊字符显示在屏幕上。下面是TextField类中可以调用的方法:


9.多行文本输入框(TextArea)

多行文本输入框的功能与单行文本输入框的功能相同, 只是它能显示更多的文字。因为单行文本输入框只能输入一行的文字, 所以在需要输入和显示较多的文字时, 就要用到多行文本输入框。多行文本输入框是由TextArea类实现的。TextArea类的构造方法有四种。

  TextArea(); //构造一个新的多行文本输入框。
  TextArea(int, int); //构造一个指定长度和宽度的多行文本输入框。
  TextArea(String); //构造一个显示指定文字的多行文本输入框。
  TextArea(String, int, int); //构造一个指定长度、指定宽度, 
                            //并显示指定文字的多行文本输入框。

下面是一个多行文本输入框的例子, 图5-7是执行后的结果。

  add(new Label("What's your opnion of eating fruit: "));
  add(new TextArea("I think ",3,60));

图5-7 多行文本输入框


10.滚动条(Scrollbar)

在列表框和多行文本输入框中, 在需要时会自动加入滚动条。在某些情况下, 特别是关于数字的操作时, 需要单独使用滚动条。滚动条是由Scrollbar类实现的。Scrollbar类的构造方法有三种。

  Scrollbar();  //构造一个新的垂直的滚动条。
  Scrollbar(int)构造一个指定方向的滚动条
  Scrollbar(int, int, int, int, int);  //根据给定参数构造一个滚动条。
            //  其中 第一个参数指定滚动条的方向。
            //       第二个参数指定滑动块的初始位置。
            //       第三个参数指定滚动条的宽度。
            //       第四、第五个参数是滚动条的最小值和最大值。

在Scrollbar类中, 定义了两个常量 HORIZONTAL和VERTICAL, 在指定滚动条的方向时可以使用这两个常量。


11.一个例子

现在, 我们可以尝试把上面介绍的这些内容组合在一起, 做一个较为复杂的例子。

1:  import java.awt.*;
2:
3:  public class ui extends java.applet.Applet {
4:
5:    public void init() {
6:      setBackground(Color.white);
7:    }
8:
9:    public void draw() {
10:
11:     add(new Label("Your name: "));
12:     add(new TextField(30));
13:     add(new Label("Sex: "));
14:     CheckboxGroup cbg = new CheckboxGroup();
15:     add(new Checkbox("Male ", cbg, true));
16:     add(new Checkbox("Female ", cbg, false));
17:     add(new Label("What are you like: "));
18:     add(new Checkbox("Apple "));
19:     add(new Checkbox("orange "));
20:     add(new Checkbox("Strawberry "));
21:     add(new Checkbox("Peach "));
22:     add(new Label("How much do you eat them per week: "));
23:     Choice c = new Choice();
24:     c.addItem("less than 1kg ");
25:     c.addItem("1kg to 3kg");
26:     c.addItem("more than 3kg");
27:     add(c);
28:     add(new Label("What's your opnion of eating fruit: "));
29:     add(new TextArea("I think ",3,60));
30:     add(new Button("OK"));
31:     add(new Button("Clear "));
32:   }
33: }

这里的内容在前面都已出现过, 所以这段程序就不详细解释了。图5-8是这段程序的运行结果。

图5-8 较复杂的例子

从这个结果来看, 如果只是简单地把构件添加到屏幕上, 效果往往不大理想, 所以必须进行排版设计。下一小节将介绍如何排版。


5.2.3 版面设置

在以往的用户界面中, 每个构件的位置一般是用点坐标确定的。但是在Java中, 情况有所不同。Java的Applet需要运行在不同操作系统、不同的屏幕设置、不同的字体下, 用点坐标来确定位置就难以适应各种情况, 所以在Java中使用版面控制器来进行排版。使用版面控制器的优点的是能根据不同的屏幕自动进行排版, 缺点是构件在屏幕上的确切位置难以确定。为此AWT提供五种版面可供选择:

FlowLayout、GridLayout、GridBagLayout、BorderLayout和CardLayout。

用户可以使用类似下面的语句来选择版面:

  public void init() {
    setLayout( new FlowLayout);
  }

AWT构件在屏幕上的确切位置只取决于当前版面控制器所使用的版面, 以及构件添加到屏幕上的顺序。下面来逐一介绍这五种版面。

1.Flowlayout

FlowLayout是最简单的版面, 也是Java的缺省版面。它的排版原则是将构件从左向右、从上向下排列, 如果这行放不下这个构件, 就放入下一行。图5-9 是它的版面效果。

图5-9 FlowLayout

在FlowLayout中也可设置版面的对齐方式, 可以是居中、靠左或靠右, 缺省是居中。要设置对齐方式可以使用FlowLayout类中的变量LEFT、CENTER和RIGHT, 语句如下。

setLayout (new FlowLayout(FlowLayout.LEFT);
此外FlowLayout中还可以设置横向和纵向的间隔, 缺省是3个像素。 设置间隔可以使用下面的语句。
setLayout (new FlowLayout(FlowLayout.CENTER, 5, 5);

语句执行后将横向和纵向间隔设置成5个像素。

图5-10 GridLayout

2.GridLayout

Grid在英语中是格子的意思, GridLayout顾名思义就是网格版面布局。在GridLayout中, 屏幕被划分成网格状, 每一个构件按照添加的顺序从左向右、从上向下地占据一个单元。所以在GridLayout中, 构件添加的顺序就相当重要。要创建一个2×3的网格版面可以用以下的语句。图5-10是执行的结果。

setLayout(new GridLayout(2,3));

同样, GridLayout中也可以设置间隔。设置间隔的语句如下。

setLayout( new GridLayout(2, 3, 5, 10);

语句执行后将把横向间隔设置成5个像素, 把纵向间隔设置成10个像素。

3.GridBagLayout

GridBagLayout是一种非常灵活的版面布局。它是在将屏幕划分成网格的基础上, 允许每个构件占据一个或多个单元(显示区域)。 GridBagLayout管理的每个构件都有一个相应的GridBagConstraints实例, 通过这个实例来安排构件的位置。 要有效的使用GridBagLayout类, 必须为该构件定义一个GridBagConstraints实例, 并正确设置实例变量。其实例变量如下。

  gridx,

gridy设置安放构件的网格单元的坐标。 屏幕左上角的网格单元坐标是gridx=0,gridy=0。如果使用系统缺省值GridBagConstraints.RELATIVE, 那么这个构件将放置在前一个添加的构件的右边或下边。

  gridwidth,

gridheight以网格单元为单位设置显示区域的宽度和高度, 缺省值为1。使用GridBagConstraints.REMAINDER可以设置这个构件是这一行或这一列中的最后一个, 即占据剩下的网格单元。 使用GridBagConstraints.RELATIVE 将这个构件设置成占据这一行或这一列中除最后一个以外的所有网格单元。fill当显示区域大于构件实际尺寸时, 设置如何重新安排构件的大小。可以使用的值有GridBagConstraints.NONE(缺省值), GridBagConstraints.HORIZONTAL(将构件横向扩充以填满显示区域), GridBagConstraints.VERTICAL(将构件纵向扩充以填满显示区域)或GridBagConstraints.BOTH(将构件扩充以填满显示区域)。

  ipadx,

ipady设置构件之间的间隔。构件间的横向间隔为ipadx*2个像素, 同样纵向间隔也为ipady*2个像素。insets设置构件与屏幕边缘之间的间隔。

  anchor, 

当构件的尺寸比显示区域小时, 用来设置放置构件的位置(可以和fill参数一起使用)。其缺省值是GridBagConstraints.CENTER, 或是GridBagConstraints.NORTH?珿ridBagConstraints.SOUTH, GridBagConstraints.EAST, GridBagConstraints.WEST?珿ridBagConstraints.NORTHEAST, GridBagConstraints.NORTHWEST, GridBagConstraints.SOUTHEAST和GridBagConstraints.SOUTHWEST。

  weightx,weighty
用来设置怎样分配空白区域(用于屏幕大小改变时)。 其缺省值是0, 所有的构件集中在包容器(container)的中央。所以需要改变设置时, 必须给这两个参数赋值。下面是使用GridBagLayout进行排版的一个例子。
1:  import java.awt.*;
2:  import java.util.*;
3:  import java.applet.Applet;
4:
5:  public class check extends Applet {
6:
7:    protected void makebutton(String name, GridBagLayout gridbag, 
8:                   GidBagConstraints c) {
9:      Button button = new Button(name);
10:     gridbag.setConstraints(button, c);
11:     add(button);
12:   }
13:
14:   public void init() {
15:     GridBagLayout gridbag = new GridBagLayout();
16:     GridBagConstraints c = new GridBagConstraints();
17:     setFont(new Font("Helvetica", Font.PLAIN, 14));
18:     setLayout(gridbag);
19:     c.fill = GridBagConstraints.BOTH;
20:     c.weightx = 1.0;
21:     makebutton("Button1", gridbag, c);
22:     makebutton("Button2", gridbag, c);
23:     makebutton("Button3", gridbag, c);
24:     c.gridwidth = GridBagConstraints.REMAINDER;//本行结束
25:     makebutton("Button4", gridbag, c);
26:     c.weightx = 0.0;//恢复初始设置
27:     makebutton("Button5", gridbag, c);//另一行
28:     c.gridwidth = 1;
29:     makebutton("Button5a", gridbag, c);
30:     c.gridwidth = GridBagConstraints.RELATIVE;//除了最后一个以外的网格
31:     makebutton("Button6", gridbag, c);
32:     c.gridwidth = GridBagConstraints.REMAINDER; //本行结束
33:     makebutton("Button7", gridbag, c);
34:     c.gridwidth = 1;//恢复初始设置
35:     c.gridheight = 2;
36:     c.weighty = 1.0;
37:     makebutton("Button8", gridbag, c);
38:     c.weighty = 0.0;//恢复初始设置
39:     c.gridwidth = GridBagConstraints.REMAINDER; //end row
40:     c.gridheight = 1;//恢复初始设置
41:     makebutton("Button9", gridbag, c);
42:     makebutton("Button10", gridbag, c);
43:     resize(300, 125);
44:   }
45:
46:   public static void main(String args[]) {
47:     Frame f = new Frame("GridBagLayout Example");
48:     check ex1 = new check();
49:     ex1.init();
50:     f.add("Center", ex1);
51:     f.pack();
52:     f.resize(f.preferredSize());
53:     f.show();
54:   }
55: }

图 5-11是执行后的结果。

图5-11 GridBagLayout

4.BorderLayout

BorderLayout又与FlowLayout和GridLayout不同, 它使用地理上的方向North, South, West, East和Center来确定构件添加的位置。其中前四个方向占据屏幕的四边, 而Center方向占据剩下的空白。这种版面一般用于在给自己定义的Panel添加边框。 下面是一个例子。

图5-12 BorderLayout

  setLayout( new BorderLayout());
  add(*North*, new Button(*one*));
  add(*South*, new Button(*three*));
  add(*West*, new Button(*two*));
  add(*East*, new Button(*four*));
  add(*Center*, new Button(*five*));

图 5-12是执行后的结果

BorderLayout中也可设置间隔, 设置方法如下。

setLayout (new BorderLayout(5,5));

5.CardLayout

CardLayout将每个构件看作一张卡片, 在一个包容器中可以加入多个卡片, 但每次只有一个是可见的。使用first(), last(), next(), previous()和show() 方法均可使卡片成为可见的。下面是一个简单的例子。

  setLayout( new CardLayout());
  Panel one = new Panel();
  add(*first*, one);
  Panel two = new Panel();
  add(*Second*, two);
  Panel three = new Panel();
  add(*third*, three);
  show(this, *second*);

在CardLayout中也可设置间隔, 方法如下

setLayout( new CardLayout(5,5));

6.insets

在某些情况下需要设置嵌板与屏幕之间的间隔, 这时可以使用insets类。它的使用方法是:
new Insets(5, 5, 5, 5);

四个参数分别表示上, 左, 下, 右的间隔, 单位是像素。


7.嵌套使用(nested Panel)

在介绍了这些版面之后, 如何才能安排出令人满意的效果呢?在上一小节的开始, 就已提到, 在Java中构件是可以嵌套使用的。 因此我们可以把一系列的嵌板(Panel)添加到屏幕上, 在每个嵌板内部可以设置成不同的版面, 添加不同的构件, 这样就可以得到较好的效果。现在, 我们来修改一下上一小节结尾的那个例子。

1:import java.awt.*;
2:
3:  public class ui extends java.applet.Applet {
4:
5:    public void init() {
6:      setBackground(Color.white);
7:      draw();
8:    }
9:
10:   public void draw() {
11:
12:     setLayout(new FlowLayout(FlowLayout.LEFT));
13:     Panel p1 = new Panel();
14:     add(p1);
15:     p1.add(new Label("Your name: "));
16:     p1.add(new TextField(30));
17:     Panel p2 = new Panel();
18:     add(p2);
19:     p2.add(new Label("Sex: "));
20:     CheckboxGroup cbg = new CheckboxGroup();
21:     p2.add(new Checkbox("Male ", cbg, true));
22:     p2.add(new Checkbox("Female ", cbg, false));
23:     Panel p3 = new Panel();
24:     add(p3);
25:     p3.add(new Label("What are you like: "));
26:     p3.add(new Checkbox("Apple "));
27:     p3.add(new Checkbox("orange "));
28:     p3.add(new Checkbox("Strawberry "));
29:     p3.add(new Checkbox("Peach "));
30:     Panel p4 = new Panel();
31:     add(p4);
32:     p4.add(new Label("How much do you eat them per week: "));
33:     Choice c = new Choice();
34:     c.addItem("less than 1kg ");
35:     c.addItem("1kg to 3kg");
36:     c.addItem("more than 3kg");
37:     p4.add(c);
38:     Panel p5 = new Panel();
39:     add(p5);
40:     p5.add(new Label("What's your opnion of eating fruit: "));
41:     add(new TextArea("I think ",3,60));
42:     Panel p6 = new Panel();
43:     add(p6);
44:     p6.add(new Button("OK"));
45:     p6.add(new Button("Clear "));
46:   }
47: }

图5-13 嵌套的例子

在这个程序中, 加入了若干个Panel类, 在屏幕上显示在同一行中的构件都属于同一个Panel。原来程序中没有设置版面, 但是缺省是FlowLayout, 排列方式缺省是居中。现在版面仍是FlowLayout, 排列方式改成靠左。图5-13是执行后的结果。在各个Panel中, 也使用相同的版面。当然也可将各个Panel设置成不同的版面, 这样效果会不同。 通过嵌套的方法, 可以设计出各式各样的屏幕效果, 这个程序只是一个简单的例子。相信读者在自我实践中会有更多的收获。

5.2.4 事件的处理

上面这些是如何设置用户界面的版面, 但用户界面还应具有一定的功能, 它应能接收用户的输入, 这就需要处理UI构件的动作。UI构件动作的处理方法与上一章介绍的事件处理相似, 不同的是UI构件所产生的特殊事件由一个专门的action方法来处理。我们所要做的就是覆盖这个方法。action方法的调用方式如下:

  public boolean action(Event evt, Object arg) {
    ………
  }

action方法的调用方式也与事件处理相似, evt参数代表这个事件, 所不同的是arg参数可以是任何类型。这个参数的类型由触发这个事件的构件所决定, 当一个构件触发一个事件时, 这个参数用来传递一些额外的信息, 所以在调用action方法时要使用这个参数。除了Label以外(Label不需要处理动作), 其它构件都有不同的动作和不同的参数。按钮(Button)在被选中时产生动作, arg参数是按钮的标识。复选框(Checkbox)当一个条目被选中时产生动作, arg参数始终是true。选择菜单(Choice)当条目被选中时产生动作, arg参数代表那个条目。单行文本输入框(TextField)当用户在框内按下Return键时产生动作。与上一节关于事件的处理比较一下, 就可以看出action方法可以由不同的事件触发, 而类似mouseDown的方法只能由一种事件触发。所以, 在action方法中判断事件的种类是不可缺少的。触发action方法的对象储存在evt参数的target实例变量中, 可以使用instanceof操作符来判断对象的种类。在判断出种类之后, 就可以进行处理或调用相应的处理程序。下面是使用action方法的一个例子。程序的实际长度不止这些, 选出这段的目的是为了说明如何处理action方法的调用。

1:  import java.io.*;
2:  import java.awt.*;
3:
4:  public class password extends java.applet.Applet {
5:    TextField t1,t2;
6:    Button b1,b2;
7:    String button;
8:
9:    public void init() {
10:     setBackground(Color.white);
11:     setLayout (new FlowLayout());
12:     add(new Label("Enter your name "));
13:     t1 = new TextField(5);
14:     add(t1);
15:     add(new Label("Enter your password "));
16:     t2 = new TextField(5);
17:     t2.setEchoCharacter('*');
18:     add (t2);
19:     b1 = new Button("Ok");
20:     add(b1);
21:     b2 = new Button("Clear");
22:     add(b2);
23:   }
24:
25:   public void handlebutton (String button) {
26:     if (button.equals("Clear")) {
27:       t1.setText("");
28:       t2.setText("");
29:     }
30: 
31:
32:   public boolean action(Event evt, Object arg) {
33:     if (evt.target instanceof Button)
34:       handlebutton ((String)arg);
35:     return true;
36:   }
37: }

这段程序是用来让用户输入密码。12-18行是添加两个单行文本输入框, 用来输入用户的姓名和密码。17行的setEchoCharacter方法设置会显字符, 在介绍单行文本输入框时提到过。19-22行添加两个按钮, 分别是“OK”和“Clear”, 其中“Clear”按钮用于清除用户已有的输入。32-36行是action方法, 当按钮按下调用action方法时, 就调用handlebutton方法。25-28行是handlebutton方法, 这里使用String类中的equals方法来<判断两个字符串是否相同, 当按下是“Clear”时, 就将两分单行文本输入框中的文字清除。如果AWT构件是嵌套使用的话, 动作的处理就更加复杂了。所有构件组成的嵌套树对确定事件的处理顺序是相当重要的。在这一章中接触到的事件处理方法, 都是需要返回一个布尔值。相应的根据返回的布尔值, 事件处理就可以分作两类。一类是接收并处理事件, 然后返回true, 这个事件就不再传递。另一类是接收并处理事件, 然后返回false, 这个事件将传递给上一层的事件处理方法。所以一旦事件发生, handleEvent方法仍然负责事件的处理。 它先将这个事件传递到事件发生所在的最低层的处理方法。如果这个方法返回是true, 那么就结束对这个事件的处理。如果这个方法返回是false, 那么就将这个事件传递到上一层包含它的包容器的处理方法中。依此类推直到有方法对其作出处理。

5.2.5 窗口构造构件

窗口构造构件也是UI元素的一部分。这里将它们独立出来的原因是因为窗口构造构件既可以用在Applet中, 又可以用在Application中, 而前面介绍的UI元素在Applet中使用较多。下面将逐一介绍窗口构造构件。


1.框架(Frame)

框架(Frame)是带标题的顶层窗口。从类的层次上来看, 它和Panel都属于Container类。所以, 在每个Frame中都可以设置版面, 缺省是BorderLayout。Frame类的构造方法有两种。
  Frame();  //构造一个没有标题的窗口。
  Frame(String title);   //构造一个给定标题的窗口。
需要说明的是, 用这两个方法创建的窗口都是非可视窗口, 只有使用Frame类的父类Windows类中的show方法后, 才能在屏幕上显示出来。设置窗口的大小可以使用resize方法。下面是一个简单的例子。
  Frame window =new Frame("A Test Window");
  window.resize(250,150);
  window.show();

图5-14是执行后的结果。


2.菜单条(MenuBar)

建立了窗口之后, 就要考虑给窗口添加菜单条。在Java中这一部分是由三个类实现的, 它们是MenuBar, Menu和MenuItem, 分别对应菜单条、菜单和菜单项。图5-15 是它们之间的类层次。

图5-15 Menu元素的类层次

MenuBar的构造方法是MenuBar(), 相当简单。在构造之后, 还要将它设置成窗口的菜单条, 这里要使用表5-11中介绍的setMenuBar方法。
MenuBar mb = new MenuBar();
window.setMenuBar(mb);

需要说明的是, MenuBar类根据Menu添加的顺序从左到右显示, 并建立整数索引。而根据Windows的使用习惯, 帮助菜单位于菜单条的最右边, 所以如果将某个菜单设置成帮助菜单, 那么它总是显示在菜单条的最右边。


3.菜单(Menu)

在添加完菜单条后, 并不会显示任何菜单, 所以还需要在菜单条中添加菜单。菜单Menu类的构造方法有两种。Menu(String)用给定的标识构造一个菜单。Menu(String, boolean)用给定的标识构造一个菜单。如果布尔值为false, 那么你释放鼠标按钮后, 菜单项会消失。如果布尔值为true, 那么你释放鼠标按钮后, 菜单项仍将显示。这时的菜单称为tearOff菜单。在构造完后, 使用MenuBar类的add方法添加到菜单条中。下面是一个简单的例子, 使用前面已定义过的菜单条。

    Menu fm = new Menu("File");
    mb.add(fm);
    Menu help = new Menu("Help");
    mb.add(help);
    mb.setHelpMenu(help);
    Menu opt = new Menu("Option");
    mb.add(opt);

下面是执行的结果。

图5-16 带菜单的窗口

在某种情况下, 需要阻止用户选择某个菜单, 这时可以使用disable方法使这个菜单成为不可选的, 需要时还可使用enable方法使它成为可选的。


4.菜单项(MenuItem)

接下来的工作是往菜单中添加内容。在菜单中可以添加不同的内容, 可以是菜单项(MenuItem), 可以是菜单选项(CheckboxMenuItem), 可以是一个子菜单, 也可以是分隔符。MenuItem类和CheckboxMenuItem类的构造方法分别是:
  MenuItem(String);  //构造一个指定标识的菜单项。
  CheckboxMenuItem(String);  //构造一个指定标识的菜单选项。

子菜单的添加是直接将一个子菜单添加到母菜单中, 而分隔行的添加只需要将*-*作为菜单项添加到菜单中。 下面的例子是将前面的例子组合在一起:

  MenuBar mb = new MenuBar();window.setMenuBar(mb);
  Menu fm = new Menu("File");
  mb.add(fm);Menu help = new Menu("Help");
  mb.add(help);mb.setHelpMenu(help);
  Menu opt = new Menu("Option");
  mb.add(opt);opt.add(new MenuItem("Change Title"));
  Menu change = new Menu ("Change Color");
  opt.add(change);change.add(new MenuItem("Blue"));
  change.add(new MenuItem("Green"));
  change.add(new MenuItem("Red"));
  change.add(new MenuItem("Yellow"));
  opt.add(new MenuItem("-"));
  CheckboxMenuItem show = new CheckboxMenuItem ("Show Title");
  opt.add(show);
图5-17 是执行的结果。

图5-17 菜单项

这里的disable和enable方法就是在Menu类中使用的方法, 因为Menu类是MenuItem类的子类, 所以Menu对象可以使用MenuItem中的方法。


5.对话框(Dialog Box)

对话框与框架(Frame)有一些相似, 但它一般是一个临时的窗口, 主要用于显示提示信息或接收用户输入。所以, 在对话框中一般不需要菜单条, 也不需要改变窗口大小。此外, 在对话框出现时, 会禁止其它窗口的输入, 直到这个对话框被关闭。对话框是由Dialog类实现的。Dialog类的构造方法有两种。

  Dialog(Frame, boolean);  //构造一个非可视的对话框。

用户可通过show方法将其变为可视的。Frame类型的参数代表对话框的拥有者, boolean类型参数用于控制对话框的工作方式。如果为true, 则对话框为可视时, 其它构件不能接收用户的输入。此时的对话框称为“静态”的。

  Dialog(Frame, String, boolean);  //构造一个非可视的对话框。

String类型参数作为对话框的标题, 其余参数与上相同。

在构造对话框之后, 就可以添加其它的构件。下面是一个简单的例子。

  Dialog dl = new Dialog(window, "Change Title", true);
  dl.setLayout(new FlowLayout(FlowLayout.CENTER));
  TextField tf = new TextField(window.getTitle(), 25);
  dl.add(tf);dl.add(new Button("OK"));
  dl.resize(200,75);dl.show();

图5-18是执行后的结果。

图5-18 对话框


6.文件对话框(FileDialog)

文件对话框是对文件进行存取时出现的对话框, 它是使用当前系统提供的文件对话框。但如果在Applet中使用的话, 受安全性限制, 实际上是不能存取文件的, 只有在Application中才能使用。文件对话框是由FileDialog类实现的, 它的构造方法有两种。

FileDialog(Frame, String); //构造一个读文件对话框。

Frame类型参数代表文件对话框的拥有者, String类型参数作为文件对话框的标题。

图5-19 文件对话框

  FileDialog(Frame, String, int);  //构造一个文件对话框。
int类型参数确定是读文件对话框还是写文件对话框, 它的值可以是FileDialog.LOAD或FileDialog.SAVE。其余参数含义与上相同。同样在构造完一个文件对话框之后, 它仍是非可视的, 需要使用show方法使其变为可视的。下面是一个简单的例子。
  FileDialog fd = new FileDialog(window, "Open File",     FileDialog.SAVE);
  fd.show();

图5-19 是执行后的结果。因为是在Windows 95下执行的, 所以是Windows 95风格的文件对话框。虽然对话框的标题是*Open File*, 但实际上还是一个写文件对话框。


7.事件的处理

这里对事件的处理方法与5.2.4中的相同, 只是增加对Menu和Window的处理。Menu在它的条目被选中时产生动作, arg参数是条目的标识。在上面的例子中, 我们可以要求在菜单项“Change Title”被选中时, 出现一个对话框让用户修改窗口的标题。这段程序可以写成。

  public boolean action (Event evt, Object arg) {
    String label = (String)arg;
    if (evt.target instanceof MenuItem) {
      if (label.equals("Change Title")) change();
      return true;
    }
    return false;
  }
  public void change() {
    Dialog dl = new Dialog(window, "Change Title", true);
    dl.setLayout(new FlowLayout(FlowLayout.CENTER));
    TextField tf = new TextField(window.getTitle(), 25);
    dl.add(tf);
    dl.add(new Button("OK"));
    dl.resize(200,75);
    dl.show();
  }

对Window的事件处理是由handleEvent方法来实现的, 在窗口打开、关闭、移动时会产生相应的事件, handleEvent方法根据事件的类型, 调用相应的处理方法。事件的类型有

  WINDOW_DESTROY在窗口被关闭时产生。
  WINDOW_EXPOSE在窗口被设置成当前窗口时产生。
  WINDOW_ICONIFY在窗口被缩小成图标时产生。
  WINDOW_DEICONIFY在窗口从图标放大时产生。
  WINDOW_MOVED在窗口移动时产生。

具体的处理过程和如何覆盖相应的处理方法请参阅5.1.4。


5.3 网络编程

设计人机界面的目的是帮助用户与远端的服务器进行交互式对话, 但在大多数情况下, 实现这种对话需要Applet对服务器上的文件进行访问, 或与服务器交换信息, 所以要实现这种对话就离不开网络的支持。在Java中, 也有网络编程。但是, 出于安全性的考虑, Java中对Applet的访问权限有严格的控制。如果一台工作站运行另一台服务器上的Applet, 那么这个Applet不经允许不能访问工作站上的文件, 除了那台服务器, Applet也不能访问其它机器上的文件, 也就是说, 在Applet中只能访问服务器中的文件。而在Application中就没有这样的限制。实际上, Java本身的用途是一种网络计算语言, 编写Applet只是Java用途中的一种, 真正复杂的Java程序是进行网络通信的Application。

Java中的网络编程, 我们分成网络资源的使用、Socket类和数据报三个部分来介绍。

5.3.1 网络资源的使用

关于网络资源的编程主要有两方面, 一个是在Applet中建立于其它URL的连接, 另一个是对服务器上的文件进行操作。 这两个方面需要使用几个重要的类 AppletContext类、URL类和URLconnection类。


1.URL类

在Internet上的所有网络资源都是用URL(Uniform Resource Locator)来表示的, URL类在Applet的网络编程中也是相对重要的, 网络编程都以它为基础, 其它两个类也要使用URL类。URL类的构造方法有四种。

URL(String, String, int, String); //构造一个URL类。
      // 第一个String类型的参数是协议的类型, 可以是http, ftp, file等。
      // 第二个String类型参数是主机名, 
      // int类型参数是指定端口号,
      // 最后一个参数是给出文件名或路径名。

URL(String, String, String); //构造一个URL类。
      //参数含义与上相同, 使用缺省端口号。
URL(URL, String); //构造一个URL类。使用给出的URL和相对路径, 
                  // String类型参数是相对路径。
URL(String); //使用URL字符串构造一个URL类。

与以往介绍的其它类不同的是, 在构造URL类时, 必须有相应的异常处理(有关异常处理在后面的第七章中将详细介绍)。因此应写成:

  String ECNU = *http://www.ecnu.edu.cn/*;
  try { url = new URL(ECNU); 
  } catch (MalformedURLException e) {
    ……//出错处理
  }

在构造完一个URL类后, 可以使用URL类中的openStream方法与服务器上的文件建立一个流的连接, 但是这个流是输入流(InputStream), 只能读而不能写。(关于流的概念, 在第六章有专门介绍)。


2.URLConnection类

使用URL类中openConnection方法可以构造一个URLConnection类。这个类中包含了更丰富的方法, 可以对服务器上的文件进行更多的处理。URLConnection类的构造方法是 URLConnection(URL), 可以构造一个对指定URL的连接对象。用URLConnection 的构造方法来构造 URLConnection类时, 并未建立与指定URL的连接, 所以还必须使用URLConnection类中的connect方法建立连接。而用URL类中的openConnection方法来构造时, 已建立了连接, 就不需要使用connect方法。


3.AppletContext类

AppletContext类是一个接口类, Applet通过AppletContext接口与环境进行通讯。可以利用这个类从Applet环境获取信息, 而这个环境一般是指浏览器。AppletContext类没有构造方法, 但可以通过Applet类中的getAppletContext方法获取AppletContext接口使用 showStatus方法可以在浏览器的状态条中显示提示信息。使用showDocument方法可以通知浏览器在指定窗口中显示另一个URL的内容。 比如要浏览器显示中国教育科研网总节点的主页, 可以写成:
  String cernet = *http://www.cernet.edu.cn/*;
  try {
    url = new URL(cernet); 
  }catch (MalformedURLException e) {
    ……//出错处理
  }
  getAppletContext().showDocument(url);

5.3.2 Socket编程

Socket原先是Unix系统中的概念, 以后在网络编程中广泛使用。 在Java的Application中可以将Socket类和ServerSocket类分别用于Client端和Server端, 在任意两台机器间建立连接。下面来介绍这两个类的使用方法。


1.Socket类

Socket类用在用户端, 用户通过构造一个Socket类来建立与服务器的连接。Socket连接可以是流连接, 也可以是数据报连接, 这取决于构造Socket类时使用的构造方法。一般使用的流连接, 流连接的优点是能所有数据都能准确、有序地送到接收方, 缺点是速度较慢。

Socket类的构造方法有四种。

  Socket(String, int); //构造一个连接指定主机、指定端口的流Socket。
  Socket(String, int, boolean); //构造一个连接指定主机、指定端口的Socket类,
        // boolean类型的参数用来设置是流Socket还是数据报Socket。
  Socket(InetAddress, int);
        //构造一个连接指定Internet地址、指定端口的流Socket。
  Socket(InetAddress, int, boolean);
        //构造一个连接指定Internet地址、指定端口的Socket类,
        // boolean类型的参数用来设置是流Socket还是数据报Socket。

在构造完Socket类后, 就可以通过Socket类来建立输入、输出流, 通过流来传送数据。


2.ServerSocket类

ServerSocket类用在服务器端, 接收用户端传送的数据。ServerSocket类的构造方法有两种。
ServerSocket(int); //在指定端口上构造一个ServerSocket类。
ServerSocket(int, int); //在指定端口上构造一个ServerSocket类, 
        //并进入监听状态, 第二个int类型的参数是监听时间长度。

3.建立连接

在介绍了Socket类和ServerSocket类后, 再来介绍一下连接的过程。

首先, 在服务器端构造一个ServerSocket类, 在指定端口上进行监听, 这将使线程处于等待状态。然后在用户端构造Socket类, 与服务器上的指定端口进行连接。服务器监听到连接请求后, 就可在两者之间建立连接。建立连接的具体细节对用户是透明的, 用户只需要构造这两个类就可以了。在建立连接之后, 就可以建立相应的输入、输出流, 两端通过流来进行通讯。下面是建立连接的一个例子, 接收服务器的IP地址是202.120.88.71, 端口号是80。服务器上的程序:

  ServerSocket server;Socket ssocket;
  try {server = new ServerSocket(80);
    svsk = server.accept();
    OutputStream out = serverSocket.getOutputStream();
    out.write(*Test*);
  }catch (IOException e) {
  }
用户端的程序:
  Socket csocket;
  try {
    cSocket = new Socket ("202.120.88.71",80);
    InputStream in = clientSocket.getInputStream();
    System.out.println("client reads: " + in.read());
  }catch (UnknownHostException e) {
  }catch (IOException e) {
  }

需要说明一下的是, 通过Socket建立的流可以是双向的, 即在一个Socket的两端可以分别同时建立输入流和输出流。

5.3.3 数据报

数据报是一种无连接的通信方式, 它的速度比较快, 但是由于不建立连接, 不能保证所有数据都能送到目的地。所以一般用于传送非关键性的数据。发送和接收数据报需要使用Java类库中的DatagramPacket类和DatagramSocket类, 下面将详细介绍。


1.DatagramPacket类

它是进行数据报通信的基本单位, 它包含了需要传送的数据、数据报的长度、IP地址和端口等。DatagramPacket类的构造方法有两种。
  DatagramPacket(byte [], int); //构造一个用于接收数据报的DatagramPacket类, 
        // byte []类型的参数是接收数据报的缓冲,
        // int类型的参数是接收的字节数。
  DatagramPacket(byte [], int, InetAddress, int); 
        // 构造一个用于发送数据的DatagramPackte类, 
        // byte []类型参数是发送数据的缓冲区, 
        // int类型参数是发送的字节数,
        // InetAddress类型参数是接收机器的Internet地址,
        // 最后一个参数是接收的端口号。

2.DatagramSocket类

DatagramSocket类是用来发送数据报的Socket, 它的构造方法有两种。

  DatagramSocket(); //构造一个用于发送的DatagramSocket类。
  DatagramSocket(int ); // 构造一个用于接收的DatagramSocket类。

构造完DatagramSocket类后, 就可以发送和接收数据报。


3.发送和接收过程

发送数据报, 需要在接收端先建立一个接收的DatagramSocket, 在指定端口上监听, 构造一个DatagramPacket类指定接收的缓冲区(DatagramSocket的监听将阻塞线程)。在发送端需要首先构造DatagramPacket类, 指定要发送的数据、数据长度、接收主机地址及端口号, 然后使用DatagramSocket类来发送数据报。接收端接收到后, 将数据保存到缓冲区, 发送方的主机地址和端口号一并保存。随后将接收到的数据报返回给发送方, 并附上接收缓冲区地址, 缓冲长度、发送方地址和端口号等信息, 等待新的数据。下面是一个发送和接收数据报的例子, 接收端的IP地址是202.120.88.71, 端口号是80, 发送的数据在缓冲区message中, 长度为200。接收端的程序:

  byte []inbuffer = new byte[1024]; //接收缓冲
  DatagramPacket inpacket = new DatagramPacket(inbuffer, inbuffer.length);
  DatagramSocket insocket = new DatagramSocket(80);
  insocket.receive(inpacket); //监听数据
  String s = new String(inbuffer, 0, 0, inpacket.getlength);
         //将接收的数据存入字符串s发送端的程序:
  DatagramPacket outpacket = new DatagramPacket(message, 200, "202.120.88.71",80);
  DatagramSocket outsocket = new DatagramSocket();
  outsocket.send(outpacket);

练习题

1. 写一段程序, 实现这样的功能:当鼠标移动到某幅图片上时, 程序自动在这一位置显示另一幅图片。

2. 写一段程序, 实现用鼠标的拖动功能。

3. 简述Java窗口中的各版面的排版原则。

4. 用GridBagLayout版面改写5.2.3中嵌套的例子, 要求达到相同的输出效果。

5. 编写一段程序, 实现类似于Windows工具条的效果。

6. 编写一段程序, 实现这样的功能, 在屏幕上显示代表若干著名公司网址的按钮, 按下按钮, 则自动显示对应公司的主页。

7. 使用URLConnection类, 显示某一URL的长度、类型、修改日期的信息。