Java_AWT

component:组件

思路:整体窗口用于展示、通过组件进行内容添加、通过特殊组件即容器进行嵌套、通过布局管理器进行布局管理、再通过监听器赋予按钮功能

一、AWT简介

​ 当 JDK 1.0发布时, Sun 提供了 一套基本的GUI类库,这个GUI类库希望可以在所有平台下都能运行 , 这套基本类库被称为”抽象窗口工具集 (Abstract Window Toolkit )”,它为Java应用程序提供了基本的图形组件 。 AWT是窗口框架,它从不同平台的窗口系统中抽出共同组件 , 当程序运行时,将这些组件的创建和动作委托给程序所在的运行平台 。 简而言之 ,当使用 AWT 编写图形界面应用 时, 程序仅指定了界面组件的位置和行为,并未提供真正的实现,JVM调用操作系统本地的图形界面来创建和平台 一致的对等体 。

​ 使用AWT创建的图形界面应用和所在的运行平台有相同的界面风格 , 比如在 Windows 操作系统上,它就表现出 Windows 风格 ; 在 UNIX 操作系统上,它就表现出UNIX 风格 。 Sun 希望采用这种方式来实现 ” Write Once, Run Anywhere ” 的目标 。

二、AWT继承体系

所有和 AWT 编程相关的类都放在 java.awt 包以及它的子包中, AWT 编程中有两个组件基类 :Component和 MenuComponent。

  • Component:代表一个能以图形化方式显示出来,并可与用户交互的对象,例如 Button 代表一个按钮,TextField 代表 一个文本框等;
  • MenuComponent:则代表图形界面的菜单组件,包括 MenuBar (菜单条)、 Menultem (菜单项)等子类。
graph TD
Object –> Component
Object –> MenuComponent
MenuComponent –> MenuItem
MenuComponent –> MenuBar
MenuItem –> menu
Component –> Choice
Component –> TextComponent
Component –> CheckBox
Component –> Lable
Component –> List
Component –> Container
Component –> Cavas
Component –> ScrollBar
Component –> Button
TextComponent –> TextArea
TextComponent –> TextField
Container –> Panel
Container –> Window
Container –> ScrollPane

其中 Container 是一种特殊的 Component,它代表一种容器,通用抽象窗口工具包(AWT)容器对象是可以包含其他AWT组件的组件。

AWT中还有一个非常重要的接口叫LayoutManager ,如果一个容器中有多个组件,那么容器就需要使用LayoutManager来管理这些组件的布局方式。

graph TD
LayoutManager:布局管理器 –> GridLayout
LayoutManager:布局管理器 –> FLowLayout
LayoutManager:布局管理器 –> …

三、Container容器

graph TD
object –> Component:组件
Component:组件 –> Container:特殊组件即容器
Container:特殊组件即容器 –> Window:窗口容器
Container:特殊组件即容器 –> Panel:内嵌容器
Container:特殊组件即容器 –> ScrollPane:含有滚动条的容器
Window:窗口容器 –> Frame:创建窗口
Window:窗口容器 –> Dialog:创建对话框
Panel:内嵌容器 –> Applet

Window:是可以独立存在的顶级窗口,默认使用BorderLayout管理其内部组件布局;

Panel:可以容纳其它组件,但是不能独立存在,它必须内嵌其它容器中使用,默认使用FlowLayout管理其内部组件布局;

ScrollPane是一个带滚动条的容器,它也不能独立存在,默认使用BorderLayout管理其内部组件布局。

四、常见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(); 返回该容器内的所有组件

五、容器演示

1、Window

参考代码:

package com.one.container;

import java.awt.*;

public class WindowDemo {
    public static void main(String[] args){
        //1、创建一个窗口对象
        Frame frame = new Frame("这里测试一个Window窗口");
        //2、指定一个窗口位置、大小
        //此处参数表示像素:即一个.表示一像素
        frame.setLocation(100, 100);
        frame.setSize(500, 300);
        //3、设置窗口对象可见
        frame.setVisible(true);
    }
}
2、Panel

参考代码:

package com.one.container;
import java.awt.*;

public class PanelDemo {
    public static void main(String[] args){
        //1、创建一个window对象,因为panel以及其它的容器,都不能独立存在,必须依附于window存在
        Frame frame = new Frame("演示Panerl");
        //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, 300);
        //6、设置window可见
        frame.setVisible(true);
    }
}
3、ScrollPane:

参考代码:

程序明明向 ScrollPane 容器中添加了 一个文本框和一个按钮,但只能看到 一个按钮,却看不到文本框 ,这是为什么 呢?这是因为ScrollPane 使用 BorderLayout 布局管理器的缘故,而 BorderLayout 导致了该容器中只有一个组件被显示出来。

package com.one.container;
import java.awt.*;

public class ScrollPaneDemo {
    public static void main(String[] args) {
        Frame frame = new Frame("这里演示ScrollPane");

        //1、创建一个ScrollPane对象
        //ScrollPane.SCROLLBARS_ALWAYS 表示总是显示进度条
        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, 300);
        frame.setVisible(true);
    }
}

六、LayoutManager布局管理器

1、概述:

​ 之前,我们介绍了Component中有一个方法setBounds()可以设置当前容器的位置和大小,但是我们需要明确一件事情,如果我们手动的为组件设置位置大小的话,就会造成程序的不通用性, 例如:

Label label = new Label("你好,世界");

​ 创建了一个lable组件,很多情况下,我们需要让lable组件的宽高和“你好,世界”这个字符串自身的宽高一致,这种大小称为最佳大小。由于操作系统存在差异,例如在windows上,我们要达到这样的效果,需要把该Lable组件的宽和高分别设置为100px,20px,但是在Linux操作系统上,可能需要把Lable组件的宽和高分别设置为120px,24px,才能达到同样的效果。

为了解决这个问题,Java提供了LayoutManager布局管理器,可以根据运行平台来自动调整组件大小,程序员不用再手动设置组件的大小和位置了,只需要为容器选择合适的布局管理器即可。

