AWT基础入门(GUI编程)
AWT编程
1.AWT简介
当JDK 1.0发布时,Sun提供了一套基本的GU|类库,这个GUI类库希望可以在所有平台下都能运行,这套基本类库被称为"抽象窗口工具集CAbstract Window Toolkit )”,它为java应用程序提供了基本的图形组件。AWT是窗口框架,它从不同平台的窗口系统中抽取出共同组件,当程序运行时,将这些组件的创建和动作委托给程序所在的运行平台。简而言之,当使用AWT编写图形界面应用时,程序仅指定了界面组件的位置和行为,并未提供真正的实现,JVM调用操作系统本地的图形界面来创建和平台一致的对等体。
使用AWT创建的图形界面应用和所在的运行平台有相同的界面风格,比如在Windows 操作系统上,它就表现出 Windows风格;在UNIX操作系统上,它就表现出UNIX风格。Sun希望采用这种方式来实现" write Once,Run Anywhere "的目标。
2.AWT继承体系
所有和AWT编程相关的类都放在java.awt包以及它的子包中,AWT编程中有两个基类:Component和MenuComponent。
- Componet:代表一个能以图形化方式显示出来,并可与用户交互的对象,例如Button代表一个按钮,TextField代表一个文本框等;
- MenuComponent:则代表图形界面的菜单组件,包括MenuBar(菜单条)、MenuItem(菜单项)等子类。
![]()
其中Container是一种特殊的Component,它代表一种容器,可以盛装扑通的Component。
AWT中还有一个非常重要的接口叫LayoutManager,如果一个容器中有多个组件,那么容器就要使用LayoutManager来管理这些组件的布局方式。

3.Container容器
1.Container继承体系

- Window是可以独立存在的顶级窗口,默认使用BorderLayout管理其内部组件布局;
- Panel可以容纳其它组件,但不能独立存在,它必须内嵌其他容器中使用,默认使用FlowLayout管理其内部组件布局;
- ScrollPane:是一个带滚动条的容器,它也不能独立存在,默认使用BorderLayout管理其内部组件布局;
2.常见API
Component作为基类,提供了如下常用的方法来设置组件的大小、位置、可见性等。
| 方法签名 | 方法功能 |
|---|---|
| setLocation(int x,int y); | 设置组件的位置。 |
| setSize(int width,int height); | 设置组件的大小。 |
| setBounds(intx,int y,int width,int height); | 同时设置组件的位置、大小。 |
| setVisible(Boolean b); | 设置该组件的可见性。 |
Container作为容器根类,提供了如下方法来访问容器中的组件
| 方法签名 | 方法功能 |
|---|---|
| Component add(Component comp); | 向容器中添加其它组件(该组件既可以是普通组件,也可以是容器),并返回被添加的组件。 |
| Component getComponentAt(int x,int y); | 返回指定点的组件。 |
| int getComponentCount(); | 返回该容器内组件的数量。 |
| Component[] getComponents(); | 返回该容器内的所有组件。 |
2.容器演示
//1.创建一个对象窗口
Frame frame = new Frame("这是一个window");
//2.指定窗口的位置,大小
frame.setLocation(100,100);
frame.setSize(500,500);
//3.设置窗口对象可见
frame.setVisible(true);
3.Panel
//1.创建一个window对象,因为,panel以及其它的容器,都不能独立存在,必须依附在window存在
Frame frame = new Frame("这是演示Panel");
//2.创建一个Panel对象
Panel panel = new Panel();
//3.创建一个文本框和一个按钮,并且把他们放入到Panel容器中
panel.add(new TextField("这里是一个测试文本"));
panel.add(new Button("这里是一个测试按钮"));
//4.把Panel放入到window中
frame.add(panel);
//5.设置window的位置以级大小
frame.setBounds(100,100,500,500);
//6.设置window可见
frame.setVisible(true);
4.ScrollPane
Frame frame = new Frame("这里演示ScrollPane");
//1.创建一个ScrollPane对象
ScrollPane sp = new ScrollPane(ScrollPane.SCROLLBARS_ALWAYS);
//2.往ScrollPane中添加内容
sp.add(new TextField("则会使测试文本"));
sp.add(new Button("这是测试按钮"));
//3.把ScrollPane添加到Frame中
frame.add(sp);
frame.setBounds(100,100,500,500);
frame.setVisible(true);
4.LayoutManager布局管理器
之前,我们介绍了Component中有一个方法setBounds()可以设置当前容器的位置和大小,但是我们需要明确一件事,如果我们手动的为组件设置位置和大小的话,就会造成程序的不通用性,例如∶
Label label = new Label("你好,世界")
创建了一个lable组件,很多情况下,我们需要让lable组件的宽高和"你好,世界"这个字符串自身的宽高一致,这种大小称为最佳大小。由于操作系统存在差异,例如在windows上,我们要达到这样的效果,需要把该Lable组件的宽和高分别设置为100px,20px,但是在Linux操作系统上,可能需要把Lable组件的宽和高分别设置为120px ,24px,才能达到同样的效果。
如果要让我么的程序在不同的操作系统下,都有相同的使用体验,那么手动设置组件的位置和大小,无疑是一种灾难,因为有太多的组件,需要分别设置不同操作系统下的大小和位置。为了解决这个问题,Java提供了LayoutManager布局管理器,可以根据运行平台来自动调整组件大小,程序员不用再手动设置组件的大小和位置了,只需要为容器选择合适的布局管理器即可。

1.FlowLayout
在FlowLayout布局管理器中,组件像水流一样向某方向流动(排列),遇到障碍(边界)就折回,重头开始排列。在默认情况下,FlowLayout布局管理器从左向右排列所有组件,遇到边界就会折回下一行重新开始。
| 构造方法 | 方法功能 |
|---|---|
| FlowLayout() | 使用默认的对齐方式及默认的垂直间距、水平间距创建FlowLayout布局管理器。 |
| FlowLayout(int align) | 使用指定的对齐方式及默认的垂直间距、水平间距创建FlowLayout布局管理器。 |
| FlowLayout(int align,int hgap,int vgap) | 使用指定的对齐方式及指定的垂直问距、水平间距创建FlowLayout布局管理器。 |
FlowLayout 中组件的排列方向(从左向右、从右向左、从中间向两边等),该参数应该使用FlowLayout类的静态常量:FlowLayout.LEFT、FlowLayout.CENTER、FlowLayout.RIGHT,默认是左对齐。
Frame frame = new Frame("这里测试FlowLayout");
//1.通过setLayout方法设置容器的布局管理器
//frame.setLayout(new FlowLayout(FlowLayout.Left,20,20));
// frame.setLayout(new FlowLayout(FlowLayout.CENTER,20,20));
frame.setLayout(new FlowLayout(FlowLayout.RIGHT,20,20));
//2.添加多个按钮到frame中
for (int i = 0; i < 100; i++) {
frame.add(new Button("按钮"+i));
}
frame.pack();
frame.setVisible(true);
2.BorderLayout
BorderLayout将容器分为EAST、SOUTH、WEST、NORTH、CENTER五个区域,普通组件可以被放置在这5个区域的任意一个中。BorderLayout布局管理器的布局示意图如图所示。

