作者:刘啸林
您可以自由下载本文,但请保留作者信息。
在上一章中,介绍了如何在Java的Applet中绘制图形、动画和加入声音。这一章要介
绍如何设计人机交互界面、如何接收用户的输入以及如何处理这些输入。
Java中的事件(Event)处理是AWT(Abstract WindowingToolkit)的一部分。当某些情? 况发生时,例如鼠标的移动、键盘的按键等,会触发相应的事件。而通过这些事件,AWT 构件与用户或AWT构件之间就可以进行某种通讯。AWT对事件的处理分为两种情况,一种 是由AWT或浏览器负责处理,例如paint()方法,还有一种是不作处理,例如鼠标的移动。 在这一节里,我们要讨论的是对于这些AWT不作处理的方法,如何自己编写并覆盖这些事 件处理方法,例如接收鼠标、键盘的输入。关于AWT如何处理这些事件,在本节的最后一 部分将专门讨论。为了接收鼠标、键盘的输入,可把它们相应的事件分成三类:鼠标的按 键;鼠标的移动或拖动;键盘的输入。
对于鼠标的按键,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()方法中,首先将已有的点重新标出(原有点已被刷新),然后判断 如果已有三个点,那么将三个点连成一个三角形。
鼠标的移动有两种情况,鼠标的移动和鼠标的拖动,同样也对应了两个方法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) { ……… }
当用户按下或松开键盘的按键时,会产生键盘事件。相应的处理方法是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 ……… //显示小写字母 }
在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的父类处理,从而保证对事件的正常处理。