graph TD
接口LayoutManager –> GridLayout
接口LayoutManager –> FLowLayout
接口LayoutManager –> 接口LayoutManager2
接口LayoutManager2 –> CardLayout
接口LayoutManager2 –> GridBagLayout
接口LayoutManager2 –> BorderLayout
2、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,默认是左对齐。

​ FlowLayout中组件中间距通过整数设置,单位是像素,默认是5个像素。

参考代码:

从某一个方向开始,直到边界或者结束为止

package com.one.Layout;
import java.awt.*;

public class FlowLayoutDemo {
    public static void main(String[] args) {
        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 = 1; i <= 100; i++){
            frame.add(new Button("按钮" + i));
        }
        //3、设置最佳大小,pack方法
        frame.pack();

        frame.setVisible(true);
    }
}
3、BorderLayout(边框布局):

​ BorderLayout 将容器分为 EAST 、 SOUTH 、 WEST 、 NORTH 、 CENTER(东、南、西、北、中)五个区域,普通组件可以被放置在这 5 个区域的任意一个中 。 BorderLayout布局 管理器的布局示意图如图所示 。

​ 当改变使用 BorderLayout 的容器大小时, NORTH 、 SOUTH 和 CENTER区域水平调整,而 EAST 、 WEST 和 CENTER 区域垂直调整。使用BorderLayout 有如下两个注意点:

  1. 当向使用 BorderLayout 布局管理器的容器中添加组件时 , 需要指定要添加到哪个区域中 。 如果没有指定添加到哪个区域中,则默认添加到中间区域中;
  2. 如果向同一个区域中添加多个组件时 , 后放入的组件会覆盖先放入的组件;
构造方法 方法功能
BorderLayout() 使用默认的水平间距、垂直 间距创建 BorderLayout 布局管理器 。
BorderLayout(int hgap,int vgap): 使用指定的水平间距、垂直间距创建 BorderLayout 布局管理器。

参考代码:

package com.one.Layout;
import java.awt.*;

public class BorderLayoutDemo1 {
    public static void main(String[] args) {
        Frame frame = new Frame("这里测试BorderLayout");

        //1、给frame设置BorderLayout布局管理器
        frame.setLayout(new BorderLayout(30, 10));
        //2、往frame的指定区域添加组件
        frame.add(new Button("北侧按钮"), BorderLayout.NORTH);
        frame.add(new Button("南侧按钮"), BorderLayout.SOUTH);
        frame.add(new Button("中测按钮"), BorderLayout.CENTER);
        frame.add(new Button("东测按钮"), BorderLayout.EAST);
        frame.add(new Button("西测按钮"), BorderLayout.WEST);

        frame.pack();   //设置最佳大小
        frame.setVisible(true);
    }
}

​ 没有放置组件的位置将会被其它组件占用,同一位置上后来的组件会替代前面的组件

package com.one.Layout;
import java.awt.*;

public class BorderLayoutDemo2 {
    public static void main(String[] args) {
        Frame frame = new Frame("这里测试BorderLayout");

        //1、给frame设置BorderLayout布局管理器
        frame.setLayout(new BorderLayout(30, 10));
        //2、往frame的指定区域添加组件
        frame.add(new Button("北侧按钮"), BorderLayout.NORTH);
        frame.add(new Button("南侧按钮"), BorderLayout.SOUTH);
        frame.add(new Button("中测按钮"), BorderLayout.CENTER);
//        frame.add(new Button("东测按钮"), BorderLayout.EAST);
//        frame.add(new Button("西测按钮"), BorderLayout.WEST);

        frame.add(new TextField("测试文本框"));  //默认放在中间,将已存在的替代了

        frame.pack();   //设置最佳大小
        frame.setVisible(true);
    }
}

​ 如果想要在某个区域放多个组件,就将多个组件先放到一个容器中

package com.one.Layout;
import java.awt.*;

public class BorderLayoutDemo2 {
    public static void main(String[] args) {
        Frame frame = new Frame("这里测试BorderLayout");

        //1、给frame设置BorderLayout布局管理器
        frame.setLayout(new BorderLayout(30, 10));
        //2、往frame的指定区域添加组件
        frame.add(new Button("北侧按钮"), BorderLayout.NORTH);
        frame.add(new Button("南侧按钮"), BorderLayout.SOUTH);
//        frame.add(new Button("中测按钮"), BorderLayout.CENTER);
//
//
//        frame.add(new TextField("测试文本框"));  //默认放在中间,将已存在的替代了

        //使用Panel容器组件,放置两个组件
        Panel p = new Panel();
        p.add(new Button("中测按钮"));
        p.add(new TextField("测试文本"));

        frame.add(p);

        frame.pack();   //设置最佳大小
        frame.setVisible(true);
    }
}
4、GridLayout(网格布局):

​ GridLayout 布局管理器将容器分割成纵横线分隔的网格 , 每个网格所占的区域大小相同。当向使用 GridLayout 布局管理器的容器中添加组件时, 默认从左向右、 从上向下依次添加到每个网格中 。 与 FlowLayout不同的是,放置在 GridLayout 布局管理器中的各组件的大小由组件所处的区域决定(每 个组件将自动占满整个区域) 。

构造方法 方法功能
GridLayout(int rows,in t cols) 采用指定的行数、列数,以及默认的横向间距、纵向间距将容器 分割成多个网格
GridLayout(int rows,int cols,int hgap,int vgap) 采用指定 的行数、列 数 ,以及指定的横向间距 、 纵向间距将容器分割成多个网格。

参考代码:

需求:使用Frame + Panel,GridLayout完成一个简单计算机效果

package com.one.Layout;
import java.awt.*;

