write:2022-3-24
1. 基本的图形用户组件
GUI的基本类库位于java.awt包中,这个包也被称为抽象窗口工具箱(AWT,Abstract Window Toolkit)。
AWT按照面向对象的思想来创建GUI,它提供了容器类、众多的组件类和布局管理器类。
1.1 GUI的基本类库
Component类:抽象组件类(所有组件类的父类)——Container子类:容器类
1.1.1 Component组件类
Component类中声明了所有组件都拥有的方法:
getBackground():返回组件的背景色。
getGraphics():返回组件使用的画笔。
isVisible():判断组件是否可见。
setBackground(Color c):设置组件的背景色。
setForeground(Color c):设置组件的前景色。
setVisible(boolean b):设置组件是否可见。
1.1.2 Container类
Container类表示容器,继承了Component类。容器用来存放别的组件,有两种类型的容器:
Window(窗口)类:Window是不依赖于其他容器而独立存在的容器。Window 有两个子类:Frame(窗体)类和Dialog(对话框)类。Frame带有标题,而且可以调整大小。Dialog可以被移动,但是不能改变大小。
Panel(面板)类:Panel不能单独存在,只能存在于其他容器(Window或其子类)中。一个Panel对象代表了一个长方形的区域,在这区域中可以容纳其他的组件。
1.2 AWT组件和Swing组件
在java.awt包中,还提供了可以加入到容器类组件中的各种具体组件,如按钮Button、文本框TextField和文本区域TextArea等。AWT组件的优点是简单、稳定,兼容于任何一个JDK版本,缺点是依赖于本地操作系统的GUI,缺乏平台独立性。由于AWT组件与本地平台的GUI绑定,因此用AWT组件创建的图形界面在不同的操作系统中会有不同的外观。
为了使用Java语言创建的图形界面也能够跨平台,即在不同操作系统中保持相同的外观,从JDK1.2版本开始引入了Swing组件,这些Swing组件位于javax.swing包中。
Swing组件类框图:(Swing组件都以字母“J”开头)
1.3 创建图形用户界面的基本步骤
JFrame窗体类有一个构造方法JFrame(String title),通过它可以创建一个以参数为标题的JFrame对象。
当JFrame被创建后,它是不可见的,必须通过以下方式使JFrame成为可见的:
1)先调用setSize(int width,int height)显式设置JFrame的大小,或者调用pack()方法自动确定JFrame的大小,pack()方法会确保JFrame容器中的组件都会有与布局相适应的合理大小。
2)然后调用setVisible(true)方法使JFrame成为可见的。
1.3.1:创建一个简单的Frame窗体
public static void main(String[] args) {
JFrame jFrame = new JFrame("perth"); //设置标题
jFrame.setSize(300,100); //设置高宽
jFrame.setVisible(true); //设置可见
}
运行结果:
1.3.2:创建一个包含按钮的Frame窗体
public static void main(String[] args) {
JFrame jFrame = new JFrame("perth"); //设置窗体标题
JButton jButton = new JButton("Swing Button"); //设置Button中的文字
jButton.setMnemonic('i'); //设置快捷键:用户按钮Alt-i键等价于点击该Button
jButton.setToolTipText("Press me"); //设置鼠标移动到该Button时的提示信息
jFrame.add(jButton); //将Button加入到该窗体,默认加入到中央区域
jFrame.setDefaultCloseOperation(jFrame.EXIT_ON_CLOSE); //当用户选择窗体的关闭图标,将结束程序
jFrame.pack(); //自动确定JFrame的大小
jFrame.setVisible(true); //设置窗体可见
}
运行结果:
1.3.3设置关闭窗体的操作
JFrame的setDefaultCloseOperation(int operation)方法用来决定如何响应用户关闭窗体的操作,参数operation有以下可选值:
JFrame.DO_NOTHING_ON_CLOSE:什么也不做。
JFrame.HIDE_ON_CLOSE :隐藏窗体,这是JFrame的默认选项。 (看不见,但存在)
JFrame.DISPOSE_ON_CLOSE:销毁窗体。(看不见,再也不存在,程序仍在运行中)
JFrame.EXIT_ON_CLOSE :结束程序。
2. 布局管理器
当一个容器被创建后,它们有相应的默认布局管理器。JFrame的默认布局管理器是BorderLayout,这意味着与它关联的内容面板的布局管理器也是BorderLayout。JPanel的默认布局管理器是FlowLayout。
程序可以通过容器类的setLayout(Layout layout)方法来重新设置容器的布局管理器。例如以下代码把JFrame的布局管理器设为FlowLayout:
JFrame jFrame=new JFrame(“Hello”);
jFrame.setLayout(new FlowLayout()) ;
2.1 布局管理器种类
java.awt包提供了5种布局管理器:
FlowLayout 流式布局管理器
BorderLayout边界布局管理器
GridLayout网格布局管理器
CardLayout卡片布局管理器
GridBagLayout网格包布局管理器。
另外,javax.swing包还提供了一种BoxLayout布局管理器
此处重点讲解前3中布局管理器:
2.1.1 FlowLayout布局管理器
FlowLayout是最简单的布局管理器,按照组件的添加次序将它们从左到右地放置在容器中。当到达容器边界时,组件将放置在下一行中。FlowLayout允许以左对齐、居中对齐(默认方式)或右对齐的方式排列组件。FlowLayout的特性如下:
1)不限制它所管理的组件的大小,而是允许它们有自己的最佳大小。
2)当容器被缩放时,组件的位置可能会变化,但组件的大小不改变。
eg:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class FlowLayoutDemo {
public static void main(String[] args) {
final JFrame jFrame = new JFrame("FlowLayout");
//创建面板
JPanel jPanel = new JPanel();
final FlowLayout f1 = (FlowLayout) jPanel.getLayout();
jFrame.setContentPane(jPanel); //JFrame以JPanel作为内容面板(可理解为将JPanel加入JFrame)
JButton leftButton = new JButton("left");
leftButton.addActionListener(new ActionListener() { //注册事件监听器
@Override
public void actionPerformed(ActionEvent e) {
f1.setAlignment(FlowLayout.LEFT); //设置对齐方式为左对齐
f1.layoutContainer(jPanel); //使面板重新布局
}
});
JButton centerButton = new JButton("center");
centerButton.addActionListener(new ActionListener() { //注册事件监听器
@Override
public void actionPerformed(ActionEvent e) {
f1.setAlignment(FlowLayout.CENTER); //设置对齐方式为居中对齐
f1.layoutContainer(jPanel); //使面板重新布局
}
});
JButton rightButton = new JButton("right");
rightButton.addActionListener(new ActionListener() { //注册事件监听器
@Override
public void actionPerformed(ActionEvent e) {
f1.setAlignment(FlowLayout.RIGHT); //设置对齐方式为右对齐
f1.layoutContainer(jPanel); //使面板重新布局
}
});
//加入按钮
jFrame.add(leftButton);
jFrame.add(centerButton);
jFrame.add(rightButton);
jFrame.setDefaultCloseOperation(jFrame.EXIT_ON_CLOSE); //当用户选择窗体的关闭图标,将结束程序
jFrame.setSize(400,100);
jFrame.setVisible(true); //设置窗体可见
}
}
运行结果:
按下left按钮:
按下right按钮:
当窗体大小发生改变时:
默认情况下,FlowLayout采用居中对齐方式,如果改变JFrame的大小,每个JButton的尺寸保持不变,不过JButton与JFrame之间的相对位置会发生变化
2.1.2 BorderLayout布局管理器
BorderLayout为在容器中放置组件提供了一个稍微复杂的布局方案。BorderLayout把容器分为五个区域:东、南、西、北和中。北占据容器的上方,东占据容器的右侧,以此类推。中间区域是在东、南、西和北都填满后剩下的区域。
BorderLayout的特性如下:
- 位于东和西区域的组件保持最佳宽度,高度被垂直拉伸至和所在区域一样高;位于南和北区域的组件保持最佳的高度,宽度被水平拉伸至和所在区域一样宽;位于中部区域的组件的宽度和高度都被拉伸至和所在区域一样大小。
- 当窗口垂直拉伸时,东、西和中区域也拉伸;而当窗口水平拉伸时,南、北和中区域也拉伸。
- 对于容器的东、南、西和北区域,如果某个区域没有组件,这个区域面积为零;对于中部区域,不管有没有组件,BorderLayout都会为它分配空间,如果该区域没有组件,那么在中部区域显示容器的背景颜色。
- 当容器被缩放时,组件所在的相对位置不变化,但组件大小改变。
- 如果在某个区域添加的组件不止一个,只有最后添加的一个是可见的。
eg:简单案例
因为东南西北4个区域没有组件,所以中间区域占据整个窗体,按钮的添加默认居中,所以b1被遮挡,只显示b2;
eg:更复杂案例
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class BorderLayoutDemo extends JFrame{
private final String names[]={"Hide North","Hide South", "Hide East","Hide West","Hide Center"};
private final String locations[]={BorderLayout.NORTH,BorderLayout.SOUTH, BorderLayout.EAST,
BorderLayout.WEST,BorderLayout.CENTER};
private JButton[] buttons=new JButton[5];
public BorderLayoutDemo(String title){
super(title);
final BorderLayout layout=(BorderLayout)this.getLayout();
ActionListener listener=new ActionListener(){ //事件监听器
public void actionPerformed(ActionEvent event){
for(int i=0;i<buttons.length;i++)
if(event.getSource()==buttons[i])
buttons[i].setVisible(false); //隐藏用户选择的按钮
else
buttons[i].setVisible(true);
layout.layoutContainer(BorderLayoutDemo.this); //重新调整JFrame的布局
}
};
for(int i=0;i<buttons.length;i++){
buttons[i]=new JButton(names[i]);
buttons[i].addActionListener(listener);
add(buttons[i],locations[i]); //在JFrame的各个区域加入JButton
}
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(250,250);
setVisible(true);
}
public static void main(String args[]){
new BorderLayoutDemo("Hello");
}
}
运行结果:
2.1.3 GridLayout布局管理器
GridLayout将容器分割成许多行和列,组件被填充到每个网格中。添加到容器中的组件首先放置在左上角的网格中,然后从左到右放置其他组件,只至占满该行的所有网格,接着继续在下一行中从左到右地放置组件。
GridLayout的特性如下:
- 组件的相对位置不随区域的缩放而改变,但组件的大小会随之改变。组件始终占据网格的整个区域。
- GridLayout总是忽略组件的最佳大小,所有组件的宽度相同,高度也相同。
- 将组件用add()方法添加到容器中的先后顺序决定了它们占据哪个网格。GridLayout从左到右,从上到下将组件填充到容器的网格中。
GridLayout的构造方法:
GridLayout(a,b,c,d) :a ——行数 b ——列数 c ——水平格子间距离 d ——垂直格子间距离
GridLayout(a,b) :a ——行数 b ——列数 (因为此时格子间距离为0,格子间紧紧靠在一起,c,d可不写)
eg:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class GridLayoutDemo extends JFrame {
private final String names[]={"one","two","three","four","five","six"};
private JButton[] buttons=new JButton[6];
private boolean flag=true;
final GridLayout layout1 = new GridLayout(2,3,5,10);
final GridLayout layout2 = new GridLayout(3,2);
public GridLayoutDemo(String title){
super(title);
setLayout();
ActionListener listener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setLayout(); //切换网格布局
GridLayoutDemo.this.validate(); //使新的布局生效
}
};
for (int i = 0; i < buttons.length; i++) {
buttons[i] = new JButton(names[i]);
buttons[i].addActionListener(listener); //添加监听事件
add(buttons[i]);
}
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(250,250);
setVisible(true);
}
private void setLayout(){
if (flag)
setLayout(layout1);
else
setLayout(layout2);
flag=!flag;
}
public static void main(String[] args) {
new GridLayoutDemo("Hello");
}
}
运行结果:
按下任意按钮:
2.1.4 同时使用多个布局管理器
3. 事件处理机制
事件:点击按钮,选择一个菜单,点击键盘,移动鼠标
事件源:每一个可以触发事件的组件被当作事件源。
事件监听器:每一种事件都对应专门的监听器。监听器负责接收和处理这种事件。
一个事件源可以触发多种事件,如果它注册了某种事件的监听器,那么这种事件就会被接收和处理。由于事件源本身不处理事件,而是委托相应的事件监听器来处理,这种设计模式被称为委托模式。
3.1 事件类和事件监听接口
每个具体的事件都是某种事件类的实例,事件类包括:ActionEvent、ItemEvent、MouseEvent、KeyEvent、FocusEvent和WindowEvent等。
每个事件类对应一个事件监听接口,例如ActionEvent 对应ActionListener,ItemEvent对应ItemListener、MouseEvent对应MouseListener,KeyEvent对应KeyListener。
eg:为JButton注册ActionEvent的监听器
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class OuterCounter extends JFrame {
private JButton jButton = new JButton("1");
public OuterCounter(String title){
super(title);
//把MyListener的实例注册为JButton的监听器
jButton.addActionListener(new MyListener(2));
add(jButton);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(100,100);
setVisible(true);
}
public static void main(String[] args) {
new OuterCounter("Hello");
}
}
class MyListener implements ActionListener{
private int step; //决定JButton上的标号每次增加的步长
public MyListener(int step) { this.step = step; }
@Override
public void actionPerformed(ActionEvent e) {
JButton button=(JButton)e.getSource(); //获得事件源
int count=Integer.parseInt(button.getText());
button.setText(new Integer(step+count).toString()); //把JButton上的标号加step
}
}
运行结果:(每按一次,标号就递增2)
3.2 使用监听接口的适配器
接口MouseListener一共定义了五个方法:mousePressed()、mouseReleased()、mouseEntered()、mouseExited()和mouseClicked()。而在实际应用中,往往不需要实现接口中所有的方法。为了编程的方便,AWT为部分方法比较多的监听接口提供了适配器类,这些类尽管实现了监听接口的所有方法,但实际上方法体都为空。
区别:若写实现MouseListener接口,则需要在实现类中重写所有的方法,但若写子类继承MouseAdapter类,可有选择的重写其中的方法
比如MouseListener的适配器类为MouseAdapter,它的定义如下:
public abstract class MouseAdapter implements MouseListener {
public void mouseClicked(MouseEvent e) {}
public void mousePressed(MouseEvent e) {}
public void mouseReleased(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
}
eg:
import javax.swing.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class AdapterCounter extends JFrame {
private JButton button = new JButton("1");
public AdapterCounter(String title){
super(title);
button.addMouseListener(new MyMouseListener(2));
add(button);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(100,100);
setVisible(true);
}
public static void main(String args[]){
new AdapterCounter("hello");
}
}
class MyMouseListener extends MouseAdapter{ //注意:此处是继承适配器,因此只需重写需要的方法即可
private int step; //决定JButton上的标号每次增加的步长
public MyMouseListener(int step) { this.step = step; }
@Override
public void mousePressed(MouseEvent e){ //重写鼠标按下方法
JButton button=(JButton)e.getSource(); //获得事件源
int count=Integer.parseInt(button.getText());
button.setText(new Integer(step+count).toString()); //把JButton上的标号加step
}
}
运行结果同上。
4. 绘图
在Component类中提供了以下与绘图有关的方法:
paint (Graphics g):绘制组件的外观。
repaint():调用paint()方法,刷新组件的外观。
4.1 调用paint()方法的时机
除了main主线程外,程序还会创建一个AWT线程:
因为paint方法绘制外观,因此paint方法由AWT线程执行;
在以下几种情况,AWT线程会执行组件的paint()方法:
当组件第一次在屏幕上显示时,AWT线程会自动调用paint()方法来绘制组件。
用户在屏幕上伸缩组件,使组件的大小发生变化,此时AWT线程会自动调用组件的paint()方法,刷新组件的外观。
用户在屏幕上最小化界面,然后又恢复界面,此时AWT线程会自动调用组件的paint()方法,重新显示组件的外观。
程序中调用repaint()方法,该方法会促使AWT线程尽可能快地执行组件的paint()方法。
4.2 JComponent类的绘图方法
JComponent类覆盖了Component类的paint()方法。JComponent类的paint()方法把绘图任务委派给三个protected类型的方法来完成:
paintComponent():画当前组件。
paintBorder():画组件的边界。
paintChildren():如果组件为容器,则画容器所包含的组件。
对于自定义的Swing组件,如果需要绘制图形,只需要覆盖paintComponent()方法。
JComponent类的paintComponent()方法会以组件的背景色来覆盖整个组件区域。
4.3 Graphics类
Component类的paint(Graphics g)方法有一个java.awt.Graphics类型的参数。Graphics类代表画笔,提供了绘制各种图形的方法,常见的有:
drawLine(int x1,int y1,int x2, int y2) :画一条直线。参数(x1,y1)和(x2,y2)设定直线的起始和终止坐标。
drawString(String string,int left,int bottom) :写一个字符串。
drawImage(Image image,int left, int top, ImageObserver observer) :画一个图片。
drawRect(int left,int top,int width,int height):画一个矩形。
drawOval(int x, int y, int width, int height) :画一个椭圆。
fillRect(int left,int top,int width,int height):填充一个矩形。
fillOval(int x, int y, int width, int height):填充一个椭圆。
eg:
import javax.swing.*;
import java.awt.*;
public class OvalDrawer extends JPanel implements Runnable {
private Color[] colors={Color.RED,Color.BLACK,Color.BLUE,
Color.GREEN,Color.DARK_GRAY};
private Color color; //当前要绘制的图形的颜色
private int x=10,y=10,width=10,height=10;
public OvalDrawer(){
new Thread(this).start();
}
@Override
public void run() {
while(true){
x=(int)(Math.random()*300); //random():0-1的随机数
y=(int)(Math.random()*300);
width=(int)(Math.random()*100);
height=(int)(Math.random()*100);
color=colors[(int)(Math.random()*(colors.length-1))];
repaint();
try{Thread.sleep(400); }
catch(InterruptedException e){ throw new RuntimeException(e); }
}
}
public void paintComponent(Graphics g){
//调用父类的paintComponent(g)方法会导致先清空面板,
//使得上次所画的椭圆被删除。
//super.paintComponent(g);
g.setColor(color);
g.fillOval(x,y,width,height); //画椭圆
}
public static void main(String[] args) {
JPanel panel = new OvalDrawer();
JFrame frame = new JFrame("Hello");
frame.setContentPane(panel); //panel不能独立存在,放在窗口中
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300,300);
frame.setVisible(true);
}
}
运行结果:
5. 课堂小结
- Component组件类可分为Container容器类与其他非容器类。Container容器类分为两种:Window和Panel。Window是可以不依赖于其他容器而独立存在的容器,Window 有两个子类:Frame和Dialog。Panel只能存在于其他的容器(Window或其子类)中。
- java.awt包中的AWT组件依赖于本地操作系统的GUI,缺乏平台独立性。而Swing组件可以跨平台运行,是一种轻量级组件。Swing组件位于javax.swing包中,多数Swing组件类都以大写字母“J”开头。除JFrame和JDialog以外,其余的Swing组件都继承了JComponent类。
- Window、Frame和Dialog、JFrame和JDialog的默认布局管理器是BorderLayout,Panel和JPanel的默认布局管理器是FlowLayout。可以通过容器类的setLayout(Layout)方法来改变容器的布局管理器。
- FlowLayout布局管理器会始终保证每个组件的最佳尺寸,BorderLayout把容器分为五个区域,如果在同一个区域加入多个组件,只有最后一个组件是可见的。
- AWT处理事件采用委托模式。组件本身不处理事件,而是委托监听器来处理。
- 在Component类中提供了与绘图有关的方法:负责绘制组件外观的paint()方法和刷新组件外观的repaint()方法。
- JComponent类覆盖了Component类的paint()方法。JComponent类的paint()方法把绘图任务委派给三个protected类型的方法来完成:
负责画当前组件的paintComponent()方法
负责画组件边界的paintBorder()方法
负责画容器所包含组件的paintChildren()方法 - 对于用户自定义的Swing组件,如果需要绘制特定的图形,只需要覆盖paintComponent()方法。