Java_AWT
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 (菜单项)等子类。
其中 Container 是一种特殊的 Component,它代表一种容器,通用抽象窗口工具包(AWT)容器对象是可以包含其他AWT组件的组件。
AWT中还有一个非常重要的接口叫LayoutManager ,如果一个容器中有多个组件,那么容器就需要使用LayoutManager来管理这些组件的布局方式。
三、Container容器
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布局管理器,可以根据运行平台来自动调整组件大小,程序员不用再手动设置组件的大小和位置了,只需要为容器选择合适的布局管理器即可。
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 有如下两个注意点:
- 当向使用 BorderLayout 布局管理器的容器中添加组件时 , 需要指定要添加到哪个区域中 。 如果没有指定添加到哪个区域中,则默认添加到中间区域中;
- 如果向同一个区域中添加多个组件时 , 后放入的组件会覆盖先放入的组件;
构造方法 | 方法功能 |
---|---|
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 | 复选框菜单项组件。 |
菜单相关组件使用:
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(); } }
本文作者:如此而已~~~
本文链接:https://www.cnblogs.com/fragmentary/p/16930636.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。