public class GridLayout {
    public static void main(String[] args) {
        //Frame默认使用BorderLayout,所以无需设置
        Frame frame = new Frame("这里测试GridLayout");

        //1、创建一个Panel对象,里面存放一个TextFiled组件
        Panel p = new Panel();
        p.add(new TextField(30));   //指定一个容纳范围,即文本框大小
        //2、把当前这个Panel添加到frame的北边区域
        frame.add(p, BorderLayout.NORTH);
        //3、再次创建一个Panel对象,并且设置它的布局管理器为GridLayout
        Panel p2 = new Panel();
        p2.setLayout(new java.awt.GridLayout(3, 5, 4, 4));
        //4、往Panel中添加内容
        for(int i = 0; i <= 9; i++){
            p2.add(new Button(i + "")); //添加默认,从左往右、从上往下
        }

        p2.add(new Button("+"));
        p2.add(new Button("-"));
        p2.add(new Button("*"));
        p2.add(new Button("/"));
        p2.add(new Button("."));

        //5、把当前的Panel添加到frame的中间区域添加,即默认添加
        frame.add(p2);

        frame.pack();
        frame.setVisible(true);
    }
}
5、CridBagLayout(网格包布局):

​ 了解即可,后续有同功能,跟简单的布局管理器。

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

​ 由于在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 (左中)

参考代码:

import java.awt.*;

public class GridBagLayoutDemo {

    public static void main(String[] args) {
        //1.创建Frame对象
        Frame frame = new Frame("这里是GridBagLayout测试");

        //2.创建GridBagLayout对象
        GridBagLayout gbl = new GridBagLayout();

        //3.把Frame对象的布局管理器设置为GridBagLayout
        frame.setLayout(gbl);

        //4.创建GridBagConstraints对象
        GridBagConstraints gbc = new GridBagConstraints();

        //5.创建容量为10的Button数组
        Button[] bs = new Button[10];

        //6.遍历数组,初始化每一个Button
        for (int i = 0; i < bs.length; i++) {
            bs[i] = new Button("按钮"+(i+1));
        }

        //7.设置所有的GridBagConstraints对象的fill属性为GridBagConstraints.BOTH,当有空白区域时,组件自动扩大占满空白区域
        gbc.fill=GridBagConstraints.BOTH;

        //8.设置GridBagConstraints对象的weightx设置为1,表示横向扩展比例为1
        gbc.weightx=1;

        //9.往frame中添加数组中的前3个Button
        addComponent(frame,bs[0],gbl,gbc);
        addComponent(frame,bs[1],gbl,gbc);
        addComponent(frame,bs[2],gbl,gbc);

        //10.把GridBagConstraints的gridwidth设置为GridBagConstraints.REMAINDER,则表明当前组件是横向最后一个组件
        gbc.gridwidth=GridBagConstraints.REMAINDER;

        //11.把button数组中第四个按钮添加到frame中
        addComponent(frame,bs[3],gbl,gbc);


        //12.把GridBagConstraints的weighty设置为1,表示纵向扩展比例为1
        gbc.weighty=1;

        //13.把button数组中第5个按钮添加到frame中
        addComponent(frame,bs[4],gbl,gbc);

        //14.把GridBagConstaints的gridheight和gridwidth设置为2,表示纵向和横向会占用两个网格
        gbc.gridheight=2;
        gbc.gridwidth=2;

        //15.把button数组中第6个按钮添加到frame中
        addComponent(frame,bs[5],gbl,gbc);

        //16.把GridBagConstaints的gridheight和gridwidth设置为1,表示纵向会占用1个网格
        gbc.gridwidth=1;
        gbc.gridheight=1;
        //17.把button数组中第7个按钮添加到frame中
        addComponent(frame,bs[6],gbl,gbc);

        //18.把GridBagConstraints的gridwidth设置为GridBagConstraints.REMAINDER,则表明当前组件是横向最后一个组件
        gbc.gridwidth=GridBagConstraints.REMAINDER;

        //19.把button数组中第8个按钮添加到frame中
        addComponent(frame,bs[7],gbl,gbc);

        //20.把GridBagConstaints的gridwidth设置为1,表示纵向会占用1个网格
        gbc.gridwidth=1;

        //21.把button数组中第9、10个按钮添加到frame中
        addComponent(frame,bs[8],gbl,gbc);
        addComponent(frame,bs[9],gbl,gbc);

        //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);
    }
}
6、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 容器中指定名字的卡片.

参考代码:

package com.one.Layout;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/*
    p1进行卡片设计
    p2进行按钮设置,同时需要设置监听器
 */
public class CardLayoutDemo {
    public static void main(String[] args) {
        Frame frame = new Frame("这里测试CardLayout");

        //1、创建一个Panel,用于存储多张卡片
        Panel p1 = new Panel();
        //2、创建CardLayout对象,并且把该对象设置给之前创建的容器
        CardLayout cardLayout = new CardLayout();
        p1.setLayout(cardLayout);
        //3、往Panel中存储多个组件
        String[] name = {"第一张", "第二张", "第三张", "第四张", "第五张"};

        for(int i = 0; i < name.length; i++){
            //当p1容器使用卡片监听器时,添加的顺序就表示其第几张
            p1.add(name[i], new Button(name[i]));
        }

        //4、把Panel放到frame中间区域
        frame.add(p1);

        //5、创建另一个Panel p2 用来存储多个按钮组件
        Panel p2 = new Panel();

        //6、创建5个按钮组件
        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();   //这个字符串其实就是按钮上的文字
                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, "第三张"); //显示属于p1容器中的第三张
                        break;
                    default:
                        break;
                }
            }
        };

        //8、把当前这个时间监听器和多个按钮绑定到一起
        //五个按钮对象 与 listener监听器绑定
        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);

        //10、把p2放到frame的南边区域
        frame.add(p2, BorderLayout.SOUTH);

        frame.pack();
        frame.setVisible(true);
    }
}
7、BoxLayout(盒子布局):

​ 为了简化开发,Swing引入一个新的布局管理器:BoxLayout。BoxLayout可以在垂直和水平两个方向上摆放GUI组件,BoxLayout提供了如下一个简单的构造器:

