社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
张老师的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功能的组件,它代表屏幕上的一块空白矩形区域,
原文链接:https://blog.csdn.net/luck638/article/details/8541675
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!