张老师的Java高级教程中图形用户界面GUI对应的笔记
网络编辑器还要重新排版,提供原始文件下载,先看个概貌
Java高级3_图形用户界面GUI
AWT的基础知识
GUI:Graphical User Interface图形用户界面。JDK中提供了AWT和Swing两个包用于GUI程序设计和开发。
AWT是Java的早期版本,里边提供的组件有限,可提供基本的GUI程序设计与开发。
Swing是对AWT的改进,包括AWT中的所有组件,还提供了更多的组件和功能,可实现GUI程序设计的所有功能。
要使用AWT组件,就需要导入java.awt.*;
软件包 java.awt的描述
包含用于创建用户界面和绘制图形图像的所有类。在 AWT术语中,诸如按钮或滚动条之类的用户界面对象称为组件。Component类是所有 AWT组件的根。有关所有 AWT组件的公共属性的详细描述,请参见 Component。
当用户与组件交互时,一些组件会激发事件。AWTEvent类及其子类用于表示 AWT组件能够激发的事件。有关 AWT事件模型的描述,请参见 AWTEvent。
容器是一个可以包含组件和其他容器的组件。容器还可以具有布局管理器,用来控制容器中组件的可视化布局。AWT包带有几个布局管理器类和一个接口,此接口可用于构建自己的布局管理器。有关更多信息,请参见 Container和 LayoutManager。
GUI组件可分为两大类:
1、基本组件:java.awt.Component及其子类
里边不能再添加其他组件,如按钮、文本框
2、容器:java.awt.Container及其子类,容器也是组件的一种,属于component的子类
可以再放置其他组件,如窗口中放置按钮、文本框等
Frame f = new Frame(“窗口显示的标题”);
刚创建的窗口并不会显示出来,还在内存中。要让它显示出来,需要:
f.setVisible(true); 设置为true就显示,false隐藏
刚创建的窗口因为没有设置大小,所以显示出来只有一个标题栏和最大最小关闭按钮,但现在的按钮都不起作用,要关闭它可在命令行中用CTRL+C结束它。
修改窗口的大小:
f.setSize(300, 500);
在窗口中增加按钮
f.add(new Button(按钮上的文字));
程序的GUI部分由AWT线程管理,主线程将窗口显示出来有就已经结束了,但窗口还显示在桌面上,在命令行状态下发现程序并没有结束。
让窗口显示5秒后自动关闭
Thread.sleep(5000)
将窗口对象释放,从内存中清除掉,GUI组件没有了AWT线程即自动结束了。
f.dispose(); JDK1.3将GUI组件释放后线程依然存在
使用setVisible(false)只是将窗口隐藏起来,窗口还存在于内存中,AWT线程依然存活
AWT的事件处理
u 事件处理机制
GUI只是提供给用户操作的图形界面,并不提供对用户操作事件的处理,比如用户点击鼠标、按下键盘等等事件,需要相应的程序代码支持。如上面的程序,点击关闭按钮窗口并不会关闭。
为了让GUI程序能够与用户交互,Java设计了事件处理机制来处理程序和用户之间的交互。
事件:用户对组件的一个操作,称为一个事件
事件源:发生事件的组件就是事件源
事件处理器:某个Java类中负责处理事件的成员方法
事件源、事件、事件处理器之间的工作关系
u 事件分类
按产生事件的物理操作和GUI组件的表现形式分类:
MouseEvent:鼠标按下、鼠标点击……
WindowEvent:窗口关闭、打开、获得焦点、最大化……
ActionEvent:并不对应某个具体事件,按钮或菜单被操作了、文本框里输入数据了……
… 用户的某个操作导致某个组件的基本作用发生了,就会发生ActionEvent
… 比如用鼠标点击或使用快捷键激活某个菜单,都会发生这个事件。
通过事件本身都可以获取事件源,以及与事件相关的详细信息。
按事件的性质分类:
低级事件
语义事件,又叫高级事件 在JDK的帮助文档中查找java.awt.event,里边所列出的event事件类对应的监听器对象listener中只有一个成员方法的就是语义事件。有多个成员方法的就是低级事件。
u 事件监听器
一个事件监听器对象只负责处理一类事件,事件监听器中的每一个方法负责处理这类事件中的某一个具体事件。
上面的限定规则在面向对象的程序设计语言中是通过接口类来实现的。在事件源和事件监听器对象之间进行约束的接口类就是事件监听器接口。事件监听器接口类的名称与事件类的名称是对应的,如MouseEvent事件类的监听器接口为MouseListener。
实例:实现关闭窗口的事件处理
1、先编写一个监听器类,来处理关闭按钮被点击时的动作,监听器内部代码将窗口关闭
class MyWindowListener implements对窗口事件处理的类必须实现WindowListener接口
{WindowListener接口中有7个方法都需要覆盖,对不需要具体操作的事件可以空实现即可
public void windowOpened(WindowEvent e)
{
}
public void windowClosing(WindowEvent e)窗口正在关闭,还没消失
{事件发生需要处理,会将事件对象传递过来,
可以用事件对象获取产生事件的窗口到底是那个
e.getWindow().setVisible(false);只是将窗口隐藏,并没消失
还可以使用e.getSource或e.getComponent得到事件对应的来源或源组件,都可以得到当前发生事件的事件源,但这三个方法返回值不同,getSource返回Object,个头Component返回Component,所以使用这两个方法时须将返回值强制转换为Window类型
e.getWindow().dispose();
System.exit(0);程序退出
}
public void windowClosed(WindowEvent e)窗口已经关闭,消失了
{
}
}
2、将时间监听器对象注册到窗口上
f.addWindowListener(new MyWindowListener());
事件处理流程:
要处理发生在某个GUI组件上的某类事件XXXEvent的某种具体情况,事件处理通用编写流程:
1、编写一个实现对应事件监听器接口XXXListener的监听器类。
2、在监听器类中处理相应事件的方法中编写具体处理代码,其他情况不处理可以空实现
3、调用组件的addXXXListener方法,将这个监听器类对象注册到GUI组件上。
u 事件适配器 与事件相关的包都在java.awt.event中
自己编写事件监听器需要实现对应事件监听器接口中的所有方法,为了简化编写过程,
JDK中提供了大多数事件监听器接口的最简单的实现类,称为事件适配器(Adapter)。
这样编写监听器时只需继承相应的事件适配器,覆盖自己想要处理情况的事件处理方法即可,其他情况由事件适配器默认方式处理。
示例:使用事件适配器关闭窗口
1、建一个时间监听器类,继承窗口事件适配器WindowAdapter
class YourWindowListener extends WindowAdapter
{只考虑关闭窗口操作,其他情况由适配器默认方式处理,即什么也不做
public void windowClosing(WindowEvent e)
{
e.getWindow().dispose();
System.exit(0);
}
}
2、将监听器注册到事件窗口上,替换原来的MyWindowListener
f.addWindowListener(new YourWindowListener);
使用事件适配器时发生的常见问题:
1、方法中的代码有问题,还是方法没被调用?
2、方法名写错了(调用的是适配器的空方法),还是没有注册事件监听器?
事件适配器的不足:
Java中不允许多继承,如果要做监听器的类已经继承其他类了,就不能在继承适配器类,这时就需要实现监听器接口才可以。
ActionListener中只有一个方法actionPerformed方法,JDK就没有提供适配器类,使用时需要实现其中的方法。
u 灵活设计事件监听器类
怎样才能在事件监听器中访问非事件源的其他GUI组件?在上面的窗口程序中,怎样
使用按钮(事件源)来关闭窗口(非事件源)?
编写一个监听器类,实现ActionListener接口
class MyActionListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
[
f.dispos(); 这句代码需要访问外边的Frame,怎么访问呢?
]
}
为了方便实现,用Frame所在的类直接实现ActionListener接口,实现其中的方法,那么这个窗口类自己本身就是一个监听器对象了。把这个监听器this注册到按钮上即可。但是用这种方式引用Frame的的时候很麻烦。可以将所有对Frame的操作定义到一个函数中,完成窗体初始化,在main方法中调用初始化方法即可。
也可以用内部类来实现ActionListener接口,作为监听器对象,注册到按钮上即可
用匿名内置类实现事件监听器
还可以用匿名内部类的形式实现addListener(new ActionListener(){实现的方法})
u 事件处理的多重运用
同一个操作触发了多个事件(低级和语义事件),如用鼠标点击一个按钮,既触发了鼠
标事件,又触发了按钮本身的事件。到底要执行哪个事件处理方法呢?或者鼠标点击后,鼠标事件执行后,按钮本身的动作事件(如改变标题)也执行。
在按钮上注册两个监听器对象,一个处理鼠标事件,一个处理按钮动作事件。
一般不需要处理引发事件的低级事件,只需处理动作事件即可,比如鼠标点击菜单,不需处理鼠标点击事件,只需处理菜单的语义事件即可。
举例:打人 司法处理就是低级事件; 公司内部处理就是语义事件,比如违反公司纪律、迟到、早退都是违反公司纪律,有多种引发方式。
一个组件上的一个动作产生多种不同类型的事件,打人违反法律、违反公司纪律
一个事件监听器对象注册到多个事件源上,多个按钮使用一种处理方式
一个事件源上注册处理同一类事件的多个监听器对象,总统的多个保镖
u 修改组件的默认事件处理方式
只有在组件上注册了某种事件监听器对象后,组件才会产生相应的事件对象。
默认的事件处理过程:processEvent方法调用相应的processXXXEvent方法,将事件对
象传递给相应的XXXListener监听器,由监听器进行相应的事件处理。
如果要修改默认的事件处理方式,需要覆盖组件的processEvent或者processXXXEvent方法,processEvent方法是处理所有事件的总入口,processXXXEvent是专门用于处理XXX事件的岔路口。
没有注册事件监听器时,对应的时间处理方法不会响应并执行,调用enableEvents(long eventsToEnable)方法,可以在没有注册事件监听器的情况下,对某些类型的事件进行响应。
编程示例:窗口上显示一个按钮,一旦鼠标移动到这个按钮上,按钮就移动到其他位置,这样,鼠标就永远无法点击到这个按钮。
需要覆盖按钮的鼠标移动事件的默认处理方式,创建button的子类,覆盖其中的processMouseMotionEvent方法,隐藏自己,显示另一个。
1、框架代码
class MyFrame extends Fream继承Fream,本身也是一个窗体
{
public MyFrame()构造函数中添加窗体监听器
{
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
dispose();
System.exit(0);
}
});
}
public static void main(String [] args)
{
MyFrame frame = new MyFrame();
frame.setSize(400, 400);
frame.setTitle(“My Frame”);
frame.setVisible(true);
}
}
2、编写自定义的button类,修改默认处理方式
class MyButton extends Button
{
按钮本身就有一个伙伴
MyButton friend = null;
public MyButton(String title)子类不会继承父类的构造函数,自定义一个一样的
{直接使用父类的构造方法
super(title);
没有注册监听器也要对某些事件进行处理,执行下边的语句后,不管以后产生的MyButton对象有没有注册鼠标移动的监听器,都会处理这个事件。
enableEvent(AWTEvent.MOUSE_MOTION_EVENT_MASK);
}
覆盖默认的鼠标移动处理方式
protected void processMouseMotionEvent(MouseEvent e)
{
this.setVisible(false);
friend.setVisible(true);
}
}
3、将按钮加入1中的窗口框架中
MyButton btn1 = new MyButton(“来点我呀”);
MyButton btn2 = new MyButton(“来点我呀”);
btn1.friend = btn2;
btn2.friend = btn1;
frame.add(btn1);
frame.add(btn2);默认布局方式but2会覆盖掉btn1,使用下面的方式就可以解决
frame.add(btn1,North);
frame.add(btn2,South);
but1.setVisible(fasle);注意此行代码不能放在frame.setVisible(true)上面,因为主窗口还没显示出来,它里边的所哟组件都处于不可显示状态,设置更别说了。
GUI组件上的图形操作
u Graphics类与图形绘制
有时需要在GUI组件上绘制图形、打印文字、显示图像……组件对象并不提供对这些
|
Graphics.drawLine(int x1, int y1, int x2, int y2)画直线
Graphics.drawString(String str, int x, int y)显示字符串,xy表示str输出区域的左下角坐标
示例:以鼠标在窗口中按下的位置作为起始点,释放时的位置为终点,在释放时将直线画出,并在每条直线的起始点和终止点位置打印出它们的坐标值。
1、产生框架窗口frame
2、在框架窗口中添加鼠标事件监听器,覆盖其中的mousePressed和mouseReleased方法
frame.addMouseListener(new MouseAdapter(){
int x, y; 记录起始点坐标
public void mousePressed(MouseEvent e)
{
x = e.getX(); y=e.getY();
}
public void mouseReleased(MouseEvent e)
{获取当前窗口的Graphics对象,画图,设置颜色……setColor
getGraphics().drawString(起始点,x,y);注意,两次调用返回的Graphics对象不是同一个
getGraphics().drawLine(x,y,e.getX(),e.getY());
可以用Graphics对象设置字体
g.setFont(new Font());Font
(String name, int style, int size)
根据指定名称、样式(常量值)和磅值大小,创建一个新Font
。
张老师演示就有效,我设置字体怎么没效果呢?颜色线上有效,字都是黑的
getGraphics()多次返回多个对象,设置在不同的对象上当然无效了。
}
});
u 组件重绘的处理
窗体重绘后不包括窗体组件上通过Graphics画出来的图形。系统只记录了窗体上的信
息,并没有记录组件表面的内容。
paint(Graphics g) 窗体移动,改变大小等等需要重绘、曝光
窗体组件被重新绘制时,AWT线程会调用窗体组件的paint(Graphics g)方法将显卡中保存的窗体信息绘制出来。,
默认重绘不包括组件表面的内容,如上个程序中画出来的直线和坐标值,如果要让它们也重绘,可以修改窗体中的paint方法,将原来的内容重新画出来。
AWT线程对组件重绘的调用过程
paint方法是由AWT线程管理调度的,应用程序不应直接调用paint方法,需要曝光刷新重绘时,就要使用paint方法,但不要直接调用paint,先调用repaint方法,repaint会调用update方法,update方法内部会现将组件表面的内容清空,再调用paint进行重绘。
示例:将上例程序改写,窗口重绘后保留窗口中原来的数据。
1、需要记录直线的起始和终止点坐标,将上例的鼠标监听器中记录终点坐标的两个变量放在方法体外。因为paint方法要访问这4个变量,它们定义在监听器的匿名内部类中就不行了,所以把它们放到外部类中。
2、因为不能直接修改Frame中的paint方法,所以在子类中覆盖掉paint方法,
public void paint(Graphics g)
{ 先把线画出来
g.drawLine(x, y, xx, yy);
}这样修改后,每次只恢复一条线,并且颜色和坐标信息都没有,因为paint方法中没有画
3、要恢复所有的线,就需要把所有直线的信息存储起来,
将直线进行封装,
class Line()
{
private int originX,originY,endX,endY;
public Liine(int orginX, int orginY, int endX, int endY)
{this.xy = xy……}
直线对象内已经有要画的坐标值,直接在直线内部提供方法画出自己,paint内部就不需再调用drawLine也不用从直线中获取4个坐标值了
public void drawMe(Graphics g)
{
g.drawLine(orginX, orginY, endX, endY);
}这样在frame中的paint方法中就不需要再画了
}
4、在frame中定义一个集合存放直线信息
Vector vLines = new Vector();
在鼠标每次释放时将坐标信息加入集合中
vLines.add(new Line(orginX, orginY, endX, endY));
在窗口重画时需要从集合中取出所有直线画
Enumeration e = vLines.elements();取出所有直线
while (e.hasMoreElements())进行遍历
{枚举中取出的是Object需要转换
Line line = (Line)e.nextElement();
line.drawMe(g);
}上面这样就实现了重绘时恢复以前的内容,不过还不完整,颜色,文字都没存储。
5、前面说过不要直接调用paint方法进行重绘,使用repaint方法
将鼠标释放方法内的代码只保留(记录坐标值并将坐标加入集合中)的部分,然后加上repaint方法。
经过上面的改动后运行发现,虽然鼠标释放的方法内部并没有drawLine,但是依然画出了,因为repaint方法会导致窗口重绘,将窗口中的内容全部清空再paint一次。这个过程相当于刷新,每次都将集合中的直线刷到窗口中。
u 图像显示drawImage方法有6个,看文档
在组件上显示图像,使用Graphics.drawImage(Image img, iint x, int y, ImageObserver observer)
abstract boolean |
drawImage(Image img, int x, int y, ImageObserver observer) |
abstract boolean |
drawImage(Image img, int x, int y, int width, int height, Color bgcolor,ImageObserver ) |
observer 监视图像创建进度的对象
创建img对象的时候,并没有在内存中创建图像数据,而是在需要时才去加载图像数据。如果img对象中还没有加载图像数据,使用drawImage方法的时候才去真正地往内存中加载图像数据。如果想知道图片加载进度,就需要一个实现了ImageObserver接口的对象作为监视者,与事件监听器类似。因为Component已经实现了ImageObserver接口,如果程序不关心图片加载进度,可以直接将窗口对象作为observer传递给drawImage方法。
Image类是一个抽象类,需要一个返回Image对象的方法,在Toolkit类中提供了这样的方法,但Toolkit本身也是一个抽象类,需要找一个返回Toolkit对象的方法来获取Toolkit的示例对象以便于调用返回Image的方法。
java.awt.Toolkit
public abstract Image createImage(String filename)
返回从指定文件获取像素数据的图像。返回的 Image是一个新对象,该对象不再由此方法的其他任何调用者或其 getImage变体共享。
public abstract Image getImage(String filename)
返回一幅图像,该图像从指定文件中获取像素数据,图像格式可以是 GIF、JPEG或 PNG。底层工具包试图对具有相同文件名的多个请求返回相同的 Image。因为便利 Image对象共享所需的机制可能在一段不明确的时间内继续保存不再使用的图像,所以鼓励开发者在所有可能的地方使用createImage变体实现自己的图像缓存。如果包含在指定文件中的图像数据发生了更改,则此方法返回的 Image对象仍然包含前一个调用之后从该文件加载的旧信息。通过对返回的 Image调用flush方法,可以手动丢弃以前加载的信息。
java.awt.Component
public Toolkit getToolkit()
获取此组件的工具包。注意,包含组件的框架控制该组件使用哪个工具包。因此,如果组件从一个框架移到另一个框架中,那么它所使用的工具包可能改变。
所有GUI组件都可以获取Toolkit对象,所以i可以使用
Component.getToolkit.getImage(String path)从一个图片文件中加载数据获得Image实例对象
示例:在窗口中显示图像文件中的图像,并解决由于drawImage内部实现机制造成的图像不能显示问题,和实现图像的永久化显示。
1、获取Image的实例对象
Image img = frame.getToolkit().getImage(“1.jpg”); 。。\\。。\\1.jpg
显示图像对象
frame.getGraphics().drawImage(img, 0, 0, frame);这里空指针异常
Component.getGraphics().如果组件当前是不可显示的,则此方法返回null
。
先将frame显示出来,在drawImage异常消失
窗口显示出来了,但图像并没有显示
创建Image时,并没有将图像文件装载进内存,只有在drawImage被调用时,才将图像文件装载进来,但是drawImage方法运行时不会等待文件装载完毕就已经返回了,所以图像就没有显示出来。drawImage的返回结果可以判断图像有没有画出来。知道原因后,就开始修改程序:
Graphics g = frame.getGraphics();
while (!g.drawImage(img, 0, 0, frame))如果没有画出来,继续画
//这个多余了空转就行了g.drawImage(img, 0, 0, frame)
组件重绘导致窗口中的图像消失,要保留数据需要在paint方法内将图片数据再画出来
将img定义在外部,方便paint方法共享
public void paint(Graphics g)
{在paint内部不用循环了,只画一次即可成功,因为每次重绘paint都会执行
if (img!=null)
while (!g.drawImage(img, 0, 0, this));
}
u 双缓冲技术
在前面的画直线例子中,为了保证重绘后之前的直线数据还在,定义了一个集合存放所
有的直线对象,在paint内部将所有的直线都画了一遍,如果窗口组件表面的数据很多,重绘时还需要存储、重画,效率会受很大影响。使用双缓冲技术可以缓解这个问题。
1、使用Component.createImage方法创建内存Image对象
2、在Image对象上进行绘制的结果就成了一副图像。
3、在窗体组件上执行绘制操作的同时也在内存Image对象上进行绘制,那么内存Image对象中的内容就是窗口表面所有内容的复制,当组件重画时,只需将内存中的image对象在组件上画出来即可。这样,不管窗口原来的组件有多少,它重画的时候只是画了内存中的一个图像而已。组件很多的时候重绘效率很高。
示例:用双缓冲技术重绘组件表面的所有内容,用前面的画线程序复制一份来实现
1、在里边定义成员变量Image和Graphics用来存放内存Image对象和用来画内存对象的Graphics对象。
Image oimg = null; Graphics og = null;
2、创建内存Image对象并得到对应的Graphics:在画线之前进行,所以在构造方法中进行操作。
创建一个内存Image对象,需要指定大小,大小要与原来一样,所以要先获取原来大小
Dimension d = this.getSize(); 调用窗口的getSize可以返回区域大小
oimg = this.createImage(d.width, d.height);
获取内存对应的Graphics对象
og = oimg.getGraphics();
3、在鼠标监听器内部的处理方法中,画线动作的代码复制一份,将画线所用的Graphics改为og即可。
4、在重绘方法paint中直接将内存对象oimgDrawImage出来即可。不用遍历集合了
注意空指针异常处理方式:窗体组件没有显示出来的时候,getGraphics和createImage都返回null
注意窗口第一次显示时也会调用paint方法,这时内存img对象还没有产生,而paintt方法内部又要画img对象,就会发生空指针异常,画之前进行判断,如果img!=null才画
常用的AWT组件
抽象类Component是所有GUI组件的父类,MenuComponent类是有关菜单组件的父类
Component中的dispathEvent(AWTEvent e)可以向AWT组件发送一个事件,比如给按钮发送一个点击事件可以模拟用户点击动作,使用getPreferredSize()可以得到组件最好的大小,返回一个Dimension对象,可以使用Dimension.getWidth和getHeight方法获取具体大小……
u Canvas
Canvas是具有最基本和最简单GUI功能的组件,它代表屏幕上的一块空白矩形区域,
程序可以在这个部件表面绘图,也能够捕获用户操作,产生相应事件。应用程序必须为Canvas
类创建子类,以获得有用的功能(如创建自定义组件)。必须重写paint
方法,以便在 canvas 上执行自定义图形。
当要设计具有自定义功能的GUI组件时,继承Canvas可以简化编程难度。只需实现自定义组件的专有外观和代码即可。
示例:设计一个自定义的计时器组件,鼠标在计时器组件上按下时开始计时,释放停止计时,以秒为单位:时:分:秒。需要编写两个类,一个是自定义的组件,一个是主窗口框架,将自定义组件增加到窗口框架中
1、编写主窗口框架
2、编写自定义计时器组件,继承Canvas
覆盖paint方法,编写自定义外观
鼠标按下释放时给start和end赋值,重绘时也要获取起止时间进行计算并显示结果,所以将这两个变亮定义在paint外部、作为自定义Canvas子类的成员变量
long startTime = 0;
long endTime = 0;
public void paint(Graphics g)
{ 将时间值显示为HH:mm:ss形式,需要使用SimpleDateFormat和Date类,将long型时间值转换为指定格式java.text.SimpleDateFormat
SimpleDateFormat sdf = new SimpleDateFormat(“HH:mm:ss”);
Date elapsedTime = new Date(endTime - startTime);计算用去的时间
String strTime = sdf.format(elapsedTime); 将时间值用指定格式进行格式化
底色黑,字白色
g.fill3DRect(0,0, 90, 30, boolean凸出还是凹进)
填充矩形区域,默认使用前景色即文字颜色填充,位置自己调整
g.setColor(Color.WHITE);
g.drawString(strTime, 10, 20);
}
3、将自定义组件增加到主框架窗口中,构造方法中增加
运行后,显示的时间为08:00:00,但是我们设置的start和end都为0,计算后的应该也为0呀,这说明使用SimpleDateFormat将long型的0秒格式化后的结果与我们要求的不符,查看SimpleDateFormat的构造方法后发现,构造时还可以接收一个表示时区的Locale对象,可能是时区问题导致它显示8个小时,但我们这个程序不要求具体时间,只想开始时显示000000,然后计算鼠标点击释放之间耗时并显示出来,怎么让它显示0呢?因为我们已经将long型时间值设置为0了,而内存中格式化后的时间变为8个小时,所以我们可以将之前的long型0秒减去8个小时,这样经过格式化后的时间字符串就是0了
Data elapsedTime = sdf.parse(“00:00:00”); 先得到内存中0秒形式表示的对应时间,
再加上实际耗用的时间start-end,就是计时器最有要显示的时间
elapsedTime.setTime(elaspedTime.getTime()+start-end);
再格式化输出
g.drawString(sdf.format(elapsedTime), 10,10);
4、增加计时功能,
需要添加鼠标事件监听器,更简单的方法时覆盖自定义组件对象父类Canvas中的处理鼠标事件的方法processMouseEvent,覆盖后使用自定义的代码进行处理
protected void processMouseEvent(MouseEvent e)
{ getID返回表示某一个具体事件的数值,为常量,如表示鼠标按下、释放等等
通过e.getID对事件具体情况进行判断
if (e.getID()==MouseEvent.MOUSE_PRESSED)
{
startTime = endTime = System.currentTimeMillis();
}
else if(e.getID==MouseEvent.MOUSE_RELEASED)
{
endTime = System.currentTimeMillis();
repaint();重绘组件
}
}
5、经过上面的修改后,运行程序,鼠标释放后并没有重画出耗用时间,可能是这个事件处理方式没有被调用。因为这个默认的事件处理方式没有注册到组件上,要让它可以处理,在构造方法中添加enableEvents方法,让组件支持鼠标时间即可。enableEvents(AWTEvent.MOUSE_EVENT_MASK);
6、完善
想让按下鼠标后计时器将1秒1秒增加的过程也画出来,停止后再次点击将原来的数据清零。 要做到显示实时计时信息,需要增加一个线程,每隔大约一秒就刷新一下,让组件进行重绘。
让自定义组件实现Runnable接口,在run方法中让线程暂停半秒后对组件进行重绘
public void run()
{
Thread.sleep(500);
重绘前需要更新endTime的值,不然每次重绘都是0
endTime = sys.currenttime
repaint();
}
鼠标只要按下没有释放,上面的run代码就需要一直执行,所以要将其放在while循环内部,while判断鼠标是否释放,怎么判断呢,可以设置一个标记变量,在鼠标释放时为false,鼠标按下时为true,。并且在按下时启动线程运行new Thread(this).start()
在鼠标每次点击时进行清零,需要在processMouseEvent方法内部处理鼠标按下的代码中进行组件重绘
if (e.getID==MouseEvent.MOUSE_PRESSED)
{
start = end = sys.currenttime
repaint();
}
u 菜单
一个完整的菜单系统由菜单条、菜单和菜单项组成
Java中与菜单相关的类主要有:MenuBar菜单条、Menu菜单、MenuItem菜单项。
示例:编写上图的菜单系统
1、产生框架窗口frame
2、在框架窗口中定义成员变量一个MenuBar、多个Menu
MenuBar = new MenuBar();
Menu fileM = new Menu(“File”);……
3、定义各个Menu内部的MenuItem
MenuItem fileMI1 = new MenuItem(“New”);
发现File菜单中还有复选功能和级联子菜单的菜单项
创建具有复选功能的菜单项 CheckboxMenuItem(Name,是否选中)
CheckboxMenuItem fileM5 = new CheckboxMenuItem(“Quit”, true);
级联子菜单可以使用Menu代替MenuItem来增加到主Menu中的方式完成
Menu file4 = new Menu(“Print”);
MenuItem file4_1 = new MenuItem(“preview”);……
4、按层级关系将菜单项增加到菜单中
menubar.add(fileM);……MenuBar.add(Menu)菜单栏增加菜单
fileM.add(fileMI1);……Menu.add(MenuItem)菜单增加菜单项
fileM4.add(file4)……Menu.add(Menu)菜单增加菜单
增加菜单项之间的分割符Menu.addSeparator()
frame.setMenuBar(MenuBar)为窗口设置菜单条
5、对菜单项事件进行处理,ActionEvent
定义一个类MenuListener实现ActionListener接口,覆盖其中的事件处理方式
public void actionPerformed(ActionEvent e)
{
String str = e.getActionCommand()获得菜单项上显示的文本标题
进行判断并做出相应处理
if (str==”quit”)……
else if(str==”print”)……
}
将事件监听器对象注册到事件源上,要监听哪个菜单项,就注册到哪个菜单项上
MenuListener m = new MenuListener();
printM.addActionListener(m);……MenuItem。add(Listener)为菜单项增加监听器
具有ActionEvent事件的组件,都可以调用setActionCommand方法为组件关联一个字符串,然后使用getActionCommand就可以获得刚才set的字串,如果没有用set进行任何关联字符串的设置,那么默认的就是组件表面默认的字符串,如创建菜单项时指定的文本。这个命令可使用在菜单语言切换上,还可以使用这两个方法实现同一个菜单项执行多个命令,比如默认为连接,点击后变为断开,点击事件发生时可以get字串进行判断并执行不同的命令。
Button和Menu组件都具有ActionEvent事件
u Container容器
组件不能独立的显示出来,必须放在容器中,frame就是一个容器.Container类是所有容
器类的父类,使用Container.add方法可以将组件添加到容器中。
Container也是Component的子类,所以容器也可以作为组件加入到其他容器中。
Window类没有标题栏,不常用
Frame常用的窗口类setVisible
Dialog对话框窗口,没有菜单条,不能改大小,用于临时窗口。
容器显示出来后,就不能再增加组件了,会显示不正常。
u Dialog类与FileDialog类
Dialog用于产生对话框,与窗口组件一样可以添加组件,但没有菜单条,不能改变大小。一般用作临时窗口,用于显示提示信息或接收用户输入。对话框不能独立存在,必须有一个上级窗口。对话框分为模态和非模态,模态时,用户不能操作同一个程序中对话框外的其他窗口,只有关闭了模态对话框,才能继续其他操作。模态对话框打开时还可以操纵程序中的其他窗口。如设置选项对话框为模态,查找对话框为非模态。
Dialog的构造方法需指定对话框的拥有者即上级窗口,还可以指定对话框标题、模态非模态。Dialog(Dialog owner[,String title][, boolean modal])默认非模态,最初不可见
示例:主框架窗口通过两个按钮分别打开一个模态与非模态对话框,并与对话框进行数据交换。打开对话框时主窗口文本框的内容会自动放进对话框中的文本框里,点击模态对话框中的确定按钮会将模态对话框关闭同时将其文本框内数据放入主窗口的文本框中。打开非模态对话框会执行相同操作,此外,点击非模态对话框的应用按钮后也会将对话框中的文本内容放入主窗口的文本框内,但不关闭对话框。
1、产生主框架窗口及其组件
在主窗口类中定义文本框组件,全局的应为要与对话框交换数据
privat TextField tf = new TextField(字符宽度);
在构造函数中定义两个按钮组件,不需与对话框交互,所以局部变量即可
Button b1 = new Button(“模态”);
Button b2 = new Button(“非模态”);
将按钮和文本框加入框架窗口中,需要使用布局管理器
add(tf.”North”); add(b1, “Center”); add(b2, “East”);
2、为按钮增加监听器,还在主窗口构造中加
b1.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e)
{模态打开,接收数据 外部类.this 代表外部类的实例对象
MyDialog dlg = new MyDialog(MainFrame.this,“模态”, true);
传递数据
dlg.setInfo(tf.getText());
dlg.setVisible(true); 关闭模态对话框以后下边的代码才能执行
进行模态对话框关闭后的操作
tf.setText(dlg.getInfo());
}
});
b2.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e)
{非模态打开,接收数据 外部类.this 代表外部类的实例对象
MyDialog dlg = new MyDialog(MainFrame.this,“非模态”, false);
传递数据
dlg.setInfo(tf.getText());
dlg.setVisible(true); 非模态对话框一显示出来下边的代码就开始执行
刚显示出来用户还没来得及输入呢,你get什么呢
所以不能调用这个语句,应该由非模态对话框自己设置主窗口中文本内容
//tf.setText(dlg.getInfo());
}
});
3、编写自己的对话框MyDialog继承Dialog类
先定义一个文本框
private TextField tf = nw TextField(10);
构造函数用于将主窗口中传递的父窗口信息、标题信息、模式信息作用在自己身上
public MyDialog(Frame owner, String title, boolean modal)
{直接使用父类的构造方法即可
super(owner, title, modal);
setBounds(0,0,200,140);设置左上角位置和大小
添加按钮组件
Button b1 = new Button(“应用”);
Button b2 = new Button(“确定”);
this.add(tf, “North“); this.add(b1, “Center”); this.add(b2, “East”);
如果对话框是模态的,应用按钮就不可用
if (this.isModal())
b1.setEnable(false)
为应用按钮增加监听器
b1.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e)
{取出内容传给主窗口
this.getOwener()返回一个Frame,需要转为我们自己的窗口类型
(MainFrame)MyDialog.this.getOwner().tf.setText(tf.getText());主窗口的文本框为私有的,这里不能访问主窗口的tf.
为便于上边一步的操作,可以在主窗口中提供setInfo方法,将指定内容写入到自己的文本框中
(Mainframe)this.getOwner().setInfo(tf.getText);
}
});
为确定按钮增加监听器
b2.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e)
{需要判断对话框是模态还是非模态
if (isModal())是模态
{是模态需要先将文本数据存起来在MyDialog中定义一个private String info成员
点击确定后将信息存起来,让主窗口获取
info = new String(tf.getText());
}
else 不是模态,主窗口不会主动获取对话框内容,需要对话框自己把信息设置到主窗口中
(Mainframe)MyDialog.this.getOwner().setInfo(tf.getText);
最后关闭对话框
dispose();
}
});
}
为MyDialog增加getInfo方法,让主窗口调用
为主窗口添加setInfo方法,在文本框中添加内容
public void setInfo(String info)
{
tf.setText(info);
}
FileDialog类是Dialog的子类,能够产生标准的文件存取对话框。可以作为打开对话框,也可以作为保存对话框,通过构造函数中的int mode进行区分,也可以设置。
FileDialog(Frame parent, [String title], [int mode])mode是一个常量,只有两种:FileDialog.LOAD和FileDialog.SAVE
因为安全性问题,FileDialog不能在Applet程序中使用,只能在普通桌面程序中使用。
u Checkbox
Checkbox类用来建立单选按钮和多选按钮(复选框),单选按钮需要用组进行约束。
可使用 CheckboxGroup类将一些复选框组成一组,作为单个对象来控制。在一个复选框组中,在任何给定时间,最多只能有一个按钮处于“开”状态。单击并打开一个复选框会强迫同组中其他原来处于打开状态的复选框变为“关”状态。
Checkbox(String label,boolean state,CheckboxGroup group)参数个数随意,指定group就是单选
构造具有指定标签的 Checkbox,并将它设置为指定状态,使它处于指定复选框组中。
单选按钮和多选按钮的语义事件为ItemEvent,对应的监听器接口为ItemListener,该接口中只有一个itemStateChanged方法
示例:创建一个多选按钮和两个同属一组的单选按钮,对每个按钮的选中情况进行处理
1、在框架窗口中定义一个多选按钮和2个单选按钮
Checkbox cb1 = new Checkbox(“你喜欢我吗?”, true);
单选按钮需要一个CheckboxGroup对象
CheckboxGroup cbg = new CheckboxGroup();
Checkbox cb2 = new Checkbox(“喜欢”, true, cbg);
Checkbox cb3 = new Checkbox(“不喜欢”, false, cbg);
2、将按钮添加到主窗口中,使用流式布局管理器,在构造中加
FlowLayout fl = new FlowLayout();
setLayout(fl);
add(cb1); add(cb2); add(cb3);
3、为按钮增加事件监听器,用同一个事件监听器类处理3个按钮的事件,用主窗口内部类
class CheckListener implements ItemListener
{
public void itemStateChanged(ItemEvent e)
{因为使用同一个事件监听器对象处理3个按钮的事件,需要对事件源进行辨别
//先获取产生事件的事件源对象,getItemSelectable返回的结果需转换一下
Checdbox cb =(Checkbox)e.getItemSelectable();
判断cb是否是cb1 cb2 cb3也可以通过标题进行判断cb.getLabel得到标题文本
if (cb.getLabel().equals(“你喜欢我吗?”))或者cb==cb1
{
cb.getState()方法返回多选框是否被选中
if (cb.getState())
SOP(很高兴你喜欢我)
else
SOP(我不高兴)
}else if (cb.getLabel().equals(“喜欢”))对单选处理
{
使用ItemEvent.getStateChange()方法也可以判断是否被选中,返回常量值
if (e.getStateChange()==e.SELECTED)SOP(高兴)
else SOP(伤心)
}另一个按钮也要进行同样处理,发现需要对同一组的多个单选按钮进行同样操作,太麻烦了,group中提供了简单的方法吧
else
{使用CheckboxGroup.getSelectedCheckbos()可以获取组中选中的哪个单选框
Checkbox cbx = cbg.getSelectedCheckbox()
if (cbx != null) SOP(cbx.getLabel())
}这段可以处理整个组中的单选按钮事件,上边的一个处理方式可以去掉,不去掉的话会优先使用单个按钮的处理方式
}
}
4、将事件监听器注册到选择框上
CheckListener cbl = new CheckListener();
cb1.add(cbl); cb2.add……
u Choice
Choice
类表示一个弹出式下拉选择菜单。Choice对应的语义事件也是ItemEvent。
示例:实现图中列表框进行事件处理,与上例Checkbox类似,添加一个Choice组件,用内部类形式定义一个监听器,实现Itemlistener接口中的itemStateChanged方法
Choice ch = new Choice()
增加选择项
ch.add(“xuanxiang1”);……
add(ch)
ch.addItemListener()
public void itemStateChanged(ItemEvent e)
{e.getItem返回选择的条目
SOP(e.getItem())
}
u Panel与ScrollPane类
Panel 是最简单的容器类。应用程序可以将其他组件放在面板提供的空间内,这些组件包括其他面板。面板的默认布局管理器是 FlowLayout布局管理器
Panel类是一个容器类,用于产生一种特殊的空白面板,可以容纳其他组件,但不能独立存在。
ScrollPane类是一种容器类,用于产生滚动窗口,通过滚动条在一个较小的容器窗口中
显示较大的子部件。不能单独使用,必须被添加到其他容器中,没有布局管理器,只能放置一个组件,如果要讲多个组件添加到ScrollPane中,可以先将多个组件排放在Panel中,再把Panel添加到ScrollPane中。
示例:ScrollPane的使用,小区域显示大内容
1、把主窗口设置的小一点,在里边增加一个大的组件,如TextArea
TextArea(String text, int rows, int columns, int scrollbars) 构造一个新文本区,该文本区具有指定的文本,以及指定的行数、列数和滚动条可见性。参数个数随意,最后一个常量指定滚动条是否显示,这个例子中就是要把文本区域设置的很大并且不现实滚动条,让主窗口显示不完。
TextArea ta = new TextArea(“”, 10, 50, TextArea.SCROLLBARS_NONE);
add(ta);
2、用上面的方式添加后文本区域不会显示滚动条,现在想加一个滚动条
ScrollPane sp = new ScrollPane();
将TextArea对象增加到滚动窗口中,再将滚动窗口增加到主窗口中,注意顺序层次
sp.add(ta);
add(sp);
布局管理器
一个容器中各个组件之间的位置和大小关系就是布局。为了让图形用户程序具有良好的平台无关性,Java提供了布局管理器来管理组件在容器中的布局,而不直接使用位置坐标来设置各个组件的具体位置和大小。一个容器上只能有一个布局管理器,可以是任何一种。当组件需要进行定位或设置大小时,就需要调用对应的布局管理器。如果在程序中改变了布局管理器,容器中的所有组件的位置和大小都会发生变化。
AWT中的布局管理器
u BorderLayout
BorderLayout是窗口类Window的默认布局管理器。
这是一个布置容器的边框布局,它可以对容器组件进行安排,并调整其大小,使其符合下列五个区域:北、南、东、西、中。每个区域最多只能包含一个组件,并通过相应的常量进行标识:NORTH、SOUTH、EAST、WEST、CENTER。当使用边框布局将一个组件添加到容器中时,要使用这五个常量之一,不指定默认为常量 CENTER
根据其首选大小和容器大小的约束 (constraints)对组件进行布局。NORTH和 SOUTH组件可以在水平方向上拉伸;而 EAST和 WEST组件可以在垂直方向上拉伸;CENTER组件可同时在水平和垂直方向上拉伸,从而填充所有剩余空间。
BorderLayout布局只能在窗口上放置5个组件,如果想放置更多的组件,可以先将组件添加到Panel中,再将Panel添加到窗口中。如果放置的组件没有5个时,没有组件的区域会被相邻的组件占用
示例:直接在Frame中增加两个不指定布局方式的按钮,第二个按钮会覆盖第一个按钮。默认BorderLayout布局管理器将按钮位置设置为CENTER。
add(new Button(“Button”), “North”);
Frame.add 中指定位置参数:North、West、South、East、Center、
u FlowLayout
FlowLayout是Palne面板的默认布局管理器,默认从左到右,从上到下
流布局用于安排有向流中的组件,这非常类似于段落中的文本行。流的方向取决于容器的 componentOrientation属性,它可能是以下两个值中的一个:
ComponentOrientation.LEFT_TO_RIGHT
ComponentOrientation.RIGHT_TO_LEFT
流布局一般用来安排面板中的按钮。它使得按钮呈水平放置,直到同一条线上再也没有适合的按钮。线的对齐方式由 align属性确定。可能的值为:LEFT、RIGHT、CENTER、LEADING、TRAILING流布局把每个组件都假定为它的自然(首选)大小。
示例:Container.setLayout(new FlowLayout());设置为流式布局管理器后再add时指定的North等参数就不起作用了。
u GridLayout
GridLayout将容器划分成若干行和列组成的网格,在容器中添加组件时,会按从左到右
从上到下的顺序在网格中排列。在GridLayout的构造方法中需要指定网格的行列数。空构造方法默认一个组件占一行一列,组件大小相同,由管理器自动设置
通过构造方法或setRows和setColumns方法将行数和列数都设置为非零值时,指定的列数将被忽略。列数通过指定的行数和布局中的组件总数来确定。因此,例如,如果指定了三行和两列,在布局中添加了九个组件,则它们将显示为三行三列。仅当将行数设置为零时,指定列数才对布局有效。
public GridLayout(int rows, int cols, int hgap, int vgap)
创建具有指定行数和列数的网格布局。给布局中的所有组件分配相等的大小。此外,将水平和垂直间距设置为指定值。水平间距将置于列与列之间。将垂直间距将置于行与行之间。 rows和 cols中的一个可以为零(但不能两者同时为零),这表示可以将任何数目的对象置于行或列中。参数:rows -该 rows具有表示任意行数的值零 cols -该 cols具有表示任意列数的值零 hgap -水平间距 vgap -垂直间距
u CardLayout
CardLayout能实现多个组件在同一个容器中交替显示,相当于多张卡片摞在一起,任何
时候都只能看到最上面的一个。相当于Window系统中的选项卡
它将容器中的每个组件看作一张卡片。一次只能看到一张卡片,容器则充当卡片的堆栈。当容器第一次显示时,第一个添加到 CardLayout对象的组件为可见组件。卡片的顺序由组件对象本身在容器内部的顺序决定。CardLayout定义了一组方法,这些方法允许应用程序按顺序地浏览这些卡片,或者显示指定的卡片。
CardLayout()创建一个间距大小为 0 的新卡片布局。
CardLayout(int hgap, int vgap) 创建一个具有指定水平间距和垂直间距的新卡片布局
first/last/next/previous(Container parent)
示例:上图,创建两个Panel对象,每个Panel都有一个布局管理器,左边的使用GridLayout放置3个按钮,右边使用CardLayout放置5张卡片(用5个按钮模拟),主窗口使用BorderLayout放置这两个面板,按下左边Panel中的prev按钮依次向前显示,按下next依次向后显示,按下three,显示第三张卡片。
下面的代码是看到题后没看视频自己写的,也完成了指定的功能,等会再看看张老师怎么弄的
publicclassLayoutDemoextends Frame
{
Panel menu = new Panel();
Panel cards = new Panel();
CardLayout layout;
public LayoutDemo(String title)
{
super(title);
addWindowListener(new WindowAdapter()
{
publicvoid windowClosing(WindowEvent e)
{
dispose();
System.exit(0);
}
});
menu.setLayout(new GridLayout(0, 1));
Button previous = new Button("向前");
Button next = new Button("向后");
Button three = new Button("第三");
menu.add(previous);
menu.add(next);
menu.add(three);
layout =new CardLayout();
cards.setLayout(layout);
Button c1 = new Button("我是老大");
Button c2 = new Button("我是老二");
Button c3 = new Button("我是老三");
Button c4 = new Button("我是老四");
Button c5 = new Button("我是老五");
cards.add(c1);
cards.add(c2);
cards.add(c3);
cards.add(c4);
cards.add(c5);
previous.addActionListener(new ActionListener()
{
publicvoid actionPerformed(ActionEvent e)
{
LayoutDemo.this.layout.previous(LayoutDemo.this.cards);
}
});
next.addActionListener(new ActionListener()
{
publicvoid actionPerformed(ActionEvent e)
{
LayoutDemo.this.layout.next(LayoutDemo.this.cards);
}
});
three.addActionListener(new ActionListener()
{
publicvoid actionPerformed(ActionEvent e)
{
cards.getComponents()[2].setVisible(true);
//这个没有提供现成方法,查看next的源码后发现可以使用这种方法
//LayoutDemo.this.layout.previous(LayoutDemo.this.cards);
}
});
setSize(500, 400);
setLayout(new BorderLayout());
add(menu,"West");
add(cards,"Center");
setVisible(true);
}
publicstaticvoid main(String[] args)
{
LayoutDemo frame =new LayoutDemo("我的卡片管理器——布局管理器模拟");
}
}
看了张老师的讲解后直到,使用CardLayout布局的组件进行add添加时默认都调用了CardLayout.addLayoutComponent(Component comp, Object constraints)方法将组件加入到管理器中,所以添加卡片时应该加一个参数用来标记卡片以便于随机访问,比如一下到第3张
public void addLayoutComponent(Component comp,Object constraints)
将指定的组件添加到此卡片布局的内部名称表。constraints指定的对象必须是一个字符串。卡片布局将此字符串作为一个键-值对存储起来,该键-值对可用于对特定卡片的随机访问。通过调用 show方法,应用程序可以显示具有指定名称的组件。
public void show(Container parent,String name)
翻转到使用 addLayoutComponent添加到此布局的具有指定 name的组件。如果不存在这样的组件,则不发生任何操作。
转到第三张的地方可以直接使用layout.show(cards,第三张添加时的标记)
张老师添加按钮监听器的方法:创建一个内部监听器类,实现ActionListener接口,在处理事件的方法内部进行判断,到底点击了哪个按钮,这样不用new3个匿名对象了。
public void actionPerformed(ActionEvent e)
{使用e.getActionCommand可以得到事件源表面的文字String
if (e.getActionCommand().equals(按钮文字))
…………
}
u GridBagLayout:布局管理器之王,功能强大,使用复杂,在Swing中有更简单的办法
实现GridBagLayout的功能。
u 取消布局管理器
调用Container.setLayout(null)方法取消布局管理器设置,取消后,可以调用Component
的setBounds()方法用绝对坐标设置容器上每个组件的大小和位置。
不使用布局管理器存在的潜在问题:当容器大小改变时,所有组件仍保持在原来的位置和大小,导致整个程序界面非常难看。
Swing和JFC
AWT产生的GUI图形用户界面程序在各种平台上的显示效果并不是很好,为了解决这个问题,增加了新的第二代GUI开发工具包,所有的Swing组件位于javax.swing包中,它们是构筑在AWT上层的GUI组件,Swing组件是JComponent的子类,JComponent又是java.awt.Container的子类。所以可以把Swing组件当成AWT中的Container甚至是Component使用。AWT使用了与绝大多数平台相关的实现方式,Swing尽量与平台无关。
软件包 javax.swing提供一组“轻量级”(全部是 Java语言)组件,尽量让这些组件在所有平台上的工作方式都相同。
Swing提供了比AWT更多的组件库,如JTable,JTree,JComboBox。
Swing也增强了AWT中原有组件的功能,如AWT中的Button对应Swing中的JButton。
Java Foundation Classes (JFC)是指SUN对早期的JDK进行扩展的部分,集合了Swing组件和其他能简化开发的API类,包括Swing、java2D、accessibility、internationalization。
Swing只是JFC中的一项用户界面扩展技术,组成了JFC的用户界面功能的核心部分
示例:从AWT过渡到Swing
在主窗口框架中增加JButton组件与以前增加Button操作一样。
u JFrame:与AWT中的Frame相对应,功能相当,使用方式有区别
JFrame上只能有一个唯一的JRootPane组件,调用JFrame.getContentPane()方法可以获
得JFrame中内置的JRootPane对象。应用程序布恩那个直接在JFrame对象上增加组件和设置布局管理器,而应该在JRootPane对象上增加子组件和设置布局管理器。
与 Frame不同,当用户试图关闭窗口时,JFrame知道如何进行响应。用户关闭窗口时,默认的行为只是简单地隐藏 JFrame。要更改默认的行为,可调用方法setDefaultCloseOperation(int)。要使 JFrame的行为与 Frame 实例相同,请使用 setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE)。将值设置为JFrame.EXIT_ON_CLOSE时,单击JFrame窗口上的关闭按钮将直接关闭JFrame框架窗口并结束程序运行。没有调用这个方法时,点击关闭按钮窗口会隐藏但程序并没有结束。
public void setDefaultCloseOperation(int operation)
设置用户在此窗体上发起 "close"时默认执行的操作。必须指定以下选项之一:
DO_NOTHING_ON_CLOSE(在 WindowConstants中定义):不执行任何操作;要求程序在已注册的 WindowListener对象的 windowClosing 方法中处理该操作。
HIDE_ON_CLOSE(在 WindowConstants中定义):调用任意已注册的 WindowListener对象后自动隐藏该窗体。
DISPOSE_ON_CLOSE(在 WindowConstants中定义):调用任意已注册 WindowListener的对象后自动隐藏并释放该窗体。
EXIT_ON_CLOSE(在 JFrame中定义):使用 System exit方法退出应用程序。仅在应用程序中使用。
默认情况下,该值被设置为 HIDE_ON_CLOSE。更改此属性的值将导致激发属性更改事件,其属性名称为 "defaultCloseOperation"。
示例:使用JFrame创建程序的主框架窗口
AWT下的窗口框架
public class AWTFrame extends Frame
{
public AWTFrame(String title)
{
super(title);
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
dispose();
System.exit(0);
}
});
add(menu, "West");
setSize(500, 400);
setVisible(true);
}
public static void main(String[] args)
{
AWTFrame frame = new AWTFrame ("AWTFrame Demo");
}
}
Swing下的框架窗口
public class AWTFrame extendsFrameJFrame
{
public AWTFrame(String title)
{
super(title);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
dispose();
System.exit(0);
}
});
this.getContentPane().add(new JButton(“OK”));
add(new Button("OK"));
setSize(500, 400);
setVisible(true);
}
public static void main(String[] args)
{
AWTFrame frame = new AWTFrame ("AWTFrame Demo");
}
}
u JScrollPane:与AWT中的ScrollPane对应
最基本的JScrollPane由水平和垂直方向上的JScrollBar以及一个JViewport组成。
JViewport代表滚动窗口中的视图区域。调用JScrollPane.getViewport方法,可以获得代表滚动窗口中的视图区域的JViewport对象。可调用JVeiwport.setView方法,将滚动窗口中要显示的内容作为子组件增加到JViewport上。《Java指南The Java Tutorial》中有详细指导。
示例:使用JScrollPane创建滚动窗口,使用上面的框架代码
JScrollPane sp = new JScrollPane();
JTextArea ta = new JTextArea(50, 50);
sp.getViewport().add(ta);
this.getContentPane().add(sp);
u Swing中的标准对话框
在AWT中创建一个对话框都需要自己编写对话框内部的组件和事件处理方式,Swing
中提供了用来产生一些简单的标准对话框的类JOptionPane,它提供了若干个showXxxDialog的静态方法,用来产生简单的标准对话框。
方法名 |
描述 |
showConfirmDialog |
询问一个确认问题,如 yes/no/cancel。 |
showInputDialog |
提示要求某些输入。 |
showMessageDialog |
告知用户某事已发生。 |
showOptionDialog |
上述三项的大统一 (Grand Unification)。 |
当其中一个 showXxxDialog方法返回整数时,可能的值为: YES_OPTION
NO_OPTION CANCEL_OPTION OK_OPTION CLOSED_OPTION
示例:
显示一个错误对话框,该对话框显示的 message为 'alert':
JOptionPane.showMessageDialog(null, "alert", "alert", JOptionPane.ERROR_MESSAGE);
显示一个内部信息对话框,其 message为 'information':
JOptionPane.showInternalMessageDialog(frame, "information","information",
JOptionPane.INFORMATION_MESSAGE);
显示一个信息面板,其 options为 "yes/no",message为 'choose one':
JOptionPane.showConfirmDialog(null, "choose one", "choose one",
JOptionPane.YES_NO_OPTION);
显示一个内部信息对话框,其 options为 "yes/no/cancel",message为 'please choose
one',并具有 title信息:
JOptionPane.showInternalConfirmDialog(frame, "please choose one", "information",
JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE);
显示一个警告对话框,其 options为 OK、CANCEL,title为 'Warning',message为 'Click OK to continue':
Object[] options = { "OK", "CANCEL" };
JOptionPane.showOptionDialog(null, "Click OK to continue", "Warning",
JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,null,
options, options[0]);
显示一个要求用户键入 String的对话框:
String inputValue = JOptionPane.showInputDialog("Please input a value");
显示一个要求用户选择 String的对话框:
Object[] possibleValues = { "First", "Second", "Third" };
Object selectedValue = JOptionPane.showInputDialog(null, "Choose one", "Input",
JOptionPane.INFORMATION_MESSAGE, null,possibleValues, possibleValues[0]);
static int |
showConfirmDialog(前两个参数必选,后边的参数可选 Component parentComponent,父窗口可设置为null代表整个桌面 Object message,String title,对话框中的信息和标题信息 int optionType, 决定按钮个数样式,是确定取消、还是确定…… int messageType对话框提示信息的图标类型) 调用一个由 optionType参数确定其中选项数的对话框,messageType参数确定要显示的图标。 |
optionType - 指定可用于对话框的选项的整数:YES_NO_OPTION、YES_NO_CANCEL_OPTION或 OK_CANCEL_OPTION
messageType - 指定此消息种类的整数;主要用于确定来自可插入外观的图标:ERROR_MESSAGE、INFORMATION_MESSAGE、WARNING_MESSAGE、QUESTION_MESSAGE或 PLAIN_MESSAGE
当其中一个 showXxxDialog方法返回整数时,可能的值为:YES_OPTION
NO_OPTION CANCEL_OPTION OK_OPTION CLOSED_OPTION
示例:使用JOptionPane类,在程序开始运行时弹出一个对话框提示用户程序开始运行,在主框架窗口的关闭按钮被单击时,弹出一个对话框询问用户是否真的要结束程序运行,选是退出,选否继续运行。
public class SwingFrame extends JFrame
{
public SwingFrame(String title)
{
super(title);
要在点击关闭按钮时进行判断,就是让JFrame什么也不做,由程序自己控制,就不能使用这个处理方式了this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setDefaultCloseOperation(WindowConstants.
DO_NOTHING_ON_CLOSE
);
程序一运行就弹出一个对话框,父窗口设置为null为桌面
JOptionPane.showMessageDialog(null, “程序开始运行了”);
将默认的关闭按钮点击效果去了,就要增加自己的监听器
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
弹出一个询问对话框,父窗口要用主框架窗口SwingFrame.this
会返回一个值,表示选择的按钮
if (JOptionPane.OK_OPTION ==JOptionPane.showConfirmDialog(
SwingFrame.this, “真的要退出吗?”,“标题:结束程序”,
JOptionPane.OK_CANCLE_OPTION,图标类型))
{ 如果选的是OK按钮就退出
dispose();
System.exit(0);
}
}
});
this.getContentPane().add(new JButton(“OK”));
setSize(500, 400);
setVisible(true);
}
public static void main(String[] args)
{
SwingFrame frame = new SwingFrame("SwingFrame Demo");
}
}
Swing中提供了一个JFileChooser类专门用来实现文件存取对话框
以下代码弹出一个针对用户主目录的文件选择器,其中只显示 .jpg和 .gif图像:
JFileChooser chooser = new JFileChooser();
FileNameExtensionFilter filter=new FileNameExtensionFilter("JPG & GIF Images", "jpg", "gif");
chooser.setFileFilter(filter);
int returnVal = chooser.showOpenDialog(parent);
if(returnVal == JFileChooser.APPROVE_OPTION) {System.out.println("You chose to open this
file: "+chooser.getSelectedFile().getName());
}
u 计算器界面程序实现:用Swing实现
单击任意按钮,按钮上面的符号数字从左到右的顺序显示在文本框中,右对齐
1、要使用两个布局管理器,将16个按钮用GridLayout放在一个Panel面板上,将文本框和Panel使用BorderLayout放在主窗口中。
public class Calculator extends JFrame implements ActionListener
{
public Calculator (String title)
{不用super也可以会自动调用父类对应方法
super(title);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
不能直接往主窗口中添加组件,需要得到JRootPane对象
Container c = this.getContentPane();
JTextField jtf = new JTextField();
设置文本框对齐方式
jtf.setHorizontalAlignment(JTextField.RIGHT);
JPanel jp = new JPanel();
c.add(jtf, “North”); c.add(jp, “Center”);
往JPanel中增加16个按钮
jp.setLayout(new GridLayout(4, 4));
每个按钮都要增加时间监听器,所以不能用匿名按钮
JButton b1 = new JButton(“1”);
给按钮增加事件监听器,用这个类实现ActionListener接口让它本身作为监听器
b1.addActionListener(this);
将Button增加到面板上
jp.add(b1);
重复上面的操作,增加其他按钮,注意不用定义16个按钮变量,b1加到面板后就不用了,可以用b1指向新创建的其他按钮即可
b1 = new JButton(“2”); b1.addActionListener(this); jp.add(b1);……重复吧
覆盖按钮事件处理代码
public void actionPerformee(ActionEvent e)
{将按钮上的文字放进文本框 需把jtf定义成成员变量
直接使用这行代码,后面点击会覆盖前面的内容jtf.setText(e.getActionCommand())
要不覆盖,就要先取出原来的内容,再放在当前内容的前面
jtf.setText(jtf.getText()+e.getActionCommand());
上面已经完成了点击后显示在文本框中的功能,但文字是左对齐的
JTextField.setHorizontalAlignment()方法可以设置对齐方式
}
}
public static void main(String[] args)
{
Calculator frame = new Calculator ("Calculator Demo");
frame.setSize(500, 400);
frame.setVisible(true);
}
}
JTextField. setHorizontalAlignment
public void setHorizontalAlignment(int alignment)
设置文本的水平对齐方式。有效值包括: JTextField.LEFT、JTextField.CENTER、JTextField.RIGHT、JTextField.LEADING、JTextField.TRAILING,当设置对齐方式时,调用 invalidate 和 repaint,并且激发 PropertyChange事件。
u BoxLayout布局管理器:Swing中新加的一种布局管理器
允许多个组件全部垂直或全部水平摆放。
示例:使用BoxLayout实现计算器界面
u 学习和开发GUI程序的建议
重点掌握GUI程序的一些基本原理和开发过程,通过学习AWT组件可以更容易掌握GUI程序的基本原理和开发过程,但在GUI程序开发中应尽量使用Swing组件,因为它功能更丰富,与平台无关,易于移植。
查阅JDK文档中的Swing包,通读其中所包含的组件以了解Swing提供了哪些组件,但不必细读每个组件的具体使用帮助。
只在用到某个GUI组件时,才有必要仔细阅读这个组件的具体使用帮助。如果要快速掌握某个新遇到的组件的用法,最好能找到并参阅该组件的例子程序。
在JDK的Demo程序目录或《Java指南The Java Tutorial》中,都能找到某些组件的应用范例。
参考别人代码。
u 思考与实践
1、什么是事件、事件源与事件处理器,描述三者工作关系。
事件:用户对组件的一个操作,称为一个事件
事件源:发生事件的组件就是事件源
事件处理器:某个Java类中负责处理事件的成员方法
事件源、事件、事件处理器之间的工作关系
2、事件处理器的编码实现过程。
事件处理流程:
要处理发生在某个GUI组件上的某类事件XXXEvent的某种具体情况,事件处理通用编写流程:
a、编写一个实现对应事件监听器接口XXXListener的监听器类。
b、在监听器类中处理相应事件的方法中编写具体处理代码,其他情况不处理可以空实现
c、调用组件的addXXXListener方法,将这个监听器类对象注册到GUI组件上。
3、事件监听器类和事件适配器类的关系与区别。
自己编写事件监听器需要实现对应事件监听器接口中的所有方法,为了简化编写过程,
JDK中提供了大多数事件监听器接口的最简单的实现类,称为事件适配器(Adapter)。
这样编写监听器时只需继承相应的事件适配器,覆盖自己想要处理情况的事件处理方法即可,其他情况由事件适配器默认方式处理。
4、在窗口上画直线程序的编写过程和组件重绘原理。
a、产生框架窗口frame
b、在框架窗口中添加鼠标事件监听器,覆盖其中的mousePressed和mouseReleased方法
窗体重绘后不包括窗体组件上通过Graphics画出来的图形。系统只记录了窗体上的信
息,并没有记录组件表面的内容。paint(Graphics g)窗体移动,改变大小等等需要重绘、曝光 窗体组件被重新绘制时,AWT线程会调用窗体组件的paint(Graphics g)方法将显卡中保存的窗体信息绘制出来。
默认重绘不包括组件表面的内容,如上个程序中画出来的直线和坐标值,如果要让它们也重绘,可以修改窗体中的paint方法,将原来的内容重新画出来。
AWT线程对组件重绘的调用过程
paint方法是由AWT线程管理调度的,应用程序不应直接调用paint方法,需要曝光刷新重绘时,就要使用paint方法,但不要直接调用paint,先调用repaint方法,repaint会调用update方法,update方法内部会现将组件表面的内容清空,再调用paint进行重绘。
5、为前边讲的自定义计时器组件增加功能:允许程序设置计时器显示文本的颜色,时间文本的字体大小随组件大小的改变而改变。
6、结合JDK帮助文档,编写一个使用FileDialog选择文件名的例子程序。单击主窗口上的打开按钮,打开一个文件对话框,并将选择的文件路径显示在主窗口上的打开按钮左边的文本框中。在文件对话框中可以对文件类型进行过滤,每次打开文件对话框时,对话框中的初始显示目录为上次选择的文件所在的目录。
7、修改前面实现计算器界面的程序代码,将增加单个按钮的代码用一个函数来实现,然后在一个循环语句中调用这个函数来增加所有的按钮。