方法名称 方法功能
BoxLayout(Container target, int axis) 指定创建基于 target 容器的 BoxLayout 布局管理器,该布局管理器里的组件按 axis 方向排列。其中 axis 有 BoxLayout.X_AXIS( 横向)和 BoxLayout.Y _AXIS (纵向〉两个方向。

参考代码:

package com.one.Layout;
import java.awt.*;
import javax.swing.BoxLayout;

public class BoxLayoutDemo {
    public static void main(String[] args){
        Frame frame = new Frame("这里测试BoxLayout");

        //1、基于frame容器,创建一个BoxLayout对象,并且,该对象存放组件时垂直存放的
        //参数1:表示这个布局管理器基于哪个组件	参数2:表示这个布局管理器的排列方式
        BoxLayout boxLayout = new BoxLayout(frame, BoxLayout.X_AXIS);
        //2、把BoxLayout对象设置给frame
        frame.setLayout(boxLayout);
        //3、往frame中添加两个按钮组件
        frame.add(new Button("按钮1"));
        frame.add(new Button("按钮2"));

        frame.pack();
        frame.setVisible(true);
    }
}

在java.swing包中,提供了一个新的容器Box,该容器的默认布局管理器就是BoxLayout。大多数情况下,使用Box容器去容纳多个GUI组件,然后再把Box容器作为一个组件,添加到其他的容器中,从而形成整体窗口布局。

方法名称 方法功能
static Box createHorizontalBox() 创建一个水平排列组件的 Box 容器 。
static Box createVerticalBox() 创建一个垂直排列组件的 Box 容器 。

参考代码:

由于Box默认使用BoxLayout布局管理器,直接通过方法创建对象进行嵌套更为方便

package com.one.Layout;
import java.awt.*;
import javax.swing.Box;

public class BoxLayoutDemo2 {
    public static void main(String[] args){
        Frame frame = new Frame();

        //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 默认使用BorderLayout
        frame.add(hBox, BorderLayout.NORTH);
        frame.add(vBox);

        frame.pack();
        frame.setVisible(true);
    }
}

Box类中,提供了5个方便的静态方法来生成这些间隔组件(作用是为了是其更为美观):

非传参的:可以随着界面一起变化,水平左右可拉伸,垂直上下可拉伸。

传参方法:根据参数,固定距离。

方法名称 方法功能
static Component createHorizontalGlue() 创建一条水平 Glue (可在两个方向上同时拉伸的间距)
static Component createVerticalGlue() 创建一条垂直 Glue (可在两个方向上同时拉伸的间距)
static Component createHorizontalStrut(int width) 创建一条指定宽度(宽度固定了,不能拉伸)的水平Strut (可在垂直方向上拉伸的间距)
static Component createVerticalStrut(int height) 创建一条指定高度(高度固定了,不能拉伸)的垂直Strut (可在水平方向上拉伸的间距)

参考代码:

package com.one.Layout;
import java.awt.*;
import javax.swing.Box;

public class BoxLayoutDemo3 {
    public static void main(String[] args){
        Frame frame = new Frame();

        //1、创建水平排列的Box容器
        Box hBox = Box.createHorizontalBox();

        //2、往Box中添加按钮,还需要在多个按钮之间添加分割
        hBox.add(new Button("水平按钮一"));
        hBox.add(Box.createHorizontalGlue());   //该分割在两个方向上都可以拉伸
        hBox.add(new Button("水平按钮二"));
        hBox.add(Box.createHorizontalStrut(30));    //给定参数的这个方法不会随着界面进行拉伸
        hBox.add(new Button("水平按钮三"));

        //3、创建垂直排列的Box容器
        Box vBox = Box.createVerticalBox();

        //4、往Box中添加按钮,还需要在多个按钮之间添加分割
        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);
    }
}

七、AWT中常用组件:

1、基本组件:
组件名 功能
Button 按钮
Canvas 用于绘图的画布
Checkbox 复选框组件(也可当做单选框组件使用,几个复选框使用同一个复选框组)
CheckboxGroup 复选框组,用于将多个Checkbox 组件组合成一组, 一组 Checkbox 组件将只有一个可以被选中 , 即全部变成单选框组件
Choice 下拉选择框
Frame 窗口 , 在 GUI 程序里通过该类创建窗口
Label 标签类,用于放置提示性文本
List JU表框组件,可以添加多项条目
Panel 不能单独存在基本容器类,必须放到其他容器中
Scrollbar 滑动条组件。如果需要用户输入位于某个范围的值 , 就可以使用滑动条组件 ,比如调 色板中设置 RGB 的三个值所用的滑动条。当创建一个滑动条时,必须指定它的方向、初始值、 滑块的大小、最小值和最大值。
ScrollPane 带水平及垂直滚动条的容器组件
TextArea 多行文本域
TextField 单行文本框

这些 AWT 组件的用法比较简单,可以查阅 API 文档来获取它们各自的构方法、成员方法等详细信息。

2、基本组件部分训练:
package com.one.component;
import java.awt.*;
import javax.swing.Box;
/*
     根据运行结果,从局部到整体进行分析,每个人有所不同
 */

public class BasicComponentDemo {
    //放在了成员变量处进行使用
    Frame frame = new Frame("这里测试基本组件");

    TextArea ta = new TextArea(5, 20);  //文本域,貌似自带滑轮
    Choice colorChooser = new Choice(); //下拉选择框

    CheckboxGroup cbg = new CheckboxGroup();    //复选框组,用于存放多个复选框
    //参数说明:第一个参数是此复选框名、第二个是存放在哪个复选框中、第三个是默认是否打开
    Checkbox male = new Checkbox("男", cbg, true);   //复选框组件,此处两个都是放到了cbg中形成了单选框
    Checkbox female = new Checkbox("女", cbg, false);

    Checkbox isMarried = new Checkbox("是否已婚?"); //复选组件

    TextField tf = new TextField(50);   //文本框按钮

    Button ok = new Button("确认");   //确认按钮
    //行数6行,默认单选,使用true使其可以多选
    List colorList = new List(6, true);    //列表框


