项目 |
内容 |
这个作业属于哪个课程 |
https://www.cnblogs.com/nwnu-daizh/ |
这个作业的要求在哪里 |
https://www.cnblogs.com/nwnu-daizh/p/11888568.html |
作业学习目标 |
(1) 掌握事件处理的基本原理,理解其用途; (2) 掌握AWT事件模型的工作机制; (3) 掌握事件处理的基本编程模型; (4) 了解GUI界面组件观感设置方法; (5) 掌握WindowAdapter类、AbstractAction类的用法; (6) 掌握GUI程序中鼠标事件处理技术。
|
第一部分:总结第十一章理论知识
第十一章 事件处理
11.1 事件处理基础
1.事件处理基础知识
JDK 1.1开始,Java的事件处理采用事件委托(代理)模型(event delegation)。在这个模型中,比较重要的几个概念如下:
1.事件源(event source)
事件源是一个能够注册监听器对象并发送事件对象的对象。例如按钮或者滚动条就是事件源。
2.事件,事件类型和事件对象
事件一般是用户在界面上的一个操作,当一个事件发生时,该事件用一个事件对象来表示,事件对象有对应的事件类。
不同的事件类描述不同类型的用户动作,不同的事件源可以产生不同类别的事件。例如,按钮可以发送ActionEvent对象,而窗口可以发送WindowEvent对象。
在Java中,所有的事件对象都最终派生于java.util.EventObject类。
3.事件监听器(event listener)
监听器对象是一个实现了特定监听器接口(listener interface)的类的实例。
事件监听器类(监听器对象所属的类)必须实现事件监听器接口或继承事件监听器适配器类。
事件监听器接口定义了处理事件必须实现的方法。
事件监听器适配器类是对事件监听器接口的简单实现。目的是为了减少编程的工作量。
处理事件的方法被称为事件处理器,即事件监听器接口定义,并在事件监听器类中实现的方法。
4.注册事件监听器
为了能够让事件监听器检查某个组件(事件源)是否发生了某些事件,并且在发生时激活事件处理器进行相应的处理,必须在事件源上注册事件监听器。
这是通过使用事件源组件的以下方法来完成的:
addXxxListener(事件监听器对象)
Xxx对应相应的事件类。
5.再论事件和监听器
每一类事件有一个相应的事件监听器接口,该接口定义了接收和处理事件的抽象方法。实现该接口的类,就是监听器类。其对象可作为监听器对象向相应的组件注册。
事件的类名通常为:XxxEvent
对应的事件监听器接口名通常为:XxxListener
一个监听器接口定义了一种以上的抽象事件处理方法(事件处理器)。
事件监听器类实现事件监听器接口,其类名可以由我们自己取。事件监听器类需要我们自己编写。
2.事件处理过程
在这个event delegation模型中,事件源产生事件对象,然后将其发送给所有注册的的事件监听器对象,监听器对象利用事件对象中的信息决定如何对事件做出响应。
从网上找的ppt中的一个图:
3.实例:处理按钮点击事件
为了加深理解,以一个简单的例子来说明(《Core Java》书中例子)。
这个例子中:在一个面板中放置三个按钮,添加三个监听器对象用来作为按钮的动作监听器。
在这个情况下,只要用户点击面板上的任何一个按钮,相关的监听器对象就会接收到一个ActionEvent对象,它表示有个按钮被点击了。在示例程序中,监听器对象将改变面板的背景颜色。
具体流程如下:
1.创建按钮JButton,将按钮添加到面板中(在面板中调用add方法);
2.需要一个实现了ActionListerner接口的类(事件监听器类),它应该包含一个actionPerformed方法,其签名为:
public void actionPerformed(ActionEvent event);
当按钮被点击时,我们希望将面板的背景颜色设置为指定的颜色。该颜色存储在监听器类中。
3.为每种颜色构造一个监听器对象,将这些对象设置为按钮监听器,即,调用按钮的addActionListener方法注册监听器。
代码如下:
例如,如果有一个用户在标有“Yellow”的按钮上点击了一下,那么yellowAction对象的actionPerformed方法就会被调用。这个对象的backgroudColor实例域设置为Color.YELLOW,然后就将面板的颜色设置为黄色了。
有一个需要考虑的问题,是ColorAction对象(监听器对象)没有权限访问panel变量。可以采用两种方式解决这个问题:
1.将面板存储在ColorAction对象中,并在ColorAction构造器中设置它;
2.将ColorAction作为ButtonPanel类的内部类。这样一来,ColorAction就自动地拥有访问外部类的权限了。
这里使用的就是第二种方法,ColorAction类中调用过了外部类ButtonPanel中的setBackground方法。这种情形十分常见,事件监听器对象通常需要执行一些对其他对象可能产生影响的操作,可以策略性地将监听器类放置在需要修改状态的那个类中。
11.1.1 实例:处理按钮点击基础
为了防止用户或者测试MM疯狂的点击某个button,写个方法防止按钮连续点击。
1 public class Utils { 2 private static long lastClickTime; 3 public synchronized static boolean isFastClick() { 4 long time = System.currentTimeMillis(); 5 if ( time - lastClickTime < 500) { 6 return true; 7 } 8 lastClickTime = time; 9 return false; 10 } 11 }
按钮点击时,增加判断就行了:
1 @Override 2 public void onClick(View v) { 3 if (Utils.isFastClick()) { 4 return ; 5 } 6 }
11.1.2 简洁地指定监听器
一、监听器是Servlet规范中定义的一种特殊类,用于监听ServletContext、HttpSession、ServletRequest等域对象的创建与销毁事件和监听域对象的属性发生修改的事件,可以在发生前和发生后做一些必要的处理。简单来说就是用来监听一个特定的事件是否发生改变。
二、监听器按照监听的对象划分为3种:
用于监听应用程序环境(ServletContext)对象的事件监听器。
用于监听用户会话对象(HttpSession)的事件监听器。
用于监听请求消息对象(ServletRequest)的事件监听器。
三、按照监听的事件划分
1.监听域对象自身的创建和销毁的事件监听器。
2.创建域对象中的属性的增加和删除的事件监听器。
被监听对象及其对应的监听器如下所示:
四、ServletContext是一个全局的储存信息的空间,其监听器从服务器启动调用contextInitialized方法到服务器停止contextDestoryed方法,对于一个web项目,ServletContext对象只能有一个,而ServletContextListener却可以有多个。ServletContext主要用于定时器和全局属性对象。
HttpSession监听器在一次会话发生时被调用sessionCreated方法会话结束时调用sessionDestoryed方法。在一个web项目中可以有多个HttpSession对象,1个HttpSession可以注册多个HttpSessionListener。HttpSession常用于记录访问人数和访问日志。
ServletRequest监听器在客户端请求事件发生时被调用requestInitialized方法事件完成时requestDestoryed方法。一个ServletRequest同样可以注册多个ServletRequestListener,ServletRequest常用于读取参数和记录访问历史。
3.监听绑定到HttpSession域中的某个对象的状态的事件监听器
HttpSessionBindingListener监听器:(不需要web.xml配置)valueBound方法绑定session对象,valueUnbound方法解绑session对象。所谓绑定就是调用session.setAttribute()方法将实现HttpSessionBindingListener接口的对象添加到session中。
HttpSessionListener只需要设置到web.xml中就可以监听整个应用中的所有session。HttpSessionBindingListener必须实例化后放入某一个session中,才可以进行监听。
HttpSessionActivationListener监听器:(不需要web.xml配置)sessionWillPassivate方法钝化,sessionDidActivate方法活化,实现此接口的JavaBean,可以感知自己被活化(从硬盘到内存)和钝化(从内存到硬盘)的过程。如果需要同时保存Session中的JavaBean则JavaBean也要实现Serializable序列化接口。实现此接口的JavaBean与HttpSessionBindingListener一样,不必配置到web.xml中。
11.1.3 实例:改变观感
在默认的情况下。Swing程序使用Metal观感,可以采用两种方式改变观感。
•第一种方式是在java安装的子目录jre/lib下有一个文件夹swing.properties。在这个文件中,
将swing.defaultlaf设置为所希望的观感类名
例如:
swing.defaultlaf=javax.swing.plaf.nimbus.NimbusLookAndFeel
属性文件中以#字符开始的行被忽略,所以可以用#将原来的观感注释掉
注意:采用这种方式改变观感时必须重新启动程序。Swing程序只在启动时读取一次swing.properties文件。
•第二种方式是动态地改变观感,网上也有很多讲到了。这需要调用静态方法UIManager.setLookAndFeel,并提供所想要的观感类名,然后再调用静态方法SwingUtilities.updateComponentTreeUI来刷新全部的组件集。需要向这个方法提供一个组件,并由此找到其他的所有组件。
我们可以调用静态方法UIManager.getInstalledLookAndFeels查看java已经安装了哪些的LookAndFeel,并获取观感的类名和名字。
下面是一个示例代码:
1 // 列出安装的所有观感 2 LookAndFeelInfo[] looksinfo = UIManager.getInstalledLookAndFeels(); 3 // 获取观感类名和名字 4 for(int i = 0; i < looksinfo.length; i++){ 5 String className = looksinfo[i].getClassName(); 6 String name = looksinfo[i].getName(); 7 System.out.println("ClassName: " + className + " Name: " + name); 8 }
改变程序的观感也很简单,例如:
1 String laf = "javax.swing.plaf.nimbus.NimbusLookAndFeel"; try { 2 UIManager.setLookAndFeel(laf); SwingUtilities.updateComponentTreeUI(this); 3 } 4 catch (Exception e) { 5 e.printStackTrace(); 6 }
11.1.4 适配器类
1. 适配器模式简介
适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适用场景:
1、已经存在的类的接口不符合我们的需求;
2、创建一个可以复用的类,使得该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作;
3、在不对每一个都进行子类化以匹配它们的接口的情况下,使用一些已经存在的子类。
其实现方式主要有两种:
1.类的适配器模式(采用继承实现)
2.对象适配器(采用对象组合方式实现
2. 类适配器
我们生活中常常听到的是电源适配器,它是用于电流变换(整流)的设备。适配器的存在,就是为了将已存在的东西(接口)转换成适合我们的需要、能被我们所利用。
在现实生活中,适配器更多的是作为一个中间层来实现这种转换作用。
其中:
• Target
— 定义Client使用的与特定领域相关的接口。
• Client
— 与符合Target接口的对象协同。
• Adaptee
— 定义一个已经存在的接口,这个接口需要适配。
• Adapter
— 对Adaptee的接口与Target接口进行适配
在上面的通用类图中,Cient 类最终面对的是 Target 接口(或抽象类),它只能够使用符合这一目标标准的子类;而 Adaptee 类则是被适配的对象(也称 源角色),
因为它包含specific (特殊的)操作、功能等,所以我们想要在自己的系统中使用它,将其转换成符合我们标准的类,使得 Client 类可以在透明的情况下任意选择使用 ConcreteTarget 类或是具有特殊功能的 Adatee 类。
代码实现如下:
// 已存在的、具有特殊功能、但不符合我们既有的标准接口的类 class Adaptee { public void specificRequest() { System.out.println("被适配类具有 特殊功能..."); } } // 目标接口,或称为标准接口 interface Target { public void request(); } // 具体目标类,只提供普通功能 class ConcreteTarget implements Target { public void request() { System.out.println("普通类 具有 普通功能..."); } } // 适配器类,继承了被适配类,同时实现标准接口 class Adapter extends Adaptee implements Target{ public void request() { super.specificRequest(); } } // 测试类 public class Client { public static void main(String[] args) { // 使用普通功能类 Target concreteTarget = new ConcreteTarget(); concreteTarget.request(); // 使用特殊功能类,即适配类 Target adapter = new Adapter(); adapter.request(); } }
上面这种实现的适配器称为类适配器,因为 Adapter 类既继承了 Adaptee (被适配类),也实现了 Target 接口(因为 Java 不支持多继承,所以这样来实现)
在 Client 类中我们可以根据需要选择并创建任一种符合需求的子类,来实现具体功能。
3.对象适配器
另外一种适配器模式是对象适配器,它不是使用多继承或继承再实现的方式,而是使用直接关联,或者称为委托的方式.
代码实现如下:
1 // 适配器类,直接关联被适配类,同时实现标准接口 2 class Adapter implements Target{ 3 // 直接关联被适配类 4 private Adaptee adaptee; 5 6 // 可以通过构造函数传入具体需要适配的被适配类对象 7 public Adapter (Adaptee adaptee) { 8 this.adaptee = adaptee; 9 } 10 11 public void request() { 12 // 这里是使用委托的方式完成特殊功能 13 this.adaptee.specificRequest(); 14 } 15 } 16 17 18 // 测试类 19 public class Client { 20 public static void main(String[] args) { 21 // 使用普通功能类 22 Target concreteTarget = new ConcreteTarget(); 23 concreteTarget.request(); 24 25 // 使用特殊功能类,即适配类, 26 // 需要先创建一个被适配类的对象作为参数 27 Target adapter = new Adapter(new Adaptee()); 28 adapter.request(); 29 } 30 }
11.2 动作
动作事件(ActionEvent)
ActionEvent包含一个事件,该事件为执行动作事件ACTION_PERFORMED.触发这个事件的动作为:
1》点击按钮。
2》双击列表中选项。
3》选择菜单项。
4》在文本框中输入回车。
常用方法如下:
public String getActionCommand()
返回引发某个事件的命令按钮的名字,如果名字为空,那么返回标签值。
public void setActionCommand(String command)
设置引发事件的按钮的名字,默认设置为按钮的标签。
程序例子:
1 //程序文件名Test.java 2 import java.applet.Applet; 3 import java.awt.Button; 4 import java.awt.Color; 5 import java.awt.Graphics; 6 import java.awt.event.ActionEvent; 7 import java.awt.event.ActionListener; 8 9 public class Test extends Applet implements ActionListener { 10 String str1=new String(); 11 Button b1;//声明按钮对象; 12 Button b2; 13 Color c; 14 public void init() { 15 b1=new Button(); 16 b2=new Button("按钮对象2"); 17 //添加事件监听者 18 b1.addActionListener(this); 19 b2.addActionListener(this); 20 this.add(b1); 21 this.add(b2); 22 } 23 public void start() 24 { 25 b1.setLabel("按钮对象1"); 26 str1=b2.getLabel(); 27 repaint(); 28 } 29 public void paint(Graphics g) { 30 g.setColor(c); 31 g.drawString("引发事件的对象的标签:"+str1, 40, 60); 32 } 33 //实现接口中的方法,响应动作事件 34 public void actionperformed(ActionEvent e) { 35 String arg=e.getActionCommand(); 36 if(arg=="按钮对象1"){ 37 c=Color.red; 38 str1="按钮对象1"; 39 } 40 else if(arg=="按钮对象2"){ 41 c=Color.blue; 42 str1="按钮对象2"; 43 } 44 repaint(); 45 } 46 47 48 }
11.3 鼠标事件
1.鼠标事件 – MouseEvent
2.鼠标监听器接口 – MouseListener – MouseMotionListener
3.鼠标监听器适配器 – MouseAdapter – MouseMotionAdapter
11.4 AWT事件继承层次
1.所有的事件都是由java.util包中的EventObject 类扩展而来。
2.AWTEevent 是所有AWT 事件类的父类, 也是 EventObject的直接子类。
3.有些Swing组件生成其他类型的事件对象,一般直 接 扩 展 于 EventObject, 而不是AWTEvent,位于 javax.swing.event.*。
4.事件对象封装了事件源与监听器彼此通信的事件 信息。在必要的时候,可以对传递给监听器对象的 事件对象进行分析。
第二部分:实验部分
1、实验目的与要求
(1) 掌握事件处理的基本原理,理解其用途;
(2) 掌握AWT事件模型的工作机制;
(3) 掌握事件处理的基本编程模型;
(4) 了解GUI界面组件观感设置方法;
(5) 掌握WindowAdapter类、AbstractAction类的用法;
(6) 掌握GUI程序中鼠标事件处理技术。
2、实验内容和步骤
实验1: 导入第11章示例程序,测试程序并进行代码注释。
测试程序1:
l 在elipse IDE中调试运行教材443页-444页程序11-1,结合程序运行结果理解程序;
l 在事件处理相关代码处添加注释;
l 用lambda表达式简化程序;
l 掌握JButton组件的基本API;
l 掌握Java中事件处理的基本编程模型。
ButtonFrame类代码如下:
1 package button; 2 3 import java.awt.*; 4 import java.awt.event.*; 5 import javax.swing.*; 6 7 /** 8 * A frame with a button panel. 9 */ 10 public class ButtonFrame extends JFrame 11 { 12 private JPanel buttonPanel; 13 private static final int DEFAULT_WIDTH = 300; 14 private static final int DEFAULT_HEIGHT = 200; 15 16 public ButtonFrame() 17 { 18 //JFrame类扩展于componnent组件类,随意在子类中可以直接调用方法,而不需要用对象调用 19 //用于设置框架大小 20 setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 21 22 // 创建了三个按钮对象 23 var yellowButton = new JButton("Yellow"); 24 var blueButton = new JButton("Blue"); 25 var redButton = new JButton("Red"); 26 27 //创建了一个面板对象 28 buttonPanel = new JPanel(); 29 30 //将三个按钮添加到面板中 31 buttonPanel.add(yellowButton); 32 buttonPanel.add(blueButton); 33 buttonPanel.add(redButton); 34 35 // 将面板添加到框架中 36 add(buttonPanel); 37 38 // 创建按钮动作 39 //为每种颜色创建一个对象,并将这些对象设置为按钮监听器 40 var yellowAction = new ColorAction(Color.YELLOW); 41 var blueAction = new ColorAction(Color.BLUE); 42 var redAction = new ColorAction(Color.RED); 43 44 // 将动作和按钮联系起来 45 yellowButton.addActionListener(yellowAction); 46 blueButton.addActionListener(blueAction); 47 redButton.addActionListener(redAction); 48 } 49 50 /** 51 * 一个动作监听器用于设置面板的背景颜色. 52 */ 53 private class ColorAction implements ActionListener 54 { 55 private Color backgroundColor; 56 57 public ColorAction(Color c) 58 { 59 backgroundColor = c; 60 } 61 62 public void actionPerformed(ActionEvent event) 63 { 64 buttonPanel.setBackground(backgroundColor); 65 } 66 } 67 }
ButtonTest类代码如下:
1 package button; 2 3 import java.awt.*; 4 import javax.swing.*; 5 6 /** 7 * @version 1.35 2018-04-10 8 * @author Cay Horstmann 9 */ 10 public class ButtonTest 11 { 12 public static void main(String[] args) 13 { 14 EventQueue.invokeLater(() -> { 15 var frame = new ButtonFrame(); 16 frame.setTitle("ButtonTest"); 17 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 18 frame.setVisible(true); 19 }); 20 } 21 }
运行结果如下:
改进代码如下:
1 package button; 2 3 import java.awt.*; 4 import java.awt.event.*; 5 import javax.swing.*; 6 7 /** 8 * A frame with a button panel. 9 */ 10 public class ButtonFrame1 extends JFrame 11 { 12 private JPanel buttonPanel; 13 private static final int DEFAULT_WIDTH = 300; 14 private static final int DEFAULT_HEIGHT = 200; 15 16 public ButtonFrame1() 17 { 18 setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 19 //创建一个面板对象 20 buttonPanel = new JPanel(); 21 22 //连续调用三次makeButton方法 23 makeButton("yellow",Color.YELLOW); 24 makeButton("blue",Color.BLUE); 25 makeButton("red",Color.RED); 26 //将面板添加至框架中 27 add(buttonPanel); 28 29 } 30 31 /*没有显示地定义一个类,每次调用这个方法时,他会建立实现了ActionListener接口的一个类的实例。 32 * 它的actionPerformed动作会引用实际上随监听器对象存储的backGroundColor值。不过,所有这些会自动 33 * 完成,而无需显示定义随监听器类、实例对象或设置这些变量的构造器。 34 */ 35 public void makeButton(String name,Color backgroundColor) 36 { 37 JButton button=new JButton(name); 38 buttonPanel.add(button); 39 button.addActionListener(event-> 40 buttonPanel.setBackground(backgroundColor)); 41 } 42 }
运行结果如下:
测试程序2:
l 在elipse IDE中调试运行教材449页程序11-2,结合程序运行结果理解程序;
l 在组件观感设置代码处添加注释;
l 了解GUI程序中观感的设置方法。
PlafFrame类代码如下:
1 package plaf; 2 3 import javax.swing.JButton; 4 import javax.swing.JFrame; 5 import javax.swing.JPanel; 6 import javax.swing.SwingUtilities; 7 import javax.swing.UIManager; 8 9 /** 10 * 11 */ 12 public class PlafFrame extends JFrame 13 { 14 15 private JPanel buttonPanel; 16 17 public PlafFrame() 18 { 19 //创建一个面板对象 20 buttonPanel=new JPanel(); 21 22 //列举安装的所有观感实现 23 UIManager.LookAndFeelInfo[] infos=UIManager.getInstalledLookAndFeels(); 24 25 //得到每一种观感的名字和类名,并创建按钮 26 for(UIManager.LookAndFeelInfo info:infos) 27 { 28 makeButton(info.getName(),info.getClassName()); 29 } 30 31 //将按钮面板添加到框架上 32 add(buttonPanel); 33 pack(); 34 } 35 36 private void makeButton(String name, String className) { 37 JButton button=new JButton(name); 38 buttonPanel.add(button); 39 40 button.addActionListener(event->{ 41 //捕捉异常 42 try 43 { 44 //设置观感 45 UIManager.setLookAndFeel(className); 46 //刷新全部的组件 47 SwingUtilities.updateComponentTreeUI(this); 48 pack(); 49 } 50 catch(Exception e) 51 { 52 e.printStackTrace(); 53 } 54 }); 55 56 } 57 58 }
PlafTest类代码如下:
1 package plaf; 2 3 import java.awt.*; 4 import javax.swing.*; 5 6 public class PlafTest 7 { 8 public static void main(String[] args) 9 { 10 EventQueue.invokeLater(() -> { 11 var frame = new PlafFrame(); 12 frame.setTitle("PlafTest"); 13 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 14 frame.setVisible(true); 15 }); 16 } 17 }
运行结果如下:
测试程序3:
l 在elipse IDE中调试运行教材457页-458页程序11-3,结合程序运行结果理解程序;
l 掌握AbstractAction类及其动作对象;
l 掌握GUI程序中按钮、键盘动作映射到动作对象的方法。
ActionFrame类代码如下:
1 package action; 2 3 import java.awt.*; 4 import java.awt.event.*; 5 import javax.swing.*; 6 7 /** 8 * 带有一个面板的框架,用于演示颜色更改操作。 9 */ 10 public class ActionFrame extends JFrame 11 { 12 private JPanel buttonPanel; 13 private static final int DEFAULT_WIDTH = 300; 14 private static final int DEFAULT_HEIGHT = 200; 15 16 public ActionFrame() 17 { 18 setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 19 20 buttonPanel = new JPanel(); 21 22 // 定义动作 23 var yellowAction = new ColorAction("Yellow", new ImageIcon("yellow-ball.gif"), 24 Color.YELLOW); 25 var blueAction = new ColorAction("Blue", new ImageIcon("blue-ball.gif"), Color.BLUE); 26 var redAction = new ColorAction("Red", new ImageIcon("red-ball.gif"), Color.RED); 27 28 // 为这些动作添加按钮 29 buttonPanel.add(new JButton(yellowAction)); 30 buttonPanel.add(new JButton(blueAction)); 31 buttonPanel.add(new JButton(redAction)); 32 33 // 将面板添加到框架中 34 add(buttonPanel); 35 36 // 将Y,B,R这些键与名称联系起来 37 /* 38 *可以使用getInputMap方法从组件中得到输入映射 39 * WHEN_ANCESTOR_OF_FOCUSED_COMPONENT条件意味着在当前组件包含了拥有键盘焦点的组件时会查看这个映射 40 */ 41 InputMap inputMap = buttonPanel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 42 inputMap.put(KeyStroke.getKeyStroke("ctrl Y"), "panel.yellow"); 43 inputMap.put(KeyStroke.getKeyStroke("ctrl B"), "panel.blue"); 44 inputMap.put(KeyStroke.getKeyStroke("ctrl R"), "panel.red"); 45 46 // 将名称和动作联系起来 47 /* 48 * InputMap不能直接地将KeyStroke对象映射到Action对象。而是先映射到任意对象上,然后由ActionMap类实现 49 * 将对象映射到动作上。 50 */ 51 ActionMap actionMap = buttonPanel.getActionMap(); 52 actionMap.put("panel.yellow", yellowAction); 53 actionMap.put("panel.blue", blueAction); 54 actionMap.put("panel.red", redAction); 55 } 56 57 public class ColorAction extends AbstractAction 58 { 59 /** 60 * 构造颜色动作. 61 * @param name the name to show on the button 62 * @param icon the icon to display on the button 63 * @param c the background color 64 */ 65 //存储这个命令的名称,图标和需要的颜色 66 public ColorAction(String name, Icon icon, Color c) 67 { 68 //将所有信息存储在AbstractAction类提供的名/值对表中 69 putValue(Action.NAME, name); 70 putValue(Action.SMALL_ICON, icon); 71 72 //SHORT_DESCRIPTION用为为工具提示设置做简要说明 73 putValue(Action.SHORT_DESCRIPTION, "Set panel color to " + name.toLowerCase()); 74 putValue("color", c); 75 } 76 77 public void actionPerformed(ActionEvent event) 78 { 79 //获取名/值对表中名为color的值并将其强制转换为颜色对象 80 var color = (Color) getValue("color"); 81 buttonPanel.setBackground(color); 82 } 83 } 84 }
ActionTest类代码如下:
1 package action; 2 3 import java.awt.*; 4 import javax.swing.*; 5 6 /** 7 * @version 1.34 2015-06-12 8 * @author Cay Horstmann 9 */ 10 public class ActionTest 11 { 12 public static void main(String[] args) 13 { 14 EventQueue.invokeLater(() -> { 15 var frame = new ActionFrame(); 16 frame.setTitle("ActionTest"); 17 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 18 frame.setVisible(true); 19 }); 20 } 21 }
运行结果如下:
测试程序4:
l 在elipse IDE中调试运行教材462页程序11-4、11-5,结合程序运行结果理解程序;
l 掌握GUI程序中鼠标事件处理技术。
MouseComponent类代码如下:
1 package mouse; 2 3 import java.awt.*; 4 import java.awt.event.*; 5 import java.awt.geom.*; 6 import java.util.*; 7 import javax.swing.*; 8 9 /** 10 * 带有鼠标操作的用于添加和删除正方形的组件。 11 */ 12 public class MouseComponent extends JComponent 13 { 14 private static final int DEFAULT_WIDTH = 300; 15 private static final int DEFAULT_HEIGHT = 200; 16 17 //正方形的边长 18 private static final int SIDELENGTH = 10; 19 20 //用于存储正方形集合 21 private ArrayList<Rectangle2D> squares; 22 private Rectangle2D current; //包含鼠标光标的正方形 23 24 public MouseComponent() 25 { 26 squares = new ArrayList<>(); 27 current = null; 28 29 addMouseListener(new MouseHandler()); 30 addMouseMotionListener(new MouseMotionHandler()); 31 } 32 33 //返回组件的首选大小 34 public Dimension getPreferredSize() 35 { 36 return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT); 37 } 38 39 //描述应该如何绘制自己的组件 40 public void paintComponent(Graphics g) 41 { 42 var g2 = (Graphics2D) g; 43 44 // 遍历正方形集合,并将每个正方形绘制出来 45 for (Rectangle2D r : squares) 46 g2.draw(r); 47 } 48 49 /** 50 * 查找包含一个点的第一个正方形 51 * @param p a point 52 * @return the first square that contains p 53 */ 54 //传入的是事件源组件左上角的坐标 55 public Rectangle2D find(Point2D p) 56 { 57 for (Rectangle2D r : squares) 58 { 59 //如何在正方形集合中找到某个正方形的左上角坐标与此坐标相同,则将此正方形对象返回 60 if (r.contains(p)) return r; 61 } 62 //否则返回空 63 return null; 64 } 65 66 /** 67 * 增加一个正方形到集合中 68 * @param p the center of the square 69 */ 70 //传入的是事件源组件左上角的坐标 71 public void add(Point2D p) 72 { 73 //获取它的横纵坐标 74 double x = p.getX(); 75 double y = p.getY(); 76 77 //创建一个正方形对象 78 current = new Rectangle2D.Double(x - SIDELENGTH / 2, y - SIDELENGTH / 2, 79 SIDELENGTH, SIDELENGTH); 80 81 //将正方形对象添加到集合中 82 squares.add(current); 83 84 //repaint方法的作用是“尽可能快地”重新绘制组件 85 repaint(); 86 } 87 88 /** 89 * 从集合中删除一个正方形 90 * @param s the square to remove 91 */ 92 //传入的是一个正方形对象 93 public void remove(Rectangle2D s) 94 { 95 //如果此正方形对象不存则停止删除 96 if (s == null) return; 97 98 //如果此正方形对象与正在绘制的正方形相同则将current赋为空 99 if (s == current) current = null; 100 101 //否则将执行递归删除 102 squares.remove(s); 103 repaint(); 104 } 105 106 private class MouseHandler extends MouseAdapter 107 { 108 //在光标的位置添加正方形 109 public void mousePressed(MouseEvent event) 110 { 111 // 如果光标所在位置在正方形集合中不包含,则添加新的正方形 112 current = find(event.getPoint()); 113 if (current == null) add(event.getPoint()); 114 } 115 116 // 如果双击,则删除当前正方形 117 public void mouseClicked(MouseEvent event) 118 { 119 //如果光标所在位置在正方形集合中包含并且鼠标点击次数>=2,则删除此位置的正方形 120 current = find(event.getPoint()); 121 if (current != null && event.getClickCount() >= 2) remove(current); 122 } 123 } 124 125 private class MouseMotionHandler implements MouseMotionListener 126 { 127 public void mouseMoved(MouseEvent event) 128 { 129 130 // 如果鼠标光标不在正方形内,则鼠标形状为默认值(箭头) 131 if (find(event.getPoint()) == null) setCursor(Cursor.getDefaultCursor()); 132 // 如果鼠标光标位于正方形内,请将其设置为十字光标 133 else setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); 134 } 135 136 public void mouseDragged(MouseEvent event) 137 { 138 if (current != null) 139 { 140 int x = event.getX(); 141 int y = event.getY(); 142 143 // 拖动当前矩形使其居中(x,y) 144 current.setFrame(x - SIDELENGTH / 2, y - SIDELENGTH / 2, SIDELENGTH, SIDELENGTH); 145 repaint(); 146 } 147 } 148 } 149 }
MouseFrame类代码如下:
1 package mouse; 2 3 import javax.swing.*; 4 5 /** 6 * 包含用于测试鼠标操作的面板的框架 7 */ 8 public class MouseFrame extends JFrame 9 { 10 public MouseFrame() 11 { 12 add(new MouseComponent()); 13 pack(); 14 } 15 }
MouseTest类代码如下:
1 package mouse; 2 3 import java.awt.*; 4 import javax.swing.*; 5 6 /** 7 * @version 1.35 2018-04-10 8 * @author Cay Horstmann 9 */ 10 public class MouseTest 11 { 12 public static void main(String[] args) 13 { 14 EventQueue.invokeLater(() -> { 15 var frame = new MouseFrame(); 16 frame.setTitle("MouseTest"); 17 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 18 frame.setVisible(true); 19 }); 20 } 21 }
运行结果如下:
实验2:结对编程练习
利用班级名单文件、文本框和按钮组件,设计一个有如下界面(图1)的点名器,要求用户点击开始按钮后在文本输入框随机显示2018级计算机科学与技术(1)班同学姓名,如图2所示,点击停止按钮后,文本输入框不再变换同学姓名,此同学则是被点到的同学姓名,如图3所示。
图1 点名器启动界面
图2 点名器随机显示姓名界面
图3 点名器点名界面
1) 程序设计思路简述;
我们先设计了一个GUI图形界面,然后将学生信息读取后存储带一个数组当中,在实现监听器类actionPerformed方法时,采用随机数下标获取学生信息数组中的值,再重写timer类的schedule类中的run方法实现定时器功能。当button中的内容为“开始”时,启动定时器,当button中的内容为“停止”时,则调用timer类对象的cancel方法停用定时器,这样就完成了对点名器的代码编程。
2) 符合编程规范的程序代码;
1 import java.awt.event.ActionListener; 2 import java.io.BufferedInputStream; 3 import java.io.BufferedReader; 4 import java.io.File; 5 import java.io.FileInputStream; 6 import java.io.FileNotFoundException; 7 import java.io.IOException; 8 import java.io.InputStreamReader; 9 import java.io.StringBufferInputStream; 10 import java.util.ArrayList; 11 import java.util.Timer; 12 import java.util.TimerTask; 13 14 import javax.swing.JButton; 15 import javax.swing.JFrame; 16 import javax.swing.JLabel; 17 import javax.swing.JPanel; 18 19 public class ButtonFrame extends JFrame { 20 private JPanel buttonPanel; 21 private static final int DEFAULT_WIDTH = 300 * 2; 22 private static final int DEFAULT_HEIGHT = 200 * 2; 23 private JButton jButton; 24 private JLabel jLabel; 25 private ArrayList<String> arrayList; 26 27 public ButtonFrame()//构造器 28 { 29 setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 30 buttonPanel = new JPanel(); 31 buttonPanel.setLayout(null); 32 add(buttonPanel); 33 jLabel = new JLabel("点名器"); 34 jButton = new JButton("开始"); 35 jButton.setBackground(Color.gray);//设置背景颜色 36 jLabel.setBounds(100, 50, 60, 30); 37 jButton.setBounds(100, 120, 60, 30); 38 arrayList = new ArrayList<>(); 39 //读文件 40 File file= new File("D:/studentnamelist.txt"); 41 FileInputStream fis; 42 try //可能出错的程序放入try子句中 43 { 44 fis = new FileInputStream(file); 45 InputStreamReader in = new InputStreamReader(fis); 46 BufferedReader buf = new BufferedReader(in); 47 String readLine; 48 while ((readLine = buf.readLine())!=null) { 49 arrayList.add(readLine); 50 51 } 52 } catch (FileNotFoundException e1) { 53 // TODO Auto-generated catch block 54 e1.printStackTrace(); 55 } catch (IOException e1) { 56 // TODO Auto-generated catch block 57 e1.printStackTrace(); 58 } 59 60 jButton.addActionListener(new ActionListener() { 61 Timer timer; 62 63 public void actionPerformed(ActionEvent e) { 64 if (jButton.getText().equals("开始")) { 65 timer = new Timer();; 66 TimerTask timerTask = new TimerTask() { 67 public void run() { 68 jButton.setText("停止"); 69 jButton.setBackground(Color.red); 70 jLabel.setText(arrayList.get((int) (Math.random() * 43))); 71 } 72 73 }; 74 timer.schedule(timerTask, 0, 10); 75 } 76 if (jButton.getText().equals("停止")) { 77 timer.cancel(); 78 jButton.setText("开始"); 79 jButton.setBackground(Color.gray); 80 } 81 } 82 }); 83 buttonPanel.add(jLabel); 84 buttonPanel.add(jButton); 85 add(buttonPanel); 86 87 } 88 }
1 import java.awt.EventQueue; 2 3 import javax.management.Query; 4 import javax.swing.JFrame; 5 6 public class Main { 7 public static void main(String[] args) { 8 EventQueue.invokeLater(()->{ 9 ButtonFrame buttonFrame = new ButtonFrame(); 10 buttonFrame.setVisible(true); 11 buttonFrame.setTitle("点名器"); 12 buttonFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 13 14 }); 15 } 16 }
3) 程序运行功能界面截图:
运行结果如图:
4) 结对过程描述,提供两人在讨论、细化和编程时的结对照片(非摆拍)
第三部分:实验总结:
这个星期我们学习了有关图形界面事件处理技术的知识,首先掌握了事件处理的基本原理,并学会了事件处理的基本编程模型;在老师的演示代码过程中,我们学习到了lambda表达式的简便性,简化代码的好处和代码的多变性;在实验中,在学长的演示和在书上源代码基础上进行的改编、添加,做出了点名器的代码程序。此次结对编程实验也让我们通过相互沟通、交流的方式,在自己思考问题的基础上也可以听取同伴的想法,让我收获了很多。