当改变使用BorderLayout的容器大小时,NORTH、SOUTH和CENTER区域水平调整,而EAST、WEST和、CENTER区域垂直调整。使用BorderLayout有如下两个注意点:
- 当向使用BorderLayout布局管理器的容器中添加组件时,需要指定要添加到哪个区域中。如果没有指定添加到哪个区域中,则默认添加到中间区域中;
- 如果向同一个区域中添加多个组件时,后放入的组件会覆盖先放入的组件
| 构造方法 | 方法功能 |
|---|---|
| BorderLayout() | 使用默认的水平间距、垂直间距创建BorderLayout布局管理器。 |
| BorderLayouot(int hgap,int vgap) | 使用指定的水平间距、垂直间距创建BorderLayout布局管理器。 |
3.GridLayout
GridLayout布局管理器将容器分割成纵横线分隔的网格,每个网格所占的区域大小相同。当向使用GridLayout布局管理器的容器中添加组件时,默认从左向右、从上向下依次添加到每个网格中。与FlowLayout不同的是,放置在GridLayout布局管理器中的各组件的大小由组件所处的区域决定(每个组件将自动占满整个区域)。
| 构造方法 | 方法功能 |
|---|---|
| GridLayout(int rows,int cols) | 采用指定的行数、列数,以及默认的横向间距、纵向间距将容器分割成多个网格 |
| GridLayout(int rows,int cols,int hgap,int vgap) | 采用指定的行数、列数,以及指定的横向间距、纵向间距将容器分割成多个网格。 |
案例:
使用Frame+Panel,配合FlowLayout和GridLayout完成一个计算器效果。

4.GridBagLayout
GridBagLayout布局管理器的功能最强大,但也最复杂,与GridLayout布局管理器不同的是,在GridBagLayout布局管理器中,一个组件可以跨越一个或多个网格,并可以设置各网格的大小互不相同,从而增加了布局的灵活性。当窗口的大小发生变化时,GridBagLayout布局管理器也可以准确地控制窗口各部分的拉伸。