    //用于初始化界面
    public void init(){
        //组装界面

        //组装选择部分
        colorChooser.add("红色");
        colorChooser.add("蓝色");
        colorChooser.add("绿色");
        Box cBox = Box.createHorizontalBox();
        cBox.add(colorChooser);
        cBox.add(male);
        cBox.add(female);
        cBox.add(isMarried);

        //组装文本域和选择部分
        Box topLefe = Box.createVerticalBox();
        topLefe.add(ta);
        topLefe.add(cBox);

        colorList.add("红色");
        colorList.add("蓝色");
        colorList.add("绿色");
        //组装顶部左右结构
        Box top = Box.createHorizontalBox();
        top.add(topLefe);
        top.add(colorList);

        //组装底部
        Box bBox = Box.createHorizontalBox();
        bBox.add(tf);
        bBox.add(ok);

        //将整体看作上下结构将其装入,frame中
        Box UDBox = Box.createVerticalBox();
        UDBox.add(top);
        UDBox.add(bBox);

        frame.add(UDBox);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args){
        new BasicComponentDemo().init();
    }

}
3、对话框Dialog:

​ Dialog 是 Window 类的子类,是一个容器类,属于特殊组件 。 对话框是可以独立存在的顶级窗口, 因此用法与普通窗口的用法几乎完全一样,但是使用对话框需要注意下面两点:

​ 对话框通常依赖于其他窗口,就是通常需要有一个父窗口;

​ 对话框有非模式(non-modal)和模式(modal)两种,当某个模式对话框被打开后,该模式对话框总是位于它的父窗口之上,在模式对话框被关闭之前,父窗口无法获得焦点。

方法名称 方法功能
Dialog(Frame owner, String title, boolean modal) 创建一个对话框对象:
owner:当前对话框的父窗口
title:当前对话框的标题
modal:当前对话框是否是模式对话框,true/false

参考代码:

package com.one.component;
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();

        //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){
                d1.setVisible(true);
            }
        });

        b2.addActionListener(new ActionListener(){
            @Override
            public void actionPerformed(ActionEvent e){
                d2.setVisible(true);
            }
        });

        //5、把按钮添加到frame中
        frame.add(b1);
        frame.add(b2, BorderLayout.SOUTH);

        //设置frame最佳大小并可见
        frame.pack();
        frame.setVisible(true);
    }
}

在Dialog对话框中,可以根据需求,自定义内容。

package com.one.component;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Box;

/*
    模式对话框本身也是一个容器,可以添加其它组件
 */

public class DialogDemo2 {
    public static void main(String[] args) {
        Frame frame = new Frame();

        //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方法设置Dialog的位置以及大小
        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);

        //设置frame最佳大小并可见
        frame.pack();
        frame.setVisible(true);
    }
}
4、FileDialog(文件对话框):

​ Dialog 类还有 一个子类 : FileDialog ,它代表一个文件对话框,用于打开或者保存 文件,需要注意的是FileDialog无法指定模态或者非模态,这是因为 FileDialog 依赖于运行平台的实现,如果运行平台的文件对话框是模态的,那么 FileDialog 也是模态的;否则就是非模态的 。

方法名称 方法功能
FileDialog(Frame parent, String title, int mode) 创建一个文件对话框:
parent:指定父窗口
title:对话框标题
mode:文件对话框类型,如果指定为FileDialog.LOAD,用于打开文件,如果指定为FileDialog.SAVE,用于保存文件
String getDirectory() 获取被打开或保存文件的绝对路径
String getFile() 获取被打开或保存文件的文件名

参考代码:

package com.one.component;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

//此处只是打印模拟了操作,可以结合文件进行实际操作

public class FileDialogTest {
    public static void main(String[] args){
        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, BorderLayout.SOUTH);


        //设置frame最佳大小并可见
        frame.pack();
        frame.setVisible(true);
    }
}

八、事件处理

​ 前面介绍了如何放置各种组件,从而得到了丰富多彩的图形界面,但这些界面还不能响应用户的任何操作。比如单击前面所有窗口右上角的“X”按钮,但窗口依然不会关闭。因为在 AWT 编程中 ,所有用户的操作,都必须都需要经过一套事件处理机制来完成,而 Frame 和组件本身并没有事件处理能力 。

1、GUI事件处理机制:

1、定义:

​ 当在某个组件上发生某些操作的时候,会自动的触发一段代码的执行。

2、在GUI事件处理机制中涉及到4个重要的概念需要理解:

事件源(Event Source):操作发生的场所,通常指某个组件,例如按钮、窗口等;
事件(Event):在事件源上发生的操作可以叫做事件,GUI会把事件都封装到一个Event对象中,如果需要知道该事件的详细信息,就可以通过Event对象来获取。
事件监听器(Event Listener):当在某个事件源上发生了某个事件,事件监听器就可以对这个事件进行处理。

注册监听:把某个事件监听器(A)通过某个事件(B)绑定到某个事件源(C)上,当在事件源C上发生了事件B之后,那么事件监听器A的代码就会自动执行。

使用步骤:

​ 1、创建事件源组件对象;

​ 2、自定义类,实现XxxListener接口,重写方法;

​ 3、创建事件监听器对象(自定义类对象)

​ 4、调用事件源组件对象的addXxxListener方法完成注册监听

参考代码:

package com.one.listener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/*
    事件源通过addActionListener()方法,传入一个监听器(实现监听器接口的类)来注册监听器从而实现绑定
 */

public class EventDemo {
    Frame frame = new Frame("这里测试事件处理");

    TextField tf = new TextField(30);
    //事件源
    Button ok = new Button("确定");

    //初始化窗口
    public void init(){
        //组装视图

        //监听器
        MyListener myListener = new MyListener();

        //注册监听
        ok.addActionListener(myListener);   //前面我们通过匿名内部类的形式进行实现的

        //把tf 和 ok 放到frame中
        frame.add(tf);
        frame.add(ok, BorderLayout.SOUTH);

        frame.pack();
        frame.setVisible(true);
    }

    //写一个内部类 实现ActionListener接口
    private class MyListener implements ActionListener{
        @Override
        public void actionPerformed(ActionEvent e){
            tf.setText("Hello World");
        }
    }

    public static void main(String[] args){
        new EventDemo().init();
    }
}
2、GUI中常见事件和事件监听器:

​ 事件监听器必须实现事件监听器接口, AWT 提供了大量的事件监听器接口用于实现不同类型的事件监听器,用于监听不同类型的事件 。 AWT 中提供了丰富的事件类,用于封装不同组件上所发生的特定操作, AWT 的事件类都是 AWTEvent 类的子类 , AWTEvent是 EventObject 的子类。

1、事件:

AWT把事件分为了两大类:

​ 低级事件:这类事件是基于某个特定动作的事件。比如进入、点击、拖放等动作的鼠标事件,再比如得到焦点和失去焦点等焦点事件。

事件 触发时机
ComponentEvent 组件事件 , 当组件尺寸发生变化、位置发生移动、显示/隐藏状态发生改变时触发该事件。
ContainerEvent 容器事件 , 当容器里发生添加组件、删除组件时触发该事件 。
WindowEvent 窗口事件, 当窗口状态发生改变 ( 如打开、关闭、最大化、最小化)时触发该事件 。
FocusEvent 焦点事件 , 当组件得到焦点或失去焦点时触发该事件 。
KeyEvent 键盘事件 , 当按键被按下、松开、单击时触发该事件。
MouseEvent 鼠标事件,当进行单击、按下、松开、移动鼠标等动作时触发该事件。
PaintEvent 组件绘制事件 , 该事件是一个特殊的事件类型 , 当 GUI 组件调 用 update/paint 方法 来呈现自身时触发该事件,该事件并非专用于事件处理模型 。

​ 高级事件:这类事件并不会基于某个特定动作,而是根据功能含义定义的事件。

事件 触发时机
ActionEvent 动作事件 ,当按钮、菜单项被单击,在 TextField 中按 Enter 键时触发
AjustmentEvent 调节事件,在滑动条上移动滑块以调节数值时触发该事件。
ltemEvent 选项事件,当用户选中某项, 或取消选中某项时触发该事件 。
TextEvent 文本事件, 当文本框、文本域里的文本发生改变时触发该事件。

2、 事件监听器:

​ 不同的事件需要使用不同的监听器监听,不同的监听器需要实现不同的监听器接口, 当指定事件发生后 , 事件监听器就会调用所包含的事件处理器(实例方法)来处理事件 。

事件类别 描述信息 监听器接口名
ActionEvent 激活组件 ActionListener
ItemEvent 选择了某些项目 ItemListener
MouseEvent 鼠标移动 MouseMotionListener
MouseEvent 鼠标点击等 MouseListener
KeyEvent 键盘输入 KeyListener
FocusEvent 组件收到或失去焦点 FocusListener
AdjustmentEvent 移动了滚动条等组件 AdjustmentListener
ComponentEvent 对象移动缩放显示隐藏等 ComponentListener
WindowEvent 窗口收到窗口级事件 WindowListener
ContainerEvent 容器中增加删除了组件 ContainerListener
TextEvent 文本字段或文本区发生改变 TextListener

参考代码:

​ 通过ContainerListener监听Frame容器添加组件;

​ 通过TextListener监听TextFiled内容变化;

​ 通过ItemListener监听Choice条目选中状态变化;

package com.one.listener;
import javax.swing.*;
import javax.swing.text.TextAction;
import java.awt.*;
import java.awt.event.*;

public class ListenerDemo {
    public static void main(String[] args){
        Frame frame = new Frame("这里测试监听器");

        //创建组件(事件源)
        TextField tf = new TextField(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.pack();
        frame.setVisible(true);
    }
}

给Frame设置WindowListner,监听用户点击 X 的动作,如果用户点击X,则关闭当前窗口

package com.one.listener;
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,则关闭窗口
        //WindowAdapter用于接收窗口事件的抽象适配器类(内部重写了不使用接口的方法的空实现)
        frame.addWindowListener(new WindowAdapter() { 
            @Override
            public void windowClosing(WindowEvent e) {
                super.windowClosing(e);
                //停止当前程序
                System.exit(0);
            }
        });

        frame.setBounds(200, 200, 500, 300);

        frame.setVisible(true);
    }
}

九、菜单组件

​ 前面讲解了如果构建GUI界面,其实就是把一些GUI的组件,按照一定的布局放入到容器中展示就可以了。在实际开发中,除了主界面,还有一类比较重要的内容就是菜单相关组件,可以通过菜单相关组件很方便的使用特定的功能,在AWT中,菜单相关组件的使用和之前学习的组件是一模一样的,只需要把菜单条、菜单、菜单项组合到一起,按照一定的布局,放入到容器中即可。

下表中给出常见的菜单相关组件:

菜单项可以放到菜单中,菜单可以放到菜单条中

菜单组件名称 功能
MenuBar 菜单条 , 菜单的容器 。
Menu 菜单组件 , 菜单项的容器 。 它也是Menultem的子类 ,所以可作为菜单项使用。
PopupMenu 上下文菜单组件(右键菜单组件)。
Menultem 菜单项组件 。
CheckboxMenuItem 复选框菜单项组件。
graph TD
MenuComponent –> MenuItem
MenuComponent –> MenuBar
MenuItem –> CheckBoxMenuItem
MenuItem –> Menu
Menu –> PopupMenu
Menu –> 接口MenuContainer
MenuBar –> 接口MenuContainer

菜单相关组件使用:

​ 1.准备菜单项组件,这些组件可以是MenuItem及其子类对象;

​ 2.准备菜单组件Menu或者PopupMenu(右击弹出子菜单),把第一步中准备好的菜单项组件添加进来;

​ 3.准备菜单条组件MenuBar,把第二步中准备好的菜单组件Menu添加进来;

​ 4.把第三步中准备好的菜单条组件添加到窗口对象中显示。

小技巧:

​ 1.如果要在某个菜单的菜单项之间添加分割线,那么只需要调用Menu的add(new MenuItem(-))即可。

