AWT的基础知识
GUI全称是Graphical User Interface,即图形用户界面。顾名思义,就是应用程序提供给用户操作的图形界面,包括窗口、菜单、按钮、工具栏和其他各种屏幕元素。
JDK中提供了AWT和Swing两个包,用于GUI程序的设计和开发。AWT是Java早期版本,其中的AWT组件种类有限,可以提供基本的GUI设计工具,却无法完全实现目前GUI设计所需的所有功能。Swing是SUN公司对早期版本的改进版本,它不仅包括AWT中具有的所有部件,并且提供了更加丰富的部件和功能,它足以完全实现GUI设计所需的一切功能。Swing会用到AWT中的许多知识,掌握了AWT,也就基本上掌握了Swing。
GUI组件可以分为两大类:基本组件和容器,分别是:java.awt.Component和java.awt.Container的直接或间接子类,Container是Component的子类,由此可见容器本身也具有组件的功能和特点,也可以被当作基本组件一样使用。
程序的GUI部分由AWT线程管理。我们可以简单的认为,程序在产生Frame对象时,创建了一个新线程,称之为AWT线程。
AWT事件处理
事件处理机制
三个重要的概念:
事件:用户对组件的一个操作,称之为一个事件
事件源:发生事件的组件就是事件源。
事件处理器:某个Java类中的负责处理事件的成员方法。
事件源、事件、事件处理器之间的工作关系
事件分类
事件用来描述发生了什么事情。AWT对各种不同的事件,按事件的动作(如鼠标操作)、状态(如窗口的关闭和激活)等进行了分类,一类事件对应一个AWT事件类。
按产生事件的物理操作和GUI组件的表现效果进行分类:
MouseEvent鼠标事件:鼠标放放、单击等
WindowsEvent窗口事件:窗口关闭、得到/失去焦点等
ActionEvent动作事件,它不是代表一个具体的动作,而是一种主义,如按钮或菜单被鼠标单击,单行文本框中按下回车键等都可以看作是ActionEvent事件。可以这么理解,如果用户的一个动作导致了某个组件本身最基本的作用发生了,这就是ActionEvent事件。菜单、按钮放在那就是用来给我们操作执行一些功能的,不管你是用鼠标还是快捷键操作这些组件,只是表示要执行这种动作或命令的事情发生了,显然对于这种情况,我们并不关心是鼠标单击的还是键盘按下的。
事件类提供了一些方法,如获得事件源对象,获得鼠标的坐标信息等。
对某一类事件的处理由一个事件监听器来完成,某一类事件又包含触发这一事件的若干具体情况,每一种情况的处理对应监听器对象的一个不同方法。每一种事件的情况的名称必须固定,每一种监听器也必须包含所有这些事件情况的处理方法,这就需要“事件类和监听器对象必须共同遵守某一规范,事件类按照这个规范进行方法调用,监听对象按照这个规范进行方法实现”,这个规范通过接口(事件监听器接口)来表示。事件监听器对象就是实现了事件监听器接口的类的实例对象。不同的事件类型对应不同的事件监听器接口。
事件监听器接口名称与事件的名称是相对应的,非常容易记忆,如:
MouseEvent事件对应的监听器接口为MouseListener,
WindowEvent----àWindowListener
ActionEvent------àActionListener
……
按事件的性质分类
低级事件(它所对应的事件监听器有多个成员方法),它根据怎么发生的这个事件执行对应监听器的其中一个方法。
语义事件(又叫高级事件)(它所对应的事件监听器只有一个成员方法),不管你是怎么发生的这个事件都是执行对应监听器的那个一方法。
AWT中所有的事件类、事件监听器接口、事件适配器都位于java.awt.event包中
所有的事件监听器方法的返回类型都是void
事件监听器
一个事件监听器对象负责处理一类事件。
一类事件的每一种发生情况,分别由事件监听器对象中的一个方法来具体处理。
在事件源和事件监听器对象中进行约定的接口类,被称为事件监听器接口。
事件接口类的名称与事件类的名称是相对应的,例如,MouseEvent事件类的监听器接口名为MouseListener
编程实例:实现关闭窗口的事件处理,讲解用不同层次的事件类型来表示同一个事件源对象。
TestFrame.java
import java.awt.*;
public class TestFrame
{
public static void main(String[] args)
{
Frame f=new Frame("QQ");
f.add(new Button("确定"));
f.setSize(300,300);
f.setVisible(true);
f.addWindowListener(new MyWindowsListener());
}
}
MyWindowsListener.java
import java.awt.event.*;
import java.util.EventListener;
import java.awt.Window;
public class MyWindowsListener implements WindowListener
{
public void windowOpened(WindowEvent e) {}
public void windowClosing(WindowEvent e)
{
e.getWindow().setVisible(false);
//e.getSource()返回的类型为Object
//e.getComponent()返回原类型为Component
((Window)e.getComponent()).dispose();
System.exit(0);
}
public void windowClosed(WindowEvent e) {}
public void windowIconified(WindowEvent e) {}
public void windowDeiconified(WindowEvent e) {}
public void windowActivated(WindowEvent e) {}
public void windowDeactivated(WindowEvent e) {}
}
处理发生在某个GUI组件上的XxxEvent事件的某种情况,其事件处理的通用编写流程:
编写一个实现了XxxListener接口的事件监听器类;
XxxListener类中的用于处理该事件情况的方法中,编写处理代码;
调用组件的addXxxListener方法,将XxxListener创建的实例对象注册到GUI组件上。
事件适配器
JDK中也提供了大多数事件监听器接口的最简单的实现类:事件适配器(Adapter)类。
用事件适配器来处理事件,可以简化事件监听器编写。
编程实例:使用事件适配器类来关闭窗口。
TestFrame.java
import java.awt.*;
public class TestFrame
{
public static void main(String[] args)
{
Frame f=new Frame("QQ");
f.add(new Button("确定"));
f.setSize(300,300);
f.setVisible(true);
f.addWindowListener(new YourWindowListener());
}
}
YourWindowListener.java
import java.awt.event.*;
public class YourWindowListener extends WindowAdapter
{
public void windowClosing(WindowEvent e)
{
e.getWindow().dispose();
System.exit(0);
}
}
初学者使用事件适配器时的常见问题,解决问题的思路:
是方法没有被调用,还是方法中的程序代码的执行有问题?
是方法名写错了,还是没有注册事件监听器?
事件适配器类的不足之外:继承了它就不能继承别的类了。
灵活设计事件监听器
如果要在事件监听器中访问非事件源的其他GUI,一种简单的办法就是将事件监听器的代码和产生GUI组件的代码放在同一个类中实现:
import java.awt.*;
import java.awt.event.*;
import java.util.EventListener;
public class TestFrame implements ActionListener
{
Frame f=new Frame("QQ");
public void init()
{
Button btn = new Button("确定");
f.add(btn);
btn.addActionListener(this);
f.setSize(300,300);
f.setVisible(true);
f.addWindowListener(new YourWindowListener());
}
public static void main(String[] args)
{
new TestFrame().init();
}
public void actionPerformed(ActionEvent e)
{
f.dispose();
//System.exit(0);
}
}
用匿名内置类实现事件监听器
如果一个事件监听器类只用于在一个组件上注册监听器事件对象,为了让程序代码更为紧凑,可以用匿名内置类来产生这个事件监听器对象,这也是一种经常使用的方法。
事件处理的多重运用
如何知道一个GUI组件到底能够触发哪几种事件可以用集成开发环境即可,如JCreator
一个组件上的一个动作可以产生多种不同类型的事件
一个事件监听器对象可以注册到多个事件源上
在一个事件源上也可以注册对同一类事件进行处理的多个事件监听器对象
修改组件默认事件处理方式
默认情况下,组件屏蔽了对所有事件的响应,也就是不管发生了什么事件,这个组件都不会产生事件对象。组件只有在注册了某种事件的事件监听器对象后,该组件才会产生相应的事件对象。
当一个组件上发生了某种事件后,系统会调用这个组件对象的processEvent方法来处理这个事件,默认的processEvent方法根据生产的事件对象类型调用相应的processXxxEvent方法(Xxx代表事件类型),processXxxEvent方法接着将Xxx事件对象传递给注册的监听器去处理。例如,如果组件上发了鼠标移动事件,组件对象的processEvent方法将默认调用processMouseMotionEvent方法进行处理。如果我们想改变某种组件的事件处理方式,需要覆盖该组件的processEvent方法或processXxxEvent方法,processEvent是处理所有事件的总入口,而processXxxEvent是专用于处理某种事件的具体方法。具体做法为新建一个该组件的子类,然后覆盖这些方法。
调用enableEvents(long eventsToEnable)方法,可以在即使没有注册事件监听器的情况下,组件也能够对某些类型的事件进行响应和产生相应的事件对象。enableEvents需要一个long型参数,这个参数指定了需要组件响应事件类型所对应的数值,这个参数里的每一位( bit)都对应某一种事件。当该变量的某位为1时,表示某种事件开启响应,当为0时则相反。一种事件类型的某一个位为1,其余都为0,所以我们可以把多个这样类型的参数进行相或操作,得到的结果就是这几种事件类型的集合。为了便于记忆,在JDK的awtEvent事件类中定义了许多这样的常量,如MOUSE_MOTION_EVENT_MAK就是鼠标移动掩码。这样我们就可以在组件不注册事件监听器的情况下使用enableEvents(MOUSE_MOTION_EVENT_MAK)也能开启组件的事件响应。
编程实例:在一个窗口上显示一个按钮,一旦鼠标移动到这个按钮上时,按钮就移动到了其他位置,这样,鼠标就永远无法点击到这个按钮。
TestMyButton.java
package myprojects.testmybutton;
import java.awt.*;
import java.awt.event.*;
public class TestMyButton extends Frame
{
public TestMyButton()
{
addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e)
{
dispose();
System.exit(0);
}
});
}
public static void main(String args[])
{
System.out.println("Starting TestMyButton..");
TestMyButton mainFrame = new TestMyButton();
MyButton btn1 = new MyButton("你来抓我啊!");
MyButton btn2 = new MyButton("你来抓我啊!");
btn2.setFriend(btn1);
mainFrame.add(btn1,"North");
mainFrame.add(btn2,"South");
mainFrame.setSize(400,400);
mainFrame.setTitle("TestMyButton");
mainFrame.setVisible(true);
btn2.setVisible(false);
}
}
MyButton.java
package myprojects.testmybutton;
import java.awt.*;
import java.awt.event.*;
public class MyButton extends Button
{
{
super(title);
enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK);
}
public void setFriend(MyButton friend)
{
this.friend = friend;
}
protected void processMouseMotionEvent(MouseEvent e)
{
friend.setVisible(true);
}
}
GUI组件上的图形操作
我们有时需要在GUI组件上绘制图形、打印文字、显示图像等操作,但是组件对象本身不提供这些操作的方法,它只提供一个getGraphics方法,getGraphics方法返回一个包含有该组件的屏幕显示外观信息的Graphics对象,Graphics类提供了在组件表面显示绘画图形、打印文字、显示图像等操作方法。
Graphics类与图形绘制
Component.getGraphics方法与Graphics
Graphics.drawLine(int x1,int y1,int x2,int y2)方法
Grapchics.drawString(String str,int x,int y)方法
注:这里坐标是左下角的坐标,如图:
组件重绘的处理
在组件改变大小,或隐藏后又显示,AWT线程都会重新绘制组件,组件上原来绘制的图形也就不复存在了,这一过程称为“曝光”。要想让用户感觉到所绘的图形一直存在,我们只需要在组件重新绘制后,立即将原来的图形重新画上去,这个过程是肉眼感觉不到的。AWT线程在重新绘制组件时是自动调用组件的paint方法实现的,所以我们的图形重绘代码应该写在paint方法中。
Paint方法的定义:public void paint(Graphics g)
可见,AWT线程已经获得了组件上的Graphics对象,并将它传递给了paint方法,在paint方法中绘图时只能使用这个Graphics对象。
当我们想要执行paint方法中的程序代码时,我们不应该直接调用paint方法,而是调用Component.repaint方法,Component.repaint又自动调用Component.update方法,Component.update再调用Componet.paint。AWT线程对组件重绘的调用过程
由于我们不能进入到某个组件的paint方法中修改程序代码,我们需要定义一个继承了该组件的子类,在子类中覆盖paint方法。
编程实例:以鼠标在窗口中按下时的位置作为起始点,鼠标释放时的位置作为终止点,在鼠标释放时将直线画出,并在每条直线的起始和终止点位置上打印出它们的坐标值。
import java.awt.event.*;
import java.util.*;
public class GraphicsTest extends Frame {
int startX; int startY;
int endX; int endY;
Vector vLine = new Vector();
public void paint(Graphics g) {
Enumeration e = vLine.elements();
while(e.hasMoreElements()) {
MyLine line = (MyLine)e.nextElement();
line.drawMe(g);
}
}
public GraphicsTest() {
addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e) {
dispose();
System.exit(0);
}
});
addMouseListener(new MouseAdapter()
{
public void mousePressed(MouseEvent e) {
startX = e.getX();
startY = e.getY();
}
public void mouseReleased(MouseEvent e) {
endX = e.getX();
endY = e.getY();
Color color = Color.RED;
Font font = new Font(null, Font.BOLD|Font.ITALIC, 24);
MyLine line = new MyLine(startX,startY,endX,endY,color,font);
vLine.add(line);
repaint();
}
});
}
public static void main(String[] args) {
GraphicsTest gt = new GraphicsTest();
gt.setSize(500,400);
gt.setVisible(true);
}
class MyLine {
int startX; int startY; int endX; int endY;
Color color; Font font;
public MyLine(int startX, int startY, int endX, int endY,Color color,Font font) {
this.startX = startX;
this.startY = startY;
this.endX = endX;
this.endY = endY;
this.color = color;
this.font = font;
}
public void drawMe(Graphics g) {
g.setFont(font);
g.setColor(color);
g.drawLine(startX,startY,endX,endY);
g.drawString(startX+","+startY , startX,startY);
g.drawString(endX+","+endY , endX,endY);
}
}
}
图像显示
使用Graphics.drawImage(Image img ,int x,int y,ImageObserver observer)方法可以在组件上显示图像,其中,img参数是要显示的图像对象,x,y是图像显示的左上角的坐标,observer是用于监视图像创建进度的一个对象。drawImage是一个异步方法,即使img对应的图像还没有完全装载(在创建Image类对象时并没有在内存中创建图像数据)时,drawImage也会立即返回。如果程序想了解图像创建的进度信息,需要编写一个实现了ImageObserver接口的类,并将该类创建的对象传递给drawImage方法,这个过程和原理与前面讲过的事件监听器相似。如果程序不关心图像创建的进度信息,可以传递要显示图像的那个组件对象,因为Component类已实现了ImageObserver接口。
使用Component.getToolkit.getImage(String path)语句获得Image实例对象。
实例:
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class DrawImage extends Frame {
Image img = getToolkit().getImage("test.JPG");
public void paint(Graphics g) {
g.drawImage(img,0,0,this);
}
public DrawImage() {
addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e) {
dispose();
System.exit(0);
}
});
}
public static void main(String[] args) {
DrawImage gi = new DrawImage();
gi.setSize(500,400);
gi.setVisible(true);
}
}
双缓冲技术
Component.createImage方法创建内存Image对象
在Image对象上进行绘制的结果就成了一幅图像
在Image对象上执行与组件表面同样的绘制,Image对象中的图像是组件表面内容的复制,当组件重画时,只需将内存中的Image对象在组件上画出。
实例:
import java.awt.*;
import java.awt.event.*;
import java.util.*;
class GraphicsTest2 extends Frame {
int startX; int startY; int endX; int endY;
Image img; Graphics g;
public void paint(Graphics g) {
g.drawImage(img,0,0,this);
}
public GraphicsTest2() {
addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e) {
dispose();
System.exit(0);
}
});
setSize(500,400);
setVisible(true);
Dimension d = getSize();
img = createImage(d.width,d.height);//如果调用这个方法时组件是不可见的则返回值为空,则下面的语句会抛出空指针异常
g = img.getGraphics();
addMouseListener(new MouseAdapter()
{
public void mousePressed(MouseEvent e) {
startX = e.getX();
startY = e.getY();
}
public void mouseReleased(MouseEvent e) {
endX = e.getX();
endY = e.getY();
Color color = Color.RED;
Font font = new Font(null, Font.BOLD|Font.ITALIC, 24);
g.setFont(font);
g.setColor(color);
g.drawLine(startX,startY,endX,endY);
g.drawString(startX+","+startY , startX,startY);
g.drawString(endX+","+endY , endX,endY);
repaint();
}
});
}
public static void main(String[] args) {
GraphicsTest2 gt = new GraphicsTest2();
}
}
图形用户界面GUI(二)
AWT中的组件及类层次关系图:
Component类
Java的图形用户界面的最基本组成部分是组件,抽象类Component是所有JavaGUI组件的共同父类,Component类规定了所有GUI组件的基本特性,该类中定义的方法实现了作为一个GUI组件所就具备的基本功能。Java程序要显示的GUI组件必须是抽象类Component或MenuComponent的子类。
Canvas可以说是具有最基本和最简单的GUI功能的组件。
Canvas代表屏幕上的一块空白的矩形区域,程序能够在这个部件表面绘图,也能够捕获用户的操作,产生相应的事件。Canvas可以说是具有最基本的和最简单的GUI功能的部件。
当要设计自定制的具有GUI功能的组件类时,继承Canvas将会大大简化编程难度。
编程实例:设计一个自定制的计时器组件,当鼠标在计时器组件上按下时,计时器开始计时,并显示计时时间,当鼠标释放时,计时器停止计时。
import java.awt.*;
import java.awt.event.*;
import java.text.SimpleDateFormat;
import java.util.Date;
public class StopWatch extends Canvas implements Runnable {
long startTime; long endTime;
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
Date d;
String strTime = "00:00:00";
boolean flag;
public void paint(Graphics g) {
g.fill3DRect(0,0,58,20,true);
g.setColor(Color.WHITE);
try{d = sdf.parse("00:00:00");}catch(Exception ex) {ex.printStackTrace();}
d.setTime(endTime - startTime + d.getTime());
strTime = sdf.format(d);
g.drawString(strTime,5,15);
}
public StopWatch() {
enableEvents(AWTEvent.MOUSE_EVENT_MASK);
}
protected void processMouseEvent(MouseEvent e)
{
if(e.getID() == e.MOUSE_PRESSED)
{
startTime = System.currentTimeMillis();
flag = true;
new Thread(this).start();
}
else if(e.getID() == e.MOUSE_RELEASED)
{
flag = false;
endTime =System.currentTimeMillis();
repaint();
}
}
public void run()
{
while(flag) {
try{Thread.sleep(100);}catch(Exception e){e.printStackTrace();}
endTime = System.currentTimeMillis();
repaint();
}
}
}
class MainFrame extends Frame {
public MainFrame() {
addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e) {
dispose();
System.exit(0);
}
});
}
public static void main(String [] args) {
MainFrame mf = new MainFrame();
StopWatch sw = new StopWatch();
mf.setSize(400,300);
mf.add(sw);
mf.setVisible(true);
}
}
Checkbox
CheckBox类用来建立单选按钮和多选按钮(也叫复选框)
创建多选按钮,只需要使用构造函数:(String label,Boolean state)
创建单选按钮,只需要使用构造函数:(String label,Boolean state,CheckboxGroup group)
单选按钮和多选按钮的语义事件为ItemEvent,对应的监听器接口为ItemListener,该接口中只有一个itemStrateChanged方法。
实例:
import java.awt.*;
import java.awt.event.*;
public class CheckboxTest extends Frame {
CheckboxGroup g = new CheckboxGroup();
Checkbox chk1 = new Checkbox("你喜欢我吗?",true);
Checkbox chk2 = new Checkbox("喜欢",true,g);
Checkbox chk3 = new Checkbox("不喜欢",false,g);
public static void main(String[] args) {
new CheckboxTest();
}
public CheckboxTest() {
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
dispose();
System.exit(0);
}
});
FlowLayout fl = new FlowLayout();
setLayout(fl);
add(chk1);
add(chk2);
add(chk3);
class MyItemListener implements ItemListener
{
public void itemStateChanged(ItemEvent e)
{
Checkbox cb = (Checkbox)e.getItemSelectable();
String label = cb.getLabel();
if(label.equals("你喜欢我吗?"))//这里也可以用cb.equal(chk1)判断选择了哪个复选框
{
if(cb.getState()) //这里也可以用e.getStateChange()==DESELECTED判断是否被选择状态
{
System.out.println("我也喜欢你");
} else
{
System.out.println("我很伤心");
}
} /*else if(label.equals("喜欢"))
{
System.out.println("喜欢");
} else
{
System.out.println("不喜欢");
}*/
else//用下面的方法比上面注释掉的方法要简单
{
Checkbox ckb = g.getSelectedCheckbox();//这个组里面可能一个按钮都没选就会返回null
if(ckb != null)
{
System.out.println(ckb.getLabel());
}
}
}
}
MyItemListener mi = new MyItemListener();
chk1.addItemListener(mi);
chk2.addItemListener(mi);
chk3.addItemListener(mi);
setBounds(500,200,400,100);
setVisible(true);
}
}
Choice
该类用来制作单选的下拉列表
Chioce类的语义事件为ItemEvent,对应的监听器接口为ItemListener,该接口中只有一个itemStateChanged方法。
实例:
package com.itheima;
import java.awt.*;
import java.awt.event.*;
public class ChoiceText extends Frame
{
private Choice ch = new Choice();
public ChoiceText()
{
ch.add("中国");
ch.add("美国");
ch.add("英国");
ch.addItemListener(new MyItemListener());
setLayout(new FlowLayout());
add(ch,"North");
setBounds(500,200,200,100);
setVisible(true);
}
class MyItemListener implements ItemListener
{
public void itemStateChanged(ItemEvent e)
{
System.out.println(e.getItem());
}
}
public static void main(String[] args) {
new ChoiceText();
}
}
菜单
一个完整的菜单系统由菜单条、菜单和、菜单项组成。
Java中与菜单相关的类主要有:MenuBar(菜单条)、Menu(菜单)、MenuItem(菜单项),它们之间的关系如图:
注:Menu类本身又是MenuItem的子类,所以一个Menu对象也可以作为一个菜单项增加到另外一个Menu对象上。
实例:
import java.awt.event.*;
import java.awt.*;
import java.util.EventListener;
import java.awt.event.ActionEvent;
public class MenuTest extends Frame {
MenuBar mb = new MenuBar();
Menu m1 = new Menu("File");
Menu m2 = new Menu("Edit");
Menu m3 = new Menu("Find");
Menu subm = new Menu("Print");
MenuItem mi1 = new MenuItem("New");
MenuItem mi2 = new MenuItem("Open");
MenuItem mi3 = new MenuItem("Save");
MenuItem mi4 = new MenuItem("Preview");
MenuItem mi5 = new MenuItem("Settings");
MenuListener ml = new MenuListener();
CheckboxMenuItem cmi = new CheckboxMenuItem("Quit",true);
public MenuTest() {
addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e) {
dispose();
System.exit(0);
}
});
mb.add(m1); mb.add(m2); mb.add(m3);
m1.add(mi1); m1.add(mi2); m1.add(mi3); m1.add(subm); m1.addSeparator(); m1.add(cmi);
subm.add(mi4); subm.add(mi5);
setMenuBar(mb);
mi3.setActionCommand("aaa");//设置由此菜单项引发的动作事件的命令名。默认情况下,动作命令名为菜单项的标签。
mi2.addActionListener(ml);
mi3.addActionListener(ml);
setSize(500,400);
setVisible(true);
}
public static void main(String[] args) {
new MenuTest();
}
}
class MenuListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
//System.out.println("菜单名为:" + e.getLabel());
System.out.println("菜单命令名为:" + e.getActionCommand());
}
}
//分别单击Open和Save菜单命令查看效果