由于在GridBagLayout布局中,每个组件可以占用多个网格,此时,我们往容器中添加组件的时候,就需要具体的控制每个组件占用多少个网格,java提供的GridBagConstraints类,与特定的组件绑定,可以完全具体大小和跨越性的设置。
5.CardLayout
CardLayout布局管理器以时间而非空间来管理它里面的组件,它将加入容器的所有组件看成一叠卡片(每个卡片其实就是一个组件),每次只有最上面的那个Component才可见。就好像一副扑克牌,它们叠在一起,每次只有最上面的一张扑克牌才可见.
| 方法名称 | 方法功能 |
|---|---|
| CardLayout() | 创建默认的CardLayouot布局管理器 |
| CardLayout(int hgap,int vgap) | 通过指定卡片与容器左右边界的间距(hgap)、上下边界(vgap)的间距来创建CardLayout布局管理器。 |
| first(Container target) | 显示target容器中的第一张卡片。 |
| last(Container target) | 显示target容器中的最后一张卡片。 |
| previous(Container target) | 显示target容器中的前一张卡片。 |
| next(Container target) | 显示target容器中的最后一张卡片。 |
| show(Container target,String name) | 显示target容器中只指定名字的卡片。 |
Frame frame = new Frame("这里是CardLayout");
//1.创建一个panel,用来存储多张卡片
Panel p1 = new Panel();
//2.创建CardLayout对象,并且把该对象设置给之前创建的容器
CardLayout cardLayout = new CardLayout();
p1.setLayout(cardLayout);
//3.往panel中存储多个组件
String[] names = {"第一张","第二张","第三张","第四张","第五张"};
for (String name : names) {
p1.add(name, new Button(name));
}
//4.把panel放到frame的中区域
frame.add(p1);
//5.创建一个panel p2,用来存储多个按钮组件
Panel p2 = new Panel();
//6.创建五个按钮组件
Button b1 = new Button("上一张");
Button b2 = new Button("下一张");
Button b3 = new Button("第一张");
Button b4 = new Button("最后一张");
Button b5 = new Button("第三张");
//7.创建一个事件监听器,监听按钮的点击动作
ActionListener listener = new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
String actionCommand = e.getActionCommand();//这个字符串
System.out.println(actionCommand);
switch (actionCommand){
case "上一张":
cardLayout.previous(p1);
break;
case "下一张":
cardLayout.next(p1);
break;
case "第一张":
cardLayout.first(p1);
break;
case "最后一张":
cardLayout.last(p1);
break;
case "第三张":
cardLayout.show(p1,"第三张");
break;
}
}
};
//8.把当前这个事件监听器和多个按钮绑定到一起
b1.addActionListener(listener);
b2.addActionListener(listener);
b3.addActionListener(listener);
b4.addActionListener(listener);
b5.addActionListener(listener);
//9.把按钮添加到容器p2中
p2.add(b1);
p2.add(b2);
p2.add(b3);
p2.add(b4);
p2.add(b5);
frame.add(p2,BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
6.BoxLayout
为了简化开发,Swing引入了一个新的布局管理器:BoxLayout。BoxLayout可以在垂直和水平俩个方向上摆放GUI组件,BoxLayout提供了如下一个简单的构造器:
| 方法名称 | 方法功能 |
|---|---|
| BoxLayout(Container target,int axis) | 指定创建基于target容器的BoxLayout布局管理器,该布局管理器里的组件按axis方向排列,其中axis有BoxLayout.X_AXIS(横向)和BoxLayout.Y_AXIS(纵向)俩个方向。 |
在java.swing包中,提供了一个新的容器Box,该容器的默认布局管理器就是BoxLayout,大多数情况下,使用Box容器去容纳多个GUI组件,然后再把Box容器作为一个组件,添加到其它的容器中,从而形成整体窗口布局
案例一:

Frame frame = new Frame("这里试测试BoxLayout");
//1.基于fram容器,创建一个BoxLayout对象,并且,该对象存放组件试垂直存放
BoxLayout boxLayout = new BoxLayout(frame,BoxLayout.Y_AXIS);
//2.把BoxLayout对象设置给Frame
frame.setLayout(boxLayout);
//3.往frame中添加俩个按钮组件
frame.add(new Button("按钮1"));
frame.add(new Button("按钮2"));
frame.pack();
frame.setVisible(true);
| 方法名称 | 方法功能 |
|---|---|
| static Box createHorizontalBox() | 创建一个水平排列组件的Box容器 |
| static Box createVerticalBox() | 创建一个垂直排列组件的Box容器 |
案例二:
使用Frame和Box,完成下图效果:

Frame frame = new Frame("这里测试BoxLayout");
//1.创建一个水平排列组件的Box容器
Box hBox = Box.createHorizontalBox();
//2.往当前容器中添加俩个按钮
hBox.add(new Button("水平按钮1"));
hBox.add(new Button("水平按钮2"));
//3.创建一个垂直排列组件的Box容器
Box vBox = Box.createVerticalBox();
//4.往当前容器中添加俩个按钮
vBox.add(new Button("垂直按钮1"));
vBox.add(new Button("垂直按钮2"));
//5.把俩个Box容器添加到Frame中展示
frame.add(hBox,BorderLayout.NORTH);
frame.add(vBox);
frame.pack();
frame.setVisible(true);
通过之前的两个BoxLayout演示,我们会发现,被它管理的容器中的组件之间是没有间隔的,不是特别的美观,但之前学习的几种布局,组件之间都会有一些间距,那使用BoxLayout如何给组件设置间距呢?
其实很简单,我们只需要在原有的组件需要间隔的地方,添加间隔即可,而每个间隔可以是一个组件,只不过该组件没有内容,仅仅起到一种分隔的作用。

Box类中,提供了5个方便的静态方法来生成这些间隔组件:
| 方法名称 | 方法功能 |
|---|---|
| static Component createHorizontalGlue() | 创建一条水平Glue(可在两个方向上同时拉伸的间距) |
| static Component createVerticalGlue() | 创建一条垂直Glue(可在两个方向上同时拉伸的间距) |
| static Component createHorizontalStrut(int width) | 创建一条指定宽度(宽度固定了,不能拉伸)的水平Strut(可在垂直方向上拉伸的间距) |
| static Component createVerticalStrut(int height) | 创建一条指定高度(高度固定了,不能拉伸)的垂直Strut(可在水平方向上拉伸的间距) |
案例3:
使用Frame和Box,完成下图效果:

Frame frame = new Frame("这里试测试BoxLayout");
//1.创建水平排列的Box容器hBox
Box hBox = Box.createHorizontalBox();
//2.往hBox容器中添加按钮,还需要再多个按钮之间添加分割
hBox.add(new Button("水平按钮一"));//该分割在俩个方向上都可以拉伸
hBox.add(Box.createHorizontalGlue());
hBox.add(new Button("水平按钮二"));
hBox.add(Box.createHorizontalStrut(30));
hBox.add(new Button("水平按钮三"));
//3.创建垂直排列的Box容器 vBox
Box vBox = Box.createVerticalBox();
//4.往vBox容器中添加按钮,还需要在多个按钮之间添加分割
vBox.add(new Button("垂直按钮一"));
vBox.add(Box.createVerticalGlue());//该分割在俩个方向上都可以拉伸
vBox.add(new Button("垂直按钮二"));
vBox.add(Box.createVerticalStrut(30));
vBox.add(new Button("垂直按钮三"));
//5.把box容器添加到frame中
frame.add(hBox,BorderLayout.NORTH);
frame.add(vBox);
frame.pack();
frame.setVisible(true);
7.AWT中常用组件
- 基本组件
| 组件名 | 功能 |
|---|---|
| Button | 按钮 |
| Canvas | 用于绘图的画布 |
| Checkbox | 复选框组件(也可以当作单选框组件使用) |
| CheckboxGroup | 用于将多个Checkbox组件组合成一组,一组Checkbox组件将只有一个可以被选中,即全部变成单选框组件 |
| Choice | 下拉选择框 |
| Frame | 窗口,在GUI程序里通过该类创建窗口 |
| Label | 标签类,用于放置提示性文本 |
| List | 列表框组件,可以添加多条项目 |
| Panel | 不能单独存在基本容器类,必须放到其它容器中 |
| Scrollbar | 滑动条组件。如果需要用户输入位于某个范围的值,就可以使用滑动条组件,比如调色板中设置RGB的三个值所用的滑动条。当创建一个滑动条时,必须指定它的方向、初始值、滑块的大小、最小值和最大值。 |
| ScrollPane | 带水平及垂直滚动条的容器组件 |
| TextArea | 多行文本域 |
| TextField | 单行文本框 |
案例四:

public class BasicComponentDemo {
Frame frame = new Frame("这里测试基本组件");
TextArea textArea = new TextArea("这里是多行文本",5,20);
Choice choice = new Choice();
CheckboxGroup cbg = new CheckboxGroup();
Checkbox male = new Checkbox("男",cbg,true);
Checkbox female = new Checkbox("女",cbg,false);
Checkbox isMarried = new Checkbox("是否已婚");
TextField tf = new TextField("这里是单行文本",20);
Button ok = new Button("确认");
List colorList = new List(6,true);
public void init(){
//组装页面
//组装底部
Box bBox = Box.createHorizontalBox();
bBox.add(tf);
bBox.add(ok);
frame.add(bBox,BorderLayout.SOUTH);
//组装 选择部分
choice.add("红色");
choice.add("蓝色");
choice.add("绿色");
Box cBox = Box.createHorizontalBox();
cBox.add(choice);
cBox.add(male);
cBox.add(female);
cBox.add(isMarried);
//组装文本域和选择部分
Box topLeft = Box.createVerticalBox();
topLeft.add(textArea);
topLeft.add(cBox);
//组装顶部左边和列表框
colorList.add("红色");
colorList.add("绿色");
colorList.add("蓝色");
Box top = Box.createHorizontalBox();
top.add(topLeft);
top.add(colorList);
frame.add(top);
//设置frame为最佳大小,并且可见
frame.pack();
frame.setVisible(true);
/*textArea.setEditable(false);
textArea.setFocusable(false);*/
}
public static void main(String[] args) {
new BasicComponentDemo().init();
}
}
8.对话框Dialog
-
Dialog是Window类的子类,是一个容器类,属于特殊组件。对话框是可以独立存在的顶级窗口,因此用法与普通窗口的用法几乎完全一样,但是使用对话框需要注意下面两点︰
-
对话框通常依赖于其它窗口,就是通常需要有一个父窗口;
-
对话框有非模式(non-modal)和模式(modal)两种,当某个模式对话框被打开后,该模式对话框总是位于它的父窗口之上,在模式对话框关闭之前,父窗口无法获得焦点
方法名称 方法功能 Dialog(Frame owner,String title,boolean modal) 创建一个对话框对象:
owner:当前对话框的父窗口
title:当前对话框的标题
modal:当前对话框是否是模式对话框,true/false
案例一:
通过Frame、Button、Dialog实现下图效果:
![]()
Frame frame = new Frame("这里测试Diglog"); //1.创建两个对话框Dialog对象,一个模式的,一个非模式的 Dialog d1 = new Dialog(frame,"模式对话框",true); Dialog d2 = new Dialog(frame,"非模式对话框 ",false); //2.通过setBounds方法设置Dialog的位置以及大小 d1.setBounds(20,30,300,200); d2.setBounds(20,30,300,200); //3.创建两个按钮 Button b1 = new Button("打开模式对话框"); Button b2 = new Button("打开非模式对话框"); //4.给这两个按钮添加点击后的行为 b1.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println(e.getActionCommand()); d1.setVisible(true); } }); b2.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println(e.getActionCommand()); d2.setVisible(true); } }); frame.add(b1,BorderLayout.NORTH); frame.add(b2); frame.pack(); frame.setVisible(true); -
9.FileDialog
Dialog类还有一个子类:FileDialog,它代表一个文件对话框,用于打开或者保存文件,需要注意的是FileDialog.无法指定模态或者非模态,这是因为FileDialog依赖于运行平台的实现,如果运行平台的文件对话框是模态的,那么FileDialog也是模态的;否则就是非模态的。
| 方法名称 | 方法功能 |
|---|---|
| FileDialog(Frame parent,String title,int mode) | 创建一个文件对话框 parent:指定父窗口 title:对话框标题 mode:文件对话框类型,如果指定为FileDialog.LOAD,用于打开文件,如果指定为FileDialog.SAVE,用于保存文件 |
| String getDirectory() | 获取被打开或保存文件的绝对路径 |
| String getFile() | 获取被打开或保存文件的文件名 |
案例二:
使用Frame、Button和FileDialog完成下图效果:

Frame frame = new Frame("这里测试FileDialog");
//1.创建两个FileDialog对象
FileDialog f1 = new FileDialog(frame,"选择要打开的文件",FileDialog.LOAD );
FileDialog f2 = new FileDialog(frame, "选择要保存的路径", FileDialog.SAVE);
//2.创建两个按钮
Button b1 = new Button("打开文件");
Button b2 = new Button("保存文件");
//3.给这两个按钮添加点击后的行为
b1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
f1.setVisible(true);//代码会阻塞到这里
//获取选择的文件及路径
String directory = f1.getDirectory();
String file = f1.getFile();
System.out.println("打开的文件路径为:" + directory);
System.out.println("打开的文件路径为" + file);
}
});
b2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
f2.setVisible(true);
//获得选择的路径及文件
String directory = f2.getDirectory();
String file = f2.getFile();
System.out.println("保存的文件路径为:" + directory);
System.out.println("保存的文件名称为:" + file);
}
});
//4.把按钮添加到Frame中
frame.add(b1,BorderLayout.NORTH);
frame.add(b2);
//设置frame最佳大小,并且可见
frame.pack();
frame.setVisible(true);
10.事件处理
前面介绍了如何放置各种组件,从而得到了丰富多彩的图形界面,但这些界面还不能响应用户的任何操作。比如单击前面所有窗口右上角的"X"按钮,但窗口依然不会关闭。因为在AWT编程中,所有用户的操作,都必须都需要经过一套事件处理机制来完成,而Frame和组件本身并没有事件处理能力。
GUI事件处理机制
定义:
当在某个组件上发生某些操作的时候,会自动的触发—段代码的执行。
在GUI事件处理机制中涉及到4个重要的概念需要理解:
事件源(Event Source):操作发生的场所,通常指某个组件,例如按钮、窗口等;
事件(Event )∶在事件源上发生的操作可以叫做事件,GUI会把事件都封装到一个Event对象中,如果需要知道
事件监听器(Event Listener):当在某个事件源上发生了某个事件,事件监听器就可以对这个事件进行处理。
注册监听︰把某个事件监听器(A)通过某个事件(B)绑定到某个事件源(C)上,当在事件源C上发生了事件B之后,那么事件监听器A的代码就会自动执行。


