2.3 Container容器
2.3.1 Container继承体系
- Winow是可以独立存在的顶级窗口,默认使用BorderLayout管理其内部组件布局;
- Panel可以容纳其他组件,但不能独立存在,它必须内嵌其他容器中使用,默认使用FlowLayout管理其内部组件布局;
- ScrollPane 是 一个带滚动条的容器,它也不能独立存在,默认使用 BorderLayout 管理其内部组件布局;
2.3.2 常见API
Component作为基类,提供了如下常用的方法来设置组件的大小、位置、可见性等。
方法签名 | 方法功能 |
---|---|
setLocation(int x, int y) | 设置组件的位置。 |
setSize(int width, int height) | 设置组件的大小。 |
setBounds(int x, 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.3.3 容器演示
2.3.3.1 Window
import java.awt.*;
public class FrameDemo {
public static void main(String[] args) {
//1.创建第一个窗口
Frame frame = new Frame("这是第一个窗口Frame");
//2.设置窗口的大小和位置
frame.setBounds(100,100,500,300);
//3.设置窗口的可见性
frame.setVisible(true);
}
}
2.3.3.2 Panel
import java.awt.*;
public class PanelDemo {
public static void main(String[] args) {
//1.创建窗口对象
Frame frame = new Frame("这里测试Fanel");
//2.创建内容面板
Panel panel = new Panel();
//3.添加一个文本到面板里面
panel.add(new Label("这是一个测试文本"));
//4.添加一个按钮到面板里面
panel.add(new Button("这是一个测试按钮"));
//5.把面板添加(容纳)到窗口中
frame.add(panel);
//6.设置窗口的位置大小
frame.setBounds(100,100,500,300);
//7.设置窗口的可见性
frame.setVisible(true);
}
}
由于IDEA默认使用utf-8进行编码,但是当前我们执行代码是是在windows系统上,而windows操作系统的默认编码是gbk,所以会乱码,如果出现了乱码,那么只需要在运行当前代码前,设置一个jvm参数 -Dfile.encoding=gbk即可。
步骤:
点那个下三角选择Edit Configurations。
来到这个界面点击Modify options,选择ADD VM Options。
在这里添加-Dfile.encoding=gbk即可。
结果:
2.3.3.3 ScrollPane
import java.awt.*;
public class ScrollPaneDemo {
public static void main(String[] args) {
//1.创建Frame窗口对象
Frame frame = new Frame("这里测试ScrollPane");
//2.创建一个ScrollPane滚动面板对象,参数ScrollPane.SCROLLBARS_ALWAYS意味默认带有滚动条
ScrollPane scrollPane = new ScrollPane(ScrollPane.SCROLLBARS_ALWAYS);
//3.添加一个文本到滚动面板
scrollPane.add(new Label("这是一个测试文本"));
//4.添加一个按钮到滚动面板
scrollPane.add(new Button("这是一个测试按钮"));
//5.将scrollPane添加到frame里面
frame.add(scrollPane);
//6.设窗口的位置大小
frame.setBounds(100,100,500,300);
//7.设置窗口的可见性
frame.setVisible(true);
}
}
(配置与上面同理)
程序明明向 ScrollPane 容器中添加了 一个文本框和一个按钮,但只能看到 一个按钮,却看不到文本框 ,这是为什么 呢?
这是因为ScrollPane 使用 BorderLayout 布局管理器的缘故,而 BorderLayout 导致了该容器中只有一个组件被显示出来 。 下一节将向详细介绍布局管理器的知识 。
2.4 LayoutManager布局管理器
之前,我们介绍了Component中有一个方法 setBounds() 可以设置当前容器的位置和大小,但是我们需要明确一件事,如果我们手动的为组件设置位置和大小的话,就会造成程序的不通用性,例如:
Label label = new Label("你好,世界");
创建了一个lable组件,很多情况下,我们需要让lable组件的宽高和“你好,世界”这个字符串自身的宽高一致,这种大小称为最佳大小。
由于操作系统存在差异,例如在windows上,我们要达到这样的效果,需要把该Lable组件的宽和高分别设置为100px,20px,但是在Linux操作系统上,可能需要把Lable组件的宽和高分别设置为120px,24px,才能达到同样的效果。
如果要让我么的程序在不同的操作系统下,都有相同的使用体验,那么手动设置组件的位置和大小,无疑是一种灾难,因为有太多的组件,需要分别设置不同操作系统下的大小和位置。为了解决这个问题,Java提供了LayoutManager布局管理器,可以根据运行平台来自动调整组件大小,程序员不用再手动设置组件的大小和位置了,只需要为容器选择合适的布局管理器即可。
也就是说布局管理器可以实现不同环境下,让面板中的组件位置自动最佳,不用手动调节组建的大小位置了。
2.4.1 FlowLayout
在 FlowLayout 布局管理器 中,组件像水流一样向某方向流动 (排列) ,遇到障碍(边界)就折回,重头开始排列 。在默认情况下, FlowLayout 布局管理器从左向右排列所有组件,遇到边界就会折回下一行重新开始。
构造方法 | 方法功能 |
---|---|
FlowLayout() | 使用默认 的对齐方式及默认的垂直间距、水平间距创建 FlowLayout 布局管理器。 |
FlowLayout(int align) | 使用指定的对齐方式及默认的垂直间距、水平间距创建 FlowLayout 布局管理器。 |
FlowLayout(int align,int hgap,int vgap) | 使用指定的对齐方式及指定的垂直问距、水平间距创建FlowLayout 布局管理器。 |
FlowLayout 中组件的排列方向(从左向右、从右向左、从中间向两边等) , align参数应该使用FlowLayout类的静态常量 : FlowLayout. LEFT 、 FlowLayout. CENTER 、 FlowLayout. RIGHT ,默认是左对齐。
FlowLayout 中组件中间距通过整数设置,单位是像素,默认是5个像素。
import java.awt.*;
public class FlowLayoutDemo {
public static void main(String[] args) {
//1.创建窗口Frame对象
Frame frame = new Frame("这里测试FlowLayout");
//2.修改Frame容器对象中的布局管理器为FlowLayout
frame.setLayout(new FlowLayout(FlowLayout.LEFT,20,20));
//setLayout函数接收一个布局管理器对象(接口),因此跟之前添加组件一样,new一个FlowLayout
//3.往Frame窗口对象中添加100个button
for(int i = 0; i < 100; i++) {
frame.add(new Button("按钮"+i));
}
//4.设置Frame为最佳大小
frame.pack();
//通常在创建完窗口的所有子组件后,调用pack函数可以确保窗口的大小适合所有子组件
//5.设置窗口的可见性
frame.setVisible(true);
}
}
特点:
-
frame.setLayout(new FlowLayout(FlowLayout.LEFT,20,20));
//setLayout函数接收一个布局管理器对象(接口),因此跟之前添加组件一样,new一个FlowLayout -
frame.pack();
//通常在创建完窗口的所有子组件后,调用pack函数可以确保窗口的大小适合所有子组件
2.4.2 BorderLayout
BorderLayout 将容器分为 EAST 、 SOUTH 、 WEST 、 NORTH 、 CENTER五个区域,普通组件可以被放置在这 5 个区域的任意一个中 。 BorderLayout布局 管理器的布局示意图如图所示 。
当改变使用 BorderLayout 的容器大小时, NORTH 、 SOUTH 和 CENTER区域水平调整(左右拉伸),而 EAST 、 WEST 和 CENTER 区域垂直调整(上下拉伸)。
使用BorderLayout 有如下两个注意点:
-
当向使用 BorderLayout 布局管理器的容器中添加组件时 , 需要指定要添加到哪个区域中 。 如果没有指定添加到哪个区域中,则默认添加到中间区域中;
-
如果向同一个区域中添加多个组件时 , 后放入的组件会覆盖先放入的组件;
(是2.3.3.3 程序明明向 ScrollPane 容器中添加了 一个文本框和一个按钮,但只能看到 一个按钮,却看不到文本框问题的原因)
构造方法 | 方法功能 |
---|---|
BorderLayout() | 使用默认的水平间距、垂直 间距创建 BorderLayout 布局管理器 。 |
BorderLayout(int hgap,int vgap): | 使用指定的水平间距、垂直间距创建 BorderLayout 布局管理器。 |
代码一:
import java.awt.*;
public class BorderLayoutDemo {
public static void main(String[] args) {
//1.创建一个窗口Frame对象
Frame frame = new Frame();
//2.Frame默认的布局管理器就是BorderLayout,所以重点是规定水平间距和垂直间距
frame.setLayout(new BorderLayout(30,5));
//3.往五个方向区域都添加一个按钮组件
//这里add(Component comp,int index)
frame.add(new Button("东侧按钮"),BorderLayout.EAST);
frame.add(new Button("西侧按钮"),BorderLayout.WEST);
frame.add(new Button("南侧按钮"),BorderLayout.SOUTH);
frame.add(new Button("北侧按钮"),BorderLayout.NORTH);
frame.add(new Button("中间按钮"),BorderLayout.CENTER);
//4.设置Frame为最佳大小
frame.pack();
//5.设置Frame的可见性
frame.setVisible(true);
}
}
如果不往某个区域中放入组件,那么该区域不会空白出来,而是会被其他区域占用
代码二:
import java.awt.*;
public class BorderLayoutDemo2 {
public static void main(String[] args) {
//1.创建Frame对象
Frame frame = new Frame();
//2.Frame默认的布局管理器就是BorderLayout,所以重点是规定水平间距和垂直间距
frame.setLayout(new BorderLayout(30,5));
//3.往五个方向区域都添加一个按钮组件
frame.add(new Button("南侧按钮"),BorderLayout.SOUTH);
frame.add(new Button("北侧按钮"),BorderLayout.NORTH);
//4.使用panel实现中间区域可以存在两个组件
Panel panel = new Panel();
panel.add(new Button("中间按钮"));
panel.add(new TextField("测试文本框"));
frame.add(panel);//这里我们可以得到Frame对象的默认添加区域是中间区域
//5.设置Frame为最佳大小
frame.pack();
//6.设置Frame的可见性
frame.setVisible(true);
}
}
2.4.3 GridLayout
GridLayout 布局管理器将容器分割成纵横线分隔的网格 , 每个网格所占的区域大小相同。当向使用 GridLayout 布局管理器的容器中添加组件时, 默认从左向右、 从上向下依次添加到每个网格中 。 与 FlowLayout不同的是,放置在 GridLayout 布局管理器中的各组件的大小由组件所处的区域决定(每个组件将自动占满整个区域) ,也就是网格大小为组件大小。
构造方法 | 方法功能 |
---|---|
GridLayout(int rows,in t cols) | 采用指定的行数、列数,以及默认的横向间距、纵向间距将容器 分割成多个网格 |
GridLayout(int rows,int cols,int hgap,int vgap) | 采用指定 的行数、列 数 ,以及指定的横向间距 、 纵向间距将容器分割成多个网格。 |
案例:
使用Frame+Panel,配合FlowLayout和GridLayout完成一个计算器效果。
代码:
import java.awt.*;
public class GridLayoutDemo {
public static void main(String[] args) {
//1.创建Frame对象,并把标题设置为计算器
Frame frame = new Frame("计算器");
//2.创建一个Panel对象,往其加入一个TextField文本框组件,并把文本宽度设置为30个字符
Panel panel1 = new Panel();
panel1.add(new TextField(30));
//3.把文本面板Panel1放置在Frame的北侧区域
frame.add(panel1, BorderLayout.NORTH);
//4.创建一个Panel对象,并设置其布局管理器为GridLayout,来存放计算机按钮
Panel panel2 = new Panel();
panel2.setLayout(new GridLayout(3,5,4,4));//三行五列,横向、纵向间距都为4
//5.往面板二添加按钮
for(int i = 0; i < 10; i++) {
panel2.add(new Button(i+"")); //+"" 将i转化为字符串类型
}
panel2.add(new Button("+"));
panel2.add(new Button("-"));
panel2.add(new Button("*"));
panel2.add(new Button("/"));
panel2.add(new Button("."));
//6.将计算机面板放置在Frame的中间区域
frame.add(panel2,BorderLayout.CENTER);
//7.设置Frame为最佳大小
frame.pack();
//8.设置Frame可见
frame.setVisible(true);
}
2.4.4 GridBagLayout
GridBagLayout 布局管理器的功能最强大 , 但也最复杂。
(简单带过,在spring会有更强大的布局管理器)
与 GridLayout 布局管理器不同的是, 在GridBagLayout 布局管理器中,一个组件可以跨越一个或多个网格 , 并可以设置各网格的大小互不相同,从而增加了布局的灵活性 。
当窗口的大小发生变化时 , GridBagLayout 布局管理器也可以准确地控制窗口各部分的拉伸 。
由于在GridBagLayout 布局中,每个组件可以占用多个网格,此时,我们往容器中添加组件的时候,就需要具体的控制每个组件占用多少个网格,java提供的GridBagConstaints类,与特定的组件绑定,可以完成具体大小和跨越性的设置。
GridBagConstraints API:
成员变量 | 含义 |
---|---|
gridx | 设置受该对象控制的GUI组件左上角所在网格的横向索引 |
gridy | 设置受该对象控制的GUI组件左上角所在网格的纵向索引 |
gridwidth | 设置受该对象控制的 GUI 组件横向跨越多少个网格,如果属性值为 GridBagContraints.REMAIND,则表明当前组件是横向最后一个组件,如果属性值为GridBagConstraints.RELATIVE,表明当前组件是横向倒数第二个组件。 |
gridheight | 设置受该对象控制的 GUI 组件纵向跨越多少个网格,如果属性值为 GridBagContraints.REMAIND,则表明当前组件是纵向最后一个组件,如果属性值为GridBagConstraints.RELATIVE,表明当前组件是纵向倒数第二个组件。 |
fill | 当"显示区域"大于"组件"的时候,如何调整组件 : GridBagConstraints.NONE : GUI 组件不扩大 GridBagConstraints.HORIZONTAL: GUI 组件水平扩大 以 占据空白区域 GridBagConstraints.VERTICAL: GUI 组件垂直扩大以占据空白区域 GridBagConstraints.BOTH: GUI 组件水平 、 垂直同时扩大以占据空白区域. |
ipadx | 设置受该对象控制的 GUI 组件横向内部填充的大小,即 在该组件最小尺寸的基础上还需要增大多少. |
ipady | 设置受该对象控制的 GUI 组件纵向内部填充的大小,即 在该组件最小尺寸的基础上还需要增大多少. |
insets | 设置受该对象控制 的 GUI 组件的 外部填充的大小 , 即该组件边界和显示区 域边界之间的 距离 . |
weightx | 设置受该对象控制 的 GUI 组件占据多余空间的水平比例, 假设某个容器 的水平线上包括三个 GUI 组件, 它们的水平增加比例分别是 1 、 2 、 3 , 但容器宽度增加 60 像素 时,则第一个组件宽度增加 10 像素 , 第二个组件宽度增加 20 像素,第三个组件宽度增加 30 像 素。 如 果其增 加比例为 0 , 则 表示不会增加 。 |
weighty | 设置受该对象控制 的 GUI 组件占据多余空间的垂直比例 |
anchor | 设置受该对象控制 的 GUI 组件在其显示区域中的定位方式: GridBagConstraints .CENTER (中 间 ) GridBagConstraints.NORTH (上中 ) GridBagConstraints.NORTHWEST (左上角) GridBagConstraints.NORTHEAST (右上角) GridBagConstraints.SOUTH (下中) GridBagConstraints.SOUTHEAST (右下角) GridBagConstraints.SOUTHWEST (左下角) GridBagConstraints.EAST (右中) GridBagConstraints.WEST (左中) |
GridBagLayout使用步骤:
1.创建GridBagLaout布局管理器对象,并给容器设置该布局管理器对象;
2.创建GridBagConstraints对象,并设置该对象的控制属性:
gridx: 用于指定组件在网格中所处的横向索引;
gridy: 用于执行组件在网格中所处的纵向索引;
gridwidth: 用于指定组件横向跨越多少个网格;
gridheight: 用于指定组件纵向跨越多少个网格;
3.调用GridBagLayout对象的setConstraints(Component c,GridBagConstraints gbc )方法,把即将要添加到容器中的组件c和GridBagConstraints对象关联起来;
4. 把组件添加到容器中;
案例:
使用Frame容器,设置GridBagLayout布局管理器,实现下图中的效果:
import java.awt.*;
public class GridBagLayoutDemo {
public static void main(String[] args) {
//1.创建Frame对象
Frame frame = new Frame("这里是GridBayLayout测试");
//2.创建GridBagLayout对象
GridBagLayout gridBagLayout = new GridBagLayout();
//3.把Frame对象的布局管理器设置为GridBagLayout
frame.setLayout(gridBagLayout);
//4.创建GridBagConstraints对象
GridBagConstraints gridBagConstraints = new GridBagConstraints();
//5.创建容量为10的数组
Button[] bs = new Button[10];
//6.遍历数组,初始化每一个Button
for (int i = 0; i < 10; i++) {
bs[i] = new Button("按钮"+(i+1));
}
//7.设置所有的GridBagConstraints对象的fill属性为GridBagConstraints.BOTH,当有空白区域时,组件自动扩大占满空白区域
gridBagConstraints.fill = GridBagConstraints.BOTH;// GUI 组件水平 、 垂直同时扩大以占据空白区域.
//8.设置GridBagConstraints对象的weightx设置为1,表示横向扩展比例为1
gridBagConstraints.weightx=1;
//9.往frame中添加数组中的前3个Button
addComponent(frame,bs[0],gridBagLayout,gridBagConstraints);
addComponent(frame,bs[1],gridBagLayout,gridBagConstraints);
addComponent(frame,bs[2],gridBagLayout,gridBagConstraints);
//10.把GridBagConstraints的gridwidth设置为GridBagConstraints.REMAINDER,则表明当前组件是横向最后一个组件
gridBagConstraints.gridwidth=GridBagConstraints.REMAINDER;
//11.把button数组中第四个按钮添加到frame中
addComponent(frame,bs[3],gridBagLayout,gridBagConstraints);
//12.把GridBagConstraints的weighty设置为1,表示纵向扩展比例为1
gridBagConstraints.weighty=1;
//13.把button数组中第5个按钮添加到frame中
addComponent(frame,bs[4],gridBagLayout,gridBagConstraints);
//14.把GridBagConstaints的gridheight和gridwidth设置为2,表示纵向和横向会占用两个网格
gridBagConstraints.gridheight=2;
gridBagConstraints.gridwidth=2;
//15.把button数组中第6个按钮添加到frame中
addComponent(frame,bs[5],gridBagLayout,gridBagConstraints);
//16.把GridBagConstaints的gridheight和gridwidth设置为1,表示纵向会占用1个网格
gridBagConstraints.gridwidth=1;
gridBagConstraints.gridheight=1;
//17.把button数组中第7个按钮添加到frame中
addComponent(frame,bs[6],gridBagLayout,gridBagConstraints);
//18.把GridBagConstraints的gridwidth设置为GridBagConstraints.REMAINDER,则表明当前组件是横向最后一个组件
gridBagConstraints.gridwidth=GridBagConstraints.REMAINDER;
//19.把button数组中第8个按钮添加到frame中
addComponent(frame,bs[7],gridBagLayout,gridBagConstraints);
//20.把GridBagConstaints的gridwidth设置为1,表示纵向会占用1个网格
gridBagConstraints.gridwidth=1;
//21.把button数组中第9、10个按钮添加到frame中
addComponent(frame,bs[8],gridBagLayout,gridBagConstraints);
addComponent(frame,bs[9],gridBagLayout,gridBagConstraints);
//22.设置frame为最佳大小
frame.pack();
//23.设置frame可见
frame.setVisible(true);
}
public static void addComponent(Container container,Component c,GridBagLayout gridBagLayout,GridBagConstraints gridBagConstraints){
gridBagLayout.setConstraints(c,gridBagConstraints);
container.add(c);
}
}
2.4.5 CardLayout
CardLayout 布局管理器以时间而非空间来管理它里面的组件,它将加入容器的所有组件看成一叠卡片(每个卡片其实就是一个组件),每次只有最上面的那个 Component 才可见。就好像一副扑克牌,它们叠在一起,每次只有最上面的一张扑克牌才可见.
方法名称 | 方法功能 |
---|---|
CardLayout() | 创建默认的 CardLayout 布局管理器。 |
CardLayout(int hgap,int vgap) | 通过指定卡片与容器左右边界的间距 C hgap) 、上下边界 Cvgap) 的间距来创建 CardLayout 布局管理器. |
first(Container target) | 显示target 容器中的第一张卡片. |
last(Container target) | 显示target 容器中的最后一张卡片. |
previous(Container target) | 显示target 容器中的前一张卡片. |
next(Container target) | 显示target 容器中的后一张卡片. |
show(Container taget,String name) | 显 示 target 容器中指定名字的卡片. |
案例:
使用Frame和Panel以及CardLayout完成下图中的效果,点击底部的按钮,切换卡片
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class GardLayoutDemo {
public static void main(String[] args) {
//1.创建Frame对象
Frame frame = new Frame("这里测试CardLayout");
//2.创建一个String数组,用来存储不同卡片的名字
String[] names = {"第一张", "第二张", "第三张", "第四张", "第五张"};
//3.创建一个面板panel1,设置它的布局管理器为CardLayout,用来存放卡片
CardLayout cardLayout = new CardLayout(); //这里需要创建CardLayout对象在后按钮事件会用到
Panel panel1= new Panel();
panel1.setLayout(cardLayout);
//4.往卡片面板panel1中添加五个Button按钮,名字从String数组中取
for (int i = 0; i < 5; i++) {
panel1.add(names[i], new Button(names[i]));
//这里add函数原型为 add(String name, Component comp)
}
//5.创建一个面板panel2,用来存储五个按钮,实现卡片的切换
Panel panel2 = new Panel();
//6.创建五个卡片切换按钮,并给按钮设置监听器
Button button1 = new Button("上一张");
Button button2 = new Button("下一张");
Button button3 = new Button("第一张");
Button button4 = new Button("最后一张");
Button button5 = new Button("第三张");
ActionListener listener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand(); //这个字符串就是按钮上的文字
switch (command) {
case "上一张":
cardLayout.previous(panel1);
break;
case "下一张":
cardLayout.next(panel1);
break;
case "第一张":
cardLayout.first(panel1);
break;
case "最后一张":
cardLayout.last(panel1);
break;
case "第三张":
cardLayout.show(panel1,"第三张");
break;
}
}
};
button1.addActionListener(listener);
button2.addActionListener(listener);
button3.addActionListener(listener);
button4.addActionListener(listener);
button5.addActionListener(listener);
//7.将五个卡片切换按钮添加到面板二中
panel2.add(button1);
panel2.add(button2);
panel2.add(button3);
panel2.add(button4);
panel2.add(button5);
//8.把面板一添加到frame的中间区域
frame.add(panel1);
//9.把面板二添加到frame的底部区域
frame.add(panel2, BorderLayout.SOUTH); //Frame默认BorderLayout布局
//10.设置frame为最佳大小并可见
frame.pack();
frame.setVisible(true);
}
}
2.4.6 BoxLayout
为了简化开发,Swing 引入了 一个新的布局管理器 : BoxLayout 。 BoxLayout 可以在垂直和 水平两个方向上摆放 GUI 组件, BoxLayout 提供了如下一个简单的构造器:
方法名称 | 方法功能 |
---|---|
BoxLayout(Container target, int axis) | 指定创建基于 target 容器的 BoxLayout 布局管理器,该布局管理器里的组件按 axis 方向排列。其中 axis 有 BoxLayout.X_AXIS( 横向)和 BoxLayout.Y _AXIS (纵向〉两个方向。 |
案例1:
使用Frame和BoxLayout完成下图效果:
import javax.swing.*;
import java.awt.*;
public class BoxLayoutDemo {
public static void main(String[] args) {
//1.创建Frame对象
Frame frame = new Frame("这里测试BoxLayout");
//2.创建BoxLayout布局管理器,并指定容器为上面的frame对象,和指定组件的排列方向为纵向
BoxLayout boxLayout = new BoxLayout(frame, BoxLayout.Y_AXIS);
frame.setLayout(boxLayout);
//3.往frame对象中添加两个按钮
frame.add(new Button("按钮1"));
frame.add(new Button("按钮2"));
//4.设置frame为最佳大小并可见
frame.pack();
frame.setVisible(true);
}
}
案例二:使用Frame和Box,完成下图效果:
import javax.swing.*;
import java.awt.*;
public class BoxLayoutDemo2 {
public static void main(String[] args) {
//1.创建Frame对象
Frame frame = new Frame("这里测试BoxLayout");
//2.创建一个横向的Box,并添加两个按钮
Box box1 = Box.createHorizontalBox();
box1.add(new Button("水平按钮一"));
box1.add(new Button("水平按钮二"));
//3.创建一个纵向的Box,并添加两个按钮
Box box2 = Box.createVerticalBox();
box2.add(new Button("垂直按钮一"));
box2.add(new Button("垂直按钮二"));
//4.把box容器添加到frame的两个区域
frame.add(box1,BorderLayout.NORTH);
frame.add(box2);
//5.设置frame为最佳大小并可见
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,完成下图效果:
import javax.swing.*;
import java.awt.*;
public class BoxLayoutDemo3 {
public static void main(String[] args) {
//1.创建Frame对象
Frame frame = new Frame();
//2.创建一个横向的Box,并添加两个按钮
Box hBox = Box.createHorizontalBox();
hBox.add(new Button("水平按钮一"));
hBox.add(Box.createHorizontalGlue());
hBox.add(new Button("水平按钮二"));
hBox.add(Box.createHorizontalStrut(10));
hBox.add(new Button("水平按钮三"));
//3.创建一个纵向的Box,并添加两个按钮
Box vBox = Box.createVerticalBox();
vBox.add(new Button("垂直按钮一"));
vBox.add(Box.createHorizontalGlue());
vBox.add(new Button("垂直按钮二"));
vBox.add(Box.createHorizontalStrut(10));
vBox.add(new Button("垂直按钮三"));
//4.把box容器添加到frame容器中
frame.add(hBox,BorderLayout.NORTH);
frame.add(vBox);
//5.设置frame为最佳大小并可见
frame.pack();
frame.setVisible(true);
}
}
2.5 AWT中常用组件
2.5.1 基本组件
组件名 | 功能 |
---|---|
Button | Button |
Canvas | 用于绘图的画布 |
Checkbox | 复选框组件(也可当做单选框组件使用) |
CheckboxGroup | 选项组,用于将多个Checkbox 组件组合成一组, 一组 Checkbox 组件将只有一个可以 被选中 , 即全部变成单选框组件 |
Choice | 下拉选择框 |
Frame | 窗口 , 在 GUI 程序里通过该类创建窗口 |
Label | 标签类,用于放置提示性文本 |
List | 列表框组件,可以添加多项条目 |
Panel | 不能单独存在基本容器类,必须放到其他容器中 |
Scrollbar | 滑动条组件。如果需要用户输入位于某个范围的值 , 就可以使用滑动条组件 ,比如调 色板中设置 RGB 的三个值所用的滑动条。当创建一个滑动条时,必须指定它的方向、初始值、 滑块的大小、最小值和最大值。 |
ScrollPane | 带水平及垂直滚动条的容器组件 |
TextArea | 多行文本域 |
TextField | 单行文本框 |
这些 AWT 组件的用法比较简单,可以查阅 API 文档来获取它们各自的构方法、成员方法等详细信息。
案例:
实现下图效果:
import javax.swing.*;
import java.awt.*;
public class BasicComponentDemo {
//之前我们演示的时候,是在main函数里面完成的。但现在我们要做的界面比之前的界面要复杂很多,所以我们重新设计一下
//未来我们设计复杂界面时,会把组件在成员变量处组建,来方便使用,
//若多个组件来组成一个子界面时,可以设计一个方法来组装界面。然后在main函数中调用这个方法。
Frame frame = new Frame("这里测试基本组件");
//创建组件
TextArea ta = new TextArea(5,20);//一个五行二十列的文本框
Choice colorChooser = 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(50); //20宽单行文本框
Button ok = new Button("确认"); //确认按钮
List colorlist = new List(6,true);//六行的列表框,不写ture是单选,写true是多选
public void init() {
//组装界面
//组装底部
Box bBox = Box.createHorizontalBox();
bBox.add(tf);
bBox.add(ok);
frame.add(bBox,BorderLayout.SOUTH);
//组装选择部分
colorChooser.add("红色");
colorChooser.add("蓝色");
colorChooser.add("绿色");
Box cBox = Box.createHorizontalBox();
cBox.add(colorChooser);
cBox.add(male);
cBox.add(female);
cBox.add(isMarried);
//组装文本域和选择部分
Box topLeft = Box.createVerticalBox();
topLeft.add(ta);
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);
}
public static void main(String[] args) {
new BasicComponentDemo().init();
}
}
2.5.2 对话框Dialog
2.5.2.1 Dialog
Dialog 是 Window 类的子类,是 一个容器类,属于特殊组件 。 对话框是可以独立存在的顶级窗口, 因此用法与普通窗口的用法几乎完全一样,但是使用对话框需要注意下面两点:
- 但对话框通常依赖于其他窗口,就是通常需要有一个父窗口;
- 对话框有非模式(non-modal)和模式(modal)两种,当某个模式对话框被打开后,该模式对话框总是位于它的父窗口之上,在模式对话框被关闭之前,父窗口无法获得焦点。
方法名称 | 方法功能 |
---|---|
Dialog(Frame owner, String title, boolean modal) | 创建一个对话框对象: owner:当前对话框的父窗口 title:当前对话框的标题 modal:当前对话框是否是模式对话框,true/false |
案例1:
通过Frame、Button、Dialog实现下图效果:
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class DialogDemo {
public static void main(String[] args) {
Frame frame = new Frame("这里测试Dialog");
//1.创建对话框Dialog对象(一个模式一个非模式)
Dialog d1 = new Dialog(frame, "模式对话框", true);
Dialog d2 = new Dialog(frame, "非模式对话框", false);
//2.通过setBounds方法设置对话框的位置和大小
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) {
d1.setVisible(true);
}
});
b2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
d2.setVisible(true);
}
});
//5.把按钮添加到frame中
frame.add(b1,BorderLayout.NORTH);
frame.add(b2);
frame.pack();
frame.setVisible(true);
}
}
在Dialog对话框中,可以根据需求,自定义内容
案例:
点击按钮,弹出一个模式对话框,其内容如下:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class DialogDemo2 {
public static void main(String[] args) {
Frame frame = new Frame("这里测试Dialog");
//1.创建对话框Dialog对象
Dialog d1 = new Dialog(frame, "模式对话框", true);
//创建一个垂直的Box容器,把一个文本框和一个按钮添加到Box容器中
Box vBox = Box.createVerticalBox();
vBox.add(new TextField(20));
vBox.add(new Button("确认"));
//把Box容器添加到Dialog中
d1.add(vBox);
//2.通过setBounds方法设置对话框的位置和大小
d1.setBounds(20,30,300,200);
//3.创建按钮
Button b1 = new Button("打开模式对话框");
//4.给按钮添加点击后的行为(事件)
b1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
d1.setVisible(true);
}
});
//5.把按钮添加到frame中
frame.add(b1,BorderLayout.NORTH);
frame.pack();
frame.setVisible(true);
}
}
2.5.2.1 FileDialog
Dialog 类还有 一个子类 : FileDialog ,它代表一个文件对话框,用于 打开或者保存 文件,需要注意的是FileDialog无法指定模态或者非模态,这是因为 FileDialog 依赖于运行平台的实现,如果运行平台的文件对话框是模态的,那么 FileDialog 也是模态的;否则就是非模态的 。
方法名称 | 方法功能 |
---|---|
FileDialog(Frame parent, String title, int mode) | 创建一个文件对话框: parent:指定父窗口 title:对话框标题 mode:文件对话框类型,如果指定为FileDialog.load,用于打开文件,如果指定为FileDialog.SAVE,用于保存文件 |
String getDirectory() | 获取被打开或保存文件的绝对路径 |
String getFile() | 获取被打开或保存文件的文件名 |
案例2:
使用 Frame、Button和FileDialog完成下图效果:
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class FileDialogDemo {
public static void main(String[] args) {
Frame frame = new Frame();
//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 = f1.getDirectory();
String file = f1.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);
}
}
2.6 事件处理
前面介绍了如何放置各种组件,从而得到了丰富多彩的图形界面,但这些界面还不能响应用户的任何操作。比如单击前面所有窗口右上角的“X”按钮,但窗口依然不会关闭。因为在 AWT 编程中 ,所有用户的操作,都必须都需要经过一套事件处理机制来完成,而 Frame 和组件本身并没有事件处理能力 。
2.6.1 GUI事件处理机制
定义:
当在某个组件上发生某些操作的时候,会自动的触发一段代码的执行。
在GUI事件处理机制中涉及到4个重要的概念需要理解:
事件源(Event Source):操作发生的场所,通常指某个组件,例如按钮、窗口等;
事件(Event):在事件源上发生的操作可以叫做事件,GUI会把事件都封装到一个Event对象中,如果需要知道该事件的详细信息,就可以通过Event对象来获取。
事件监听器(Event Listener):当在某个事件源上发生了某个事件,事件监听器就可以对这个事件进行处理。
注册监听:把某个事件监听器(A)通过某个事件(B)绑定到某个事件源©上,当在事件源C上发生了事件B之后,那么事件监听器A的代码就会自动执行。
使用步骤:
1.创建事件源组件对象;
2.自定义类,实现XxxListener接口,重写方法;
3.创建事件监听器对象(自定义类对象)
4.调用事件源组件对象的addXxxListener方法完成注册监听
案例:
**
**
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class EventDemo1 {
Frame frame = new Frame("这里测试事件处理");
TextField tf = new TextField(30);
//事件源
Button ok = new Button("确定");
public void init() {
//组装视图
//监听器
//MyListener myListener = new MyListener();
//注册监听
ok.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("ok按钮被点击了...");
tf.setText("Hello World");
}
});
//把tf和ok放入到frame中
frame.add(tf, BorderLayout.NORTH);
frame.add(ok);
//设置最佳大小和可见性
frame.pack();
frame.setVisible(true);
}
/*private class MyListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
tf.setText("Hello World");
}
}*/
public static void main(String[] args) {
new EventDemo1().init();
}
}
2.6.2 GUI中常见事件和事件监听器
事件监听器必须实现事件监听器接口, AWT 提供了大量的事件监听器接口用于实现不同类型的事件监听器,用于监听不同类型的事件 。 AWT 中提供了丰富的事件类,用于封装不同组件上所发生的特定操作, AWT 的事件类都是 AWTEvent 类的子类 , AWTEvent是 EventObject 的子类。
2.6.2.1 事件
AWT把事件分为了两大类:
1.低级事件:这类事件是基于某个特定动作的事件。比如进入、点击、拖放等动作的鼠标事件,再比如得到焦点和失去焦点等焦点事件。
指向性明确:进入、点击、拖放等
事件 | 触发时机 |
---|---|
ComponentEvent | 组件事件 , 当 组件尺寸发生变化、位置发生移动、显示/隐藏状态发生改变时触发该事件。 |
ContainerEvent | 容器事件 , 当容器里发生添加组件、删除组件时触发该事件 。 |
WindowEvent | 窗口事件, 当窗 口状态发生改变 ( 如打开、关闭、最大化、最 小化)时触发该事件 。 |
FocusEvent | 焦点事件 , 当组件得到焦点或失去焦点 时触发该事件 。 |
KeyEvent | 键盘事件 , 当按键被按下、松开、单击时触发该事件。 |
MouseEvent | 鼠标事件,当进行单击、按下、松开、移动鼠标等动作 时触发该事件。 |
PaintEvent | 组件绘制事件 , 该事件是一个特殊的事件类型 , 当 GUI 组件调 用 update/paint 方法 来呈现自身时触发该事件,该事件并非专用于事件处理模型 。 |
2.高级事件:这类事件并不会基于某个特定动作,而是根据功能含义定义的事件。
事件 | 触发时机 |
---|---|
ActionEvent | 动作事件 ,当按钮、菜单项被单击,在 TextField 中按 Enter 键时触发 |
AjustmentEvent | 调节事件,在滑动条上移动滑块以调节数值(颜色、亮度)时触发该事件。 |
ltemEvent | 选项事件,当用户选中某项, 或取消选中某项时触发该事件 。 |
TextEvent | 文本事件, 当文本框、文本域里的文本发生改变时触发该事件。 |
2.6.2 事件监听器
不同的事件需要使用不同的监听器监听,不同的监听器需要实现不同的监听器接口, 当指定事件发生后 , 事件监听器就会调用所包含的事件处理器(实例方法)来处理事件 。
Event—>Listener
事件类别 | 描述信息 | 监听器接口名 |
---|---|---|
ActionEvent | 激活组件 | ActionListener |
ItemEvent | 选择了某些项目 | ItemListener |
MouseEvent | 鼠标移动 | MouseMotionListener |
MouseEvent | 鼠标点击等 | MouseListener |
KeyEvent | 键盘输入 | KeyListener |
FocusEvent | 组件收到或失去焦点 | FocusListener |
AdjustmentEvent | 移动了滚动条等组件 | AdjustmentListener |
ComponentEvent | 对象移动缩放显示隐藏等 | ComponentListener |
WindowEvent | 窗口收到窗口级事件 | WindowListener |
ContainerEvent | 容器中增加删除了组件 | ContainerListener |
TextEvent | 文本字段或文本区发生改变 | TextListener |
2.6.3 案例
案例一:
通过ContainerListener监听Frame容器添加组件;
通过TextListener监听TextFiled内容变化;
通过ItemListener监听Choice条目选中状态变化;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
public class ListenerDemo1 {
public static void main(String[] args) {
//处理控制台乱码的问题
try {
System.setOut(new PrintStream(System.out, true, "GBK")); // 设置控制台输出编码为GBK
} catch (UnsupportedEncodingException e) {
// 处理异常的代码
}
Frame frame = new Frame("这里测试监听器");
//创建组件 (事件源)
TextField tf = new TextField(30); //30宽度的文本框
Choice names = new Choice(); //下拉选择框
names.add("柳岩");
names.add("舒淇");
names.add("闫妮");
//给文本域添加TextListener,监听内容的变化
tf.addTextListener(new TextListener() {
@Override
public void textValueChanged(TextEvent e) {
String text = tf.getText(); //得到文本域中的内容
System.out.println("当前文本框中的内容为:" + text);
}
});
//给下拉选择框添加ItemListener,监听条目选项的变化
names.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
Object item = e.getItem(); //得到当前选择框的条目内容
System.out.println("当前选中的条目为:" + item);
}
});
//给frame注册ContainerListener监听器,监听容器中组件的添加
frame.addContainerListener(new ContainerListener() {
@Override
public void componentAdded(ContainerEvent e) {
Component child = e.getChild();
System.out.println("frame中添加了:" + child);
}
@Override
public void componentRemoved(ContainerEvent e) {
}
});
//添加到frame中
Box hBox = Box.createHorizontalBox();//横向
hBox.add(names);
hBox.add(tf);
frame.add(hBox);
//设置frame最佳大小并可见
frame.pack();
frame.setVisible(true);
}
}
案例2:
给Frame设置WindowListner,监听用户点击 X 的动作,如果用户点击X,则关闭当前窗口。
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class ListenerDemo2 {
public static void main(String[] args) {
Frame frame = new Frame("这里测试WindowListener");
//设置WindowListener,监听用户点击X的动作,如果点击X,则关闭窗口
frame.addWindowListener(new WindowAdapter() { //WindowAdapter可以只重写一个操作方法
@Override
public void windowClosing(WindowEvent e) {
//停止当前程序
System.exit(0);
}
});
frame.setBounds(300,300,500,200);
frame.setVisible(true);
}
}
2.7 菜单组件
前面讲解了如果构建GUI界面,其实就是把一些GUI的组件,按照一定的布局放入到容器中展示就可以了。在实际开发中,除了主界面,还有一类比较重要的内容就是菜单相关组件,可以通过菜单相关组件很方便的使用特定的功能,在AWT中,菜单相关组件的使用和之前学习的组件是一模一样的,只需要把菜单条、菜单、菜单项组合到一起,按照一定的布局,放入到容器中即可。
下表中给出常见的菜单相关组件:
菜单组件名称 | 功能 |
---|---|
MenuBar | 菜单条 , 菜单的容器 。 |
Menu | 菜单组件 , 菜单项的容器 。 它也是Menultem的子类 ,所以可作为菜单项使用 |
PopupMenu | 上下文菜单组件(鼠标右键菜单组件) |
Menultem | 菜单项组件 。 |
CheckboxMenuItem | 复选框菜单项组件 |
下图是常见菜单相关组件集成体系图:
菜单相关组件使用:
1.准备菜单项组件,这些组件可以是MenuItem及其子类对象
2.准备菜单组件Menu或者PopupMenu(右击弹出子菜单),把第一步中准备好的菜单项组件添加进来;
3.准备菜单条组件MenuBar,把第二步中准备好的菜单组件Menu添加进来;
4.把第三步中准备好的菜单条组件添加到窗口对象中显示。
小技巧:
1.如果要在某个菜单的菜单项之间添加分割线,那么只需要调用**Menu的add(new MenuItem(“-”))**即可。
2.如果要给某个菜单项关联快捷键功能,那么只需要在创建菜单项对象时设置即可,例如给菜单项关联 ctrl+shif+/ 快捷键,只需要:new MenuItem(“菜单项名字”,new MenuShortcut(KeyEvent.VK_Q,true);
案例1:
使用awt中常用菜单组件,完成下图效果
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
public class SimpleMenu {
//创建窗口
Frame frame = new Frame("这里测试菜单相关组件");
//创建菜单条
MenuBar menuBar = new MenuBar();
//创建菜单组件
Menu fileMenu = new Menu("文件");
Menu editMenu = new Menu("编辑");
Menu formatMenu = new Menu("格式");
//菜单项组件
MenuItem auto = new MenuItem("自动换行");
MenuItem copy = new MenuItem("复制");
MenuItem paste = new MenuItem("粘贴");
MenuItem comment = new MenuItem("注释 Ctrl+Shift+Q", new MenuShortcut(KeyEvent.VK_Q,true)); //关联快捷键ctrl+shift+Q
MenuItem cancelComment= new MenuItem("取消注释");
TextArea ta = new TextArea(6,40); //六行四十列的文本框
public void init() {
//组装视图
comment.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
ta.append("您点击了菜单项:" + e.getActionCommand()); //将事件发生地方的内容加入到文本框中
}
});
formatMenu.add(comment);
formatMenu.add(cancelComment);
//组装编辑菜单条
editMenu.add(auto);
editMenu.add(copy);
editMenu.add(paste);
editMenu.add(new MenuItem("-"));
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 SimpleMenu().init();
}
}
案例2:
通过PopupMenu实现下图效果:
实现思路:
1.创建PopubMenu菜单组件;
2.创建多个MenuItem菜单项,并添加到PopupMenu中;
3.将PopupMenu添加到目标组件中;
4.为需要右击出现PopubMenu菜单的组件,注册鼠标监听事件,当监听到用户释放右键时,弹出菜单。
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class PopupMenuDemo {
Frame frame = new Frame("这里测试PopupMenu");
//创建文本域
TextArea ta = new TextArea("我爱中华",6,40);
//创建panel容器
Panel panel = 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() {
//创建一个事件监听器
ActionListener listener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String actionCommand = e.getActionCommand();
ta.append("您点击了:" + actionCommand + "\n");
}
};
//每个菜单项都进行了事件处理
comment.addActionListener(listener);
cancelComment.addActionListener(listener);
copy.addActionListener(listener);
save.addActionListener(listener);
//组装视图
popupMenu.add(comment);
popupMenu.add(cancelComment);
popupMenu.add(copy);
popupMenu.add(save);
panel.add(popupMenu);
//设置Panel的大小
panel.setPreferredSize(new Dimension(400,300));
//给Panel注册鼠标事件,监听用户释放鼠标的动作,展示菜单
panel.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
boolean flag = e.isPopupTrigger();
if(flag) {
//显示PopupMenu
popupMenu.show(panel,e.getX(),e.getY());
}
}
});
frame.add(ta);
frame.add(panel,BorderLayout.SOUTH);
//设置frame最佳大小并可见
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
new PopupMenuDemo().init();
}
}
2.8 绘图
很多程序如各种小游戏都需要在窗口中绘制各种图形,除此之外,即使在开发JavaEE项目时, 有 时候也必须"动态"地向客户 端生成各种图形、图表,比如 图形验证码、统计图等,这都需要利用AWT的绘图功能。
2.8.1 组件绘图原理
之前我们已经学习过很多组件,例如Button、Frame、Checkbox等等,不同的组件,展示出来的图形都不一样,其实这些组件展示出来的图形,其本质就是用AWT的绘图来完成的。
在AWT中,真正提供绘图功能的是Graphics对象,那么Component组件和Graphics对象存在什么关系,才能让Component绘制自身图形呢?在Component类中,提供了下列三个方法来完成组件图形的绘制与刷新:
paint(Graphics g):绘制组件的外观;
update(Graphics g):内部调用paint方法,刷新组件外观;
repaint():调用update方法,刷新组件外观;
一般情况下,update和paint方法是由AWT系统负责调用,如果程序要希望系统重新绘制组件,可以调用repaint方法完成。
手动重绘:reqaint方法
自己绘制组件:重写paint方法
2.8.2 Graphics类的使用
实际生活中如果需要画图,首先我们得准备一张纸,然后在拿一支画笔,配和一些颜色,就可以在纸上画出来各种各样的图形,例如圆圈、矩形等等。
程序中绘图也一样,也需要画布,画笔,颜料等等。AWT中提供了Canvas类充当画布,提供了Graphics类来充当画笔,通过调用Graphics对象的setColor()方法可以给画笔设置颜色。
画图的步骤:
1.自定义类,继承Canvas类,重写paint(Graphics g)方法(桥梁)完成画图;
2.在paint方法内部,真正开始画图之前调用Graphics对象的setColor()、setFont()等方法设置画笔的颜色、字体等属性;
3.调用**Graphics画笔的drawXxx()**方法开始画图。
其实画图的核心就在于使用Graphics画笔在Canvas画布上画出什么颜色、什么样式的图形,所以核心在画笔上,下表中列出了Graphics类中常用的一些方法:
方法名称 | 方法功能 |
---|---|
setColor(Color c) | 设置颜色 |
setFont(Font font) | 设置字体 |
drawLine() | 绘制直线 |
drawRect() | 绘制矩形 |
drawRoundRect() | 绘制圆角矩形 |
drawOval() | 绘制椭圆形 |
drawPolygon() | 绘制多边形 |
drawArc() | 绘制圆弧 |
drawPolyline() | 绘制折线 |
fillRect() | 填充矩形区域 |
fillRoundRect() | 填充圆角矩形区域 |
fillOval() | 填充椭圆区域 |
fillPolygon() | 填充多边形区域 |
fillArc() | 填充圆弧对应的扇形区域 |
drawImage() | 绘制位图 |
案例:
使用AWT绘图API,完成下图效果
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,100);
}else if(shape.equals(OVAL_SHAPE)){
//绘制椭圆
g.setColor(Color.red);
g.drawOval(100,100,150,100);
}
}
}
//创建自定义的画布对象(也是组件)
MyCanvas drawArea = new MyCanvas();
public void init() {
//组装视图
btnRect.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//修改标记的值为rect
shape = RECT_SHAPE;
drawArea.repaint(); //刷新重新画,不是调用paint函数
}
});
btnOval.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//修改标记的值为oval
shape = OVAL_SHAPE;
drawArea.repaint();
}
});
//创建面板承载按钮
//Panel的默认管理器为FlowLayout,默认横向布局
Panel panel = new Panel();
panel.add(btnRect);
panel.add(btnOval);
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 Draw;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class PinBall {
//创建窗口对象
private Frame frame = new Frame("弹球游戏");
//1.设置桌面和球拍各自的宽度和高度
private final int TABLE_WIDTH = 300;
private final int TABLE_HEIGHT = 400;
private final int RACKET_WIDTH = 60;
private final int RACKET_HEIGHT = 20;
//2.设置小球的大小,即小球的直径
private final int BALL_SIZE = 16;
//3.记录小球的坐标
//注意坐标原点是窗口左上角
private int ballX = 120; //并且初始化小球的坐标
private int ballY = 20;
//4.设置小球在X和Y方向上分别移动的速度
private int speedY = 10;
private int speedX = 5;
//5.记录球拍的坐标
private int racketX = 120;
private final int racketY = 340; //球拍的Y坐标一直不变,即球拍一直在水平移动
//6.游戏是否结束的标识
private boolean isOver = false;
//7.定时器:声明一个定时器
private Timer timer;
//8.画布:自定义一个类,继承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); //设置球拍的坐标和大小
}
}
}
//9.画笔:创建绘画区域
MyCanvas drawArea = new MyCanvas();
public void init() {
//组装视图,游戏逻辑的控制
//如何控制小球和球拍的变换
//10.完成球拍坐标的变化,通过键盘左右键来实现
KeyListener listener = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
//获取当前按下的键
int keyCode = e.getKeyCode();
if(keyCode == KeyEvent.VK_LEFT) { //这个KeyEvent.VK_LEFT意味这是键盘的左箭头
//向左移动
//TODO
if(racketX <= 10) racketX = 0;
else racketX -= 10;
}
if(keyCode == KeyEvent.VK_RIGHT) {
//向右移动
if(racketX > (TABLE_WIDTH - RACKET_WIDTH - 10)) racketX = TABLE_WIDTH - RACKET_WIDTH;
else racketX += 10;
}
}
};
//给Frame和drawArea注册监听器
frame.addKeyListener(listener);
drawArea.addKeyListener(listener);
//11.小球坐标的控制
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 && ballX <racketX+RACKET_WIDTH)) {
//碰到上边界,以及碰到球拍,判定条件是小球y>球拍y,并且小球在球拍宽度的范围内
speedY = -speedY;
}
if(ballY>racketY-BALL_SIZE && (ballX < racketX || ballX >racketX+RACKET_WIDTH)){
//当前小球超出了球拍能接到的范围,游戏结束
//停止定时器
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();
}
}
2.8.3 处理位图
如果仅仅绘制一些简单的几何图形,程序的图形效果依然比较单调 。 AWT 也允许在组件上绘制位图, Graphics 提供了 drawlmage() 方法用于绘制位图,该方法需要一个Image参数一一代表位图,通过该方法就可 以绘制出指定的位图 。
位图使用步骤:
1.创建Image的子类对象BufferedImage(int width,int height,int ImageType),创建时需要指定位图的宽高及类型属性;此时相当于在内存中生成了一张图片;
2.调用BufferedImage对象的getGraphics()方法获取画笔(缓冲区的画笔),此时就可以往内存中的这张图片上绘图了,绘图的方法和之前学习的一模一样;
3.调用组件paint方法中提供的Graphics对象(最终画布上的画笔)的drawImage()方法,一次性的内存中的图片BufferedImage绘制到特定的组件上。
使用位图绘制组件的好处:
使用位图来绘制组件,相当于实现了图的缓冲区,此时绘图时没有直接把图形绘制到组件上,而是先绘制到内存中的BufferedImage上,等全部绘制完毕,再一次性的图像显示到组件上即可,这样用户的体验会好一些。
案例:
通过BufferedImage实现一个简单的手绘程序:通过鼠标可以在窗口中画图。
package Draw;
import javax.swing.plaf.ComboBoxUI;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
public class HandDraw {
//定义窗口对象
private Frame frame = new Frame("简单手绘程序");
//定义画图区的宽高
private final int AREA_WIDHT = 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_WIDHT,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中注册鼠标事件监听器
drawArea.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) { //当释放鼠标键时被调用
boolean popupTrigger = e.isPopupTrigger(); //获取点击的是鼠标的哪个键
if(popupTrigger) { //true则说明是右键
colorMenu.show(drawArea,e.getX(),e.getY());
}
else {
//倘若不是右键,则说明监听到拖动结束的标志了
//重置preX和preY
preX = -1;
preY = -1;
}
}
});
//实现绘图的逻辑
//先设置位图的背景为白色
g.setColor(Color.WHITE);
g.fillRect(0,0,AREA_WIDHT,AREA_HEIGHT);
//通过监听鼠标的移动,完成线条绘制
drawArea.addMouseMotionListener(new MouseMotionAdapter() {
//该方法,当鼠标左键按下,并进行拖动时,会被调用
@Override
public void mouseDragged(MouseEvent e) {
//注意这里是画在缓冲区
//判断一下是否第一次拖动
if(preX>0 && preY>0) {
//画线条,需要两组坐标,分别是线条的起点和终点, e.getX()和 e.getY()可以获取鼠标坐标
//这里是终点的坐标,因此需要先记录起点的坐标
g.setColor(forceColor);
g.drawLine(preX, preY, e.getX(), e.getY());
}
//修正preX和preY的值
preX = e.getX();
preY = e.getY();
//重绘组件,到画布中
drawArea.repaint();
}
});
drawArea.setPreferredSize(new Dimension(AREA_WIDHT,AREA_HEIGHT));
frame.add(drawArea);
//设置窗口最佳大小和可见性
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
new HandDraw().init();
}
}
2.8.4 ImageIO的使用
在实际生活中,很多软件都支持打开本地磁盘已经存在的图片,然后进行编辑,编辑完毕后,再重新保存到本地磁盘。如果使用AWT要完成这样的功能,那么需要使用到ImageIO这个类,可以操作本地磁盘的图片文件。
方法名称 | 方法功能 |
---|---|
static BufferedImage read(File input) | 读取本地磁盘图片文件 |
static BufferedImage read(InputStream input) | 读取本地磁盘图片文件 |
static boolean write(RenderedImage im, String formatName, File output) | 往本地磁盘中输出图片文件 |
案例:
编写图片查看程序,支持另存操作
package Draw;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class ReadAndSaveImage {
private Frame frame = new Frame("图片查看器");
//创建菜单组件
MenuBar menuBar = new MenuBar();
Menu menu = new Menu("文件");
MenuItem open = new MenuItem("打开");
MenuItem save = new MenuItem("另存为");
//声明BufferedImage对象,作为缓冲区记录本地读取的内存中的图片
BufferedImage image; //不用初始化,等打开图片时再初始化
//自定义类继承Canvas,把图片绘制出来
private class MyCanvas extends Canvas {
@Override
public void paint(Graphics g) {
g.drawImage(image,0,0,null);
}
}
MyCanvas drawArea = new MyCanvas();
public void init() throws Exception {
//组装视图
//实现打开文件和保持文件的逻辑
//给open组件注册监听器
open.addActionListener(e -> { //gdk9
//打开一个文件对话框
FileDialog fileDialog = new FileDialog(frame,"打开图片",FileDialog.LOAD);
fileDialog.setVisible(true);
//获取用户选择的图片路径以及名称
String dir = fileDialog.getDirectory();
String fileName = fileDialog.getFile();
try {
image = ImageIO.read(new File(dir,fileName));
drawArea.repaint();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
});
//给save注册监听器
save.addActionListener(e -> {
//打开保存图片的对话框
FileDialog fileDialog = new FileDialog(frame,"保存图片",FileDialog.SAVE);
fileDialog.setVisible(true);
//获取用户选择的保存路径和文件名称
String dir = fileDialog.getDirectory();
String fileName = fileDialog.getFile();
try {
ImageIO.write(image,"JPEG", new File(dir,fileName));
} catch (IOException ex) {
throw new RuntimeException(ex);
}
});
//将菜单组件添加到菜单条组件中
menu.add(open);
menu.add(save);
//将菜单条组件添加到菜单条中
menuBar.add(menu);
//将菜单条,画布放入到窗口中
frame.setMenuBar(menuBar);
frame.add(drawArea);
//设置窗口大小和可见性
frame.setBounds(200,200,740,508);
frame.setVisible(true);
//监听窗口关闭
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public static void main(String[] args) throws Exception {
new ReadAndSaveImage().init();
}
}
2.8.5 五子棋
接下来,我们使用之前学习的绘图技术,做一个五子棋的游戏。
注意,这个代码只实现了五子棋的落子、删除棋子和动画等逻辑实现,并没有把五子棋的游戏逻辑编写完整,比较简单易上手。
图片素材
package 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 JFrame jframe = new JFrame("五子棋游戏"); //改动一,使用swing中的JFrame解决画面闪烁
//声明四个BufferedImage对象,分别记录四张图片
BufferedImage table;
BufferedImage black;
BufferedImage white;
BufferedImage selected;
//声明棋盘的宽和高(与棋盘图片的宽和高一致)
private final int TABLE_WIDTH = 535;
private final int TABLE_HEIGHT = 536;
//声明棋盘横向和纵向分别可以下多少子,它们的值都为十五(多少行多少列)
final int BOARD_SIZE =15;
//声明每个棋子占用棋盘的比率
final int RATE = TABLE_WIDTH/BOARD_SIZE;
//声明变量记录棋子对于x方向和y方向的偏移量,在像素中量出来的
final int X_OFFSET = 5;
final int Y_OFFSET = 6;
//声明一个二维数组,记录当前位置棋子的状态,如果索引[i][j]的值为 0-没有棋子, 1-白棋, 2-黑棋
int[][] board = new int[BOARD_SIZE][BOARD_SIZE];
//声明红色选择框的坐标,也是二维数组中的索引
int selected_X = -1;
int selected_Y = -1;
//自定义类继承Canvas,充当画布
private class ChessBoard extends JPanel{ //改动二,继承swing中JPanel而不是Canvas,解决画面闪烁
@Override
public void paint(Graphics g) {
//绘图
//绘制棋盘
g.drawImage(table,0,0,null);
//绘制选择框
if(selected_X>0 && selected_Y>0) //判断有移动时再开始绘制
//注意索引与真实位置的转换
g.drawImage(selected,selected_X*RATE+X_OFFSET,selected_Y*RATE+Y_OFFSET,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+X_OFFSET,j*RATE+Y_OFFSET,null);
//绘制白棋
if(board[i][j] == 1) g.drawImage(white,i*RATE+X_OFFSET,j*RATE+Y_OFFSET,null);
}
}
}
}
ChessBoard chessBoard = new ChessBoard();
//声明变量,记录当前下棋的颜色,1-白棋, 2-黑棋
int board_type = 2;
//声明底部需要用的组件
Panel p = new Panel();
Button whiteBtn = new Button("白棋");
Button blackBtn = new Button("黑棋");
Button deleteBtn = new Button("删除");
public void refreshBtnColor(Color whitBtnColor, Color blackBtnColor, Color deleteBtnColor) { //用来刷新按钮的颜色
whiteBtn.setBackground(whitBtnColor); //setBackground来设置按钮(背景)颜色
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);
//将面板添加到frame的南部区域
jframe.add(p,BorderLayout.SOUTH);
//组装棋盘
//初始化图片
//这里保存图片时需要在项目里建立一个文件夹,来存放图片,不然放在其他地方好像都读不到。。
table = ImageIO.read(new File("E:\\java_untitled\\calculatoe\\img\\table.jpg"));
black = ImageIO.read(new File("E:\\java_untitled\\calculatoe\\img\\black.gif"));
white = ImageIO.read(new File("E:\\java_untitled\\calculatoe\\img\\white.gif"));
selected = ImageIO.read(new File("E:\\java_untitled\\calculatoe\\img\\selected.gif"));
//处理棋盘的游戏逻辑,如红色选择框随鼠标移动,鼠标点击便下子
//处理鼠标移动
chessBoard.addMouseMotionListener(new MouseMotionAdapter() {
//当鼠标移动时会调用该方法
@Override
public void mouseMoved(MouseEvent e) {
selected_X = (e.getX()-X_OFFSET)/RATE;//获取此时鼠标的坐标-偏移量/比率,就能得到下棋子的坐标
selected_Y = (e.getY()-Y_OFFSET)/RATE;
chessBoard.repaint();
}
});
//处理鼠标点击
chessBoard.addMouseListener(new MouseAdapter() {
//当鼠标被点击后会调用该方法
@Override
public void mouseClicked(MouseEvent e) {
int xPos = (e.getX()-X_OFFSET)/RATE;//跟上面一样得到真实坐标
int yPos = (e.getY()-Y_OFFSET)/RATE;
board[xPos][yPos] = board_type;//更新坐标中的标记意味已经下子了
chessBoard.repaint();
}
//当鼠标退出区域时,重置界面,使selected_X和selected_Y为-1
@Override
public void mouseExited(MouseEvent e) {
selected_X = -1;
selected_Y = -1;
chessBoard.repaint();
}
});
chessBoard.setPreferredSize(new Dimension(TABLE_WIDTH,TABLE_HEIGHT));//设置画布
jframe.add(chessBoard);
//设置frame最佳大小并可见
jframe.pack();
jframe.setVisible(true);
}
public static void main(String[] args) throws Exception {
new Gobang().init();
}
}