首页 Java就业培训教程第八章_GUI(上)

Java就业培训教程第八章_GUI(上)

举报
开通vip

Java就业培训教程第八章_GUI(上)第8章 GUI(上) GUI全称是Graphical User Interface,即图形用户界面。顾名思义,就是应用程序提供给用户操作的图形界面,包括窗口、菜单、按钮、工具栏和其他各种屏幕元素。目前,图形用户界面已经成为一种趋势,它的好处自不必多说了,所以几乎所有的程序设计语言都提供了GUI设计功能。在Java里有两个包为GUI设计提供丰富的功能,它们是AWT和Swing。AWT是Java的早期版本,其中的AWT组件种类有限,可以提供基本的GUI设计工具,却无法完全实现目前GUI设计所需的所有功能。Swing是...

Java就业培训教程第八章_GUI(上)
第8章 GUI(上) GUI全称是Graphical User Interface,即图形用户界面。顾名思义,就是应用程序提供给用户操作的图形界面,包括窗口、菜单、按钮、工具栏和其他各种屏幕元素。目前,图形用户界面已经成为一种趋势,它的好处自不必多说了,所以几乎所有的程序设计语言都提供了GUI设计功能。在Java里有两个包为GUI设计提供丰富的功能,它们是AWT和Swing。AWT是Java的早期版本,其中的AWT组件种类有限,可以提供基本的GUI设计工具,却无法完全实现目前GUI设计所需的所有功能。Swing是SUN公司对早期版本的改进版本,它不仅包括AWT中具有的所有部件,并且提供了更加丰富的部件和功能,它足以完全实现GUI设计所需的一切功能。Swing会用到AWT中的许多知识,掌握了AWT,也就基本上掌握了Swing,我们就从AWT开始我们的图形界面设计之旅吧! 8.1 初识AWT AWT中定义了多种类和接口,用于在Java应用程序和Java Applet中进行GUI设计。我们首先通过下面的示例程序来感受一下Java的图形界面编程。 import java.awt.*; public class TestFrame { public static void main(String [] args) { Frame f=new Frame(“IT人资讯交流网”); f.add(new Button(“ok”)); f.setSize(300,300); f.setVisible(true); } } 图形界面程序中可以使用各种各样的图形界面元素,如文本框,按钮,列 关于同志近三年现实表现材料材料类招标技术评分表图表与交易pdf视力表打印pdf用图表说话 pdf 框,对话框等等,我们将这些图形界面元素称为GUI组件。AWT为各种GUI组件提供了对应的Java组件类,这些组件类都是java.awt.Component的直接或间接子类。其中,Frame类用于产生一个具有标题栏的框架窗口。Frame.setSize方法设置窗口的大小,Frame.setVisible显示或隐藏窗口,程序运行后产生一个如图8.1所示的非常 标准 excel标准偏差excel标准偏差函数exl标准差函数国标检验抽样标准表免费下载红头文件格式标准下载 的框架窗口。用AWT编写Java的GUI程序的图形用户界面的各种组件类都位于JDK的java.awt包中,程序开始必须导入java.awt包,可以导入整个java.awt包,也可以只导入程序中用到的那些组件类。编译运行此程序,结果如图8.1所示。 图8.1 对于众多的GUI组件,根据其作用可以又分为两大类:基本组件(下面就全部简称为组件)和容器。 组件又被称为构件,它是诸如按钮、文本框之类的图形界面元素,在这些组件上不能容纳其他的组件。容器其实也是一种组件,是一种比较特殊的组件,它可以用来容纳其他组件,如窗口,对话框等等,所有的容器类都是java.awt.Container的直接或间接子类。 Container类是Component类的一个子类,由此可见容器本身也具有组件的功能和特点,也可以被当作基本组件一样使用。在上面的程序中,Frame就是一个容器,它容纳了一个Button部件。 8.2 AWT线程 细心的读者也许注意到了,在运行上面写的那段程序时,主调用类的main方法执行f.setVisible(true)语句后就退出了,程序的main线程也随之结束了,但程序并没有结束,窗口不仅正常显示在桌面上,而且我们还可以对这些窗口进行一些常规操作,如拖动窗口,改变窗口的大小等。我们在多线程的课程中曾经讲过,对Java程序来说,只要还有一个前台线程在运行,整个进程就不会结束。这说明我们的程序还有其它线程在运行,那么其它线程是谁创建的?又是在什么时候创建的呢?读者可以简单地认为,程序在产生Frame对象时,创建了一个新的线程,我们称之为AWT线程。AWT线程的内部实现,在不同的JDK版本下不太一样,从我们对Java的使用经验上来看,在不同的版本下,我们时常碰到我们的应用程序(不仅仅是AWT)有不同的执行结果,这是令人很痛苦和无奈的事情,也是我们使用Java所要经常承担的风险。如果我们的程序调用Frame.dispose方法关闭了我们的框架窗口(具体实现细节,读者在本章稍后的部分能够看到),当程序放在JDK1.3下运行时,我们发现AWT线程没有结束,程序也不会自动结束。但在JDK1.4下运行这个程序,当框架窗口被关闭后,AWT线程也结束了,程序随之结束。 搬出那些过时的小经验来给读者讲解,并不是什么好主意,甚至会造成误导。所以在这里,我的侧重点并不是要讲解与 分析 定性数据统计分析pdf销售业绩分析模板建筑结构震害分析销售进度分析表京东商城竞争战略分析 AWT线程在不同JDK版本下的差异的具体案例本身,因为百分之九十九的读者都不会再用到旧的JDK版本,也不会再碰到这样的问题的,既然人家在新的版本中已经做出了修改,你就没必要白费精力、花功夫去了解那些陈年旧事了。虽然如此,但我还要在此提及这个问题,是要告诉大家下面的信息:在实际开发中,要注意到有时候碰到的一些莫名奇妙的问题,并不是我们程序本身的问题,不妨换个角度去想,可能是开发工具或系统版本的问题,在通常的应用程序开发中,不要太相信你的系统是绝对的。 从事计算机软件开发,对绝大多数人来说,不是科学研究,而是一个工程项目的实施,我们使用的开发环境,开发工具,甚至编程语言本身就是我们的工程工具,这些工具不可能是完全理想和完美的,经常会有这样或那样的小问题,往往都是软件开发人员在应用过程中发现了它们在某个应用上的问题后,系统供应商再去修改他们的的开发环境,升级他们的开发工具,完善编程语言。在这之前,我们只能想别的办法来完成我们程序中的有关任务,或是通过别的手段来回避这些开发工具、环境、语言本身的问题。当新手碰到这些开发工具和语言本身的问题后,一般都不知所措,只会从程序的角度上去找错误,而不是怀疑开发工具或程序语言本身的问题。所以,在软件开发过程中,老手的经验是非常重要的,脱离实际应用,只学习编程语言本身的人们,往往都很难独当一面地从事软件开发。 8.3 AWT事件处理 8.3.1 事件处理机制 我们在上面写的这个程序有一个明显的问题,鼠标点击窗口标题栏上的关闭按钮并不能够使窗口关闭和使程序结束,我们只能用操作系统自带的杀死进程的办法来关闭这个程序,在命令行窗口中用ctrl+c就可以强制结束正在运行的程序。其实,这也没有什么奇怪的,通过GUI组件,用户可以对应用程序进行各种操作,反之,应用程序可以通过GUI组件收集用户的操作信息,如用户在窗口上移动了鼠标、按下了键盘等。别忘了GUI的意义,是应用程序提供给用户操作的图形界面,而GUI本身并不对用户操作的结果负责。大家可以想想,我们在这个窗口上添加了一个按钮,当用户用鼠标点击这个按钮时,程序不也是什么都不做吗?如果我们想对鼠标点击按钮这个事件执行某种功能,就必须编写相应的处理程序代码。 对于这种GUI程序与用户操作的交互功能,Java使用了一种自己专门的方式,称之为事件处理机制。在事件处理机制中,我们需要理解三个重要的概念: · 事件:用户对组件的一个操作,我们称之为一个事件。 · 事件源:发生事件的组件就是事件源。 · 事件处理器:负责处理事件的方法。 三者之间的关系如图8.2所示: 图8.2 Java程序对事件进行处理的方法是放在一个类对象中的,这个类对象就是事件监听器。 我们必须将一个事件监听器对象同某个事件源的某种事件进行关联,这样,当某个事件源上发生了某种事件后,关联的事件监听器对象中的有关代码才会被执行。我们把这个关联的过程称为向事件源注册事件监听器对象。从上面的图例中,我们能够看到,事件处理器(事件监听器)首先与组件(事件源)建立关联,当组件接受外部作用(事件)时,组件就会产生一个相应的事件对象,并把此对象传给与之关联的事件处理器,事件处理器就会被启动并执行相关的代码来处理该事件。 基本上明白了Java的事件处理机制,我们接着详细介绍事件和事件监听器的一些编程方面的有关知识。 事件用以描述发生了什么事情。AWT对各种不同的事件,按事件的动作(如鼠标操作)、效果(如窗口的关闭和激活)等进行了分类,一类事件对应一个AWT事件类。我们这里并不想象通常的 关于书的成语关于读书的排比句社区图书漂流公约怎么写关于读书的小报汉书pdf 籍一样,给大家罗列各种各样的事件并解释一番。我在这里为大家简要介绍几个具有典型代表意义的事件,就足以让大家掌握相关的知识了,如果有人想了解所有的事件,不用去查找什么大全之类的书籍,在这里我告诉你一个简单的办法,你只要参阅JDK文档中的java.awt.event包,那里列出了所有的事件类。 · MouseEvent类对应鼠标事件,包括鼠标按下,鼠标释放,鼠标点击(按下后释放)等。 · WindowEvent类对应窗口事件,包括用户点击了关闭按钮,窗口得到与失去焦点,窗口被最小化等。 · ActionEvent类对应一个动作事件,它不是代表一个具体的动作,而是一种语义,如按钮或菜单被鼠标单击,单行文本框中按下回车键等都可以看作是ActionEvent事件。读者可以这么理解ActionEvent事件,如果用户的一个动作导致了某个组件本身最基本的作用发生了,这就是ActionEvent事件。菜单、按钮放在那就是用来发出某种动作或命令的,鼠标单击(也可以用键盘来操作)这些组件,只是表示要执行这种动作或命令的事情发生了,显然对于这种情况,我们并不关心是鼠标单击,还是键盘按下的。 通过各种事件类提供的方法,我们可以获得事件源对象,以及程序中对这一事件可能要了解的一些特殊信息,如对于鼠标事件,我们很可能要获得鼠标的坐标信息,经过查JDK文档就能知道用MouseEvent.getX,MouseEvent.getY这两个方法。小时候常听人讲“人有多大胆,地有多大产”,同样,对于我们编程中遇到的一般正常的需求,开发工具包都会提供,没有解决不了的,只有我们还没找到的。解决问题的关键就看我们有没有现用现找的本领了。 某一类事件,其中又包含触发这一事件的若干具体情况。对一类事件的处理由一个事件监听器对象来完成,对于触发这一事件的每一种情况,都对应着事件监听器对象中的一个不同的方法。某种事件监听器对象中的每个方法名称必须是固定的,事件源才能依据事件的具体发生情况找到事件监听器对象中对应的方法,事件监听器对象也包含事件源可能调用到的所有事件处理方法。这正是“调用者和被调用者必须共同遵守某一限定,调用者按照这个限定进行方法调用,被调用者按照这个限定进行方法实现 ”的应用规则,在面向对象的编程语言中,这种限定就是通过接口类来表示的。事件源和事件监听器对象就是通过事件监听器接口进行约定的,事件监听器对象就是实现了事件监听器接口的类对象。不同的事件类型对应不同的事件监听器接口。 事件监听器接口的名称与事件的名称是相对应的,非常容易记忆,如MouseEvent的监听器接口名为MouseListener,WindowEvent的监听器接口名为WindowListener,ActionEvent的监听器接口名为ActionListener。 有许多书上将众多的事件分为两大类:低级事件和语义事件(又叫高级事件),并列出了所有属于低级事件的事件,所有属于高级事件的事件。凭作者的经验,不相信真有读者能够很清楚的记住哪些是高级事件和哪些是低级事件,包括那些书的作者们本人也做不到。虽然作者认为,记住哪些是高级事件和哪些是低级事件毫无意义,但作者也可以让你不用记忆,就能够轻松地进行这种区分。如果某个事件的监听器接口中只有一个方法,那么这个事件就是语义事件,如ActionListener中只有一个方法,ActionEvent就是一种语义事件,反之,则为低级事件。另外,从字面上,我们也能够想象,语义事件关心的是一个具有特殊作用的GUI组件对应的动作发生了,而不关心这个动作是怎样发生的。 8.3.2 用事件监听器处理事件 我们来看看如何为上面的程序添加窗口关闭的代码,从而学习事件处理的具体编码实现: 程序清单:TestFrame.java import java.awt.*; import java.awt.event.*; public class TestFrame { public static void main(String [] args) { Frame f=new Frame(“IT人资讯交流网”); f.setSize(300,300); f.setVisible(true); f.addWindowListener(new MyWindowListener()); } } class MyWindowListener implements WindowListener { public void windowClosing(WindowEvent e) { e.getWindow().setVisible(false); ((Window)e.getComponent()).dispose(); System.exit(0); } public void windowActivated(WindowEvent e){} public void windowClosed(WindowEvent e){} public void windowDeactivated(WindowEvent e){} public void windowDeiconified(WindowEvent e){} public void windowIconified(WindowEvent e){} public void windowOpened(WindowEvent e){} } 在上面的程序代码中,由于AWT中的事件类和监听器接口类都位于java.awt.event包中,所以在程序的开始处,import了java.awt.event.*。注意,import一个包中的所有类,并没有import该包的子包中的类,所以,我们 import了java.awt包,还要单独import java.awt.event包,其实,这个问题,即使我们不讲,大家也应该能够自己通过实验弄明白的。只要勇于实践,多动手编写些程序,通过观察编译运行的结果,就可以验证我们的许多想法或去除心中的疑惑,同时也锻炼和逐渐提高了我们的编程能力。 我们编写了一个新类MyWindowListener来实现窗口事件监听器对象的程序代码,并调用Window.addWindowListener方法将事件监听器对象注册到Frame类(Frame类继承java.awt.Window类)创建的框架窗口上。在WindowListener接口里有七个方法,正好对应窗口事件的七种情况,因为我们只想处理鼠标点击窗口标题栏上的关闭按钮这一事件,对其他窗口事件我们并不关心,所以,在类MyWindowListener的代码中,我们只对windowClosing方法进行了编码,其他方法只是简单实现(因为Java语法的要求,必须实现接口的所有方法),什么也没做。注意windowClosing方法和windowClosed方法的区别,windowClosing对应用户想关闭窗口的情况,而windowClosed对应窗口已经被关闭时的情况。 (指点迷津: 如我们要获得汽车类的发动机,我们可以用一个汽车类的getEngine方法来实现,显然该方法的返回值类型只能是发动机(而不能是发动机的某个子类)。现在,有个东风汽车类继承了汽车类,如果我们直接使用从汽车类继承到的getEngine方法来获得一辆东风汽车的发动机,尽管我们知道获得的发动机是东风发动机,但对编译器来说,它只能从getEngine方法的语法上知道返回的是发动机,如果对于这个返回的发动机对象,我们要使用东风发动机特有的功能,我们就需要使用以前讲过的类型转换。如果东风汽车类专门增加了一个新的getDongFengEngine方法,这个方法的返回值类型就是东风发动机,返回的对象可以直接使用东风发动机特有的功能,不用再作类型转换。 明白了东风汽车这个比喻,读者就不难理解在上面程序的windowClosing方法中,作者用了两种方式来返回那个窗口对象的代码了。下面是WindowEvent的继承层次图: java.lang.Object | +--java.util.EventObject | +--java.awt.AWTEvent | +--java.awt.event.ComponentEvent | +--java.awt.event.WindowEvent 由于EventObject.getSource、ComponentEvent.getComponent、WindowEvent.getWindow等方法都可以返回事件源对象,由于子类可以继承父类的方法,所以WindowEvent事件对象,返回事件源对象的方法可以有多个。不管用哪种方法,返回的都是内存中的同一个对象,只是对编译器来说,返回的类型不同罢了,越是底层的getXxx方法,返回的类型越具体。 小结:要处理GUI组件上的XxxEvent事件下的某种情况,首先要编写一个实现了XxxListener接口的事件监听器类,然后在XxxListener类和要处理的具体事件情况相对应的方法中编写处理程序代码,最后将类XxxListener创建的对象通过addXxxListener方法注册到GUI组件上。Xxx可以是各种不同的事件,如Window,Mouse,Key,Action等。 小经验:所有事件监听器方法返回的返回类型都是void。 8.3.3 事件适配器 为简化编程,JDK针对大多数事件监听器接口定义了相应的实现类,我们称之为事件适配器(Adapter)类。在适配器类中,实现了相应监听器接口中所有的方法,但不做任何事情,子类只要继承适配器类,就等于实现了相应的监听器接口,如果要对某类事件的某种情况进行处理,只要覆盖相应的方法就可以了,其他的方法再也不用“简单实现”了。可见,如果想用作事件监听器的类已经继承了别的类,就不能再继承适配器类了,只能去实现事件监听器接口了。 修改MyWindowListener,代码如下: class MyWindowListener extends WindowAdapter { public void WindowClosing(WindowEvent e) { e.getWindow().setVisible(false); ((Window)e.getComponent()).dispose(); System.exit(0); } } 重新编译没有错误,可是,点击关闭按钮后程序并没有退出。这是怎么回事呢?要么是WindowClosing方法中的代码有问题,要么是WindowClosing方法没有调用,怎么判定这两种情况呢?我们只要在WindowClosing方法的开始处,添加System.out.println(“coming here!”);,编译运行后再看看命令行窗口上(不是GUI窗口上)有没有打出“coming here!”,就知道WindowClosing方法是否被调用过。运行后屏幕上并没有打印出“coming here!”,可以确定WindowClosing方法没有被调用,这时又有两种可能性,一种是还没有将MyWindowListener对象注册到框架窗口上,所以即使框架窗口上发生了WindowEvent,也不会来调用类MyWindowListener中的任何方法,仔细检查程序代码,我们可以排除这种可能。还有一种可能就是,WindowClosing方法名拼写有问题,仔细比较后发现第一个字母应该是小写,即windowClosing。哦!Java是区分大小写的,MyWindowListener.WindowClosing是一个与WindowAdapter.windowClosing毫无关系的方法,并没有起到对WindowAdapter.windowClosing方法覆盖的目的。将WindowClosing修改成windowClosing后编译运行,一切正常。 在上面的例子中,事件监听器的类MyWindowListener和产生GUI组件的类TestFrame是两个完全分开的类,事件监听器的类中的代码所访问到的对象也正好是事件源。如果我们要在件监听器的类中的代码中访问其他的非事件源的GUI组件,程序就得想别的办法了,如我们单击图8.1窗口中的ok按钮来关闭框架窗口并退出程序,这就要求程序在按钮的事件监听器代码中要访问框架窗口,我们又该怎么做呢?一种简单的办法就是将事件监听器的代码和产生GUI组件的代码放在同一个类中实现,程序代码如下: import java.awt.*; import java.awt.event.*; public class TestFrame implements ActionListener { public static void main(String [] args) { Frame f=new Frame(“IT人资讯交流网”); Button btn=new Button(“退出”); btn.addActionListener(new TestFrame());//注册事件监听器对象 f.add(btn);//将按钮增加到框架窗口上 f.setSize(300,300); f.setVisible(true); } public void actionPerformed(ActionEvent e) { f.setvisible(false); f.dispose(); System.exit(0); } } 编译肯定有错,因为actionPerformed方法中不能访问在main方法中定义的f变量。修改程序,将Frame f=new Frame(“IT人资讯交流网”);移动到main方法的上面,也就是将f定义成一个成员变量,编译还会有错,因为main是个静态方法,不能直接访问同类中的成员变量。再次修改程序,如下所示: import java.awt.*; import java.awt.event.*; public class TestFrame implements ActionListener { Frame f=new Frame(“IT人资讯交流网”); public static void main(String [] args) { TestFrame tf=new TestFrame(); Button btn=new Button(“退出”); btn.addActionListener(new TestFrame()); tf.f.add(btn); tf.f.setSize(300,300); tf.f.setVisible(true); } public void actionPerformed(ActionEvent e) { f.setvisible(false); f.dispose(); System.exit(0); } } 上面的程序虽然编译没有问题,但程序代码规范极差,在main方法中,对于每一次f对象的引用,都必须在f前增加tf,如tf.f.setSize。如果添加到f上的GUI组件很多,那么将有很多的tf.f.xxx语句,这样的程序代码,相信你自己都不会满意。我们怎样将上面的代码修改得规范些呢?将程序改成下面这样。 import java.awt.*; import java.awt.event.*; public class TestFrame implements ActionListener { Frame f=new Frame(“IT人资讯交流网”); public static void main(String [] args) { TestFrame tf=new TestFrame(); tf.init(); } public void init() { Button btn=new Button(“退出”); btn.addActionListener(new TestFrame()); f.add(btn); f.setSize(300,300); f.setVisible(true); } public void actionPerformed(ActionEvent e) { f.setVisible(false); f.dispose(); System.exit(0); } } 将在main方法中调用的GUI初始化代码全部放到了init方法中,在main方法中只要调用这个init方法就可以了,init方法是非静态的,可以直接访问非静态的成员变量。编译运行后,退出按钮并不能关闭窗口,读者应先自己思考问题的原因并上机调试后,再来参看下面的解答。在上面的程序中,创建了两个TestFrame对象,桌面上显示的窗口是第一个TestFrame中的f对象,而退出按钮的事件处理代码中关闭的是第二个TestFrame中的f对象,所以我们看到的窗口没有关闭,将init方法中的btn.addActionListener(new TestFrame());改为btn.addActionListener(this);就可以了。 我们在这里再次强调要面向对象,脑子里想的就是程序中各对象在各个时刻的内存布局和状态,以及每行程序代码执行后对内存中的对象的影响。 我们通过将事件监听器的代码和产生GUI组件的代码放在同一个类中实现,就很轻松的解决了我们的问题。 8.3.4 事件监听器的匿名内置类实现方式 如果一个事件监听器类只用于在一个组件上注册监听器事件对象,为了让程序代码更为紧凑,我们可以用匿名内置类的语法来产生这个事件监听器对象,这也是一种经常使用的方法。 对于上面的例子,我们用匿名内置类的语法进行改写,程序代码如下: import java.awt.*; import java.awt.event.*; class TestFrame { Frame f=new Frame("IT人资讯交流网"); public static void main(String [] args) { new TestFrame().init(); } public void init() { Button btn=new Button("退出"); btn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { f.setVisible(false); f.dispose(); System.exit(0); } }); f.add(btn); f.setSize(300,300); f.setVisible(true); } } 8.3.5 事件处理的多重运用 用户的一个操作,在触发了低级事件的同时,可能也会触发语义事件,这时令初学者困惑的是,不知道该选择哪种类型事件监听器来进行处理。其实道理很简单,一个员工打伤了他的同事,如果程序代表法律部门,要处理的就是打人这一低级事件,如果程序代表公司经理,要处理的便是违犯公司纪律这一语义事件,如果程序即代表法律部门,又代表公司经理,便需对这两个事件都做处理。一般情况下,如果对语义事件的处理能够满足我们的需求,我们就不再处理低级事件。用户在一个按钮上按下鼠标,触发了鼠标事件,也触发了按钮的动作事件,我们根据我们程序的需要来编码处理某一个事件或是两者都处理。例如当按钮被点击后,我们除了要执行该按钮对应的程序功能外,还希望鼠标按下时能改变按钮标题内容,鼠标释放时能恢复标题内容,我们在按钮上就要注册两个事件监听器对象,一个处理鼠标事件,另一个处理按钮的动作事件。一个GUI组件倒底能够触发哪几种事件,我们没必要死记硬背,在一般的集成开发环境下,如JBuilder,JCreator等,当我们输入某个对象的成员分隔符,在下拉的成员列表提示框中,我们就能看到这个组件支持的事件,如图8.3所示的JCreator下的提示情况。 图8.3 我们通过查看这些addXxxListener方法,就知道了这个组件所支持的事件类型。 一个组件上的一个动作可以产生多种不同类型的事件,因而我们可以向同一个事件源上注册多种不同类型的监听器,如图8.4所示: 图8.4 一个事件监听器对象可以注册到多个事件源上,即多个事件源的同一事件都由一个对象统一来处理,如图8.5所示: 图8.5 一个事件源上也可以注册对同一事件进行处理的多个事件监听器对象,当这一事件发生时,各事件监听器对象依次被调用,如图8.6所示: 图8.6 8.3.6 高级事件处理 默认情况下,组件屏蔽了对所有事件的响应,也就是不管发生了什么情况,事件都不会在这个组件上发生,组件都不会产生任何事件对象。只有在一个组件上注册某种事件的事件监听器对象后,组件才可以对这种事件做出响应,当发生了对应这种事件的情况后,事件才能在这个组件上发生,组件才会产生这种事件对象。 当一个组件上发生了某种事件后,系统会调用这个组件对象的processEvent方法来处理,缺省的processEvent方法将根据事件的类型调用相应的processXxxEvent方法,其中Xxx代表事件类型,processXxxEvent方法接着将Xxx事件传递给注册的监听器去处理。例如,如果组件上发生了鼠标移动事件,组件对象的processEvent方法将调用processMouseMotionEvent方法进行处理。 如果我们想改变某种组件的事件处理方式,我们需要覆盖该组件的processEvent方法或processXxxEvent方法,processEvent是处理所有事件的总入口,而processXxxEvent是专用于处理某种事件的分岔入口。显然,如果要在一个方法中就改变所有事件的处理方式,我们需要覆盖processEvent,如果只想改变某种或少数几种事件的处理方式,而不想影响其它事件的处理方式,我们还是覆盖processXxxEvent方法,而不再直接覆盖processEvent方法为好。由于我们不可能直接进入到某个组件的processXxxEvent方法中去修改程序代码,我们需要定义一个继承了该组件的子类,在子类中覆盖processXxxEvent方法,并将原先创建的组件对象改为由这个子类创建,就可以达到我们的目的了。 就象前面讲的,如果我们没有在组件上注册Xxx事件监听器,组件就不会发生Xxx事件,我们的processXxxEvent方法根本就不可能被调用。即使没有在组件上注册事件监听器,我们只要调用了enableEvents函数,设置组件能够进行响应的事件,在相应的情况发生后,组件仍然能够产生对应的事件。enableEvents函数的定义如下: protected final void enableEvents(long eventsToEnable) 其中,参数eventsToEnable指定了需要组件响应的事件类型所对应的数值。在我们的程序中经常要用到一类变量,这个变量里的每一位(bit)都对应某一种特性。当该变量的某位为1时,表示有该位对应的那种特性,当该位为0时,即没有那位所对应的特性。当变量中的某几位同时为1时,就表示同时具有那几种特性的组合。那种特性在我们这里就是要响应的事件类型,我们将不同的事件类型用一个不同的long型数值来表示,且每个这些数值中只有与其事件类型对应的那一位(一个bit)为1,其余的bit都为0,这样,我们就可以对多个事件类型所对应的数值进行相或(|)操作,得到的结果正好就是这几种事件类型的组合。一个数值中的哪一位代表哪种事件类型,是不容易记忆的,所以我们就将这些表示不同事件类型的long型数值定义成常量,常量名就是根据事件类型的英文拼写去定义。可见,参数eventsToEnable可以是多个事件类型所对应的数值相或的结果,我们又怎样找到表示事件类型数值的常量名呢?如果你明白了在Java中是如何定义常量的,你就应该想到在JDK文档中去查找与事件有关的类,在其中的某个类中就会有这些常量的定义的。作者看到processEvent((AWTEvent e))方法中的参数类型是AWTEvent,就很自然地想到了去AWTEvent类中查找,果然在那里看到了这些常量的定义,如鼠标移动事件对应的常量为MOUSE_MOTION_EVENT_MASK,这样,当我们想让组件响应鼠标移动事件时,我们可以使用enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK);语句来完成。 明白了组件内部的事件处理过程和相关知识,我们现在就来编写一个这样的程序:在一个窗口上显示一个按钮,一旦鼠标移动到这个按钮上时,按钮就移动到了其他位置,这样,鼠标就永远无法点击到这个按钮。在写这个程序之前,我先讲个故事,我们假设有两个孙悟空,刚开始时第二个孙悟空使用了隐身术,所以你只能看见第一个孙悟空。当你靠近第一个孙悟空时,这个孙悟空马上使用了隐身术而隐藏,同时 通知 关于发布提成方案的通知关于xx通知关于成立公司筹建组的通知关于红头文件的使用公开通知关于计发全勤奖的通知 第二个孙悟空现身,你就会误以为第一个孙悟空跑到了第二个孙悟空的位置上。同样,当你靠近第二个孙悟空时,这个孙悟空马上使用了隐身术而隐藏,同时通知第一个孙悟空现身,这时,你以为还是第一个孙悟空又跑回到了他原来的位置。如此往复,你永远也抓不到孙悟空的,你却还以为只有一个孙悟空在跑来跑去的呢!作者就使用抓孙悟空的这个思路来编写我们的程序,首先要对按钮的鼠标移动事件进行处理,所以我定义了一个Button的子类MyButton,在MyButton中对processMouseMotionEvent方法进行覆盖,在该方法中隐藏自己,显示自己的伙伴。下面是程序代码,其中的细节在前面都分析过了,就不再多说了。 程序清单:TestMyButton.java import java.awt.*; import java.awt.event.*; class MyButton extends Button { private MyButton friend; public void setFriend(MyButton friend) { this.friend = friend; } public MyButton(String name) { super(name); enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK); } protected void processMouseMotionEvent(MouseEvent e) { setVisible(false); friend.setVisible(true); } } public class TestMyButton { public static void main(String [] args) { MyButton btn1 =new MyButton("你来抓我呀!"); MyButton btn2 =new MyButton("你来抓我呀!"); btn1.setFriend(btn2); btn2.setFriend(btn1); btn1.setVisible(false); Frame f =new Frame("it315"); f.add(btn1, "North");//将btn1增加到f的北部 f.add(btn2, "South");//将btn2增加到f的南部 f.setSize(300,300); f.setVisible(true); btn1.setVisible(false); } } 8.4 GUI组件上的图形操作 8.4.1 Graphics类 我们有时需要在GUI组件上绘制图形,打印文字,显示图像等操作。组件对象本身不提供这些操作的方法,它只提供一个getGraphics方法,getGraphics方法返回一个包含有该组件的屏幕显示外观信息的Graphics类对象,Graphics类提供了在组件显示表面绘画图形,打印文字,显示图像等操作方法。 下面,我们通过编写一个画线程序来讲解在GUI组件上的图形操作,同时帮助大家更好地理解和掌握AWT事件处理机制。画线用的是Graphics.drawLine(int x1, int y1, int x2, int y2)方法,其中的参数意义,读者应该能够猜想得到,如果连这点想象力都没有,以后没法面对实际项目的开发。大家在使用陌生的方法时,可以反过来想,如果这个方法是你写的,你会提供给人家什么样的参数?这样一来,就基本上明白了这个方法的用法了。尽管我们有时在不得已的情况下,还是需要去仔细参看新方法的使用文档的,但经常多思考一下,我们能够潜移默化的从中体验出一些心得,借鉴到工具开发商的一些好的编码经验和思想。我们的程序要实现这样的功能:鼠标按下时的位置作为线的起始点,鼠标释放时的位置作为终止点,并在鼠标释放时画线,所以,我们需要对鼠标事件进行处理,在鼠标按下时记住鼠标的坐标,鼠标释放时画线。程序代码如下: 程序清单:DrawLine.java import java.awt.*; import java.awt.event.*; public class DrawLine { Frame f= new Frame("IT人资讯交流网"); public static void main(String [] args) { new DrawLine().init(); } public void init() { f.setSize(300,300); f.setVisible(true); f.addMouseListener(new MouseAdapter() { int orgX; int orgY; public void mousePressed(MouseEvent e) { orgX=e.getX(); orgY=e.getY(); } public void mouseReleased(MouseEvent e) { f.getGraphics().setColor(Color.red); //设置绘图颜色为红色 f.getGraphics().drawLine(orgX,orgY,e.getX(),e.getY()); } }); } } 在上面的程序代码中,读者也必须能够做到通过查阅JDK文档看懂f.getGraphics().setColor(Color.red);这句代码为什么是这么写,特别是Color.red部分。编译运行,线条被画出来了,但颜色却不是红色。这是一个非常隐蔽的问题,程序中的两处都用f.getGraphics()返回Graphics对象引用,返回的两个引用指向的并不是同一个的Graphics对象,而是两个完全不同的对象,设置一个Graphics对象上的绘图颜色,不会影响另一个Graphics对象上的绘图输出。 我们接着为这个程序添加文本打印功能,顺便修正上面碰到的问题。 程序清单:DrawLine2.java import java.awt.*; import java.awt.event.*; public class DrawLine2 { Frame f= new Frame("IT人资讯交流网"); public static void main(String [] args) { new DrawLine2().init(); } public void init() { f.setSize(300,300); f.setVisible(true); f.addMouseListener(new MouseAdapter() { int orgX; int orgY; public void mousePressed(MouseEvent e) { orgX=e.getX(); orgY=e.getY(); } public void mouseReleased(MouseEvent e) { Graphics g=f.getGraphics(); g.setColor(Color.red);//设置绘图颜色为红色 g.setFont(new Font("隶书",Font.ITALIC|Font.BOLD,30)); //设置文本的字体 g.drawString(new String(orgX +"," +orgY),orgX,orgY); //打印鼠标按下时的坐标文本 g.drawString(new String(e.getX() +"," +e.getY()) ,e.getX(),e.getY()); //打印鼠标释放时的坐标文本 g.drawLine(orgX,orgY,e.getX(),e.getY()); } }); } } 其实,上面的代码是作者一边查看JDK文档,一边现炒现卖的。在实际开发中,我们不可能以前就接触和掌握了程序中碰到的每个细节问题,这就需要我们在编程过程中遇到小的需求和问题时,马上能去查阅文档资料来解决,程序员就是这样工作的。上面的代码中,我们只调用了一次f.getGraphics()方法返回了一个Graphics对象,以后调用的全部是这个Graphics对象上的方法。当Y坐标小于30时,只有文本的下半部分被显示,是因为Graphics.drawString方法中的坐标是指整个文本块显示时的左下角位置,如图8.7所示: 图8.7 然而在其他语言中一般都指的是左上角位置,读者只要知道这个问题就够了,我们也不必把这作为程序问题去改正。 (指点迷津: 我们使用System.out.println()语句是不能够在图形窗口中打印字符文本的,只有使用Grapchics.drawString()语句才能够在图形窗口中打印字符文本。在GUI程序中仍然可以使用System.out.println()语句打印字符文本,只是打印的字符文本会显示在命令行窗口中。 (独家见解: 我们以前讲过,在Java的命名习惯中,常量中的每个字母都大写。我们程序中用到了Color.red这个常量,可其中red的每个字母全是小写。作者在很早以前,第一次碰到这个问题时,就断定:当初这一部分的设计者没有良好的命名习惯,弄了个监守自盗,贻笑大方了。果不其然,在JDK1.4的文档中,我们能够看到又增加了一个Color.RED常量,这正是SUN公司对以前的失误作出的弥补,所以,爱琢磨的读者不要再问Color.red与Color.RED有什么区别这个问题了,这是对同一事物的两种称呼,只是前者的命名不太正规,后者更为正规。 8.4.2 组件重绘 我们将程序窗口最小化后再恢复正常化显示,发现所绘图形全部消失了,这又是怎么回事呢?如何解决这个问题呢?在组件大小改变或隐藏后又显示,AWT线程都会重新绘制组件,组件上原来绘制的图形也就不复存在了,这一过程称为“曝光”。要想让用户感觉到所绘的图形一直存在,我们只需在组件重新绘制后,立即将原来的图形重新画上去,这个过程是肉眼感觉不到的。我碰到过有个学员对此很不高兴,说这不是在骗人吗?记住,我们程序在于效果,而不关心你是用那种方式实现的,我不知道这位学员最后的工作情况,但我真的感受到了人的思维是千奇百怪的。AWT线程在重新绘制组件后,会立即调用组件的paint方法,所以我们的图形重绘代码应该在paint方法中编写。 paint方法是这样定义的: public void paint(Graphics g) 可见,AWT线程已经获得了组件上的Graphics对象,并将它传递给了paint方法,在paint方法中绘图时只能使用这个Graphics对象。 当我们想要执行paint方法中的程序代码时,应用程序不应直接调用paint方法,如果我们想执行paint方法中的程序代码,需调用Component.repaint方法,Component.repaint方法调用Component.update方法,Component.update再调用Component.paint。组件重绘的调用关系如图8.8所示: 图8.8 由于我们不可能直接进入到某个组件的paint方法中修改程序代码, 我们需要定义一个继承了该组件的子类,在子类中覆盖paint方法,在新的paint方法中编写重绘图形程序代码,并将原先创建的组件对象改为由这个子类创建,就可以达到我们的目的了。我们修改上面的程序代码,使其具有重绘效果: import java.awt.*; import java.awt.event.*; public class DrawLine extends Frame { int orgX; int orgY; int endX; int endY; public static void main(String [] args) { DrawLine dl=new DrawLine(); dl.init(); } public void paint(Graphics g) { g.drawLine(orgX,orgY,endX,endY); } public void init() { /*基础差的读者如果难以理解下面这句代码,可将前面this处的注释去掉后再理解。也可这样理解,由于DrawLine是Frame的子类,所以可以直接调用Frame中的方法。*/ /*this.*/addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { orgX=e.getX(); orgY=e.getY(); }
本文档为【Java就业培训教程第八章_GUI(上)】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_288224
暂无简介~
格式:doc
大小:450KB
软件:Word
页数:26
分类:互联网
上传时间:2010-08-07
浏览量:31