GUI中常见事件和事件监听器
事件监听器必须实现事件监听器接口,AWT提供了大量的事件监听器接口用于实现不同类型的事件监听器,用于监听不同类型的事件。AMT中提供了丰富的事件类,用于封装不同组件上所发生的特定操作,AMT的事件类都是AWTEvent类的子类,AWTEvent是 EventObject的子类。
事件
AWT把事件分为了两大类:
1.低级事件:这类事件是基于某个特定动作的事件。比如进入、点击、拖放等动作的鼠标事件,再比如得到焦点和失去焦点等焦点事件。
| 事件 | 触发时机 |
|---|---|
| ComponentEvent | 组件事件,当组件尺寸发生变化、位置发生移动、显示/隐藏状态发生改变时触发该事件。 |
| ContainerEvent | 容器事件,当容器里发生添加组件、删除组件时触发该事件。 |
| WindowEvent | 窗口事件,当窗口状态发生改变(如打开、关闭、最大化、最小化)时触发该事件。 |
| FocusEvent | 焦点事件,当组件得到焦点或失去焦点时触发该事件。 |
| KeyEvent | 键盘事件,当按键被按下、松开、单击时触发该事件。 |
| MouseEvent | 鼠标事件,当进行单击、按下、松开、移动鼠标等动作时触发该事件。 |
| PaintEvent | 组件绘制事件,该事件是一个特殊的事件类型,当GUI组件调用update/paint方法来呈现自身时触发该事件,该事件并非专用于事件处理模型。 |
⒉高级事件∶这类事件并不会基于某个特定动作,而是根据功能含义定义的事件。
| 事件 | 触发时机 |
|---|---|
| ActionEvent | 动作事件,当按钮、菜单项被单击,在 TextField中按Enter键时触发 |
| AdjustmentEvent | 调节事件,在滑动条上移动滑块以调节数值时触发该事件。 |
| ItemEvent | 选项事件,当用户选中某项,或取消选中某项时触发该事件。 |
| TextEvent | 文本事件,当文本框、文本域里的文本发生改变时触发该事件。 |
事件监听器
不同的事件需要使用不同的监听器监听,不同的监听器需要实现不同的监听器接口,当指定事件发生后,事件监听器就会调用所包含的事件处理器(实例方法)来处理事件。
| 事件类别 | 描述信息 | 监听器接口名称 |
|---|---|---|
| ActionEvent | 激活组件 | ActionListener |
| ItemEvent | 选择了某些项目 | ItemListenr |
| MouseEvent | 鼠标移动 | MouseMotionListener |
| MouseEvent | 鼠标点击等 | MouseListener |
| KeyEvent | 键盘输入 | KeyListener |
| FocusEvent | 组件收到或失去焦点 | FocusListener |
| AdjustmentEvent | 移动了滚动条等组件 | AdjustmentListener |
| ComponentEvent | 对象移动缩放显示隐藏等 | ComponentListener |
| WindowEvent | 窗口收到窗口级事件 | WindowListener |
| ContainerEvent | 容器中增加删除了组件 | ContainerListener |
| TextEvent | 文本字段或文本区发生改变 | TextListener |
案例2:
给Frame设置WindowListener,监听用户点击X的动作,如果用户点击X,则关闭当前窗口
演示代码2:
Frame frame = new Frame("这里测试WindowListener");
frame.setBounds(200,200,500,300);
//设置WindowListener,监听用户点击X的动作,如果点击X,则关闭窗口
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
frame.setVisible(true);
11.菜单组件
前面讲解了如果构建GUI界面,其实就是把一些GUI的组件,按照一定的布局放入到容器中展示就可以了。在实际开发中,除了主界面,还有一类比较重要的内容就是菜单相关组件,可以通过菜单相关组件很方便的使用特定的功能,在AWT中,菜单相关组件的使用和之前学习的组件是一模一样的,只需要把菜单条、菜单、菜单项组合到—起,按照—定的布局,放入到容器中即可。
下图中给出常见的菜单相关组件:
| 菜单组件名称 | 功能 |
|---|---|
| MenuBar | 菜单条,菜单的容器 |
| Menu | 菜单组件,菜单项的容器。它也是MenuItem的子类,所以可作为菜单项使用 |
| PopupMenu | 上下文菜单组件(右键菜单组件) |
| MenuItem | 菜单项组件。 |
| CheckboxMenuItem | 复选框菜单项组件 |
下图是常见菜单相关组件集成体系图:

