Java图形化界面编程——AWT概论 笔记

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

特点:

  1. frame.setLayout(new FlowLayout(FlowLayout.LEFT,20,20));
    //setLayout函数接收一个布局管理器对象(接口),因此跟之前添加组件一样,new一个FlowLayout

  2. frame.pack();
    //通常在创建完窗口的所有子组件后,调用pack函数可以确保窗口的大小适合所有子组件

在这里插入图片描述

在这里插入图片描述

2.4.2 BorderLayout

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

在这里插入图片描述

当改变使用 BorderLayout 的容器大小时, NORTH 、 SOUTH 和 CENTER区域水平调整(左右拉伸),而 EAST 、 WEST 和 CENTER 区域垂直调整(上下拉伸)。

使用BorderLayout 有如下两个注意点:

  1. 当向使用 BorderLayout 布局管理器的容器中添加组件时 , 需要指定要添加到哪个区域中 。 如果没有指定添加到哪个区域中,则默认添加到中间区域中;

  2. 如果向同一个区域中添加多个组件时 , 后放入的组件会覆盖先放入的组件

    (是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 基本组件

组件名功能
ButtonButton
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();
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/387956.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DOM事件练习1

DOM事件练习1 1. 演示效果 2. 分析思路 用 ul 创建四个 li 列表整个列表的背景是红色的&#xff0c;鼠标悬浮在列表上&#xff0c;一行的变为蓝色点击任意列表&#xff0c;整个列表的背景变为白色&#xff0c;被点击的列表变为粉色需要用到 js 的点击事onclick件和forEach循环…

【并发编程】ThreadPoolExecutor类

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;并发编程⛺️稳重求进&#xff0c;晒太阳 ThreadPoolExecutor 1) 线程池状态 ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态&#xff0c;低 29 位表示线程数量 状态名 高三位 …

网络安全威胁,如何解决缓冲区溢出攻击

目录 一、什么是网络安全 二、什么是缓冲区 三、缓冲区溢出 四、缓冲区溢出攻击的类型 一、什么是网络安全 网络安全&#xff08;Network Security&#xff09;指的是保护计算机网络及其相关设备、系统和数据免受未经授权访问、破坏、篡改、窃取或滥用的威胁和攻击。随着网…

【C语言必刷题】1.打印1~100之间的奇数

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更新的动力❤️ &#x1f64f;小杨水平有…

[职场] 应聘销售的简历怎么写 #职场发展#笔记

应聘销售的简历怎么写 应聘销售的简历怎么写1 基本信息 姓名&#xff1a;吴x 性别&#xff1a;女 毕业院校&#xff1a;徐州师范大学计算机科学院 学历&#xff1a;大专 联系电话&#xff1a;电子邮件&#xff1a; 工作经验&#xff1a;4年 求职意向 期望从事职业&#xff1a;销…

前端秘法基础式(HTML)(第二卷)

目录 一.表单标签 1.表单域 2.表单控件 2.1input标签 2.2label/select/textarea标签 2.3无语义标签 三.特殊字符 一.表单标签 用来完成与用户的交互,例如登录系统 1.表单域 <form>通过action属性,将用户填写的数据转交给服务器 2.表单控件 2.1input标签 type…

(03)Hive的相关概念——分区表、分桶表

目录 一、Hive分区表 1.1 分区表的概念 1.2 分区表的创建 1.3 分区表数据加载及查询 1.3.1 静态分区 1.3.2 动态分区 1.4 分区表的本质及使用 1.5 分区表的注意事项 1.6 多重分区表 二、Hive分桶表 2.1 分桶表的概念 2.2 分桶表的创建 2.3 分桶表的数据加载 2.4 …

UART通信中的奇偶校验

UART通信中的奇偶校验&#xff1a;提升数据传输可靠性的简单方法 在微控制器&#xff08;MCU&#xff09;和各种电子设备之间的数据通信领域&#xff0c;UART&#xff08;Universal Asynchronous Receiver/Transmitter&#xff0c;通用异步收发传输器&#xff09;协议是一种广泛…

23款奔驰S400商务版没有后排电动座椅那改装一套跟选装有区别吗

改装的后排电动座椅通常提供以下功能&#xff1a; 电动调节&#xff1a;座椅可以通过按钮或控制面板进行前后调节&#xff0c;以适应乘客的腿部空间需求。 靠背角度调节&#xff1a;乘客可以通过电动调节功能来调整座椅的靠背角度&#xff0c;以获得更舒适的坐姿。 座椅倾斜调…

投资银行在网络安全生态中的作用

文章目录 一、投资银行的含义(一)并购买方。(二)并购卖方。(三)IPO辅助。(四)投资银行业务的另一方面是帮助这些交易融资。二、从投资银行角度看网络安全产业(一)行业的短期前景三、复杂的网络安全并购(一)行业知识对投资银行业务很重要(二)在网络安全领域,技术…

嵌入式C语言学习——基于Linux与GCC(二)

系列文章目录 一.C语言常用关键字及运算符操作 文章目录 系列文章目录内存四区指针指针概述指针 修饰符constvoliatiletypedef 指针运算符多级指针 数组数组空间字符空间及地址 结构体、共用体定义、字节对齐位域 内存分布图段错误分析 内存四区 C/C语言的内存四区&#xff…

MySQL 基础知识(六)之数据查询(一)

目录 1 基本查询 1.1 查询相关列 (select * / 列名) 1.2 别名 (as) 1.3 去重 (distinct) 1.4 对列中的数据进行运算 (、-、*、/) 2 条件查询 (where) 2.1 等值查询 () 2.2 非等值查询 (>、<、>、<、!、><) 2.3 逻辑判断 (and、or、not) 2.4 区间判…

Shell 学习笔记(一)-Shell脚本编程简介

一 什么是shell&#xff1f; shell是一个用 C 语言编写的程序&#xff0c;它是用户使用 Linux 的桥梁。Shell 既是一种命令语言&#xff0c;又是一种程序设计语言。 Shell 是指一种应用程序&#xff0c;这个应用程序提供了一个界面&#xff0c;用户通过这个界面访问操作系统内…

rocketMQ下载、安装及配置

topic主题 - 里边存在多个队列&#xff08;队列是真实存在的&#xff09; rocketMQ安装及配置 一、官网下载 windows和linux系统版本都一样。Binary 下载 下载 | RocketMQ (apache.org) 二、修改运行内存及broker.conf、配置环境变量 1、修改根目录->bin目录下runserve…

ubuntu22.04@laptop OpenCV Get Started: 010_blob_detection

ubuntu22.04laptop OpenCV Get Started: 010_blob_detection 1. 源由2. blob应用Demo2.1 C应用Demo2.2 Python应用Demo 3. 重点分析3.1 Threshold3.2 Area3.3 Circularity3.4 Convexity3.5 Inertia Ratio 4. 总结5. 参考资料6. 补充 1. 源由 Blob是图像中的一组连接像素&#…

猫头虎分享已解决Bug || 代码部署失败(Code Deployment Failure):DeploymentError, FailedRelease

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

点云旋转处理

实现代码为&#xff1a; //以中心化点进行旋转double theta atan(maindirection.a);//计算的是弧度单位for (int i 0; i < origipts.size(); i){pcl::PointXYZ tempone;tempone.x aftercenerlizepts[i].x*cos(theta) aftercenerlizepts[i].y*sin(theta) center.x;temp…

SPFA最短路

文章目录 从Bellman-Ford开始核心思想模拟算法执行过程时间复杂度模板 spfaspfa优化的思想模板 从Bellman-Ford开始 对于所有边权都大于等于0的图&#xff0c;任意两个顶点之间的最短路&#xff0c;显然不会经过重复的顶点或者边。也就是说任意一条最短路经过的定点数不会超过…

动态内存管理:new和delete的底层探索

之前我们在C语言上是学过malloc和calloc还要realloc等函数来在堆上获取相应的内存&#xff0c;但是这些函数是存在缺陷的&#xff0c;今天引入对new和delete的学习&#xff0c;来了解new和delete的底层实现。 首先就是在C中我们为什么要对内存进行区域的分块&#xff1f; 答案…

MyBatisPlus基础操作之增删改查

目录 一、基本使用 1.1 插入数据 1.2 删除操作 1.3 更新操作 二、条件构造器Wrapper 2.1 常用AbstractWrapper方法 2.1.1 示例一 2.2.2 示例二 2.2.3 示例三 2.2 常用QueryWrapper方法 2.2.1 示例一 2.2.2 示例二 2.2.3 示例三&#xff08;常用&#xff09; 2.3 常…
最新文章