​ 2.如果要给某个菜单项关联快捷键功能,那么只需要在创建菜单项对象时设置即可,例如给菜单项关联 ctrl+shif+Q快捷键,只需要:new MenuItem(“菜单项名字”,new MenuShortcut(KeyEvent.VK_Q,true);

参考代码:

菜单条->菜单->菜单项

package com.one.menu;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
//菜单条就和编译器那上面的一行一样

public class SimpleMenu {
    //创建窗口
    private Frame frame = new Frame("这里测试菜单相关组件");

    //1、创建菜单条
    MenuBar menuBar = new MenuBar();

    //2、创建菜单组件
    Menu fileMenu = new Menu("文件");
    Menu editMenu = new Menu("编辑");
    Menu formatMenu = new Menu("格式");

    //3、创建菜单项组件
    MenuItem auto = new MenuItem("自动换行");
    MenuItem copy = new MenuItem("复制");
    MenuItem paste = new MenuItem("粘贴");

    //4、创建子菜单项组件
    //管理快捷键,ctrl+shift+Q,true表示使用Shift
    MenuItem comment = new MenuItem("注释 Ctrl+Shift+Q", new MenuShortcut(KeyEvent.VK_Q, true));
    MenuItem cancelComment = new MenuItem("取消注释");

    //5、创建文本域组件
    TextArea ta = new TextArea(6, 40);


    //初始化窗口
    public void init(){
        //组装视图

        //给comment注册一个监听
        comment.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                ta.append("你点击了菜单项:" + e.getActionCommand() + "\n");
            }
        });

        //添加好子菜单
        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  SimpleMenu().init();
    }
}

练习PopupMenu(右键菜单组件):

package com.one.menu;
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 PopupMenuTest {

    private Frame frame = new Frame();

    //1、创建文本域
    TextArea ta = new TextArea("我爱中华\n", 6, 20);

    //2、创建Panel容器
    Panel p = new Panel();

    //3、创建PopupMenu
    PopupMenu popupMenu = new PopupMenu();

    //4、创建菜单项
    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);

        p.add(popupMenu);

        //设置Panel的大小
        p.setPreferredSize(new Dimension(400, 300));

        //给Panel注册鼠标事件,监听用户点击右键
        p.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent e) {
                super.mouseReleased(e);
                boolean flag = e.isPopupTrigger();  //右键为 true
                if(flag){
                    //显示PopupMenu
                    //参数:显示在哪个组件上, 显示坐标x, 显示坐标y
                    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项目时, 有 时候也必须”动态”地向客户端生成各种图形、图表,比如 图形验证码、统计图等,这都需要利用AWT的绘图功能。

1、 组件绘图原理:

​ 之前我们已经学习过很多组件,例如Button、Frame、Checkbox等等,不同的组件,展示出来的图形都不一样,其实这些组件展示出来的图形,其本质就是用AWT的绘图来完成的。

​ 在AWT中,真正提供绘图功能的是Graphics对象,那么Component组件和Graphics对象存在什么关系,才能让Component绘制自身图形呢?在Component类中,提供了下列三个方法来完成组件图形的绘制与刷新:

​ paint(Graphics g):绘制组件的外观;

​ update(Graphics g):内部调用paint方法,刷新组件外观;

​ repaint():调用update方法,刷新组件外观;

​ 一般情况下,update和paint方法是由AWT系统负责调用,如果程序要希望系统重新绘制组件,可以调用repaint方法完成。

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() 绘制位图

参考代码:

package com.one.draw;
import com.one.menu.SimpleMenu;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class SimpleDraw {
    private final String REACT_SHAPE = "react";
    private final String OVAL_SHAPE = "oval";

    private Frame frame = new Frame("这里测试绘图");

    Button btnReact = new Button("绘制矩形");
    Button btnOval = new Button("绘制椭圆");

    //定义一个变量,记录当前要绘制椭圆还是矩形
    private String shape = "";

    //1.自定义类,继承Canvas类,重写paint(Graphics g)方法完成画图;
    private class MyCanvas extends Canvas{
        @Override
        public void paint(Graphics g) {
            //绘制不同的图形
            if(shape.equals(REACT_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(){
        //组装视图
        btnReact.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //修改标记值为react
                shape = REACT_SHAPE;
                drawArea.repaint();
            }
        });

        btnOval.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //修改标记值为Oval
                shape = OVAL_SHAPE;
                drawArea.repaint();
            }
        });

        //创建一个Panel,承载按钮
        Panel p = new Panel();
        p.add(btnReact);
        p.add(btnOval);

        frame.add(p, BorderLayout.SOUTH);

        //drawArea的大小需要设置
        drawArea.setPreferredSize(new Dimension(500, 500));

        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方法,完成组件重绘。

package com.one.draw;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

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 speedY = 10;
    private int speedX = 5;

    //定义变量,记录球拍的坐标
    private int racketX = 10;
    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("Time", 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 listener = 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和drawArea注册监听器
        frame.addKeyListener(listener);
        drawArea.addKeyListener(listener);

        ActionListener task = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //根据边界范围,修正速度
                //桌面边界 - 球大小
                if(ballX <= 0 || ballX >= (TABLE_WIDTH - BALL_SiZE)){
                    speedX = -speedX;
                }

                //1&&2&&3
                //1:表示小球的y坐标在球拍上
                //2+3:表示一个区间范围:左边大于拍横坐标,右边小于球拍的横坐标加宽度(此处横坐标表示左端点)
                if(ballY <= 0 || (ballY > racketY - BALL_SiZE && ballX > racketX && ballX < racketX + RACKET_WIDTH)){
                    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.pack();
        frame.setVisible(true);
    }


    public static void main(String[] args){
        new PinBall().init();
    }
}
3、处理位图:

​ 如果仅仅绘制一些简单的几何图形,程序的图形效果依然比较单调 。 AWT 也允许在组件上绘制位图, Graphics 提供了 drawlmage(Image image) 方法用于绘制位图,该方法需要一个Image参数一一代表位图,通过该方法就可以绘制出指定的位图 。

位图使用步骤:

​ 1.创建Image的子类对象BufferedImage(int width,int height,int ImageType),创建时需要指定位图的宽高及类型属性;此时相当于在内存中生成了一张图片;

​ 2.调用BufferedImage对象的getGraphics()方法获取画笔,此时就可以往内存中的这张图片上绘图了,绘图的方法和之前学习的一模一样;

​ 3.调用组件paint方法中提供的Graphics对象的的drawImage()方法,一次性的内存中的图片BufferedImage绘制到特定的组件上。

使用位图绘制组件的好处(可以类比文件缓冲区):

​ 使用位图来绘制组件,相当于实现了图的缓冲区,此时绘图时没有直接把图形绘制到组件上,而是先绘制到内存中的BufferedImage上,等全部绘制完毕,再一次性的图像显示到组件上即可,这样用户的体验会好一些。

参考代码:

package com.one.draw;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;

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_BGR);

    //通过位图,获取关联的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和preY
                preX = -1;
                preY = -1;
            }
        });

        //设置位图的背景为白色
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, AREA_WIDTH, AREA_HEIGHT);  //并填充好

        //通过监听鼠标的移动,完成线条的绘制
        drawArea.addMouseMotionListener(new MouseMotionAdapter() {
            //该方法,当鼠标左键按下,并进行拖动时,会被调用
            @Override
            public void mouseDragged(MouseEvent e) {
                if(preX > 0 && preY > 0){
                    g.setColor(forceColor);

                    //画线条
                    //两点构成一条直线,一个是起点,一个是终点(使用e.getX e.getY)
                    g.drawLine(preX, preY, e.getX(), e.getY());
                }

                //修正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();
    }
}
4、ImageIO的使用:

​ 在实际生活中,很多软件都支持打开本地磁盘已经存在的图片,然后进行编辑,编辑完毕后,再重新保存到本地磁盘。如果使用AWT要完成这样的功能,那么需要使用到ImageIO这个类,可以操作本地磁盘的图片文件。

方法名称 方法功能
static BufferedImage read(File input) 读取本地磁盘图片文件
static BufferedImage read(InputStream input) 读取本地磁盘图片文件
static boolean write(RenderedImage im, String formatName, File output) 往本地磁盘中输出图片文件

参考代码:

package com.one.draw;
import javax.imageio.ImageIO;
import java.awt.*;
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;
    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.addActionListener(e->{
            //打开一个文件对话框
            FileDialog fileDialog = new FileDialog(frame, "打开图片", FileDialog.LOAD);
            fileDialog.setVisible(true);

            //获取用户选择的图片路径以及名称
            String dir = fileDialog.getDirectory();
            String fileName = fileDialog.getFile();

            //调用ImageIO.read()方法
            try {
                image = ImageIO.read(new File(dir, fileName));  //传入了一个文件对象
                drawArea.repaint(); //重绘
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }

        });

        save.addActionListener(e->{
            //展示一个文件对话框
            FileDialog fileDialog = new FileDialog(frame, "保存图片", FileDialog.SAVE);
            fileDialog.setVisible(true);

            //获取用户设置的保存路径以及文件名称
            String dir = fileDialog.getDirectory();
            String fileName = fileDialog.getFile();

            //调用ImageIO.write()方法
            try {
                ImageIO.write(image, "JPEG", new File(dir, fileName));
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
        });


        menu.add(open);
        menu.add(save);

        menuBar.add(menu);

        //把菜单条放入到窗口中
        frame.setMenuBar(menuBar);
        frame.add(drawArea);

        frame.setBounds(200, 200, 740, 506);

        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();
    }
}
5、五子棋游戏:

参考代码:

package com.one.draw;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.File;

//图片来源于:本模块img中
//为了得到更好的展示效果,此处使用了swing中的两个类

public class Gobang {
    //定义五子棋游戏入口
    private JFrame f = new JFrame("五子棋游戏"); //此处使用了swing中的组件

    //声明四个BufferedImage对象,分别记录四张图片
    BufferedImage table;
    BufferedImage black;
    BufferedImage white;
    BufferedImage selected;

    //声明棋盘的宽和高
    final int TABLE_WIDTH = 535;
    final int TABLE_HEIGHT = 536;

    //声明棋盘横向和纵向分别可以下多少子,他们的值都为15
    final int BOARD_SIZE = 15;

    //需要一个变量声明每一个棋子占用棋盘的比率
    final int RATE = TABLE_WIDTH / BOARD_SIZE;  //棋盘宽除以棋子个数

    //声明变量,记录对于棋子x方向和y方向的偏移量(更具棋盘差来的)
    final int X_OFFSET = 5;
    final int Y_OFFSET = 6;

    //声明一个二维数组,记录棋子,如果值为    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_X * RATE 是为了把后续处理修正回来
                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();

    //声明变量,记录当前下棋的颜色
    int board_type = 2;

    //声明底部需要用到的组件
    Panel p = new Panel();
    Button whiteBtn = new Button("白棋");
    Button blackBtn = new Button("黑棋");
    Button deleteBtn = new Button("删除");


    //定义一个方法刷新按钮的颜色 (及选中的按钮会变色)
    /**
     *
     * @param whiteBtnColor 白色按钮颜色
     * @param blackBtnColor 黑色按钮颜色
     * @param deleteBtnColor    删除按钮颜色
     */
    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);
        });

        //添加按钮到Panel组件(默认流式布局)中
        p.add(whiteBtn);
        p.add(blackBtn);
        p.add(deleteBtn);
        f.add(p, BorderLayout.SOUTH);   //将Panel组件放到frame的南边

        //给四个BufferedImage对象赋值
        table = ImageIO.read(new File("awt\\img\\board.jpg"));
        white = ImageIO.read(new File("awt\\img\\white.gif"));
        black = ImageIO.read(new File("awt\\img\\black.gif"));
        selected = ImageIO.read(new File("awt\\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();
            }

            //当鼠标移除界面外时
            @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);

        //给 X 注册监听
        f.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });

        //设置frame最佳大小值
        f.pack();
        f.setVisible(true);
    }

    public static void main(String[] args) throws Exception{
        new Gobang().init();
    }
}
版权声明:本文为编程初学者求大佬指点原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/fragmentary/p/16930636.html