菜单相关组件使用:
- 准备菜单项组件,这些组件可以是MenuItem及其子类对象
- 准备菜单组件Menu或者PopupMenu(右击弹出子菜单),把第一步中准备好的菜单项组件添加进来;
- 准备菜单条组件MenuBar,把第二部中准备好的菜单组件Menu添加进来;
- 把第三步中准备好的菜单条组件添加到窗口对象中显示。
小技巧
- 如果要在某个菜单的菜单项之间添加分割线,那么只需要调用Menu的add ( new Menultem("-"))即可。
- 如果要给某个菜单项关联快捷键功能,那么只需要在创建菜单项对象时设置即可,例如给菜单项关联ctrl+shift+Q快捷键,只需要:new MenuItem("菜单项名字",new MenuShortcut(KeyEvent.VK_Q,true));
案例1:
使用awt中常用菜单组件,完成下图效果

//创建窗口
private Frame frame = new Frame("这里测试菜单相关组件");
//创建菜单条
private MenuBar menuBar = new MenuBar();
//创建菜单组件
private Menu fileMenu = new Menu("文件");
private Menu editMenu = new Menu("编辑");
private Menu formatMenu = new Menu("格式");
//创建菜单项组件
private MenuItem auto = new MenuItem("自动换行");
private MenuItem copy = new MenuItem("复制");
private MenuItem paste = new MenuItem("粘贴");
private MenuItem comment = new MenuItem("注释",new MenuShortcut(KeyEvent.VK_Q,true));//注释 Ctrl+Shift+Q
private MenuItem cancelComment = new MenuItem("取消注释");
TextArea ta = new TextArea(6,3);
public void init(){
//组装试图
comment.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
ta.append("点击了"+e.getActionCommand());
}
});
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
formatMenu.add(comment);
formatMenu.add(cancelComment);
//组装编辑菜单
editMenu.add(auto);
editMenu.add(copy);
editMenu.add(paste);
editMenu.add(formatMenu);
//组装菜单条
menuBar.add(fileMenu);
menuBar.add(editMenu);
//把菜单条放入到Frame中
frame.setMenuBar(menuBar);
frame.add(ta);
//设置frame最佳大小并且可见
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
new SimpleMenuDemo().init();
}
案例2:
通过PopupMenu实现下图效果:
实现思路:
- 创建PopupMenu菜单组件:
- 创建多个MenuItem菜单项,并添加到PopupMenu中;
- 将PopupMenu添加到目标组中;
- 为需要右击出现PopupMenu菜单的组件,注册鼠标监听事件,当监听到用户释放右键时,弹出菜单。
- boolean isPopupTrigger()
- true:是鼠标右键
- false:是鼠标左键

public class PopupMenuTest {
private Frame frame = new Frame("这里测试PopupMenu");
//创建文本域
TextArea ta = new TextArea("我爱中华!!!",6,30);
//创建Panel容器
Panel p = new Panel();
//创建PopupMenu
PopupMenu popupMenu = new PopupMenu();
//创建菜单项
MenuItem comment = new MenuItem("注释");
MenuItem cancelComment = new MenuItem("取消注释");
MenuItem copy = new MenuItem("复制");
MenuItem save = new MenuItem("保存");
public void init(){
//组装视图
popupMenu.add(comment);
popupMenu.add(cancelComment);
popupMenu.add(copy);
popupMenu.add(save);
ActionListener listener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String actionCommand = e.getActionCommand();
ta.append("点击了:"+actionCommand+"\n");
}
};
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(2);
}
});
comment.addActionListener(listener);
cancelComment.addActionListener(listener);
copy.addActionListener(listener);
save.addActionListener(listener);
p.add(popupMenu);
//设置Panel的大小
p.setPreferredSize(new Dimension(200,100));
//给Panel注册鼠标事件,监听用户释放鼠标的动作,显示菜单
p.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
boolean flag = e.isPopupTrigger();
if (flag){
//显示PopupMenu
// popupMenu.show();
popupMenu.show(p,e.getX(),e.getY());
}
}
});
//放置ta和p
frame.add(ta);
frame.add(p,BorderLayout.SOUTH);
//设置frame最佳大小,并可视;
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
new PopupMenuTest().init();
}
}
绘图
很多程序如各种游戏都需要在窗口中绘制各种图形,除此之外,即使在开发JavaEE项目时,有时候也必须"动态"地向客户端生成各种图形、图表,比如图形验证码、统计图等,这都需要利用AMT的绘图功能。
1.组件绘图原理
之前我们已经学习过很多组件,例如Button、Frame、Checkbox等等,不同的组件,展示出来的图形都不一样,其实这些组件展示出来的图形,其本质就是用AWT的绘图来完成的。
在AMT中,真正提供绘图功能的是Graphics对象,那么Component组件和Graphics对象存在什么关系,才能让Component绘制自身图形呢?在Component类中,提供了下列三个方法来完成组件图形的绘制与刷新:
- paint(Graphics):绘制组件的外观;
- update(Graphics g):内部调用paint方法,刷新组件外观;
- repaint():调用update方法,刷新组件外观;

一般情况下,update和paint方法是由AWT系统负责调用,如果程序要希望系统重新绘制组件,可以调用repaint方法完成。
2.Graphics对象的使用
实际生活中如果需要画图,首先我们得准备一张纸,然后在拿一支画笔,配和一些颜色,就可以在纸上画出来各种各样的图形,例如圆圈、矩形等等。

程序中绘图也一样,也需要画布,画笔,颜料等等。AMT中提供了Canvas类充当画布,提供了Graphics类来充当画笔,通过调用Graphics对象的setColor()方法可以给画笔设置颜色。
画图的步骤:
- 自定义类,继承Canvas类,重写paint(Graphics g)方法完成画图:
- 在paint方法内部,真正开始画图之前调用Graphics对象setColor()、setFont()等方法设置画笔的颜色、字体等属性;
- 调用Graphics画笔的drawXxx()方法开始画笔。
其实画图的核心就是在于Graphics画笔在Canvas画布上画出什么颜色、什么样式的图形,所以核心在画笔上
画图的步骤:
-
自定义类,继承Canvas类,重写paint(Graphics g)方法完成画图;
-
在paint方法内部,真正开始画图之前调用Graphics对象的setColor()、 setFont()等方法设置画笔的颜色、字体等属性;
-
调用Graphics画笔的drawXxx()方法开始画图。
其实画图的核心就在于使用Graphics画笔在Canvas画布上画出什么颜色、什么样式的图形,所以核心在画笔上,下表中列出了Graphics类中常用的一些方法∶
| 方法名称 | 方法功能 |
|---|---|
| setColor(Color c) | 设置颜色 |
| setFont(Font font) | 设置字体 |
| drawLine() | 绘制直线 |
| drawRect() | 绘制矩形 |
| drawRoundRect() | 绘制圆角矩形 |
| drawOval() | 绘制椭圆形 |
| drawPolygon() | 绘制多边形 |
| drawArc() | 绘制圆弧 |
| drawPolyline() | 绘制折线 |
| fillRect() | 填充矩形区域 |
| fillRoundRect() | 填充圆角矩形区域 |
| fillOval() | 填充椭圆区域 |
| fillPolygon() | 填充多边形区域 |
| fillArc() | 填充圆弧对应的扇形区域 |
| drawImage() | 绘制位图 |
setSize()、setPreferredSize()区别
setSize()、setPreferredSize()都可以设置组件的大小,但二者的使用有所不同。
1、setSize()的使用方式
setSize(int width,int height)
setSize(Dimension d)
2、setPreferredSize()的使用方式
setSize(Dimension d)
setPreferredSize()是设置首选尺寸。一般情况下:
如果该组件受布局管理器管理(使用默认的也算),需要使用setPreferredSize()设置尺寸,setSize()无效。
如果该组件不受布局管理器管理,需要使用setSize(),setPreferredSize()无效。
比如:
JFrame设置了布局管理器,往JFrame中添加一个JLabel,JLabel受布局管理器管理,需要使用setPreferredSize(),setSize()无效。受指的是JLabel,不是布局管理器所在的容器JFrame,JFrame没有受到布局管理器的管理,要使用setSize()。
是说该组件受到布局管理器的管理,不是说该组件本身设置了布局管理器。
3、Dimension的使用方式
构造函数:
Dimension(int width,int height) //只能是int ,不能是double
常用方法:
getWidth/Height() //返回值均为double。也可直接访问属性width、height。
setSize(int width,int height) //不能单独设置其中一个
setSIze(double width,double height)
案例:
使用AWT绘图API,完成下图效果

package com.qiu.draw;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class SimpleDraw {
private final String RECT_SHAPE = "rect";
private final String OVAL_SHAPE = "oval";
private Frame frame = new Frame("测试绘图");
Button btnRect = new Button("绘制矩形");
Button btnOval = new Button("绘制椭圆");
//定义一个变量,记录当前要绘制椭圆还是矩形
private String shape = "";
//自定义类,继承Canvas类,重写paint(Graphics g)方法完成画图;
private class MyCanvas extends Canvas{
@Override
public void paint(Graphics g) {
//绘制不同的图形
if (shape.equals(RECT_SHAPE)){
//绘制矩形
g.setColor(Color.BLACK);
g.drawRect(100,100,150,150);
}else if (shape.equals(OVAL_SHAPE)){
//绘制椭圆
g.setColor(Color.RED);
g.drawOval(100,100,150,150);
}
}
}
//创建自定义的画布对象
MyCanvas drawArea = new MyCanvas();
public void init(){
btnRect.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//修改标记值为rect
shape = RECT_SHAPE;
drawArea.repaint();
}
});
btnOval.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//修改标记值为rect
shape = OVAL_SHAPE;
drawArea.repaint();
}
});
//创建Panel,承载按钮
Panel panel = new Panel();
panel.add(btnOval);
panel.add(btnRect);
frame.add(panel,BorderLayout.SOUTH);
//drawArea的大小需要设置
drawArea.setPreferredSize(new Dimension(300,300));
frame.add(drawArea);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
new SimpleDraw().init();
}
}
动画
Java也可用于开发一些动画。所谓动画,就是间隔一定的时间(通常小于0.1秒)重新绘制新的图像,两次绘制的图像之间差异较小,肉眼看起来就成了所谓的动画。
为了实现间隔一定的时间就重新调用组件的repaint()方法,可以借助于Swing 提供的Timer类,Timer类是一个定时器,它有如下一个构造器:
Timer(int delay, ActionListener listener):每间隔delay毫秒,系统自动触发ActionListener监听器里的事件处理器方法,在方法内部我们就可以调用组件的repaint方法,完成组件重绘。
案例2
使用AWT画图技术及Timer定时器,完成下图中弹球小游戏。

package com.qiu.draw;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class PinBall {
//创建窗口对象
private Frame frame = new Frame("弹球游戏");
//桌面区域
private final int TABLE_WIDTH = 300;
private final int TABLE_HEIGHT = 400;
//球拍的高度和宽度
private final int RACKET_WIDTH = 60;
private final int RACKET_HEIGHT = 20;
//小球大小
private final int BALL_SIZE = 16;
//定义变量,记录小球的坐标
private int ballX = 120;
private int ballY = 20;
//定义变量,记录小球在x和y方向上分别移动的速度
private int speedX = 5;
private int speedY = 10;
//定义变量,记录球拍的坐标
private int racketX = 120;
private final int racketY = 340;
//定义变量,标识当前的游戏是否已经结束
private boolean isOver = false;
//声明一个定时器
private Timer timer;
//自定义一个类,继承canvas,充当画布
private class MyCanvas extends Canvas{
@Override
public void paint(Graphics g) {
//TODO 在这里绘制内容
if (isOver){
//游戏结束
g.setColor(Color.blue);
g.setFont(new Font("Times",Font.BOLD,30));
g.drawString("游戏结束",50,200);
}else {
//游戏中
//绘制小球
g.setColor(Color.red);
g.fillOval(ballX,ballY,BALL_SIZE,BALL_SIZE);
//绘制球拍
g.setColor(Color.pink);
g.fillRect(racketX,racketY,RACKET_WIDTH,RACKET_HEIGHT);
}
}
}
//创建绘画区域
MyCanvas drawArea = new MyCanvas();
public void init(){
//组装试图,游戏逻辑的控制
//完成球拍坐标的变化
KeyListener keyListener = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_LEFT){
//<- 应该向左移动
if (racketX>0){
racketX -= 10;
}
}
if (keyCode == KeyEvent.VK_RIGHT){
//->应该向右移动
if (racketX < TABLE_WIDTH - RACKET_WIDTH){
racketX += 10;
}
}
}
};
//给Frame和drawAArea注册监听器
frame.addKeyListener(keyListener);
drawArea.addKeyListener(keyListener);
//小球坐标的控制
ActionListener task = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//根据边界范围,修正速度
if (ballX <= 0 || ballX >= (TABLE_WIDTH - BALL_SIZE)){
speedX = -speedX;
}
if (ballY <= 0 || (ballY > racketY - BALL_SIZE && ballX >= racketX-BALL_SIZE/2 && ballX <= racketX + RACKET_WIDTH+BALL_SIZE/2)){
speedY = -speedY;
}
if (ballY > racketY - BALL_SIZE && (ballX < racketX-BALL_SIZE/2 || ballX > racketX + RACKET_WIDTH+BALL_SIZE/2)){
//当前小球超出了球拍的范围,游戏结束
//停止定时器
timer.stop();
//修改游戏结束标记
isOver = true;
//重绘界面
drawArea.repaint();
}
//更新小球的坐标,重绘界面
ballX += speedX;
ballY += speedY;
//重绘界面
drawArea.repaint();
}
};
timer = new Timer(100,task);
timer.start();
drawArea.setPreferredSize(new Dimension(TABLE_WIDTH,TABLE_HEIGHT));
frame.add(drawArea);
//设置frame最佳大小,并可视
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
new PinBall().init();
}
}
处理位图
如果仅仅绘制一些简单的几何图形,程序的图形效果依然比较单调。AMT 也允许在组件上绘制位图,Graphics提供了drawlmage(Image image)方法用于绘制位图,该方法需要一个Image参数------代表位图,通过该方法就可以绘制出指定的位图。
位图使用步骤:
-
创建lmage的子类对象Bufferedlmage(int width,int height,int lmageType),创建时需要指定位图的宽高及类型属性;此时相当于在内存中生成了一张图片;
-
调用Bufferedlmage对象的getGraphics()方法获取画笔,此时就可以往内存中的这张图片上绘图了,绘图的方法和之前学习的一模一样;
-
调用组件paint方法中提供的Graphics对象的drawlmage()方法,一次性的内存中的图片Bufferedlmage绘制到特定的组件上。
使用位图绘图组件的好处:
使用位图来绘制组件,相当于实现了图的缓冲区,此时绘图时没有直接把图形绘制到组件上,而是先绘制到内存中的Bufferedlmage上,等全部绘制完毕,再一次性的图像显示到组件上即可,这样用户的体验会好一些。
案例:
通过BufferedImage实现一个简单的手绘程序∶通过鼠标可以在窗口中画图。

package com.qiu.draw;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.sql.Struct;
public class HandDraw {
//定义窗口对象
private Frame frame = new Frame("简单手绘");
//定义画图区的宽高
private final int AREA_WIDTH = 500;
private final int AREA_HEIGHT = 400;
//定义一个右键菜单,用于设置画笔的颜色
private PopupMenu colorMenu = new PopupMenu();
private MenuItem redItem = new MenuItem("红色");
private MenuItem greenItem = new MenuItem("绿色");
private MenuItem blueItem = new MenuItem("蓝色");
//定义一个变量,记录当前画笔的颜色
private Color forceColor = Color.BLACK;
//创建一个BufferedImage位图对象
BufferedImage image = new BufferedImage(AREA_WIDTH,AREA_HEIGHT,BufferedImage.TYPE_INT_RGB);
//通过位图,获取关联的Graphics对象
Graphics g = image.getGraphics();
//自定义一个类,继承Canvas
private class MyCanvas extends Canvas{
@Override
public void paint(Graphics g) {
g.drawImage(image,0,0,null);
}
}
MyCanvas drawArea = new MyCanvas();
//定义变量,记录鼠标拖动过程中,上一次所处的坐标
private int preX = -1;
private int preY = -1;
public void init(){
ActionListener listener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String actionCommand = e.getActionCommand();
switch (actionCommand){
case "红色":
forceColor = Color.RED;
break;
case "绿色":
forceColor = Color.GREEN;
break;
case "蓝色":
forceColor = Color.BLUE;
break;
}
}
};
redItem.addActionListener(listener);
greenItem.addActionListener(listener);
blueItem.addActionListener(listener);
colorMenu.add(redItem);
colorMenu.add(greenItem);
colorMenu.add(blueItem);
//把colorMenu设置给绘图区域
drawArea.add(colorMenu);
drawArea.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
boolean popupTrigger = e.isPopupTrigger();
if (popupTrigger){
colorMenu.show(drawArea,e.getX(),e.getY());
}
preX = -1;
preY = -1;
}
});
//设置位图的背景为白色
g.setColor(Color.white);
g.fillRect(0,0,AREA_WIDTH,AREA_HEIGHT);
//通过监听鼠标的移动,完成先条绘制
drawArea.addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
if (preX > 0 && preY > 0){
g.setColor(forceColor);
g.setFont(new Font("微软雅黑",1,5));
//画线条 需要两组坐标,分别代表线条的起始点和终点 e.getX(),e.getY()可以获取坐标()
g.drawLine(preX,preY,e.getX(),e.getY());
// g.drawOval(e.getX(),e.getY(),100,100);
}
//修正preX和preY的值
preX = e.getX();
preY = e.getY();
//重绘组件
drawArea.repaint();
}
});
drawArea.setPreferredSize(new Dimension(AREA_WIDTH,AREA_HEIGHT));
frame.add(drawArea);
//设置frame最佳大小并可见
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
new HandDraw().init();
}
}
ImageIO的使用
在实际生活中,很多软件都支持打开本地磁盘已经存在的图片,然后进行编辑,编辑完毕后,再重新保存到本地磁盘。如果使用AMT要完成这样的功能,那么需要使用到ImagelO这个类,可以操作本地磁盘的图片文件。
| 方法名称 | 方法功能 |
|---|---|
| static BufferedImage read(File input) | 读取本地磁盘图片文件 |
| static BufferedImage read(InputStream input) | 读取本地磁盘图片文件 |
| static Boolean write(RenderedImage im,String formatName,File output) | 往本地磁盘中输出图片文件 |
案例

package com.qiu.draw;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.image.BufferedImage;
import java.io.File;
public class Gobang {
//定义五子棋游戏窗口
private Frame f = new JFrame("五子棋游戏");
//声明四个BufferedImage对象,分别记录四张图片
BufferedImage table;
BufferedImage black;
BufferedImage white;
BufferedImage selected;
//声明棋盘的宽和高
final int TABLE_WIDTH = 689;
final int TABLE_HEIGHT = 689;
//声明棋盘横向和纵向分别可以下多少个子,他们的值都为15
final int BOARD_SIZE = 15;
//声明每个棋子占用的比率
final int RATE = TABLE_WIDTH/BOARD_SIZE;
//声明变量,记录棋子对于x方向和y方向的偏移量
final int X_OFFSET = 5;
final int Y_OFFSET = 5;
//声明一个二维数组,记录棋子,如果索引[i][j]初的值为 0-没有棋子 1-白棋 2-黑棋
int[][] board = new int[BOARD_SIZE][BOARD_SIZE];
//声明红色选择框的坐标,该坐标其实就是二维数组board中的索引
int selected_X = -1;
int selected_Y = -1;
//自定义类,继承Canvas
private class ChessBoard extends JPanel{
@Override
public void paint(Graphics g) {
//绘图
//绘制棋盘
g.drawImage(table,0,0,null);
//绘制选择框
if ((selected_X >= 0 && selected_Y >= 0) && (selected_Y <= 14 && selected_X <= 14)){
g.drawImage(selected,selected_X*RATE,selected_Y*RATE,null);
}
//绘制棋子
for (int i = 0; i < BOARD_SIZE; i++) {
for (int j = 0; j < BOARD_SIZE; j++) {
//绘制黑棋
if (board[i][j] == 2){
g.drawImage(black,i*RATE,j*RATE,null);
}else if (board[i][j] == 1){
g.drawImage(white,i*RATE,j*RATE,null);
}
}
}
}
}
ChessBoard chessBoard = new ChessBoard();
//声明变量,记录当前下棋的颜色
int board_type = 2;
//声明底部需要用到的组件
Panel p = new Panel();
Button whiteBtn = new Button("白棋");
Button blackBtn = new Button("黑棋");
Button deleteBtn = new Button("删除");
public void refreshBtnColor(Color whiteBtnColor,Color blackBtnColor,Color deleteBtnColor){
whiteBtn.setBackground(whiteBtnColor);
blackBtn.setBackground(blackBtnColor);
deleteBtn.setBackground(deleteBtnColor);
}
public void init() throws Exception{
//组装试图,编写逻辑
whiteBtn.addActionListener(e -> {
//修改当前要下的棋子的标志为1
board_type = 1;
//刷新按钮的颜色
refreshBtnColor(Color.GREEN,Color.GRAY,Color.GRAY);
});
blackBtn.addActionListener(e -> {
//修改当前要下的棋子的标志为2
board_type = 2;
//刷新按钮的颜色
refreshBtnColor(Color.GRAY,Color.GREEN,Color.GRAY);
});
deleteBtn.addActionListener(e -> {
//修改当前要下的棋子的标志为0
board_type = 0;
//刷新按钮的颜色
refreshBtnColor(Color.GRAY,Color.GRAY,Color.GREEN);
});
p.add(whiteBtn);
p.add(blackBtn);
p.add(deleteBtn);
f.add(p,BorderLayout.SOUTH);
//组装棋盘
table = ImageIO.read(new File("images/board.jpg"));
black = ImageIO.read(new File("images/black.png"));
selected = ImageIO.read(new File("images/select.png"));
white = ImageIO.read(new File("images/white.png"));
//处理鼠标移动
chessBoard.addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
selected_X = e.getX()/RATE;
selected_Y = e.getY()/RATE;
System.out.println("("+e.getX()+","+e.getY()+")");
chessBoard.repaint();
}
});
//处理鼠标点击
chessBoard.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
int xPos = e.getX()/RATE;
int yPos = e.getY()/RATE;
board[xPos][yPos] = board_type;
chessBoard.repaint();
}
//处理鼠标移出
@Override
public void mouseExited(MouseEvent e) {
selected_X = -1;
selected_Y = -1;
chessBoard.repaint();
}
});
chessBoard.setPreferredSize(new Dimension(TABLE_WIDTH,TABLE_HEIGHT));
f.add(chessBoard);
//设置frame最佳大小并可见
f.pack();
f.setVisible(true);
}
public static void main(String[] args) throws Exception {
new Gobang().init();
}
}



浙公网安备 33010602011771号