一、GUI是什么
GUI的全称为Graphical User Interface,图形化界面或图形用户接口,是指采用图形方式显示的计算机操作环境用户接口。与早期计算机使用的命令行界面相比,图形界面对于用户来说更为简便易用。GUI的广泛应用是当今计算机发展的重大成就之一,它极大地方便了非专业用户的使用人们从此不再需要死记硬背大量的命令,取而代之的是通过窗口、菜单、按键等方式来方便地进行操作。而嵌入式GUI具有下面几个方面的基本要求:轻型、占用资源少、高性能、高可靠性、便于移植、可配置等特点。
1、 不流行的主要原因
界面不美观
需要 jre 环境
2、为什么我们要学习
可以写出自己心中想要的一些小工具
工作之时可能需要维护道swing界面
了解MVC架构,了解监听
二、AWT
1、AWT介绍
AWT(Abstract Window Toolkit),中文译为抽象窗口工具包,该包提供了一套与本地图形界面进行交互的接口,是Java提供的用来建立和设置Java的图形用户界面的基本工具。其中包含了许多类和接口以及众多元素如窗口、按钮、文本框等,且包都在java.awt包下,Container和Component是AWT中的两个核心类。
所有的可以显示出来的图形元素都称为Component,Component代表了所有的可见的图形元素, Component里面有一种比较特殊的图形元素叫Container,Container(容器)在图形界面里面是一种可以 容纳其它Component元素的一种容器,Container本身也是一种Component的,Container里面也可以 容纳别的Container。
Container里面又分为Window和Panel,Window是可以独立显示出来的,平时我们看到的各种各样的 应用程序的窗口都可以称为Window,Window作为一个应用程序窗口独立显示出来,Panel也可以容 纳其它的图形元素,但一般看不见Panel,Panel不能作为应用程序的独立窗口显示出来,Panel要想 显示出来就必须得把自己装入到Window里面才能显示出来。
Panel应用比较典型的就是Applet(JAVA的页面小应用程序),现在基本上已经不用了,AJAX和 JAVASCRIPT完全取代了它的应用。
Window本身又可以分为Frame和Dialog,Frame就是我们平时看到的一般的窗口,而Dialog则是那些需要用户进行了某些操作(如点击某个下拉菜单的项)才出现的对话框,这种对话框就是Dialog。
2、组件和容器(Component和Container)
Component & Container
- Java图形用户界面最基本的组成部分是Component,Component类及其子类的对象用来描述以图形化的方式显示在屏幕上并能与用户进行交互的GUI元素,例如,一个按钮、一个标签等;
- 一般的 Component 对象不能独立地显示出来,必须将“放在”某一Container对象中才可以显示出来
- Container 是 Component 的子类,Container 子类对象可以“容纳”别的 Component 对象;
- Container 对象可使用方法 add()向其中添加其他 Component 对象;
- Container 是 Component 的子类,因此 Container 对象也可以被当做 Component 对象添加到其他 Container 对象中
- 有两种常用的 Container: Window和Panel,前者表示自由停泊的顶级窗口,后者对象则可用作容纳其他的 Component 对象,但不能独立存在,必须被添加到其他 Container 中(如Window或Applet)
3、常见API
Component作为基类,提供了如下的方法来设置组件的大小、位置、可见性
方法签名 | 方法功能 |
---|---|
setLocation(int x,int y) | 设置组件的位置 |
setSize(int width,intheight) | 设置组件的大小 |
setBounds(int x,int y,int width,intheight) | 同时设置组件的位置、大小 |
setVisible(Boolean b) | 设置该组件的可见性 |
Container作为容器根类,提供了如下方法来访问容器中的组件
方法签名 | 方法功能 |
---|---|
Component add(Component comp) | 像容器中添加其他组件(该组件既可以是普通组件,也可以是容器,并返回被添加的组件) |
Component getComponentAt(int x,int y) | 返回指定点的组件 |
int getComponentCount() | 返回该容器内组件的数量 |
Component [] getComponents() | 返回该容器内的所有的组件 |
4、Container容器
4.1 Frame
- Frame():无参构造器
- Frame(String s):有参构造器创建标题栏为字符串s的窗体
- setSize(int width, int height):设置窗体的大小,参数分别为宽度和高度
- setLocation(int x, int y):设置窗体的位置,参数为左上角坐标
- setBounds(int x, int y, int width, int height):设置窗体的位置和大小,参数分别为左上角坐标、宽度和高度
- setBackground(Color c):设置背景颜色,参数为Color对象
- setVisible(boolean b):设置可见性, 默认为false
- setTitle(String name) / String getTile():设置标题以及获得窗体的标题
- setResizable(boolean b):设置是否可以调整大小,默认为true
public class WindowDemo {
public static void main(String[] args) {
//1.创建一个窗口对象
Frame frame = new Frame();
//2.指定窗口的位置、大小、标题、背景、是否可以调整
frame.setTitle("这是一个Window窗口");
frame.setBackground(Color.green);
frame.setResizable(true);
frame.setLocation(100, 100);
frame.setSize(500,300);
//3.设置窗口对象可见
frame.setVisible(true);
}
}
4.2 Panel
- Panel():使用默认FlowLayout类布局管理器初始化;
- Panel(LayoutManager layout):使用指定的布局管理器进行初始化
- setBounds(int x, int y, int widtth, int height)
- setSize(int width, int height)
- setLocation(int x, inty)
- setBackgroud(Color c)
- setLayout(LayoutManager mgr):设置布局管理器
public class PanelDemo {
public static void main(String[] args) {
//1.创建一个窗口对象,因为panel以及其他的容器,都不能独立存在,必须依附于window存在
Frame frame = new Frame("这里演示panel");
//2.创建一个panel对象
Panel panel = new Panel();
//3.创建一个文本框和一个按钮,并且把他们放入到Panel容器中
panel.add(new TextField("这里是一个测试文本"));
panel.add(new Button("这里是一个测试文本"));
//4.把panel放入到window中
frame.add(panel);
//5.设置window的位置以及大小
frame.setBounds(100,100,500,300);
//6.设置window可见
frame.setVisible(true);
}
}
4.3 ScrollPane
public class ScrollPaneDemo {
public static void main(String[] args) {
Frame frame = new Frame("这是一个Window窗口");
//1.创建一个ScrollPane窗口对象
ScrollPane scrollPane = new ScrollPane(ScrollPane.SCROLLBARS_ALWAYS);
//2.往scrollPane中添加内容
scrollPane.add(new TextField("这里是一个测试文本"));
scrollPane.add(new Button("这里是一个测试按钮"));
frame.add(scrollPane);
//2.指定窗口的位置、大小
frame.setLocation(100, 100);
frame.setSize(500,300);
//3.设置窗口对象可见
frame.setVisible(true);
}
}
明明向容器中添加了一个文本框和一个按钮,但只能看到一个按钮,却看不到文本框,这是因为ScrollPane和BorderLayout布局管理器的缘故,而BorderLayout导致了该容器中只有一个组件被显示出来
4.4 Dialog
对话框是Window类的子类,是一个容器类,属于特殊组件。对话框是可以独立存在的顶级窗口(像frame和scrollPane),因此用法与普通窗口的用法几乎完全一样,但是使用对话框需要注意下面两点:
- 对话框通常依赖于其他窗口,就是通常需要有一个父窗口
- 对话框有非模式和模式两种,当某个模式对话框被打开后,该模式对话框总是位于它的父窗口之上,在模式对话框被关闭之前,父窗口无法获得焦点
方法名称 | 方法功能 |
---|---|
Dialog(Frame owner,String title,boolean modal) | 创建一个对话框对象: owner:当前对话框的父窗口 title:当前对话框的标题 modal:当前对话框是否是模式对话框,true/false |
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, "非模式对话框", true);
//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,BorderLayout.NORTH);
frame.add(b2);
frame.pack();
frame.setVisible(true);
}
}
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, "非模式对话框", 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(100, 100, 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);
}
}
4.4.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() | 获取被打开或保存的文件名 |
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.通过setBounds方法设置Dialog的位置以及大小
f1.setBounds(100, 100, 300, 200);
f2.setBounds(20, 30, 300, 200);
//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);
}
});
frame.add(b1, BorderLayout.NORTH);
frame.add(b2);
frame.pack();
frame.setVisible(true);
}
}
5、LayoutManager布局管理器
Java语言中,提供了布局管理类的对象可以管理。当需要管理Component在Container中的布局时,不必直接设置Component位置和大小。每个Container都会有一个布局管理器对象,当容器需要对某个组件进行定位或判断其大小尺寸时,就会调用其对应的布局管理器,调用Container的setLayout()方法改变其布局管理器对象。不必设置当前容器的位置和大小,因为如果我们手动的为组件设置位置和大小时,就会出现程序的不通用性
不通用性出现的原因:Label label = new Label(“你好,世界”),想让组件和字符串自身宽高一致;由于操作系统存在差异,例如,window上,我们要达到这样的效果,需要把该Lable组件的宽和高分别设置为100px,20px,但是再linux操作系统上,可能需要把Lable组件的宽和高分别设置为120px,24px,才能达到同样的效果。如果想在不同的操作系统下,都有相同的使用体验,那么手动设置组件的位置和大小,无疑是一种灾难,因为有太多的组件,需要分别设置不同操作系统下的大小和位置,为了解决这个问题,java提供LayoutManager布局管理器,可以根据运行平台来自动调整组件大小,程序员不用再手动设置组件的大小和位置了,只需要为容器选择合适的布局器即可。
在AWT中提供了5种布局管理器:
- FlowLayout
- BorderLayout
- GridLayour
- CardLayout
- GridBagLayou
5.1 FlowLayout
FlowLayout是Panel类的默认布局管理器,在FlowLayout布局管理器其对组件逐行定位,行内从左到右,一行排满后换行。流式布局不改变组件的大小,按组建原有的尺寸显示组件,可设置不同的组件间距、行距以及其对齐方式,默认的对齐方式为(CENTER)
构造方法 | 方法功能 |
---|---|
FlowLayout() | 使用默认的对齐方式及默认的垂直间距、水平间距创建FlowLayout布局管理器 |
FlowLayout(int align) | 使用指定的对齐方式及默认的垂直间距、水平间距创建FlowLayout布局管理器 |
FlowLayout(int align,int hgap,int agap) | 使用特定的对齐方式及指定的垂直间距、水平间距创建FlowLayout布局管理器 |
指定的对齐方式应该使用FlowLayout类的静态常量:FlowLayout.LEFT、FlowLayout.CENTER、FlowLayout.RIGHT,水平间距和垂直间距单位是像素
public class FlowLayoutDemo {
public static void main(String[] args) {
//1.创建一个窗口对象
Frame frame = new Frame("这里演示panel");
frame.setLayout(new FlowLayout(FlowLayout.CENTER,20,20));
//2.添加多个按钮到frame中
for(int i = 1;i <= 100;i++){
frame.add(new Button("按钮"+i));
}
//3.设置最佳大小
//frame.pack();
frame.setTitle("这是一个Window窗口");
frame.setBackground(Color.green);
frame.setResizable(true);
frame.setBounds(100,100,500,300);
frame.setVisible(true);
}
}
5.2 BorderLayout
BorderLayout是Frame类的默认布局管理器 ,它将整个容器的布局划分为东西南北中五个区域,组件只能被添加到指定的区域,如果不指定组件加入的部位,将会默认加入到CENTER区。值得注意的是每个区域只能加入一个组件,如加入多个,则先前加入的会被覆盖
当改变使用BorderLayout的容器大小时,NORTH,SOUTH,CENTER区域是水平调整,而EAST,WEST和CENTER区域是垂直调整。
构造方法 | 方法功能 |
---|---|
BorderLayout() | 使用默认的水平间距、垂直间距创建BorderLayout布局管理器 |
BorderLayout(int hgap,int vgap) | 使用指定的水平间距、垂直间距创建BorderLayout布局管理器 |
public class BorderLayoutDemo {
public static void main(String[] args) {
//1.创建一个窗口对象
Frame frame = new Frame("这里演示panel");
//给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.EAST);
frame.add(new Button("西侧按钮"),BorderLayout.WEST);
frame.add(new Button("中侧按钮"),BorderLayout.CENTER);
//3.设置框架的外观
frame.setTitle("这是一个Window窗口");
frame.setBackground(Color.green);
frame.setResizable(true);
frame.setBounds(100,100,500,300);
frame.setVisible(true);
}
}
public class BorderLayoutDemo {
public static void main(String[] args) {
//如果五个方向中有的地方没有组件,将被中间给占用
//1.创建一个窗口对象
Frame frame = new Frame("这里演示panel");
//给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.EAST);
//frame.add(new Button("西侧按钮"), BorderLayout.WEST);
frame.add(new Button("中侧按钮"), BorderLayout.CENTER);
frame.add(new TextField("这是一个文本框"));
//3.设置框架的外观
frame.setTitle("这是一个Window窗口");
frame.setBackground(Color.green);
frame.setResizable(true);
frame.setBounds(100, 100, 500, 300);
frame.setVisible(true);
}
}
public class BorderLayoutDemo {
public static void main(String[] args) {
//若想让文本框和中间按钮一起出现
//1.创建一个窗口对象
Frame frame = new Frame("这里演示panel");
//给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.EAST);
//frame.add(new Button("西侧按钮"), BorderLayout.WEST);
//frame.add(new Button("中侧按钮"), BorderLayout.CENTER);
//frame.add(new TextField("这是一个文本框"));
Panel panel = new Panel();
panel.add(new Button("中侧按钮"), BorderLayout.CENTER);
panel.add(new TextField("这是一个文本框"));
frame.add(panel);
//3.设置框架的外观
frame.setTitle("这是一个Window窗口");
frame.setBackground(Color.green);
frame.setResizable(true);
frame.setBounds(100, 100, 500, 300);
frame.setVisible(true);
}
}
5.3 GridLayout
GridLayout型布局管理器将空间划分成规则的矩形网格,每个单元格区域大小相等。组件被添加到每个单元格中,先从左到右填满一行后换行,再从上到下。与FlowLayout不同的是,放置在GridLayout布局管理器中的各组件的大小由组件所处的区域决定(每个组件将自动占满整个区域)(即涉及BorderLayout)
public class GridLayoutDemo {
public static void main(String[] args) {
Frame frame = new Frame("计算器");
frame.setLayout(new BorderLayout(30, 30));
frame.add(new TextField(""),BorderLayout.NORTH);
Panel panel2 = new Panel();
panel2.setLayout(new GridLayout(3,5,4,4));
for(int i = 0;i < 10;i++){
panel2.add(new Button(i+""));
}
panel2.add(new Button("+"));
panel2.add(new Button("-"));
panel2.add(new Button("*"));
panel2.add(new Button("/"));
panel2.add(new Button("%"));
frame.add(panel2,BorderLayout.CENTER);
frame.setBackground(Color.white);
frame.setResizable(true);
frame.setBounds(100,100,500,300);
frame.setVisible(true);
}
}
5.4 GridBagLayout(可用swing中来替换)
GridBagLayout布局管理器的功能最强大,但也最复杂,与GridLayout布局管理器不同的是,在GridBagLayout中,一个组件可以跨越一个或多个网格,并可以设置各网格的大小互不相同,从而增加了布局的灵活性,当窗口的大小发生变化时,GridBagLayout布局管理器也可以准确地控制窗口各部分地拉伸
由于在GridBagLayout布局中,每个组件可以占用多个网格,此时,我们往容器中添加组件地时候,就需要具体地控制每个组件占用多少个网格,java提供的GridBagConstains类,与特定的组件绑定,可以完成具体大小和跨越性的设置
public class GridBagLayoutDemo {
public static void main(String[] args) {
//1.创建Frame对象
Frame frame = new Frame("这里是GridBagLayoutDemo测试");
//2.创建GridBagLayout对象
GridBagLayout gbl = new GridBagLayout();
//3.把GridBagLayout布局管理器设置为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中添加数组的前三个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;
//把GridBagConstraints的gridwidth设置为GridBagConstraints.REMAINDER,则表明当前组件是横向最后一个组件
gbc.gridwidth = GridBagConstraints.REMAINDER;
//13.把Button数组中第5个按钮添加到frame中
addComponent(frame, bs[4], gbl, gbc);
//14.把GridBagConstraints的griheight和griweight设置为2,表示纵向和横向会占用两个网络
gbc.gridwidth = 2;
gbc.gridheight = 2;
//15.把Button数组中的第6个按钮添加到frame中
addComponent(frame, bs[5], gbl, gbc);
//16.把GridBagConstraints的gridwidth和height设置为1,表示横、纵向会占用1个网格
gbc.gridheight = 1;
gbc.gridwidth = 1;
//17.把Buton数组中第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.把GridBagConstraints的gridwidth设置为1,表示横向会占用1个网格,剩下一个横向给第十个
gbc.gridwidth = 1;
//21.把button数组中di9、10个按钮添加到frame中
addComponent(frame, bs[8], gbl, gbc);
addComponent(frame, bs[9], gbl, gbc);
//22.设置frame为最佳大小
frame.pack();
//23.设置frame可见
frame.setVisible(true);
frame.setResizable(true);
}
public static void addComponent(Container container,Component c,GridBagLayout gridBagLayout,GridBagConstraints gbc){
gridBagLayout.setConstraints(c,gbc);
container.add(c);
}
}
5.5 CardLayout
以时间而非空间来管理它里面的组件,它将加入容器的所有组件看成一叠卡片,每次只有最上面的哪个Component才可见,就好像一副扑克牌,它们叠在一起,每次只有最上面的一张扑克牌才可见
方法名称 | 方法功能 |
---|---|
CardLayout | 创建默认的CardLayout布局管理器 |
CardLayout(int hgpa,int vgap) | 通过指定卡片与容器左右边界的间距(hgap)、上下边界(vgap)的间距来创建CardLayout布局管理器 |
first(Container target) | 显示target容器中的第一张卡片 |
last(Container target) | 显示target容器中的最后一张卡片 |
previous (Container target) | 显示target容器中的前一张卡片 |
next(Container target) | 显示target容器中的后一张卡片 |
show(Container target,String name) | 显示target容器中指定名字的卡片 |
public class CardLayoutDemo {
public static void main(String[] args) {
Frame frame = new Frame("这里演示CardLayout");
//1.创建Panel,用来存储多张卡片
Panel panel = new Panel();
//2.创建CardLayout对象,并且把该对象设置个之前创建的容器
CardLayout cardLayout = new CardLayout();
panel.setLayout(cardLayout);
//3.往panel中存储多个组件
String[] name = {"第一张","第二张","第三张","第四张","第五张"};
for(int i = 0;i < name.length;i++){
panel.add(name[i],new Button(name[i]));
}
//4.把panel放到frame的中间区域
frame.add(panel);
//5.创建另外一个panel p2,用来存储多个按钮组件
Panel panel1 = new Panel();
//6.创建一个5个按钮组件
Button button1 = new Button("上一张");
Button button2 = new Button("下一张");
Button button3 = new Button("第一张");
Button button4 = new Button("最后一张");
Button button5 = new Button("第三张");
//7.创建一个事件监听器,监听按钮的点击动作
ActionListener actionListener = new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
String actionCommand = e.getActionCommand();
switch (actionCommand){
case "上一张":
cardLayout.previous(panel);
break;
case "下一张":
cardLayout.next(panel);
break;
case "第一张":
cardLayout.first(panel);
break;
case "最后一张":
cardLayout.last(panel);
break;
case "第三张":
cardLayout.show(panel,"第三张");
break;
}
}
};
//8.把当前这个时间监听器和多个按钮绑定到一起
button1.addActionListener(actionListener);
button2.addActionListener(actionListener);
button3.addActionListener(actionListener);
button4.addActionListener(actionListener);
button5.addActionListener(actionListener);
//9.把按钮添加到容器中
panel1.add(button1);
panel1.add(button2);
panel1.add(button3);
panel1.add(button4);
panel1.add(button5);
//10.把panel1放到frame的南边区域
frame.add(panel1,BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
}
}
5.6 BoxLayout
为了简化开发,Swing引入一个新的布局管理器:BoxLayout。BoxLayout可以在水平和垂直两个方向上摆放GUI组件,BoxLayout提供了如下一个简单的构造器
方法名称 | 方法功能 |
---|---|
BoxLayout(Container target,int axis) | 指定创建基于target容器的BoxLayout布局管理器,该布局管理器里的组件按axis方向排列。其中axis有BoxLayout.X_AXIS(横向)和BoxLayout.Y_AXIS(纵向)两个方向 |
public class BoxLayoutDemo {
public static void main(String[] args) {
Frame frame = new Frame("这里是BoxLayout测试");
//1.基于frame容器,创建一个BoxLayout对象,并且,该对象存放组件是垂直存放
BoxLayout boxLayout = new BoxLayout(frame,BoxLayout.Y_AXIS);
//2.把BoxLayout对象设置给frame
frame.setLayout(boxLayout);
//3.往frame中添加两个按钮组件
frame.add(new Button("按钮1"));
frame.add(new Button("按钮2"));
frame.pack();
frame.setVisible(true);
}
}
5.7 Box
在java.swing包中,提供了一个新的容器Box,该容器的默认布局管理器就是BoxLayout,大多数情况下,使用Box容器去容纳多个GUI,然后再把Box容器作为一个组件,添加到其他的容器中,从而形成整体窗口布局。
方法名称 | 方法功能 |
---|---|
static Box createHorizontalBox() | 创建一个水平排列组件的Box容器 |
static Box createVerticalBox() | 创建一个垂直排列组件的Box容器 |
public class BoxLayoutDemoDemo {
public static void main(String[] args) {
Frame frame = new Frame("这里是BoxLayout测试");
//1.创建一个水平排列组件的Box容器
Box horizontalBox = Box.createHorizontalBox();
//2.往当前容器中添加两个按钮
horizontalBox.add(new Button("水平按钮1"));
horizontalBox.add(new Button("水平按钮2"));
//3.创建一个垂直排列组件的Box容器
Box verticalBox = Box.createVerticalBox();
//4.往当前容器中添加两个按钮
verticalBox.add(new Button("垂直按钮1"));
verticalBox.add(new Button("垂直按钮2"));
//5.把两个Box容器添加到frame中展示
frame.add(horizontalBox,BorderLayout.NORTH);
frame.add(verticalBox,BorderLayout.CENTER);
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(可在水平方向上拉伸的间距) |
public class BoxLayoutDemoDemo {
public static void main(String[] args) {
Frame frame = new Frame("这里是BoxLayout测试");
//1.创建一个水平排列组件的Box容器
Box horizontalBox = Box.createHorizontalBox();
//2.往当前容器中添加两个按钮
horizontalBox.add(new Button("水平按钮1"));
horizontalBox.add(Box.createHorizontalGlue());
horizontalBox.add(new Button("水平按钮2"));
horizontalBox.add(Box.createHorizontalStrut(30));
horizontalBox.add(new Button("水平按钮3"));
//3.创建一个垂直排列组件的Box容器
Box verticalBox = Box.createVerticalBox();
//4.往当前容器中添加两个按钮
verticalBox.add(new Button("垂直按钮1"));
verticalBox.add(Box.createVerticalGlue());
verticalBox.add(new Button("垂直按钮2"));
verticalBox.add(Box.createVerticalStrut(30));
verticalBox.add(new Button("垂直按钮3"));
//5.把两个Box容器添加到frame中展示
frame.add(horizontalBox,BorderLayout.NORTH);
frame.add(verticalBox,BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
}
}
6.AWT常用组件
组件名 | 功能 |
---|---|
Button | 按钮 |
Canvas | 用于绘图的画布 |
Checkbox | 复选框组件 |
CheckboxGroup | 用于将多个checkbox组件组合成一组,一组checkbox组件将只有一个可以被选中 |
Choice | 下拉选择框 |
Frame | 窗口,在GUI程序里通过该类创建窗口 |
label | 标签类,用于放置提示性文本 |
list | 列表框组件,可以添加多项条目 |
Panel | 不能单独存在基本容器类,必须放到其他容器中 |
scrollbar | 滑动条组件,如果需要用户输入位于某个范围的值,就可以使用滑动条组件,比如调色板中设置RGB的三个值所使用的滑动条。当创建一个滑动条时,必须指定它的方向、初始值、滑块的大小、最大值和最小值 |
scrollPane | 带水平及垂直滚动条的容器组件 |
TextArea | 多行文本域 |
TextField | 单行文本框 |
public class BasicComponentDemo {
public static void main(String[] args) {
new BasicComponentDemo().init();
}
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);
Button ok = new Button("确认");
List colorList = new List(6,true);
//组装界面
public void init(){
//组装底部
Box bBox = Box.createHorizontalBox();
bBox.add(tf);
bBox.add(ok);
frame.add(bBox,BorderLayout.SOUTH);
//组装选择部分
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);
}
}
7.事件处理机制
7.1 概述和使用
前面介绍了如何放置各种组件,从而得到图形界面,但这些界面还不能响应用户的任何操作。比如单击前面所有窗口右上角的”x"按钮,但窗口依然不会关闭。因为在AWT编程中,所有用户的操作,都必须需要经过一套事件处理机制来完成,而Frame和组件本身并没有事件处理能力
定义:当在某个组件上发生某些操作的时候,会自动地触发一段代码的执行
在GUI事件处理机制中涉及到4个重要的概念需要理解:
事件源:操作发生的场所,通常指某个组件,例如按钮,窗口;
事件:在事件源上发生的操作可以叫做事件,GUI会把事件都封装到一个Event对象,如果需要知道该事件的详细信息,就可以通过Event对象来获取
事件监听器:当在某个事件源上发生了某个事件,事件监听器就可以对这个事件进行处理
注册监听:把某个事件监听器通过某个事件(B)绑定到某个事件源(C)上,当在事件源C上发生了事件B之后,那么事件监听器A的代码就会自动执行
使用步骤:
1.创建事件源组件对象;
2.自定义类,实现XxxxListener接口,重写方法
3.创建事件监听器对象(自定义对象)
4.调用事件源组件对象的addXxxxListener()方法完成注册监听
public class EventDemo {
public static void main(String[] args) {
new EventDemo().init();
}
public void init(){
//组装试图
//监听器
//创建事件监听器对象(自定义对象)
//MyListener myListener = new MyListener();
//注册监听,调用事件源组件对象的addXxxxListener()方法完成
//如果实现一个监听,可以使用匿名对象的匿名内部类
//多个的话,用自定义类,实现XxxxListener接口,重写方法
ok.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("确认按钮被点击了");
tf.setText("hello world");
}
});
//把tf和ok放入到frame中
frame.add(tf,BorderLayout.NORTH);
frame.add(ok);
frame.pack();
frame.setVisible(true);
}
//创建事件源组件对象
Frame frame = new Frame("这里测试事件处理");
TextField tf = new TextField(30);
Button ok = new Button("确定");
// private class MyListener implements ActionListener{
//
// @Override
// public void actionPerformed(ActionEvent e) {
//
// }
// }
}
7.2 常见事件和事件监听器
事件监听器必须实现监听器接口,AWT提供了大量的事件监听器接口用于实现不同类型的事件监听器,用于监听不同类型的事件。AWT中提供了丰富的事件类,用于封装不同组件上所发生的特定操作,AWT的事件都是AWTEvent类的子类,AWTEvent是EventObject的子类。
7.2.1 事件
AWT把事件分为了两大类:
1.低级事件:这类事件是基于某个特定动作的事件,比如进入、点击、拖放等的鼠标事件,再比如得到焦点和失去焦点事件
事件 | 触发时机 |
---|---|
ComponentEvent | 组件事件,当组件尺寸发生变化、位置发生变动、显示/隐藏状态发生改变时触发该事件 |
ContainerEvent | 容器事件,当容器里发生添加组件、删除组件时触发该事件 |
WindowEvent | 窗口事件,当窗口状态发生改变(如打开、关闭、最大化、最小化)时触发该事件 |
FocusEvent | 焦点事件,当组件得到焦点或失去焦点时触发该事件 |
KeyEvent | 键盘事件,当按键被按下、松开、单击时触发该事件 |
MouseEvent | 鼠标事件,当进行单击、按下、松开、移动鼠标等动作时触发该事件 |
PaintEvent | 组件绘制事件,该事件是一个特殊的事件类型,当GUI组件调用update/paint()方法来呈现自身时触发该事件,该事件并非专用于事件处理模型 |
2.高级事件:这类事件并不会基于某个特定动作,而是根据功能含义定义的事件
事件 | 触发时机 |
---|---|
ActionEvent | 动作事件,当按钮、菜单项被单击时,在TextField中按Enter键时触发 |
AjustmentEvent | 调节事件,在滑动条上移动滑块以调节数值时触发该事件 |
ItemEvent | 选项事件,当用户选中某项,或取消选中某项时触发该事件 |
TextEvent | 文本事件,当文本框、文本域里的文本发生改变时触发该事件 |
7.2.2 事件监听器
不同的事件需要不同的监听器监听,不同的监听器需要实现不同的监听器接口,当指定事件发生后,事件监听器就会调用所包含的事件处理器(实例方法)来处理事件。
事件类别 | 描述信息 | 监听器接口名 |
---|---|---|
ActionEvent | 激活组件 | ActionListener |
ItemEvent | 选择了某些项目 | ItemListener |
MouseEvent | 鼠标移动 | MouseMotionListener |
MouseEvent | 鼠标点击 | MouseListener |
KeyEvent | 键盘输入 | KeyListener |
FocusEvent | 组件收到或失去焦点 | FocusListener |
AjustmentEvent | 移动了滚动条等组件 | AjustmentListener |
ComponentEvent | 对象移动缩放显示隐藏等 | ComponentListener |
WindowEvent | 窗口收到窗口级事件 | WindowListener |
ContainerEvent | 容器中增加删除了组件 | ContainerListener |
TextEvent | 文本字段或文本区域发生改变 | TextListener |
案例一:
public class ListenerDemo {
Frame frame = new Frame("这里测试监听器");
Choice choice = new Choice();
TextField tf = new TextField(20);
public void init(){
choice.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
Object item = e.getItem();
System.out.println("当前选中的条目为"+item);
}
});
tf.addTextListener(new TextListener() {
@Override
public void textValueChanged(TextEvent e) {
String text = tf.getText();
System.out.println("当前文本框中的内容为"+text);
}
});
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) {
Component child = e.getChild();
System.out.println("frame中删除了"+child);
}
});
Box horizontalBox = Box.createHorizontalBox();
choice.add("柳岩");
choice.add("孙楠");
choice.add("孙俪");
horizontalBox.add(choice);
horizontalBox.add(tf);
frame.add(horizontalBox);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
new ListenerDemo().init();
}
}
案例二:给 Frame设置 WindowListener,监听用户点击X,则关闭当前窗口
public class ListenerDemo2 {
public static void main(String[] args) {
Frame frame = new Frame("这里测试WindowListener");
frame.setBounds(200,200,500,300);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
frame.setVisible(true);
}
}
8.菜单组件
前面讲解了如果构建GUI界面,其实就是把一些GUI的组件,按照一定的布局放入到容器中展示就可以了。在实际开发中,除了主界面,还有一类比较重要的内容就是菜单相关组件,可以通过菜单相关组件很方便地使用特定的功能,在AWT中,菜单相关组件的使用和之前学习的组件是一模一样,只需要把菜单条、菜单、菜单项组合到一起,按照一定的布局,放入到容器中即可。
菜单组件名称 | 功能 |
---|---|
MenuBar | 菜单条,菜单的容器 |
Menu | 菜单组件,菜单项的**容器。**它也是MenuItem的子类,所以可作为菜单项使用 |
PopupMenu | 上下文菜单组件(右键菜单组件) |
MenuItem | 菜单项组件 |
CheckboxMenuItem | 复选框菜单项组件 |
菜单相关组件使用:
1.准备菜单项组件,这些组件可以是MenuItem及其子类对象
2.准备菜单组件Menu或者PopupMenu(右击弹出子菜单),把第一步中准备好的菜单项添加进来
3.准备菜单条组件MenuBar,把第二步中准备好的菜单组件Menu添加进来
4.把第三步中准备好的菜单条组件添加到窗口对象中显示
小技巧:
1.如果要在某个菜单的菜单项之间添加分割线,那么只需要调用Menu的add(new MenuItem(“-”)即可
2.如果要给某个菜单项关联快捷键功能,那么只需要在创建菜单项对象时设置即可,例如给菜单项关联ctrl+shift+Q,只需要:new MenuItem(“菜单项名字”,new MenuShortcut(KeyEvent.Vk_Q,true);
8.1 案例一
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("粘贴");
//关联快捷键ctrl+shift+Q
MenuItem comment = new MenuItem("注释", new MenuShortcut(KeyEvent.VK_Q, true));
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()+"\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();
}
}
8.2 案例二
public class PopupMenuTest {
Frame frame = new Frame("这里测试PopupMenu");
TextArea ta = new TextArea("我爱中华", 6, 40);
//创建Panel容器
Panel p = new Panel();
PopupMenu popupMenu = new PopupMenu();
MenuItem commet = new MenuItem("注释");
MenuItem cancelComment = new MenuItem("取消注释");
MenuItem copy = new MenuItem("复制");
MenuItem save = new MenuItem("保存");
public void init() {
ActionListener actionListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
ta.append("您点击了:" + e.getActionCommand() + "\n");
}
};
commet.addActionListener(actionListener);
cancelComment.addActionListener(actionListener);
copy.addActionListener(actionListener);
save.addActionListener(actionListener);
popupMenu.add(commet);
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) {
boolean popupTrigger = e.isPopupTrigger();
if(popupTrigger){
popupMenu.show(p,e.getX(),e.getY());
}
}
});
frame.add(ta,BorderLayout.NORTH);
frame.add(p);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
new PopupMenuTest().init();
}
}
9.绘图
很多程序如各种游戏都需要在窗口中绘制各种图形,除此之外,即使在开发JavaEE项目时,有时候也必须”动态“地向客户端生成各种图形、图表,比如图形验证码、统计图等,这都需要利用AWT的绘图功能
9.1组件绘图原理
之前我们学习过的组件,如Button、Frame、Checkbox等等,不同的组件,展示出来的图形都不一样,其实这些组件展示出来的图形,其本质就是用AWT的绘图来完成的。
在AWT中,真正提供绘图的是Graphics对象,那么Component组件和Graphics对象存在上面关系,才能让Component绘制自身的图形呢?在Component类中,提供了下列三个方法来完成足迹爱你图形的绘制与刷新
paint(Graphics g):绘制组件的外观
update(Graphics g):内部调用paint方法,刷新组件外观
repaint():调用update方法,刷新组件外观
一般情况下,update()和paint()方法是由AWT系统负责使用,如果如果程序要希望系统重新绘制组件,可以调用repaint()方法
绘图工具:画布、画笔、颜料
AWT工具:Canvas类充当画布,提供了Graphics类来充当画笔,通过调用Graphics对象的setColor()方法可以给画笔设置颜色
画图的步骤:
1.自定义类,继承Canvas类,重写paint(Graphics g)方法完成画图
2.在paint方法内部,真正开始画图之前调用Graphics对象的setColor()、setFont()等方法设置画笔的颜色、字体等属性
3.调用Graphics画笔的drawXxxx()方法开始画图
真正画图的核心在于使用Graphics画笔在Canvas画布上画出什么颜色,什么样式的图形,所以核心在画笔上,下表中列出了Graphics类中常用的一些方法
方法名称 | 方法功能 |
---|---|
setColor(Color c) | 设置颜色 |
setFont(Font font) | 设置字体 |
drawLine() | 绘制直线 |
drawRect() | 绘制矩形 |
drawRoundRect() | 绘制圆角矩形 |
drawOval() | 绘制椭圆形 |
drawPolygon() | 绘制多边形 |
drawArc() | 绘制圆弧 |
drawPolyline() | 绘制折线 |
fillRect() | 填充矩形区域 |
fillRoundRect() | 填充圆角矩形区域 |
fillOval() | 填充椭圆区域 |
fillPolygon() | 填充多边形区域 |
fillArc() | 填充圆弧对应的扇形区域 |
drawImage() | 绘制位图 |
9.2 案例一
public class SimpleDraw {
public static void main(String[] args) {
new SimpleDraw().init();
}
private final String RECT_SHAPE = "rect";
private final String OVAL_SHAPE = "oval";
Frame frame = new Frame("这里测试绘图");
Button b1 = new Button("绘制矩形");
Button b2 = new Button("绘制椭圆");
//定义一个变量,记录当前要绘制椭圆还是矩形
private String shape = "";
//自定义类,继承Canvas类,重写paint(Graphics g)方法完成画图
public 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.BLUE);
g.drawOval(100, 100, 150, 100);
}
}
}
public void init() {
MyCanvas drawArea = new MyCanvas();
b1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//修改标记的值为rect
shape = RECT_SHAPE;
drawArea.repaint();
}
});
b2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//修改标记的值为rect
shape = OVAL_SHAPE;
drawArea.repaint();
}
});
//创建Panel,承载按钮
Panel p = new Panel();
p.add(b1);
p.add(b2);
//drawArea的大小需要设置
drawArea.setPreferredSize(new Dimension(300,300));
frame.add(drawArea);
frame.add(p,BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
}
}
9.3 案例二
java也可用于开发一些动画,所谓动画,就是间隔一定的时间(通常小于0.1秒)重新绘制新的图像,两次绘制的图像之间差异较小,肉眼看起来就成了所谓的动画。
为了实现间隔一定的时间就重新调用组件的repaint()方法,可以借助Swing提供的Timer类,Timer类是一个定时器, 它有如下一个构造器:
Timer(int delay ,ActionListener listener):每间隔delay毫秒,系统自动触发ActionListener监听器里的事件处理器方法,在方法内部我们就可以调用组件的repaint方法,完成组件重绘
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 = 120;
private final int racketY = 340;
//定义变量,标识当前游戏是否已经结束
private boolean isOver = false;
//声明一个定时器
private Timer timer;
//自定义一个类,继承canvas,充当画布
private class MyCanvas extends Canvas {
@Override
public void paint(Graphics g) {
//在这里绘制内容
//游戏结束
if (isOver) {
g.setColor(Color.BLUE);
g.setFont(new Font("Times", Font.BOLD, 30));
g.drawString("游戏结束", 50, 200);
} else {
//游戏中
//绘制小球
g.setColor(Color.RED);
g.fillOval(ballx, bally, BALL_SIZE, BALL_SIZE);
//绘制球拍
g.setColor(Color.pink);
g.fillRect(racketX, racketY, RACKET_WIDTH, RACKET_HEIGHT);
}
}
}
//创建绘画区域
MyCanvas drawArea = new MyCanvas();
public void init() {
//组装视图,游戏逻辑的控制
//完成球拍坐标的变化
KeyListener 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;
}
}
}
};
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)) {
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和drawArea注册监听器
frame.addKeyListener(listener);
drawArea.addKeyListener(listener);
//设置frame最佳大小,并可视
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
new PinBall().init();
}
}
10.位图
如果仅仅绘制一些简单的几何图形,程序的图形效果依然比较单调。AWT也允许在组件上绘制位图,Graphics提供了drawimage(Image image)方法用于绘制位图,该方法需要一个image参数–代表位图,通过该方法就可以绘制出指定的位图。
10.1 Graphics使用概述
位图使用步骤:
1.创建image的子类对象BufferedImage(int width,int height,int ImageType),创建时需要指定位图的宽高以及类型属性;此时相当于在内存中生成一张图片。
2.调用BufferedImage对象的getGraphics()方法获取画笔,此时就可以往内存中的这张图片上绘图,绘图的方法和之前学习的一模一样
3.调用组件paint()方法中提供的Graphics对象的drawImage()方法,一次性的内存中的图片BufferedImage绘制到特定的组件上
使用位图绘制组件的好处:
使用位图来绘制组件,相当于实现了图的缓冲区,此时绘图时没有直接把图形绘制到组件中,而是先绘制内存中的BufferedImage上,等全部绘制完毕中,再一次性的图像显示到组件上即可,这样用户的体验会好一点。
案例:通过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 red = new MenuItem("红色");
private MenuItem green = new MenuItem("绿色");
private MenuItem blue = new MenuItem("蓝色");
//定义一个变量,记录当前画笔的颜色
private Color forceColor = Color.BLACK;
//创建一个BufferedImage位图对象
BufferedImage image = new BufferedImage(AREA_WIDTH, AREA_HEIGHT, BufferedImage.TYPE_INT_RGB);
//通过位图,获取关联的Graphics对象
Graphics g = image.getGraphics();
//自定义一个类,继承Canvas
private class MyCanvas extends Canvas {
@Override
public void paint(Graphics g) {
g.drawImage(image, 0, 0, null);
}
}
MyCanvas drawArea = new MyCanvas();
//定义变量,记录鼠标拖放过程中,上一次所处的坐标
private int preX = -1;
private int preY = -1;
public void init() {
//组装视图,逻辑控制
ActionListener listener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String actionCommand = e.getActionCommand();
switch (actionCommand) {
case "红色":
forceColor = Color.RED;
break;
case "绿色":
forceColor = Color.GREEN;
break;
case "蓝色":
forceColor = Color.BLUE;
break;
}
}
};
red.addActionListener(listener);
green.addActionListener(listener);
blue.addActionListener(listener);
colorMenu.add(red);
colorMenu.add(green);
colorMenu.add(blue);
//把popupMenu设置给绘图区域
drawArea.add(colorMenu);
drawArea.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
//当鼠标键抬起时被调用
boolean popupTrigger = e.isPopupTrigger();
if (popupTrigger) {
colorMenu.show(drawArea, e.getX(), e.getY());
}
// 当鼠标右键抬起时,重置开始坐标
preX = -1;
preY = -1;
}
});
g.setColor(Color.white);
g.fillRect(0, 0, AREA_WIDTH, AREA_HEIGHT);
//通过监听鼠标的移动,完成线条绘制
drawArea.addMouseMotionListener(new 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的值
preY = e.getY();
preX = e.getX();
//重绘组件
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();
}
}
10.2 ImageIO的使用
在实际生活中,很多软件都支持打开本地磁盘已经存在的图片,然后进行编辑,编辑完毕后,再重新保存到本地磁盘。如果使用AWT要完成这样的功能,那么需要使用到ImageIO这个类,可以操作本地磁盘的图片文件
方法名称 | 方法功能 |
---|---|
static BufferedImage read(File input) | 读取本地磁盘图片文件 |
static BufferedImage read(InputStream input) | 读取本地磁盘图片文件 |
static boolean write(Rendereding im,String formatName,File output) | 往本地磁盘中输出图片文件 |
10.3 案例一
编写图片查看程序,支持另存操作
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 image;
//声明BufferedImage对象,记录本地存取到内存中的图片
private class MyCanvas extends Canvas{
@Override
public void paint(Graphics g) {
g.drawImage(image,0,0,null);
}
}
MyCanvas drawArea = new MyCanvas();
public void init() {
//组装视图
open.addActionListener(e->{
//打开一个文件对话框
FileDialog fileDialog = new FileDialog(frame,"打开图片",FileDialog.LOAD);
fileDialog.setVisible(true);
//获取用户选择的图片路径以及名称
String directory = fileDialog.getDirectory();
String file = fileDialog.getFile();
try {
image = ImageIO.read(new File(directory,file));
drawArea.repaint();
} catch (IOException ioException) {
ioException.printStackTrace();
}
});
save.addActionListener(e->{
FileDialog fileDialog = new FileDialog(frame,"保存图片",FileDialog.SAVE);
fileDialog.setVisible(true);
//获取用户选择的图片路径以及名称
String directory = fileDialog.getDirectory();
String file = fileDialog.getFile();
try {
ImageIO.write(image,"JPEG",new File(directory,file));
drawArea.repaint();
} catch (IOException ioException) {
ioException.printStackTrace();
}
});
menu.add(open);
menu.add(save);
menuBar.add(menu);
frame.setMenuBar(menuBar);
frame.add(drawArea);
frame.pack();
frame.setVisible(true);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public static void main(String[] args) {
new ReadAndSaveImage().init();
}
}
10.4 案例二(五子棋游戏)
public class Gobang {
private JFrame f = new JFrame("五子棋游戏");
//声明四个BufferedImage对象,分别记录四张图片
BufferedImage table;
BufferedImage black;
BufferedImage white;
BufferedImage selected;
//声明棋盘棋盘的宽和高
private final int TABLE_WIDTH = 474;
private final int TABLE_HEIGHT = 488;
//声明棋盘横向和纵向分别可以下多少子,他们的值都为15
private int BOARD_SIZE = 15;
//声明每个棋子占用棋盘的比率
private final int RATE = TABLE_WIDTH / BOARD_SIZE;
//声明变量,记录妻子对于x方向和y方向的偏移量
private final int X_OFFSET = 4;
private final int Y_OFFSET = 5;
//声明一个二位数组,记录棋子,如果索引[i}[j}处的值为 0没有棋子,1-白棋,2-黑棋
private int[][] board = new int[BOARD_SIZE][BOARD_SIZE];
//声明红色选择框的坐标
private int selected_X = -1;
private int selected_Y = -1;
private class ChessBoard extends JPanel {
@Override
public void paint(Graphics g) {
//绘图
//绘制棋盘
g.drawImage(table, 0, 0, null);
//绘制选择框
if (selected_Y > 0 && selected_X > 0) {
g.drawImage(selected, selected_X + X_OFFSET, selected_Y + 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, j * RATE + X_OFFSET, i * RATE + Y_OFFSET, null);
}
//绘制白棋
if (board[i][j] == 1) {
g.drawImage(white, j * RATE + X_OFFSET, i * 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("删除");
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 -> {
//修改当前要下的棋子的颜色
board_type = 1;
//刷新按钮的颜色
refreshBtnColor(Color.GREEN, Color.GRAY, Color.GRAY);
});
blackBtn.addActionListener(e -> {
//修改当前要下的棋子的颜色
board_type = 2;
//刷新按钮的颜色
refreshBtnColor(Color.GRAY, Color.GREEN, Color.GRAY);
});
deleteBtn.addActionListener(e -> {
//修改当前要下的棋子的颜色
board_type = 0;
//刷新按钮的颜色
refreshBtnColor(Color.GRAY, Color.GRAY, Color.GREEN);
});
p.add(whiteBtn);
p.add(blackBtn);
p.add(deleteBtn);
f.add(p, BorderLayout.SOUTH);
table = ImageIO.read(new File("src\\linyinlei\\java\\img.png"));
white = ImageIO.read(new File("C:\\Users\\linyinlei\\Desktop\\OIP-C(1).jfif"));
black = ImageIO.read(new File("C:\\Users\\linyinlei\\Desktop\\OIP-C.jfif"));
selected = ImageIO.read(new File("src\\linyinlei\\java\\img_1.png"));
//设置frame最佳大小并可见
f.pack();
f.setVisible(true);
//组装棋盘
//处理鼠标移动
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.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);
f.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public static void main(String[] args) throws Exception {
new Gobang().init();
}
}
三、Swing概述
3.1 概述
Swing是由100%纯java实现的,不再依赖于本地平台的GUI,因此可以在所有平台上都保持相同的界面外观,独立于本地平台的Swing组件被称为轻量级组件,而依赖于本地平台的AWT组件被称为重量级组件。
由于Swing的所有组件完全采用java实现,不再调用本地平台的GUI,所以导致Swing图形界面的希纳是速度要比AWT图形界面的显示速度慢一些。但相对于快速发展的硬件设施而言,这种微小的速度差别无妨大碍。
3.1.1使用Swing的优势:
1.Swing组件不再依赖于本地平台的GUI,无需采用各种平台的GUI交集,因此Swing提供了大量图形界面组件,远远超出了AWT所提供的图形界面组件集。
2.Swing组件不再依赖于本地平台GUI,因此不会产生与平台相关的bug
3.Swing组件在各种平台上运行时可以保证具有相同的图形界面外观
Swing提供的这些优势,让java图形界面程序真正实现了“一次实现,多次运行”
3.1.2 Swing的特征:
1.Swing组件采用MVC模式,及模式-视图-控制器设计模式
模型(Model):用于维护组件的各种状态
视图(View):是组件的可视化表现
控制器(Controller):用于控制对于各种事件、组件做出响应
当模型发生改变时,它会通知所有依赖它的视图,视图会根据模型数据来更新自己。Swing使用UI代理来包装视图和控制器,还有一个模型对象来维护该组件的状态。例如,按钮JButton有一个维护其状态信息的模型
ButtonModel对象,Swing组件的模型是自动设置的,因此一般都使用JButton,而无需关心ButtonModel对象
2.Swing在不同的平台上表现一致,并且有能力提供本地平台不支持的显示外观。由于Swing采用MVC模式来维护各组件,所以当组件的外观被改变时,对组件的状态信息(有模型维护)没有任何影响。因此,Swing可以使用插拔式外观感觉来控制组件外观,使得Swing图形界面在同一个平台上运行时能拥有不同的外观,用户可以选择自己喜欢的外观。相比之下,在AWT图形界面中,由于控制组件外观的对等类与具体平台相关,因此AWT组件总是具有与本地平台相同的外观。
大部分组件Swing都是JComponent抽象类的直接或间接子类(并不是全部的Swing组件),JComponent类定义了所有的子类组件的通用方法,JComponent类是AWT里java.awt.Container的子欸,这也是AWT和Swing的联系之一。绝大部分Swing组件继承了Container类,所以Swing组件都可作为容器使用(JFrame继承了Frame类)
3.1.3 Swing和AWT的对应关系:
大部分情况,只需要在AWT组件的名称前面加个J,就可以得到其对应的Swing罪案名称,但有几个例外:
1.JComboBox:对应于AWT里的组件choice,但比choice组件功能更丰富
2.JFileChooser:对应于AWT里的FileDialog组件
3.JScrollBar:对应于AWT里的Scrollbar组件,注意两个组件类名中b字母的大小差别
4.JCheckBox:对应于AWT里的Checkbox组件,注意两个组件类名的b字母的大小写差别
5.JCheckBoxMenuItem:对应于AWT里的CheckboxMenuItem组件,注意两个组件类名的b字母的大小写差别
3.1.4 Swing组件按照功能来分类:
1.顶层容器:JFrame,JDialog,JApplet,JWindow
2.中间容器:JPanel,JScrollPane,JSplitPane,JToolBar
3.特殊容器:在用户界面上具有特殊作用的中间容器,如JIntemalFrame,JRootPane,JLayeredPane,JDestopPane
4.基本组件:实现人机交互的组件,如JButton,JComoBox,JList,JMenu,JSlider
5.不可编辑信息的显示组件:向用户显示不可编辑信息的组件,如JLabel,JProgressBar,JToolTip
6.可编辑信息的显示组件:向用户显示能被编辑的格式化信息,如JTable,JTextArea,JTextField等
7.特殊对话框组件:可以直接产生特殊对话框的组件,如JColorChooser和JFileChooser
3.1.5 AWT组件的Swing实现
Swing为除Canvas之外的所有AWT的组件提供了相应的实现,Swing组件比AWT组件的功能更加强大。相对于AWT组件,Swing组件具有如下4个额外的功能
1.可以为Swing组件设置提示信息,使用setToolTipText()方法,为组件设置对用户有帮助的提示信息
2.很多Swing组件如按钮、标签、菜单项等,除使用文字外,还可以使用修饰自己。为了允许在Swing组件中使用图标,Swing为Icon接口提供了一个实现类:ImageIcon,该实现类代表一个图像图标
3.支持插拔式的外观风格。每个JComponent对象都有一个相应的JComponentUI对象,为它完成所有的绘画,事件处理,决定尺寸大小等工作,ComponentUI对象依赖当前使用的PLAF,使用UIManage.setLookAndFeel()方法可以改变图形界面的外观风格
4.支持设置边框,Swing组件可以设置一个或多个边框。Swing中提供了各式各样的边框供用户使用,也能建立组合边框或自己设计边框。一种空白边框可以用于增大组件,同时协助布局管理器对容器中的组件进行合理的布局
每个Swing组件都有一个对应的UI类,例如JButton组件就有一个对应的ButtonUI类来作为UI代理。每个Swing组件的UI代理的类名总是将该Swing组件类名的J去掉,然后再后面添加UI后缀。UI代理类通常是一个抽象基类,不同的PLAF会有不同的UI代理实现类。Swing类库中包含了几套UI代理,分别放在不同的包下,每套UI代理都几乎包含了所有Swing的ComponentUI实现,每套这样的实现都被称为一种PLAF实现。以JButton为例,其UI代理的继承层次下图:
//容器
JFrame jf = new JFrame();
try{
//设置外观风格
UIManage.setLookAndFeel("com.sun.java.swing.plaf.windows.windowsLookAndFeel");
//刷新jf容器及其内部组件的外观
SwingUtilities.updateComponentTreeUI(jf)
}
catch(Exception e){
e.printStackTrace();
}
3.2 Swing常用组件
下拉选择框:choice(choice.add())—JComboBox 对象名.addItem
单选框:[复选框:Jcheckbox CheckboxGroup(用于将多个checkbox组件组合成一组,一组checkbox组件将只有一个可以被选中)]------------>JRadioButton
复选框:Jcheckbox
文本框:JTextField
文本域:JTextArea
按钮:JButton
菜单项:JMenuBar
菜单:JMenu
菜单条目:JMenuItem
菜单分割项:xxxx.addSeparator()
右键菜单:JPopupMenu
单选菜单项:JRadioButtonMenuItem
文本域对鼠标右键菜单的事件不需要再监听鼠标事件,只需要ta.setComponentPopupMenu(jPopupMenu);
文本域对象内加内容:append()
3.3 基本案例代码实现
package linyinlei.java;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* @author linyinlei
* @create 2022-02-24 13:46
*/
public class SwingComponentDemo {
JFrame f = new JFrame("测试Swing基本组件");
//声明菜单相关的组件
JMenuBar menubar = new JMenuBar();
JMenu fileMenu = new JMenu("文件");
JMenu editMenu = new JMenu("编辑");
JMenuItem auto = new JMenuItem("自动换行");
JMenuItem copy = new JMenuItem("复制",new ImageIcon("src\\linyinlei\\java\\img_1.png"));
JMenuItem paste = new JMenuItem("粘贴",new ImageIcon("src\\linyinlei\\java\\img_1.png"));
JMenu formatMenu= new JMenu("格式");
JMenuItem comment = new JMenuItem("注释");
JMenuItem cancelComment = new JMenuItem("取消注释");
//声明文本域
JTextArea ta = new JTextArea(8,20);
String[] colors = {"红色","绿色","蓝色"};
//声明列表框
JList<String> colorList = new JList<>(colors);
//声明选择相关组件
JComboBox<String> colorSelect = new JComboBox();
ButtonGroup bg = new ButtonGroup();
JRadioButton male= new JRadioButton("男",false);
JRadioButton female= new JRadioButton("女",true);
JCheckBox isMarried = new JCheckBox("是否已婚",true);
//声明底部
JTextField tf = new JTextField(40);
JButton ok = new JButton("确定",new ImageIcon("src\\linyinlei\\java\\img_1.png"));
//声明右键菜单
JPopupMenu jPopupMenu = new JPopupMenu();
ButtonGroup poupButtonBg = new ButtonGroup();
//声明单选菜单项
JRadioButtonMenuItem metal = new JRadioButtonMenuItem("Meta 风格");
JRadioButtonMenuItem nimbus = new JRadioButtonMenuItem("Nimbus 风格");
JRadioButtonMenuItem window = new JRadioButtonMenuItem("Windows 风格");
JRadioButtonMenuItem windowClassic = new JRadioButtonMenuItem("Windows 经典风格");
JRadioButtonMenuItem motifItem = new JRadioButtonMenuItem("Motif 风格");
public void init(){
//组装视图
//组装底部
JPanel bottomPanel = new JPanel();
bottomPanel.add(tf);
bottomPanel.add(ok);
f.add(bottomPanel, BorderLayout.SOUTH);
JPanel selectPanel = new JPanel();
colorSelect.addItem("红色");
colorSelect.addItem("蓝色");
colorSelect.addItem("绿色");
selectPanel.add(colorSelect);
bg.add(male);
bg.add(female);
selectPanel.add(male);
selectPanel.add(female);
selectPanel.add(isMarried);
//组装文本域和选择相关组件
Box topLeft = Box.createVerticalBox();
topLeft.add(ta);
topLeft.add(selectPanel);
//组装中间
Box top =Box.createHorizontalBox();
top.add(topLeft);
top.add(colorList);
f.add(top,BorderLayout.CENTER);
//组装顶部
formatMenu.add(comment);
formatMenu.add(cancelComment);
editMenu.add(auto);
editMenu.addSeparator();
editMenu.add(copy);
editMenu.add(paste);
editMenu.addSeparator();
editMenu.add(formatMenu);
menubar.add(fileMenu);
menubar.add(editMenu);
f.setJMenuBar(menubar);
//组装右键菜单
poupButtonBg.add(metal);
poupButtonBg.add(nimbus);
poupButtonBg.add(window);
poupButtonBg.add(windowClassic);
poupButtonBg.add(motifItem);
ActionListener listener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//当前选择的损失哪一个风格
String actionCommand = e.getActionCommand();
try {
changeFlavor(actionCommand);
} catch (Exception exception) {
exception.printStackTrace();
}
}
};
metal.addActionListener(listener);
nimbus.addActionListener(listener);
window.addActionListener(listener);
windowClassic.addActionListener(listener);
motifItem.addActionListener(listener);
jPopupMenu.add(metal);
jPopupMenu.add(nimbus);
jPopupMenu.add(window);
jPopupMenu.add(windowClassic);
jPopupMenu.add(motifItem);
//不需要再监听鼠标事件
ta.setComponentPopupMenu(jPopupMenu);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setVisible(true);
}
public void changeFlavor(String command) throws Exception{
switch(command){
case "Metal 风格":
UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
break;
case "Nimbus 风格":
UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
break;
case "Windows 风格":
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
break;
case "Windows 经典风格":
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsClassicalLookAndFeel");
break;
case "Motif 风格":
UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel");
break;
}
SwingUtilities.updateComponentTreeUI(f.getContentPane());
SwingUtilities.updateComponentTreeUI(menubar);
SwingUtilities.updateComponentTreeUI(jPopupMenu);
}
public static void main(String[] args) {
new SwingComponentDemo().init();
}
}
3.4 为组件设置边框
我们常喜欢给不同的组件设置边框,从而让界面的层次感更明显,Swing中提供了Border对象来代表一个边框
特殊的边框:
1.TitledBorder:它的作用并不是直接为其他组件添加边框,而是为其他边框设置标题,创建该类的对象时,需要传入一个其他的Border对象
3.CompoundBorder:用来组合其他两个边框,创建该类的对象时,需要传入其他两个Border对象,一个作为内边框,一个作为外边框
给组件设置边框步骤:
1.使用BorderFactory或者XxxBorder创建Border的实例对象
2.调用swing组件的setBorder(Border b)方法为组件设置边框
案例:
public class BorderTest {
JFrame jf = new JFrame("测试边框");
public void init(){
//组装视图
//1.JFrame的布局修改为GridLayout
jf.setLayout(new GridLayout(2,4));
//2.往网格中填入不同的JPanel组件,并且设置边框和内容
//创建BevelBorder
Border bevelBorder = BorderFactory.createBevelBorder(BevelBorder.RAISED, Color.RED, Color.GREEN, Color.BLUE, Color.gray);
jf.add(getJPanelWithBorder(bevelBorder,"BevelBordr"));
//创建LineBorder
Border lineBorder = BorderFactory.createLineBorder(Color.ORANGE, 10);
jf.add(getJPanelWithBorder(lineBorder,"lineBorder"));
//创建EmptyBorder
Border emptyBorder = BorderFactory.createEmptyBorder(10, 5, 20, 10);
jf.add(getJPanelWithBorder(emptyBorder,"emptyBorder"));
//创建EchtedBorder
Border etchedBorder = BorderFactory.createEtchedBorder(EtchedBorder.RAISED, Color.RED, Color.GREEN);
jf.add(getJPanelWithBorder(etchedBorder ,"EtchedBorder"));
//创建TitleBorder
TitledBorder titledBorder = new TitledBorder(new LineBorder(Color.ORANGE, 10), "测试标题",TitledBorder.LEFT,TitledBorder.BOTTOM,new Font("StSong",Font.BOLD,18),Color.BLUE);
jf.add(getJPanelWithBorder(titledBorder,"titledBorder"));
//创建MatteBorder
MatteBorder matteBorder = new MatteBorder(10, 5, 20, 10, Color.GREEN);
jf.add(getJPanelWithBorder(matteBorder,"MatteBorder"));
//创建CompoundBorder
CompoundBorder compoundBorder = new CompoundBorder( new LineBorder(Color.cyan, 10),titledBorder);
jf.add(getJPanelWithBorder(compoundBorder,"compoundBorder"));
//3.设置窗口最佳大小,设置窗口可见,处理关闭操作
jf.pack();
jf.setVisible(true);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public JPanel getJPanelWithBorder(Border border,String content){
JPanel jPanel = new JPanel();
//提示信息不会被改动
jPanel.add(new JLabel(content));
//设置边框
jPanel.setBorder(border);
return jPanel;
}
public static void main(String[] args) {
new BorderTest().init();
}
}
3.5 使用JToolBar创建工具条
Swing提供了JToolBar类来创建工具条,并且可以往JToolBar中添加多个工具按钮
方法名称 | 方法功能 |
---|---|
JToolBar(String name,int orientation) | 创建一个名字为name,方向为orientation的工具条对象,其orientation的是取值可以是SwingConstants.HORIZONTAL或SwingConstants.VERTICAL |
JButton add(Action a) | 通过Action对象为JToolBar工具条添加对应的工具按钮,可以将按钮的名称和图标显示出来 |
addSeparation(Dimension size) | 向工具条中添加指定大小的分隔符 |
setFloatable(boolean b) | 设定工具条是否可以被拖动 |
setMargin(Insets m) | 设置工具条域工具按钮的边距 |
setOrientation(int o) | 设置工具条的方向 |
add(Action a)方法:
上述API中add(Action a )这个方法是再给工具条添加一个工具按钮,不是事件监听器。要明确的是不管是菜单条中的菜单项还是工具条中的工具按钮,最终肯定的是需要点击来完成一些操作,所以JToolBar以及JMenu都提供了更加便捷eider添加子组件的方法add(Action a),在这个方法的内部会做如下几件事:
1.创建一个适用于该容器的组件(例如,在工具栏中创建一个工具按钮)
2.从Action对象中获得对应的属性来设置该组件(通过Icon来设置按钮图标)
3.把Action监听器注册到刚才创建的组件上
public class JToolBarTest {
JFrame jf = new JFrame("测试工具条");
JTextArea jta = new JTextArea(6,35);
//声明工具条相关内容
JToolBar jToolBar = new JToolBar("测试工具条",SwingConstants.HORIZONTAL);
//创建三个Action对象
//传递的参数,name和icon,最终在添加到工具条中时,会被拿出来作为按钮的名称和图标
Action pre = new AbstractAction("上一曲",new ImageIcon("src\\linyinlei\\java\\img_1.png")) {
@Override
public void actionPerformed(ActionEvent e) {
jta.append("上一曲"+"\n");
}
};
Action pause = new AbstractAction("暂停",new ImageIcon("src\\linyinlei\\java\\img_1.png")) {
@Override
public void actionPerformed(ActionEvent e) {
jta.append("暂停"+"\n");
}
};
Action next = new AbstractAction("下一曲",new ImageIcon("src\\linyinlei\\java\\img_1.png")) {
@Override
public void actionPerformed(ActionEvent e) {
jta.append("下一曲"+"\n");
}
};
public void init(){
//组装视图
//通过Action对象来创建JButton
JButton preBtn = new JButton(pre);
JButton pauseBtn = new JButton(pause);
JButton nextBtn = new JButton(next);
// jToolBar.add(pre);
// jToolBar.addSeparator();
// jToolBar.add(pause);
// jToolBar.addSeparator();
// jToolBar.add(next);
jToolBar.add(preBtn);
jToolBar.addSeparator();
jToolBar.add(pauseBtn);
jToolBar.addSeparator();
jToolBar.add(nextBtn);
jToolBar.setFloatable(true);
jf.add(jToolBar, BorderLayout.NORTH);
//文本框默认不支持滚动条,可以把文本域设置到JScrollPane中,那么该组件就支持滚动条
JScrollPane jScrollPane = new JScrollPane(jta);
jf.add(jScrollPane);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
public static void main(String[] args) {
new JToolBarTest().init();
}
}
3.6 JColorChooser和JFileChooser
Swing提供了JColorChooser和JFileChooser这两种对话框,可以很方便的完成颜色的选择和本地文件的选择。
3.6.1 JColorChooser
用于创建颜色选择器对话框,该类的用法非常简单,只需要调用它的静态方法就可以快速生成一个颜色选择对话框:
public static Color showDialog (Component component,String title,Color initalColor){
}
public class JColorChooserDemo {
JFrame jf = new JFrame("测试颜色选择器");
JTextArea jta = new JTextArea("我爱中华",6,35);
//声明按钮
JButton btn = new JButton(new AbstractAction("改变文本框背景颜色") {
@Override
public void actionPerformed(ActionEvent e) {
//弹出颜色选择器
Color result = JColorChooser.showDialog(jf, "颜色选择器",Color.cyan);
//修改文本框背景
jta.setBackground(result);
}
});
public void init(){
//组装视图
jf.add(jta);
jf.add(btn,BorderLayout.SOUTH);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
public static void main(String[] args) {
new JColorChooserDemo().init();
}
}
3.6.2 JFileChooser(对应于AWT里的FileDialog组件)
1.创建JFileChooser对象
JFileChooser chooser = new JFileChooser("D:\\a");//指定默认打开的本地磁盘路径
2.调用JFileChooser的一系列可选方法,进行初始化
setSelextedFile(File file)/setSelextedFiles(File[] selextedFiles):设定默认选中的文件
setMutiSelectionEnabled(boolean b):设定是否允许多选,默认是单选
setFileSelectionMode(int Mode):可以设置选择内容,例如文件,文件夹,默认只能选择文件
3.打开文件对话框
showOpenDialog(Component parent):打开文件加载对话框,并指定父组件
showSaveDialog(Component parent):打开文件保存对话框,并指定父组件
4.获取用户选择的结果
File getSelectedFile():获取用户选择的一个文件
File[] getSelectedFiles():获取用户选择的多个文件
public class JFileChooserDemo {
JFrame jf = new JFrame("测试JFileChooser");
//创建菜单条
JMenuBar jmb = new JMenuBar();
//创建菜单
JMenu jMenu = new JMenu("文件");
JMenuItem open = new JMenuItem(new AbstractAction("打开") {
@Override
public void actionPerformed(ActionEvent e) {
//显示一个文件选择器
JFileChooser fileChooser = new JFileChooser(".");
fileChooser.showOpenDialog(jf);
//获取用户选择的文件
File file = fileChooser.getSelectedFile();
//进行显示
try {
image = ImageIO.read(file);
drawArea.repaint();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
});
BufferedImage image;
//swing提供的组件,都使用了图像缓冲技术
private class MyCanvas extends JPanel {
@Override
public void paint(Graphics g) {
g.drawImage(image, 0, 0, null);
}
}
MyCanvas drawArea = new MyCanvas();
JMenuItem save = new JMenuItem(new AbstractAction("另存为") {
@Override
public void actionPerformed(ActionEvent e) {
//显示一个文件选择器
JFileChooser fileChooser = new JFileChooser(".");
fileChooser.showSaveDialog(jf);
//获取用户选择的文件
File file = fileChooser.getSelectedFile();
//进行显示
try {
ImageIO.write(image, "jpeg", file);
drawArea.repaint();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
});
public void init() {
//组装视图
jMenu.add(open);
jMenu.add(save);
jmb.add(jMenu);
jf.setJMenuBar(jmb);
drawArea.setPreferredSize(new Dimension(740, 500));
jf.add(drawArea);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
public static void main (String[]args){
new JFileChooserDemo().init();
}
}
3.7 使用JOptionalPane(类似于Dialog)
通过JOptionalPane可以非常方便地创建一些简单地对话框,Swing已经为这些对话框添加了相应的组件,无须程序员手动添加组件,JOptionalPane提供了如下4个方法来创建对话框
方法名称 | 方法功能 |
---|---|
showMesssageDialog/showInternalMessaageDialog | 消息对话框,告知用户某事已发生,用户只能单击“确定”按钮,类似于JavaScript的alert函数 |
showConfirmDialog/showInternalConfirmDialog | 确认对话框,向用户确认某个问题,用户可以选择yes,no-cancel等选项,类似于javascript的confirm函数,该方法返回用户输入的字符串。 |
showInputDialog/showInternallInputDialog | 输入对话框,提示要求输入某些信息,类似于javascript的prompt函数,该方法返回用户输入的字符串 |
showOptionDialog/showInternalOptionDialog | 自定义选项对话框,允许使用自定义选项,可以取代showConfirmDialog所产生的对话框,只是用起来更复杂 |
showXxxDialog(Component parentComponent,
object message,
String title,
int optionType,
int messageType,
Icon icon,
Object[l options,
obiect initialvalue
)
参数解释:
parentComponent:当前对话框的父组件
message:对话框上显示的信息,信息可以是字符串、组件、图片等
title:当前对话框的标题
optionType:当前对话框上显示的按钮类型:
(DEFAULT OPTION,YES_NO_OPTION,YES_NO_CANCEL_OPTION,YES_NO_CANCEL_OPTION)
(结果集YES_OPTION, NO_OPTION,CANCEL_OPTION,OK_OPTION)
messageType:当前对话框的类型:ERROR_MESSAGE、INFORMATION_MESSAGE、WARNING_MESSAGE. QUESTION_MESSAGE、PLAIN_MESSAGE
icon:当前对话框左上角的图标 optiors:自定义下拉列表的选项
initialvalue:白定义选项中的默认选中项
当用户与对话框交互结束后,不同类型对话框的返回值如下:
·showMessageDialog:无返回值。
·showInputDialog:返回用户输入或选择的字符串。
showConfirmDialog:返回一个整数代表用户选择的选项。
·showOptionDialog:返回一个整数代表用户选择的选项,如果用户选择第一项,则返回0;如果选择第二项,则返回1……依此类推。
对showConfirmDialog所产生的对话框,有如下几个返回值:
YESOPTION:用户单击了"是"按钮后返回
NOOPTION:用户单击了"否按钮后返回。
CANCELOPTION:用户单击了"取消"按钮后返回。
OKOPTION:用户单击了“确定”按钮后返回。
CLOSEDOPTION:用户单击了对话框右上角的"*按钮后返回。
四种对话框演示消息对话框:
3.7.1 MesssageDialog
public class MessageDialogDemo {
JFrame jf = new JFrame("测试消息对话框");
JTextArea jta = new JTextArea(6, 30);
//声明按钮
JButton btn = new JButton(new AbstractAction("弹出消息对话框") {
@Override
public void actionPerformed(ActionEvent e) {
//弹出消息对话框,并且显示文本域中输入的内容
String text = jta.getText();
//参数:int messageType ,指定消息对话框的类型,错误消息,警告消息,问题消息
// JOptionPane.showMessageDialog(jf,text,"消息对话框",JOptionPane.INFORMATION_MESSAGE);
// JOptionPane.showMessageDialog(jf,text,"消息对话框",JOptionPane.WARNING_MESSAGE);
// JOptionPane.showMessageDialog(jf,text,"消息对话框",JOptionPane.ERROR_MESSAGE);
// JOptionPane.showMessageDialog(jf,text,"消息对话框",JOptionPane.QUESTION_MESSAGE);
// JOptionPane.showMessageDialog(jf,text,"消息对话框",JOptionPane.PLAIN_MESSAGE);
JOptionPane.showMessageDialog(jf, text, "消息对话框", JOptionPane.WARNING_MESSAGE, new ImageIcon("src\\linyinlei\\java\\img_1.png"));
}
});
public void init() {
//组装视图
jf.add(jta);
jf.add(btn, BorderLayout.SOUTH);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
public static void main(String[] args) {
new MessageDialogDemo().init();
}
}
3.7.2 ConfirmDialog
public class ConfirmDialogDemo {
JFrame jf = new JFrame("测试消息对话框");
JTextArea jta = new JTextArea(6, 30);
//声明按钮
JButton btn = new JButton(new AbstractAction("弹出确认对话框") {
@Override
public void actionPerformed(ActionEvent e) {
//弹出确认对话框
String text = jta.getText();
jta.append("\n");
int result = JOptionPane.showConfirmDialog(jf, text, "消息对话框", JOptionPane.YES_NO_CANCEL_OPTION);
if (result == JOptionPane.YES_OPTION) {
jta.append("用户点击了是按钮\n");
}
if (result == JOptionPane.NO_OPTION) {
jta.append("用户点击了否按钮\n");
}
if (result == JOptionPane.CANCEL_OPTION) {
jta.append("用户点击了取消按钮\n");
}
//在底层代码中,yes和ok的底层是一样的静态常量,所以两个不要同时出现
if (result == JOptionPane.OK_OPTION) {
jta.append("用户点击了确定按钮\n");
}
}
});
public void init() {
//组装视图
jf.add(jta);
jf.add(btn, BorderLayout.SOUTH);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
public static void main(String[] args) {
new ConfirmDialogDemo().init();
}
}
3.7.3 InputDialog
public class InputDialogDemo {
JFrame jf = new JFrame("测试消息对话框");
JTextArea jta = new JTextArea(6, 30);
//声明按钮
JButton btn = new JButton(new AbstractAction("弹出输入对话框") {
@Override
public void actionPerformed(ActionEvent e) {
String text = jta.getText();
jta.append("\n");
String result = JOptionPane.showInputDialog(jf, "请填写您的银行账号", "输入对话框", JOptionPane.INFORMATION_MESSAGE);
jta.append(result);
}
});
public void init() {
//组装视图
jf.add(jta);
jf.add(btn, BorderLayout.SOUTH);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
public static void main(String[] args) {
new InputDialogDemo().init();
}
}
3.7.4OptionDialog
public class OptionDialogDemo {
JFrame jf = new JFrame("测试消息对话框");
JTextArea jta = new JTextArea(6, 30);
//声明按钮
JButton btn = new JButton(new AbstractAction("弹出确认对话框") {
@Override
public void actionPerformed(ActionEvent e) {
//弹出选项对话框
int result = JOptionPane.showOptionDialog(jf, "请选择号码", "选项对话框", JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, new String[]{"大号", "中号","小号"}, "小号");
switch (result) {
case 0:
jta.setText("用户选择了大号");
break;
case 1:
jta.setText("用户选择了中号");
break;
case 2:
jta.setText("用户选择了小号");
break;
}
}
});
public void init() {
//组装视图
jf.add(jta);
jf.add(btn, BorderLayout.SOUTH);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
public static void main(String[] args) {
new OptionDialogDemo().init();
}
}
3.8 Swing中的特殊容器
这些特殊容器可以用于创建一些更复杂的用户界面
3.8.1 JSplitPane
它用于创建一个分割面板,它可以将一个组件(通常是一个容器)分割成两个部分,并提供一个分割条,用户可以拖动分割条来调整两个部分的大小
1.创建JSpiltPane对象
通过如下构造方法可以创建JSplitPane对象
JSplitPane(int neworientation,Component newLeftcomponent,Component rewRightComponent)
newOrientation:指定JSplitPane容器的分割方向:
如果值为JSplitPaneVERTICAL SPLIT,为纵向分割;
如果值为JSplitPaneHORIZONTAL SPLIT,为横向分割:
newLeftcomponent:左侧或者上侧的组件;
newRightComponent:右侧或者下侧的组件;
2.设置是否开启连续布局的支持(可选)
setContinuousLayout(boolean newContinuousLayout):
默认是关闭的,如果设置为true,则打开连续布局的支持,但由于连续布局支持需要不断的重绘组件,所以效率会低一些
3.设置是否支持"一触即展”的支持(可选)(即idea的调试面板)
setOneTouchExpandable(boolean newValue):
默认是关闭的,如果设置为true则打开"一触即展"的支持
4.其他设置
setDividerLocation(double proportionallocation):设置分隔条的位置为JSplitPane的某个百分比
setDividerLocation(int location):通过像素值设置分隔条的位置 setDividersize(int newsize):通过像素值设置分隔条的大小
setLeftComponent(Component comp)/setTopComponent(Component comp)/setRightComponent(Componentcomp)/setBottomComponent(Componentcomp):设置指定位置的组件
public class SplitPaneTestDemo {
JFrame jf = new JFrame("测试JSplitPane");
Book[] books ={
new Book("JAVA自学宝典",new ImageIcon("src\\linyinlei\\java\\Android.jpg"),"国内关于java编程最全面的图书\n看得懂,学得会"),
new Book("轻量级的JAVAEE企业应用实战",new ImageIcon("src\\linyinlei\\java\\javaEE.jpg"),"SSM整合开发的经典图书,值得拥有"),
new Book("Android基础教程",new ImageIcon("src\\linyinlei\\java\\java自学宝典.jpg"),"全面介绍Android平台应用程序\n开发的各方面知识")
};
//声明程序中用到的组件
JList<Book> bookJList = new JList<>(books);
JLabel bookCover = new JLabel();
JTextArea bookDesc = new JTextArea();
public void init() {
//组装视图
//设定组件大小
bookJList.setPreferredSize(new Dimension(150,400));
bookCover.setPreferredSize(new Dimension(220,270));
bookDesc.setPreferredSize(new Dimension(220,270));
//为jList设置条目选中监听器
bookJList.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
//获取当前选中的是哪个条目
Book book = bookJList.getSelectedValue();
//把书籍的图片用bookCover展示
bookCover.setIcon(book.getIcon());
//把书籍的描述用bookDesc展示
bookDesc.setText(book.getDesc());
}
});
//组装左边区域
JSplitPane left = new JSplitPane(JSplitPane.VERTICAL_SPLIT,bookCover,new JScrollPane(bookDesc));
left.setOneTouchExpandable(true);
//组装整体
JSplitPane whole = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,left,bookJList);
whole.setOneTouchExpandable(true);
whole.setDividerSize(10);
jf.add(whole);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
public static void main(String[] args) {
new SplitPaneTestDemo().init();
}
}
3.8.2使用JTablePane
可以很方便地在窗口上放置多个标签页,每个标签页相当于获得了一个与外部容器具有相同大小的组件摆放区域,通过这种方式,就可以在一个容器里放置更多的组件,例如右击桌面上的”我的电脑",在弹出的快捷菜单里点击“属性"菜单工页,就可以看到一个”系统属性“对话框,这个对话框里包含了若干个标签页
1.创建TabbedPane对象
JTabbedPane(int tabPlacement,int tablayoutPolicy):
tabplacement:
指定标签标题的放置位置,可以选择SwingConstants中的四个常量:TOP、LEFT、BOTTOM、RIGHT
tabLayoutPolicy:
指定当窗口不能容纳标签页标题时的布局策略,可以选择JTabbedPaneWRAP TAB LAYOUT(换行)和
JTabbedPane.SCROLL TAB LAYOUT(滑动)(多用)
2.通过jTabbedPane对象堆标签进行增删改查
addtab(string title, Icon icon, Component component,string tip):添加标签
title:标签的名称
icon:标签的图标
component:标签对应的组件
tip:光标放到标签上的提示
insertTab(string title, Icon icon, Component component, string tip, int index):插入标签页
title:标签的名称
icon:标签的图标
component:标签对应的组件
tip:光标放到标签上的提示
index:在哪个索引外插入标签页
setComponentAt(int index,Component component):修改标签页对应的组件
index:修改哪个索引处的标签
component:标签对应的组件
removeTabAt(int index):
index:删除哪个索引处的标签
3.设置当前显示的标签页
setSelectedIndex(int index):设置哪个索引处的标签被选中
4.设置JTabbedPane的其他属性
setDisabledIconAt(int index,Icon disabledIcon):将指定位置的禁用图标设置为icon,该图标也可以是 null表示不使用禁用图标。
setEnabledAt(int index,boolean enabled):设置指定位置的标签页是否启用。
setTitleAt(int index, string title):设置指定位置标签页的标题为title,该title可以是null,这表明设置该标签页的标题为空。
setToolTipTextAt(int index,string toolTipText):设置指定位置标签页的提示文本
5.为jTabbedPane设置监听器
addchangeListener(ChangeListener 1)
public class JTabbedPaneDemo {
JFrame jf = new JFrame("测试JSplitPane");
JTabbedPane tabbedPane = new JTabbedPane(SwingConstants.LEFT, JTabbedPane.SCROLL_TAB_LAYOUT);
public void init() {
//添加标签
tabbedPane.addTab("用户管理", new ImageIcon("src\\linyinlei\\java\\img_1.png"), new JList<>(new String[]{"用户一", "用户二", "用户三"}));
tabbedPane.addTab("商品管理", new ImageIcon("src\\linyinlei\\java\\img_1.png"), new JList<>(new String[]{"商品一", "商品二", "商品三"}));
tabbedPane.addTab("订单管理", new ImageIcon("src\\linyinlei\\java\\img_1.png"), new JList<>(new String[]{"店铺一", "店铺二", "店铺三"}));
tabbedPane.setEnabledAt(0,false);
tabbedPane.setSelectedIndex(1);
//监听标签面板的选中情况
tabbedPane.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
int selectedIndex = tabbedPane.getSelectedIndex();
JOptionPane.showMessageDialog(jf,"当前选中了第"+(selectedIndex+1)+"个标签");
}
});
jf.add(tabbedPane);
//设置窗口的位置和大小
jf.setBounds(1000,1000,1000,1000);
//固定窗口大小
// jf.setResizable(false);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
public static void main(String[] args) {
new JTabbedPaneDemo().init();
}
}
3.9 JProgressBar、ProgressMonitor、BoundedRangeModel实现进度条
进度条是图形界面中广泛使用的GUI组件,当复制一个较大的文件时,操作系统会显示一个进度条,用于标识复制操作完成的比例:当启动Eclipse等程序时,因为需要加载较多的资源,故而启动速度较慢,程序也会在启动过程中显示一个进度条,用以表示该软件启动完成的比例
3.9.1 JProgressBar
1.创建进度条
public JProgressBar(int orient, int min, int max):
orient:方向
min:最小值
max:最大值
2.设置属性
setBorderPainted(booleanb)设置进度条是否有边框
setIndeterminate(boolean newvalue):设置当前进度条是不是进度不确定的进度条,如果是,则将看到一个滑块在进度条中左右移动
setStringPainted(boolean b):设置进度条是否显示当前完成的百分比
3.获取和设置当前进度条的进度状态
setValue(int n)设置当前进度值
double getPercentComplete():获取进度条的完成百分比
public class JProgressTest {
JFrame jf = new JFrame("测试进度条");
JCheckBox indeterminate = new JCheckBox("不确定进度");
JCheckBox noBorder = new JCheckBox("不绘制边框");
//创建进度条
JProgressBar bar = new JProgressBar(JProgressBar.HORIZONTAL, 0, 100);
public void init() {
//处理复选框的点击行为
indeterminate.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//获取一下复选框有没有选中
boolean selected = indeterminate.isSelected();
bar.setIndeterminate(selected);
bar.setStringPainted(!selected);
}
});
noBorder.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//获取一下复选框有没有选中
boolean selected = noBorder.isSelected();
bar.setBorderPainted(!selected);
}
});
Box vbox = Box.createVerticalBox();
vbox.add(indeterminate);
vbox.add(noBorder);
//设置进度条的默认属性
bar.setStringPainted(true);
bar.setIndeterminate(true);
jf.setLayout(new FlowLayout());
jf.add(vbox);
jf.add(bar);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
//通过循环模拟修改进度条的进度
for (int i = 0; i <= 100; i++) {
bar.setValue(i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new JProgressTest().init();
}
}
上面的代码的问题就是耗时操作放在了主方法中,得等它睡眠完执行完,其他操作才可以进行,就是卡死在这里,所以我们应该设法将耗时操作放到别处
代码改进
public class JProgressTest2 {
JFrame jf = new JFrame("测试进度条");
JCheckBox indeterminate = new JCheckBox("不确定进度");
JCheckBox noBorder = new JCheckBox("不绘制边框");
//创建进度条
JProgressBar bar = new JProgressBar(JProgressBar.HORIZONTAL, 0, 100);
public void init() {
//处理复选框的点击行为
indeterminate.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//获取一下复选框有没有选中
boolean selected = indeterminate.isSelected();
bar.setIndeterminate(selected);
bar.setStringPainted(!selected);
}
});
noBorder.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//获取一下复选框有没有选中
boolean selected = noBorder.isSelected();
bar.setBorderPainted(!selected);
}
});
Box vbox = Box.createVerticalBox();
vbox.add(indeterminate);
vbox.add(noBorder);
//设置进度条的默认属性
bar.setStringPainted(true);
bar.setIndeterminate(true);
jf.setLayout(new FlowLayout());
jf.add(vbox);
jf.add(bar);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
//开启子线程,模拟耗时操作
SimulaterActivity simulaterActivity = new SimulaterActivity(bar.getMaximum());
new Thread(simulaterActivity).start();
//设置一个定时任务
Timer timer = new Timer(500, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//读取线程任务对象的当前完成量,设置给进度条
int current = simulaterActivity.getCurrent();
bar.setValue(current);
}
});
timer.start();
bar.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
int value = bar.getValue();
if(value == simulaterActivity.getAmount()){
timer.stop();
}
}
});
}
public class SimulaterActivity implements Runnable {
//记录任务总量
private int amount;
//记录当前任务的完成量
//因为timer的完成量显示线程和子线程的耗时操作线程不是在一个线程里面,为了两个线程之间可以及时接受到信息
//把current设置为volatile
private volatile int current =bar.getMinimum();
public SimulaterActivity() {
}
public SimulaterActivity(int amount) {
this.amount = amount;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public int getCurrent() {
return current;
}
public void setCurrent(int current) {
this.current = current;
}
@Override
public void run() {
//子线程的任务
//模拟睡眠操作
while (current < amount){
try {
Thread.currentThread().sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
current++;
}
}
}
public static void main(String[] args) {
new JProgressTest2().init();
}
}
3.9.2 BoundedRangeModel
Swing中很多组件的界面与数据都采用了MVC的设计思想
Swing组件大都将外观显示和内部数据分离,JProgressBar页不例外,JProgressBar组件有一个内置的用于保存其状态数据的Model对象,这个对象由BoundedRangeModel对象显示,程序调用JProgressBar对象的方法完成百分比的设置,监听进度条的数据变化,其实都是通过它内置的BoundedRangeModel对象完成的,下面的代码是对之前的代码的改进。
public class JProgre3sTest2 {
JFrame jf = new JFrame("测试进度条");
JCheckBox indeterminate = new JCheckBox("不确定进度");
JCheckBox noBorder = new JCheckBox("不绘制边框");
//创建进度条
JProgressBar bar = new JProgressBar(JProgressBar.HORIZONTAL, 0, 100);
//获取进度条内置的数据模型对象
BoundedRangeModel model = bar.getModel();
public void init() {
//处理复选框的点击行为
indeterminate.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//获取一下复选框有没有选中
boolean selected = indeterminate.isSelected();
bar.setIndeterminate(selected);
bar.setStringPainted(!selected);
}
});
noBorder.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//获取一下复选框有没有选中
boolean selected = noBorder.isSelected();
bar.setBorderPainted(!selected);
}
});
Box vbox = Box.createVerticalBox();
vbox.add(indeterminate);
vbox.add(noBorder);
//设置进度条的默认属性
bar.setStringPainted(true);
bar.setIndeterminate(true);
jf.setLayout(new FlowLayout());
jf.add(vbox);
jf.add(bar);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
//开启子线程,模拟耗时操作
SimulaterActivity simulaterActivity = new SimulaterActivity(bar.getMaximum());
new Thread(simulaterActivity).start();
//设置一个定时任务
Timer timer = new Timer(500, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//读取线程任务对象的当前完成量,设置给进度条
int current = simulaterActivity.getCurrent();
model.setValue(current);
}
});
timer.start();
model.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
int value = model.getValue();
if(value == simulaterActivity.getAmount()){
timer.stop();
}
}
});
}
public class SimulaterActivity implements Runnable {
//记录任务总量
private int amount;
//记录当前任务的完成量
//因为timer的完成量显示线程和子线程的耗时操作线程不是在一个线程里面,为了两个线程之间可以及时接受到信息
//把current设置为volatile
private volatile int current =bar.getMinimum();
public SimulaterActivity() {
}
public SimulaterActivity(int amount) {
this.amount = amount;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public int getCurrent() {
return current;
}
public void setCurrent(int current) {
this.current = current;
}
@Override
public void run() {
//子线程的任务
//模拟睡眠操作
while (current < amount){
try {
Thread.currentThread().sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
current++;
}
}
}
public static void main(String[] args) {
new JProgre3sTest2().init();
}
}
3.9.3 ProgressMonitor
ProgressMonitor和JProgress的用法基本相似,只是ProgressMoniter可以直接创建一个进度对话框(可独立存在),它提供了下面的构造器完成对话框的创建:
public ProgressMonitor(Component parentComponent,Object message,String note,int min,int max ){
Component parentComponent:对话框的父组件
Object message:、对话框的描述信息
String note:进度的提示信息
int min:进度条的最小值
int max:进度条的最大值
}
public class ProgressMonitorTest {
Timer timer;
public void init(){
//组装视图
//创建进度对话框
ProgressMonitor monitor = new ProgressMonitor(null,"等待任务完成","已完成",0,100);
SimulaterActivity1 simulaterActivity = new SimulaterActivity1(monitor.getMaximum());
new Thread(simulaterActivity).start();
//设置定时任务
timer = new Timer(50, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//
int current = simulaterActivity.getCurrent();
monitor.setProgress(current);
//判断用户是否点击了取消按钮,停止定时任务,关闭对话框,退出程序
if(monitor.isCanceled()){
timer.stop();
monitor.close();
System.exit(0);
}
}
});
timer.start();
}
public class SimulaterActivity1 implements Runnable {
private int amount;
//记录当前任务的完成量
//因为timer的完成量显示线程和子线程的耗时操作线程不是在一个线程里面,为了两个线程之间可以及时接受到信息
//把current设置为volatile(内存可见)
private volatile int current = 0 ;
public SimulaterActivity1() {
}
public SimulaterActivity1(int amount) {
this.amount = amount;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public int getCurrent() {
return current;
}
public void setCurrent(int current) {
this.current = current;
}
@Override
public void run() {
//子线程的任务
//模拟睡眠操作
while (current < amount){
try {
Thread.currentThread().sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
current++;
}
}
}
public static void main(String[] args) {
new ProgressMonitorTest().init();
}
}
3.10 JList、JComboBox实现列表框
JList和JComboBox是及其相似,它们都有一个列表框,只是JComponent的列表框需要以下拉方式显示出来, JList和JComboBox都可以调用setRenderer()方法来改变列表项的表现形式。甚至维护这两个组件的Model都是相似的,JList使用ListModel,JComboBox使用ComboBoxModel,而ComboBoxModel是ListModel的子类。
3.10.1 简单列表框
使用JList和JComboBox实现简单列表框的步骤:
1.创建JList或JComboBox对象
JList(final E[] llistData):创建JList对象,把listData数组中的每项内容转换成一个列表项展示
JList(final Vector<? extends E> listData):创建JList对象,把listData数组中的每项内容转换成一个列表项展示
JComboBox(E[]items):
JComboBox(Vector<E>items):
2.设置IList或IComboBox的外观行为
----------------------JList---------------------------
addSelectionInterval(int anchorint lead):在已经选中列表项的基础上,增加选中从anchor到lead索引范围内的所有列表项
setFixedCellHeight(int height)/setFixedCellwidth(int width):设置列表项的高度和宽度
setlayoutOrientation(int layoutorientation):设置列表框的布局方向
setselectedIndex(int index):设置默认选中项
setSelectedIndices(int[lindices):设置默认选中的多个列表项
setselectedValue(Object anobject,boolean shouldscroll):设置默认选中项,并滚动到该项显示
setselectionBackground(Color selectionBackground):设置选中项的背景颜色
setSelectionForeground(Color selectionForeground):设置选中项的前景色
setselectionInterval(int anchor,int lead)设置从anchor到lead范围内的所有列表项被选中
setselectionMode(int selectionMode):设置选中模式,默认没有限制,也可以设置为单选或者区域选中
setvisibleRowCount(int visibleRowCount):设置可视列
----------------------JComboBox-------------------------
setEditable(booleanaFlag):设置是否可以直接修改列表文本框的值,默认为不可以
setMaximumRowCount(intcount):设置列表框的可视高度足以显示多少行列表项
setSelectedIndex(int anIndex):设置默认选中项
setselectedItem(Object anobject):根据列表项的值,设置默认选中项
3.设置监听器,监听列表项的变化,List通过addListSelectionListener完成,JComboBox通过addItemListener完成
package linyinlei.java;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.*;
import javax.swing.border.EtchedBorder;
import javax.swing.border.TitledBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.transaction.TransactionRequiredException;
import com.sun.xml.internal.bind.v2.schemagen.xmlschema.List;
import javafx.scene.control.ScrollPane;
public class ListTest {
JFrame jf = new JFrame("列表框测试");
String[] books = { "JAVA自学宝典", "轻量级的JAVAEE企业应用实战", "Android基础教程", "JQuery实战教程", "SpringBoot企业级开发" };
// 定义布局选择按钮所在的面板
JPanel layoutPanel = new JPanel();
ButtonGroup layoutGroup = new ButtonGroup();
// 定义选择模式按钮所在面板
JPanel selectModelPanel = new JPanel();
ButtonGroup selectModelGroup = new ButtonGroup();
// 定义一个文本域
JTextArea favorite = new JTextArea(4, 40);
// 用一个字符串数组来创建一个JList对象
JList<String> bookList;
JComboBox<String> bookSelector;
public void init() {
// 组装视图
// 设置标题边框
layoutPanel.setBorder(new TitledBorder(new EtchedBorder(), "确定选项布局"));
selectModelPanel.setBorder(new TitledBorder(new EtchedBorder(), "确定选择模式"));
addBtn2LayoutPanel("纵向滚动", JList.VERTICAL);
addBtn2LayoutPanel("纵向换行", JList.VERTICAL_WRAP);
addBtn2LayoutPanel("横向换行", JList.HORIZONTAL_WRAP);
addBtn2SelectModelPanel("无限制", ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
addBtn2SelectModelPanel("单选", ListSelectionModel.SINGLE_SELECTION);
addBtn2SelectModelPanel("单范围", ListSelectionModel.SINGLE_INTERVAL_SELECTION);
// 对JList做设置
// 组装JList相关内容
bookList = new JList<>(books);
bookList.setVisibleRowCount(3);
bookList.setSelectionInterval(2, 4);
bookList.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
// 获取当前选中的条目
java.util.List<String> selectedValuedList = bookList.getSelectedValuesList();
// 把当前条目的内容设置到文本域中
favorite.setText("");
// 用循环把获取的条目追加到文本域中
for (String str : selectedValuedList) {
favorite.append(str + "\n");
}
}
});
Box bookListVBox = Box.createVerticalBox();
bookListVBox.add(new JScrollPane(bookList));
bookListVBox.add(layoutPanel);
bookListVBox.add(selectModelPanel);
// 组装JComboBox
bookSelector = new JComboBox<>(books);
bookSelector.setEditable(true);
bookSelector.setMaximumRowCount(4);
// 创建JComboBox监听
bookSelector.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
//获取当前已经选中的条目,把内容设置到文本域中
Object selectedItem = bookSelector.getSelectedItem();
favorite.setText(selectedItem.toString());
}
});
// 组装顶部的左右两部分
Box topBox = Box.createHorizontalBox();
topBox.add(bookListVBox);
JPanel bookSelectPanel = new JPanel();
bookSelectPanel.add(bookSelector);
topBox.add(bookSelectPanel);
// 组装底部
JPanel bottomPanel = new JPanel();
bottomPanel.setLayout(new BorderLayout());
bottomPanel.add(new JLabel("您最喜欢的图书:"), BorderLayout.NORTH);
bottomPanel.add(favorite);
// 组装整体
Box wholeVBox = Box.createVerticalBox();
wholeVBox.add(topBox);
wholeVBox.add(bottomPanel);
jf.add(wholeVBox);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
// 封装方法,往LayoutPanel中添加单选按钮
public void addBtn2LayoutPanel(String name, int layoutType) {
// 创建单选按钮
JRadioButton button = new JRadioButton(name);
layoutGroup.add(button);
layoutPanel.add(button);
// 让第一个按钮默认选中
if (layoutGroup.getButtonCount() == 1) {
button.setSelected(true);
}
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
bookList.setLayoutOrientation(layoutType);
}
});
}
// 封装方法,给selectModelPanel添加按钮
public void addBtn2SelectModelPanel(String name, int layoutType) {
// 创建单选按钮
JRadioButton button = new JRadioButton(name);
selectModelGroup.add(button);
selectModelPanel.add(button);
// 让第一个按钮默认选中
if (selectModelGroup.getButtonCount() == 1) {
button.setSelected(true);
}
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
bookList.setSelectionMode(layoutType);
}
});
}
public static void main(String[] args) {
new ListTest().init();
}
}
3.10.2 不强制存储列表项的ListModel和ComboBoxModel
与]ProgressBar一样,JList和ComboBlx也采用了MVC的设计模式,List和JComboBox只负责外观的显示,而组件底层的状态数据则由对应的Model来维护。JList对应的Model是ListModel接口,ComboBox对应的Model是 ComboBox接口,其代码如下:
public interface ListModel<E>{
int getsize();
E getElementAt(int index);
void addlistDatalistener(ListDatalistenerl);
void removeListDatalistener(ListDatalistener1);}
public interface ComboBoxModel<E>extends ListModel<E>{
void setSelectedItem(object anItem);
object getselectedItem();
}
从上面接口来看,这个ListModel不管JList里的所有列表项的存储形式,它甚至不强制存储所有的列表项,只要 ListModel的实现类提供了getSize()和getElementAt()两个方法,JList就可以根据该ListModel对象来生成列表框。ComboBoxModel继承了ListModel,它添加了"选择项"的概念,选择项代表jComboBox显示区域内可见的列表项。
在使用List和|ComboBox时,除了可以使用jdk提供的Model实现类,程序员自己也可以根据需求,自己定义 Model的实现类,实现对应的方法使用。
public class ListModelTest {
JFrame jf = new JFrame("列表框测试");
JList<BigDecimal> jList = new JList<>(new NumberListModel(new BigDecimal(1),new BigDecimal(21),new BigDecimal(2)));
JComboBox<BigDecimal> jComboBox = new JComboBox<BigDecimal>(new NumbeComboBoxListModel(new BigDecimal(0.1),new BigDecimal(1.2),new BigDecimal(0.1)));
JLabel label = new JLabel("您选择的值是:");
JTextField jTextField = new JTextField(15);
public void init() {
//组装视图
//组装顶部
jList.setVisibleRowCount(4);
jList.setSelectionInterval(2, 4);
jList.setFixedCellWidth(90);
jList.setFixedCellHeight(30);
jList.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
List<BigDecimal> selectedValueList = jList.getSelectedValuesList();
jTextField.setText("");
for(BigDecimal item : selectedValueList) {
jTextField.setText(jTextField.getText()+item.toString()+",");
}
}
});
jComboBox.setMaximumRowCount(4);
jComboBox.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
Object selectedItem = jComboBox.getSelectedItem();
jTextField.setToolTipText(selectedItem.toString());
}
});
Box hBox = Box.createHorizontalBox();
hBox.add(new JScrollPane(jList));
JPanel tempPanel = new JPanel();
tempPanel.add(jComboBox);
hBox.add(tempPanel);
jf.add(hBox,BorderLayout.NORTH);
//组装底部
JPanel bottomPanel = new JPanel();
bottomPanel.add(label);
bottomPanel.add(jTextField);
jf.add(bottomPanel);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
public static void main(String[] args) {
new ListModelTest().init();
}
}
public class NumbeComboBoxListModel extends NumberListModel implements ComboBoxModel<BigDecimal> {
//当前选中条目的索引
private int selectedId;
public NumbeComboBoxListModel(BigDecimal start, BigDecimal end, BigDecimal step) {
super(start, end, step);
}
@Override
public void setSelectedItem(Object anItem) {
if (anItem instanceof BigDecimal) {
BigDecimal curr = (BigDecimal) anItem;
selectedId = curr.subtract(super.start).divide(super.step,2,RoundingMode.HALF_DOWN).intValue();
}
}
@Override
public Object getSelectedItem() {
BigDecimal item = new BigDecimal(selectedId).multiply(super.step).add(super.start).setScale(1,RoundingMode.HALF_DOWN);
return item;
}
}
public class NumberListModel extends AbstractListModel<BigDecimal> {
// 因为不想实现ListModel接口未完成的所有方法,所以我们继承它的实现子类
BigDecimal start;
BigDecimal end;
BigDecimal step;
public NumberListModel() {
super();
}
public NumberListModel(BigDecimal start, BigDecimal end, BigDecimal step) {
super();
this.start = start;
this.end = end;
this.step = step;
}
@Override
public int getSize() {
int floor = (int) Math.floor(end.subtract(start).divide(step, 2, RoundingMode.HALF_DOWN).doubleValue());
return (floor + 1);
}
@Override
public BigDecimal getElementAt(int index) {
BigDecimal ele = new BigDecimal(index).multiply(step).add(start).setScale(2, RoundingMode.HALF_DOWN);
return ele;
}
}
3.10.3 强制存储列表项的DefultListModel和DefaultComboBoxModel
前面只是介绍了如何创建JList、JComboBox对象当调用JList和JComboBox构造方法时时传入数组或
Vector作为参数,这些数组元素或集合元素将会作为列表项。当使用JList或jComboBox时常常还需要动态地增加、删除列表项
例如]ComboxBox提供了下列方法完成增删操作:
addItem(Eitem)添加一个列表项
insertitemAt(E item,int index):向指定索引处插入一个列表项 removeAllitems():删除所有列表项
removeItem(Object an0bject):删除指定列表项
removeItemAt(int anIndex):删除指定索引处的列表项
jList并没有提供这些类似的方法。如果需要创建一个可以增加、删除列表项的IList对象,则应该在创建|List时显式使用DefaultListModel作为构造参数。因为DefaultListModel作为IList的Model,它负责维护IList组件的所有列表数据,所以可以通过向DefaultListModel中添加删除元素来实现向List对象中增加、删除列表项。 DefaultListModel提供了如下几个方法来添加、删除元素:
add(int index,Eelement):在该ListModel的指定位置处插入指定元素。
addElement(E obi):将指定元素添加到该ListModel的末尾
insertElementat(E obj,int index):在该ListModel的指定位置处插入指定元素。
Obiect remove(int index):删除该ListModel中指定位置外的元素
removeAllElements():删除该ListModel中的所有元素,并将其的大小设置为零。
removeElement(E obi);删除该ListModel中第一个与参数匹配的元素。
removeElementAt(int index):删除该ListModel 中指定索引处的元素。
removeRange(int fromIndex,int toIndex):删除该ListModel中指定范围内的所有元素。
set(int index,Eelement):将该ListModel指定索引处的元素替换成指定元素。
setElementAt(E obi,int index):将该ListModel指定索引外的元素替换成指定元素
public class DefaultListModelTest {
JFrame jf = new JFrame("测试DefaultListModel");
JTextField bookName = new JTextField(30);
JButton removeBtn = new JButton("删除选中图书");
JButton addBtn = new JButton("添加指定图书");
// 创建jList
JList<String> bookList;
DefaultListModel<String> model = new DefaultListModel();
public void init() {
// 组装视图
model.addElement("java自学宝典");
model.addElement("轻量级javaEE/企业应用实战");
model.addElement("Android基础教程");
model.addElement("JQuery实战教程");
bookList = new JList<String>(model);
// 设置JList
bookList.setVisibleRowCount(4);
// 设置单选
bookList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
jf.add(new JScrollPane(bookList));
// 组装底部
JPanel bottomPanel = new JPanel();
bottomPanel.add(bookName);
bottomPanel.add(addBtn);
bottomPanel.add(removeBtn);
jf.add(bottomPanel,BorderLayout.SOUTH);
//创建监听
addBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String name = bookName.getText();
if(!name.trim().equals("")){
model.addElement(name);
}
}
});
removeBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int selectedIndex = bookList.getSelectedIndex();
if(selectedIndex >= 0){
model.remove(selectedIndex);
}
}
});
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
public static void main(String[] args) {
new DefaultListModelTest().init();
}
}
3.10.4 使用ListCellRenderer改变列表外观
前面程序中的JList和JComboBox 采用的都是简单的字符串列表项,JList和JComboBox还可以支持图标列表项,如果创建JList或JComboBox时传入图标数组,则创建的JList和JComboBox的列表项就是图标
如果希望列表项是更复杂的组件,例如,希望像QQ程序那样每个列表项既有图标,又有字符串,此时需要使用ListCellRenderer接口的实现类对象,自定义每个条目组件的渲染过程
public interface ListCellRenderer{
Component getListCellRendererComponent(
JList<? extends E> list,//列表zujian
E value,//当前列表项的内容
int index,//当前列表项的索引
boolean isSelected,//当前猎表项是否被选中
Boolean cellHasFocus;//当前列表项是否获取了焦点
)
}
通过JList的setCellRenderer(listCellRenderer<? super E> cellRendereer)方法,把自定义的ListCellRenderer对象传递给JList,就可以按照自定义的规则绘制列表项组件了
public class ListCellRcendererDemo {
private JFrame mainWin = new JFrame("好友列表");
private String[] friends = {"李清照","苏格拉底","李白","弄玉","虎头"};
//定义一个JList对象
JList friendsList = new JList(friends);
public void init() {
//组装视图
//给JList设置ListCellRenderer对象,指定列表项绘制的组件
friendsList.setCellRenderer(new MyRenderer());
mainWin.add(friendsList);
mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWin.pack();
mainWin.setVisible(true);
}
private class MyRenderer extends JPanel implements ListCellRenderer{
//JPanel具有绘制和缓冲的功能
private String name;
private ImageIcon icon;
//记录背景色
private java.awt.Color background;
//记录前景色,文字的颜色
private java.awt.Color forceground;
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
boolean cellHasFocus) {
this.name = value.toString();
this.icon = new ImageIcon("linyinlei\\java\\"+name+".jpg");
//当被选中时获取默认被选中的前景色和背景色
this.background = isSelected ? list.getSelectionBackground() : list.getBackground();
this.forceground = isSelected ? list.getSelectionForeground() : list.getForeground();
return this;
}
//绘制列表项的内容
public Dimension getPreferredSize() {
return new Dimension(100,150);
}
@Override
public void paint(Graphics g) {
int imageWidth = icon.getImage().getWidth(null);
int imageHeight = icon.getImage().getHeight(null);
//填充背景矩形
g.setColor(background);
g.fillRect(0, 0, getWidth(), getHeight());
//绘制头像
g.drawImage(icon.getImage(),this.getWidth()/2-imageWidth/2,10,null);
//绘制昵称
g.setColor(forceground);
g.setFont(new Font("'StSong",Font.BOLD,18));
g.drawString(this.name,this.getWidth()/2 - this.name.length()*20/2,imageHeight+30);
}
}
public static void main(String[] args) {
new ListCellRcendererDemo().init();
}
}
3.11 Tree、TreeModel
3.11.1 概述
树也是图形界面中使用非常广泛的GUI组件,例如windows资源管理器时,将看到如下图所示的目录树:
如上图所示的树,代表计算机世界里的树,它从自然界实际的树抽象而来。计算机世界里的树是由一系列具有严格父子关系的节点组成的,每个节点既可以是其上一级节点的子节点,也可以是其下一级节点的父节点,因此同一个节点既可以是父节点,也可以是子节点(类似于一个人,他既是他儿子的父亲,又是他父亲的儿子)。
按照结点是否包含子结点,可以把结点分为下面两类:
普通结点:包含子结点的结点T
叶子结点:没有子结点的结点;
按照结点是否具有唯一的父结点,可以把结点分为下面两类:
根结点:没有父结点的结点,计算机中,一棵树只能有一个根结点
普通结点:具有唯一父结点的结点
使用Swing里的jTree、TreeModel及其相关的辅助类可以很轻松地开发出计算机世界里的树。
3.11.2 创建树
Swing使用ITree对象来代表一棵树,JTree树中结点可以使用TreePath来标识,该对象封装了当前结点及其所有的父结点。
当一个结点具有子结点时,该结点有两种状态:
展开状态:当父结点处于展开状态时,其子结点是可见的
折叠状态:当父结点处于展开状态时,其子结点是可见的
如果某个结点是可见的,则该结点的父节点(包括直接的、间接的父节点)都必须处于展开状态,只要有任意一个父节点处于折叠状态,该节点就是不可见的。
JTree的构造方法:
JTree(TreeModel newModel):使用指定的数据模型创建JTree对象,它默认显示根结点。
JTree(TreeNode root):使用root作为根节点创建JTree对象,它默认显示根结点。
JTree(TreeNode root, boolean asksAllowsChildren):使用root作为根结点创建JTree对象,它默认显示根结点。asksAllowsChildren参数控制怎样的结点才算叶子结点,如果该参数为true,则只有当程序使用
setAllowsChildren(false)昂式设置某个结点不允许添加子结点时(以后也不会拥有子结点),该结点才会被 JTree当成叶子结点。如果该参数为false,则只要某个结点当时没有子结点(不管以后是否拥有子结点),该结点都会被 JTree 当成叶子结点。
public class SimpleJTree {
JFrame jf = new JFrame("简单树");
public void init() {
// 组装视图
// 创建节点
DefaultMutableTreeNode root = new DefaultMutableTreeNode("中国");
DefaultMutableTreeNode guangdong = new DefaultMutableTreeNode("广东");
DefaultMutableTreeNode foshan = new DefaultMutableTreeNode("佛山");
DefaultMutableTreeNode shantou = new DefaultMutableTreeNode("汕头");
DefaultMutableTreeNode guangxi = new DefaultMutableTreeNode("广西");
DefaultMutableTreeNode guilin = new DefaultMutableTreeNode("桂林");
DefaultMutableTreeNode nanning = new DefaultMutableTreeNode("南宁");
// 组装节点
root.add(guangdong);
root.add(guangxi);
guangdong.add(foshan);
guangdong.add(shantou);
guangxi.add(guilin);
guangxi.add(nanning);
// 创建JTree对象
JTree tree = new JTree(root);
jf.add(tree);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
public static void main(String[] args) {
new SimpleJTree().init();
}
}
JTree的其他外观设置方法:
tree.putClientproperty("JTree.lineStyle","None"):设置结点之间没有连接线
tree.puClientProperty("JTree.lineStyle”,"Horizontal"):设置结点之间只有水平分割线
3.11.3 DefaultMutableTreeNode其他成员方法:
Enumeration breadthfirstEnumeration/preorderEnumeration():按广度优先的顺序遍历以此结点为根的子树,并返回所有结点组成的枚举对象。
Enumeration depthFirstEnumeration)/postorderEnumeration)):按深度优先的顺序遍历以此结点为根的子树,并返回所有结点组成的枚举对象。
DefaultMutableTreeNode getNextsibling():返回此结点的下一个兄弟结点。
TreeNode getParent():返回此结点的父结点。如果此结点没有父结点,则返回null。
TreeNode[] getPath():返回从根结点到达此结点的所有结点组成的数组。
DefaultMutableTreeNode getPreviousSibling():返回此结点的上一个兄弟结点。
TreeNode getRoot():返回包含此结点的树的根结点。
TreeNode getsharedAncestor(DefaultMutableTreeNode aNode):返回此结点和aNode最近的共同祖先。
int getsiblingCount():返回此结点的兄弟结点数。
boolean isleafo:返回该结点是否是叶子结点
boolean isNodeAncestor(TreeNode anotherNode):判anotherNode是否是当前结点的祖先结点(包括父结点)
boolean isNodeChild(TreeNode aNode):如果aNode是此结点的子结点,则返回true。
boolean isNodeDescendant(DefaultMutableTreeNode anotherNode):如果anotherNode 是此结点的后代,包括是此结点本身,此结点的子结点或此结点的子结点的后代,都将返回true。
boolean isNodeRelated(DefaultMutableTreeNode aNode):当aNode和当前结点位于同一棵树中时返回true。
boolean isNodeSibling(TreeNode anotherNode):返回anotherNode是否是当前结点的兄弟结点。
boolean isRoot():返回当前结点是否是根结点。
public class SimpleJTree2 {
JFrame jf = new JFrame("简单树");
public void init() {
// 组装视图
// 创建节点
DefaultMutableTreeNode root = new DefaultMutableTreeNode("中国");
DefaultMutableTreeNode guangdong = new DefaultMutableTreeNode("广东");
DefaultMutableTreeNode foshan = new DefaultMutableTreeNode("佛山");
DefaultMutableTreeNode shantou = new DefaultMutableTreeNode("汕头");
DefaultMutableTreeNode guangxi = new DefaultMutableTreeNode("广西");
DefaultMutableTreeNode guilin = new DefaultMutableTreeNode("桂林");
DefaultMutableTreeNode nanning = new DefaultMutableTreeNode("南宁");
// 组装节点
root.add(guangdong);
root.add(guangxi);
guangdong.add(foshan);
guangdong.add(shantou);
guangxi.add(guilin);
guangxi.add(nanning);
// 创建JTree对象
JTree tree = new JTree(root);
// tree.putClientProperty("JTree.lineStyle","None");
tree.putClientProperty("JTree.lineStyle", "Horizontal");
DefaultMutableTreeNode nextSibling = guangdong.getNextSibling();
System.out.println(nextSibling);
TreeNode parent = guangdong.getParent();
System.out.println(parent);
System.out.println(guangdong.isLeaf());
System.out.println(guangdong.isRoot());
jf.add(tree);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
public static void main(String[] args) {
new SimpleJTree2().init();
}
}
3.11.4 编辑树节点
JTree生成树默认是不可以编辑的,不可以添加的,删除节点,也不可以改变节点数据,如果想让某个JTree对象变成可编辑状态,则可以调用JTree的setEditable(boolean b)方法,传入true即可把这颗树变成可编辑状态的树)可以添加、删除节点,也可以改变节点数据)
编辑树节点的步骤:
1.获取当前被选中的节点
获取当前被选中的节点,会有两种方式:
一:
通过JTree对象的某些方法,例如TreePath getSelectionPath()等,得到一个TreePath对象,包含了从根节点到当前节点路径上的所有节点
调用TreePath对象的Object getlastPathComponent()方法,得到当前选中节点
二:
调用JTree对象的Object getLastSelectedPathComponent()方法获取当前被选中的节点
2.调用DefaultTreeModel数据模型有关增删改的一系列方法完成编辑,方法执行完后,重绘JTree
public class EditTree {
JFrame jf = new JFrame("可编辑的树的节点");
// 创建节点
DefaultMutableTreeNode root = new DefaultMutableTreeNode("中国");
DefaultMutableTreeNode guangdong = new DefaultMutableTreeNode("广东");
DefaultMutableTreeNode foshan = new DefaultMutableTreeNode("佛山");
DefaultMutableTreeNode shantou = new DefaultMutableTreeNode("汕头");
DefaultMutableTreeNode guangxi = new DefaultMutableTreeNode("广西");
DefaultMutableTreeNode guilin = new DefaultMutableTreeNode("桂林");
DefaultMutableTreeNode nanning = new DefaultMutableTreeNode("南宁");
JButton addSiblingBtn = new JButton("添加兄弟节点");
JButton addChildBtn = new JButton("添加兄弟节点");
JButton deleteBtn = new JButton("添加兄弟节点");
JButton editBtn = new JButton("添加兄弟节点");
public void init() {
// 组装视图
root.add(guangdong);
root.add(guangxi);
guangdong.add(foshan);
guangdong.add(shantou);
guangxi.add(guilin);
guangxi.add(nanning);
// 创建JTree对象
JTree tree = new JTree(root);
// 完成树的节点的编辑
tree.setEditable(true);
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
addSiblingBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// 添加兄弟节点的逻辑
// 1.获取当前选中的节点
DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
if (selectedNode == null) {
return;
}
// 2.获取当前节点的父节点
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) selectedNode.getParent();
if (parentNode == null) {
return;
}
// 3.创建新节点
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("新节点");
// 4.把新节点通过父节点进行添加
int index = parentNode.getIndex(selectedNode);
model.insertNodeInto(newNode, parentNode, index);
// 5.显示新节点
TreeNode[] pathToRoot = model.getPathToRoot(newNode);
TreePath treePath = new TreePath(pathToRoot);
tree.scrollPathToVisible(treePath);
// 6.重绘tree
tree.updateUI();
}
});
addChildBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// 为选中节点添加子节点
// 1.获取选中节点
DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
if (selectedNode == null) {
return;
}
// 2,创建新节点
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("新节点");
// 3.把新节点添加到当前节点中
selectedNode.add(newNode);
// 4.显示新节点
TreeNode[] pathToRoot = model.getPathToRoot(newNode);
TreePath treePath = new TreePath(pathToRoot);
tree.scrollPathToVisible(treePath);
// 5.重绘UI
tree.updateUI();
}
});
deleteBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
if (selectedNode != null && selectedNode.getParent() != null) {
model.removeNodeFromParent(selectedNode);
}
}
});
editBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// 获取当前选中的节点的路径
TreePath selectionPath = tree.getSelectionPath();
if (selectionPath != null) {
tree.startEditingAtPath(selectionPath);
}
}
});
jf.add(new JScrollPane(tree));
JPanel jPanel = new JPanel();
jPanel.add(addSiblingBtn);
jPanel.add(addChildBtn);
jPanel.add(deleteBtn);
jPanel.add(editBtn);
jf.add(jPanel,BorderLayout.SOUTH);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
public static void main(String[] args) {
new EditTree().init();
}
}
3.11.5 监听树结点事件
为JTree添加监听器:
1.addTreeExpansionListener(TreeExpansionListener tel):添加树节点展开和折叠事件的监听器
2.addTreeSelectionListener(TreeSelectionListenertsl):添加树节点选择事件的监听器。
修改JTree的选择模式:
JTree专门提供了一个TreeSelectionModel对象来保存该JTree选中状态的信息。也就是说,JTree组件背后隐藏了两个model对象,其中TreeModel用于保存该|Tree的所有节点数据,而TreeSelectionModel用于保存该 JTree的所有选中状态的信息。
程序可以改变JTree的选择模式,但必须先获取该JTree对应的TreeSelectionModel对象,再调用该对象的 setSelectionMode(intmode);方法来设置该Tree的选择模式,其中model可以有如下3种取值:
1.TreeSelectionModel.CONTIGUOUS_TREE SELECTION:可以连续选中多个TreePath。
2.TreeSelectionModel.DISCONTIGUOUS_TREE SELECTION:该选项对于选择没有任何限制
3.TreeSelectionModel.SINGLE _TREE _SELECTION:每次只能选择一个TreePath。
public class SelectJTree {
JFrame jf = new JFrame("可编辑的树的节点");
JTree tree;
DefaultMutableTreeNode root = new DefaultMutableTreeNode("中国");
DefaultMutableTreeNode guangdong = new DefaultMutableTreeNode("广东");
DefaultMutableTreeNode foshan = new DefaultMutableTreeNode("佛山");
DefaultMutableTreeNode shantou = new DefaultMutableTreeNode("汕头");
DefaultMutableTreeNode guangxi = new DefaultMutableTreeNode("广西");
DefaultMutableTreeNode guilin = new DefaultMutableTreeNode("桂林");
DefaultMutableTreeNode nanning = new DefaultMutableTreeNode("南宁");
JTextArea jTextArea = new JTextArea(5, 20);
public void init() {
// 组装视图
root.add(guangdong);
root.add(guangxi);
guangdong.add(foshan);
guangdong.add(shantou);
guangxi.add(guilin);
guangxi.add(nanning);
tree = new JTree(root);
//设置选择模式
TreeSelectionModel selectionModel = tree.getSelectionModel();
selectionModel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
//设置监听器
tree.addTreeSelectionListener(new TreeSelectionListener() {
@Override
public void valueChanged(TreeSelectionEvent e) {
TreePath newLeadSelectionPath = e.getNewLeadSelectionPath();
jTextArea.append(newLeadSelectionPath.toString()+"\n");
}
});
Box createHorizontalBox = Box.createHorizontalBox();
createHorizontalBox.add(new JScrollPane(tree));
createHorizontalBox.add(new JScrollPane(jTextArea));
jf.add(createHorizontalBox);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
public static void main(String[] args) {
new SelectJTree().init();
}
}
3.11.节点外观改变
JTree默认的外观是比较单一的,它提供了如下几种改变结点外观的方式:
1.使用DefaortTreeCelRenderer直接改变节点的外观,这种方式以改变整棵树所有节点的字体、颜色和图
标。
2.为|Tree指定DefaultTreeCellRenderer的扩展类对象作为Tree的节点绘制器,该绘制器负责为不同节点使
用不同的字体、颜色和图标。通常使用这种方式来改变节点的外观。
3.为]Tree指定一个实现TreeCellRenderer接口的节点绘制器,该绘制器可以为不同的节点自由绘制任意内容,这是最复杂但最灵活的节点绘制器。
第一种方式最简单,但灵活性最差,因为它会改变整棵树所有节点的外观。在这种情况下,jtree的所有节点依然使用相同的图标,相当于整体替换了jtree中节点的所有默认图标。用户指定的节点图标未必就比JTree默认的图标美观。
DefaultTreeCellRenderer提供了如下几个方法来修改节点的外观:
第一种方式:
setBackgroundNonSelectionColor(Color newColor):设置用于非选定节点的背景颜色。
setBackgroundSelectionColor(Color newColor):设置节点在选中状态下的背景颜色。
setBorderSelectionColor(Color newColor):设置选中状态下节点的边框颜色。
setClosedIcon(Icon newIcon):设置处于折叠状态下非叶子节点的图标。
setFont(Font font):设置节点文本的字体。
setLeaflcon(Icon newIcon):设置叶子节点的图标。
setOpenlcon(Icon newlcon):设置处于展开状态下非叶子节点的图标。
setTextNonSelectionColor(Color newColor):设置绘制非选中状态下节点文本的颜色。
setTextSelectionColor(Color newColor):设置绘制选中状态下节点文本的颜色。
public class ChangeAllCellRenderer {
JFrame jf = new JFrame("改变所有节点的外观");
JTree tree;
DefaultMutableTreeNode root = new DefaultMutableTreeNode("中国");
DefaultMutableTreeNode guangdong = new DefaultMutableTreeNode("广东");
DefaultMutableTreeNode foshan = new DefaultMutableTreeNode("佛山");
DefaultMutableTreeNode shantou = new DefaultMutableTreeNode("汕头");
DefaultMutableTreeNode guangxi = new DefaultMutableTreeNode("广西");
DefaultMutableTreeNode guilin = new DefaultMutableTreeNode("桂林");
DefaultMutableTreeNode nanning = new DefaultMutableTreeNode("南宁");
public void init() {
// 组装视图
root.add(guangdong);
root.add(guangxi);
guangdong.add(foshan);
guangdong.add(shantou);
guangxi.add(guilin);
guangxi.add(nanning);
tree = new JTree(root);
DefaultTreeCellRenderer defaultTreeCellRenderer = new DefaultTreeCellRenderer();
// 设置非选中节点的背景颜色
defaultTreeCellRenderer.setBackgroundNonSelectionColor(new Color(220, 220, 220));
// 设置选中节点的背景色
defaultTreeCellRenderer.setBackgroundSelectionColor(new Color(140, 140, 140));
// 设置选中状态下节点的边框颜色
defaultTreeCellRenderer.setBorderSelectionColor(Color.BLACK);
// 设置处于折叠状态下非叶子节点的图标
defaultTreeCellRenderer.setClosedIcon(new ImageIcon("linyinlei\\java\\弄玉"));
// 设置节点文本的字体
defaultTreeCellRenderer.setFont(new Font("StSong", Font.BOLD, 16));
// 设置叶子节点图标
defaultTreeCellRenderer.setLeafIcon(new ImageIcon("linyinlei\\java\\李白"));
// 设置处于展开状态下非叶子节点图标
defaultTreeCellRenderer.setOpenIcon(new ImageIcon("linyinlei\\java\\虎头"));
// 设置绘制非选中状态下节点文本颜色
defaultTreeCellRenderer.setTextNonSelectionColor(new Color(255, 0, 0));
// 设置选中状态下节点的文本颜色
defaultTreeCellRenderer.setTextSelectionColor(new Color(0, 0, 255));
// 把节点绘制给树
tree.setCellRenderer(defaultTreeCellRenderer);
jf.add(new JScrollPane(tree));
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
public static void main(String[] args) {
new ChangeAllCellRenderer().init();
}
}
第二种方式:扩展DefaultTreeCellRenderer改变结点外观
DefaultTreeCellRenderer类**继承了Label(只能绘制图片和文字)**实现getTreeCellRendererComponent()方法时返回this,即返回一个特殊的JLabel对象。如果需要根据节点内容来改变节点的外观,则可以再次扩展DefaultTreeCellRenderer类,并再次重写它提供的getTreeCellRendererComponent()方法。
public class ExtendsDefaultCellTreeRenderer {
JFrame jf = new JFrame("根据节点类型定义图标");
JTree tree;
ImageIcon rootIcon = new ImageIcon("linyinlei\\java\\虎头");
ImageIcon databaseIcon = new ImageIcon("linyinlei\\java\\李白");
ImageIcon tableIcon = new ImageIcon("linyinlei\\java\\李清照");
ImageIcon columnIcon = new ImageIcon("linyinlei\\java\\弄白");
ImageIcon indexIcon = new ImageIcon("linyinlei\\java\\虎头");
// 定义几个节点
DefaultMutableTreeNode root = new DefaultMutableTreeNode(new NodeData(rootIcon, "数据库导航"));
DefaultMutableTreeNode salary = new DefaultMutableTreeNode(new NodeData(databaseIcon, "公司工资数据库"));
DefaultMutableTreeNode customer = new DefaultMutableTreeNode(new NodeData(databaseIcon, "公司工资数据库"));
DefaultMutableTreeNode employee = new DefaultMutableTreeNode(new NodeData(tableIcon, "员工表"));
DefaultMutableTreeNode attend = new DefaultMutableTreeNode(new NodeData(tableIcon, "考勤表"));
DefaultMutableTreeNode concat = new DefaultMutableTreeNode(new NodeData(tableIcon, "联系方式表"));
DefaultMutableTreeNode id = new DefaultMutableTreeNode(new NodeData(indexIcon, "员工ID"));
DefaultMutableTreeNode name = new DefaultMutableTreeNode(new NodeData(columnIcon, "姓名"));
DefaultMutableTreeNode gender = new DefaultMutableTreeNode(new NodeData(columnIcon, "性别"));
public void init() {
root.add(salary);
root.add(customer);
salary.add(employee);
salary.add(attend);
concat.add(id);
concat.add(name);
concat.add(gender);
tree = new JTree();
// 通过扩展DefaultTreeCellRenderer改变结点外观
tree.setCellRenderer(new MyRenderer());
jf.add(new JScrollPane(tree));
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
// 自定义类,继承DefaultTreeCellRendereer,完成节点的绘制
class MyRenderer extends DefaultTreeCellRenderer {
// 当前间接的继承了JLabel这个组件类,展示一些图片和文字
// Object value这个参数,代表的就是即将要绘制的节点
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean leaf, int row,
boolean hasFocus) {
// 获取当前节点
DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
// 获取到当前即将绘制的节点的名称和图标
NodeData nodeData = (NodeData) node.getUserObject();
this.setText(nodeData.name);
this.setIcon(nodeData.icon);
return (Component) this;
}
}
public class NodeData {
public ImageIcon icon;
public String name;
public NodeData(ImageIcon icon, String name) {
super();
this.icon = icon;
this.name = name;
}
}
public static void main(String[] args) {
new ExtendsDefaultCellTreeRenderer().init();
}
}
第三种方式:实现TreeCellRendereer接口改变节点外观
这种改变外观的方式是最灵活的,程序实现TreeCellRenderer接口时同样需要实现getTreeCellRendererComponent()方法,该方法可以返回任意类型的组件,该组件将作为JTree的节点,通过 这种方式可以最大程度的改变节点的外观
继承JPanel实现TreeCellRendereer接口
public class CustomerTreeNode {
JFrame jf = new JFrame("定制书的节点");
JTree tree;
// 创建节点
DefaultMutableTreeNode friends = new DefaultMutableTreeNode("我的好友");
DefaultMutableTreeNode liqingzhao = new DefaultMutableTreeNode("李清照");
DefaultMutableTreeNode sugeladi = new DefaultMutableTreeNode("苏格拉底");
DefaultMutableTreeNode libai = new DefaultMutableTreeNode("李白");
DefaultMutableTreeNode nongyv = new DefaultMutableTreeNode("弄玉");
DefaultMutableTreeNode hutou = new DefaultMutableTreeNode("虎头");
public void init() {
// 组装视图
friends.add(liqingzhao);
friends.add(sugeladi);
friends.add(libai);
friends.add(nongyv);
friends.add(hutou);
tree = new JTree(friends);
//设置节点绘制器
MyRender myRender = new MyRender();
tree.setCellRenderer(myRender);
jf.add(new JScrollPane(tree));
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
class MyRender extends JPanel implements TreeCellRenderer{
private ImageIcon icon;
private String name;
private Color background;
private Color foreground;
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded,
boolean leaf, int row, boolean hasFocus) {
this.name = value.toString();
this.icon = new ImageIcon("linyinlei\\java\\"+name+".jpg");
this.background = hasFocus ? tree.getBackground() : Color.white;
this.foreground = hasFocus ? tree.getForeground() : Color.black;
return this;
}
//通过重写getPreferenceSize方法,指定当前JPanel组件的大小
public Dimension getPrefeerenceSize() {
return new Dimension(80,80);
}
@Override
public void paint(Graphics g) {
//绘制组件内容
int iconWidth = this.icon.getIconWidth();
int iconHeight = this.icon.getIconHeight();
//填充背景
g.setColor(background);
g.fillRect(0, 0, getWidth(), getHeight());
//绘制头像
g.drawImage(this.icon.getImage(),getWidth()/2-iconWidth/2,10,null);
//绘制昵称
g.setColor(foreground);
g.setFont(new Font("StSong",Font.BOLD,18));
g.drawString(this.name, getWidth()/2-this.name.length()*20/2, iconHeight+30);
}
}
public static void main(String[] args) {
new CustomerTreeNode().init();
}
}
3.12 JTable TableModel
表格也是GUI程序中常用的组件
3.12.1 创建表格
使用JTable创建简单表格步骤
1.创建一个一维数组,存储表格中每一列的标题
2.创建一个二维数组。存储表格中每一行的数据,其中二维数组内部的每一个一维数组,代表表格中的一行数据
3.根据第一步和第二步创建的一维数组和二维数组,创建JTble对象
4.把JTable添加到其他的容器中显示
public class SimpleTable {
JFrame jf = new JFrame("简单表格");
Object[] titles = {"姓名","年龄","性别"};
Object[][] data = {
{"李清照",29,"女"},
{"苏格拉底",56,"男"},
{"李白",35,"男"},
{"弄玉",18,"女"},
{"虎头",2,"男"},
};
public void init() {
//组装视图
JTable jTable = new JTable(data,titles);
jf.add(new JScrollPane(jTable));
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
public static void main(String[] args) {
new SimpleTable().init();
}
}
1.把鼠标移动到两列之间的分界符时,鼠标形状会变成可调整大小的形状,表明用户可以自由调整表格列的大小。
2.在表格列上按下鼠标并拖动时,可以将表格的整列拖动到其他位置。
3.当单击某一个单元格时,系统会自动选中该单元格所在的行。
4.当双击某一个单元格时,系统会自动进入该单元格的修改状态。
JTable调整列宽:
JTable使用TableColumi来表示表格中的每一列,JTable中表格列的所有属性,如最佳宽度、是否可调整宽度最小和最大宽度等都保存在该TableColumn中。
1.setMaxWidth(intmaxWidth):设置该列的最大宽度。如果指定的maxWidth小于该列的最小宽度,则 maxWidth被设置成最小宽度。
2.setMinWidth(int minWidth):设置该列的最小宽度。
3setPreferredWidth(intpreferredWidth):设置该列的最佳宽度。
JTable调整表格选择模式:
与JList类似的是,JTable使用了一个ListSelectionModel表示该表格的选择状态,程序可以通过
getSelectionModel().setSelectionMode(int mode)控制JTable的选择模式。JTable的选择模式有如下三种:
1.ListSelectionMode.MULTIPLE_INTERVAL_SELECTION:没有任何限制。
2.ListSelectionMode.SINGLE_INTERVAL_SELECTION:选择单个连续区域。
3.ListSelectionMode.SINGLE_SELECTION只能选择单行。
public class SimpleTable {
JFrame jf = new JFrame("简单表格");
Object[] titles = { "姓名", "年龄", "性别" };
Object[][] data = { { "李清照", 29, "女" }, { "苏格拉底", 56, "男" }, { "李白", 35, "男" }, { "弄玉", 18, "女" },
{ "虎头", 2, "男" }, };
public void init() {
// 组装视图
JTable jTable = new JTable(data, titles);
// 设置选择模式
jTable.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
// 设置列宽
TableColumn column_1 = jTable.getColumn(titles[0]);
TableColumn column_3 = jTable.getColumn(titles[2]);
column_1.setMinWidth(40);
column_3.setMaxWidth(50);
jf.add(new JScrollPane(jTable));
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
public static void main(String[] args) {
new SimpleTable().init();
}
}
3.11.2 TableModel
与List、JTree类似的是,Table采用了TableModel来保存表格中的所有状态数据:与ListModel类似的是, TableModel也不强制保存该表格显示的数据。虽然在前面程序中看到的是直接利用一个二维数组来创建JTable对象,但也可以通过TableModel对象来创建表格。
自定义TableModel步骤:
1.自定义类,继承AbstractTableModel抽象类,重写下面几个方法:
int getcolumnCount():返回表格列的数量
int getRowCount():返回表格行的数量
Object getvalueAt(int rowIndex,int columnIndex):返回rowIndex行,column列的单元格的值
String getcolumnName(int columnIndex):返回columnIndex列的列名称
boolean isCellEditable(int rowIndex,int columnIndex):设置rowIndex行columnIndex列单元格是否可编辑
2.创建自定义类对象,根据该对象,创建|Table对象
public class TableModelTest {
JFrame jf = new JFrame("简单表格");
Object[] titles = { "姓名", "年龄", "性别" };
Object[][] data = { { "李清照", 29, "女" }, { "苏格拉底", 56, "男" }, { "李白", 35, "男" }, { "弄玉", 18, "女" },
{ "虎头", 2, "男" }, };
// 创建两个vector数组,表格增删改较方便
private Vector titlesV = new Vector<>();// 存储标题
private Vector<Vector> dataV = new Vector<>();// 存储数据
public void init() {
// 组装视图
for (int i = 0; i < titles.length; i++) {
titlesV.add(titles[i]);
}
for (int i = 0; i < data.length; i++) {
Vector t = new Vector<>();
for (int j = 0; j < data[i].length; j++) {
t.add(data[i][j]);
}
dataV.add(t);
}
MyTableModel myTableModel = new MyTableModel();
JTable jTable = new JTable(new MyTableModel());
JButton button = new JButton("获取选中行数据");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int selectedColumn = jTable.getSelectedColumn();
int selectedRow = jTable.getSelectedRow();
System.out.println("当前选中行的索引" + selectedRow);
System.out.println("当前选中列的索引" + selectedColumn);
Object valueAt = myTableModel.getValueAt(selectedRow, selectedColumn);
System.out.println("当前选中行的第一个单元格的内容为" + valueAt);
System.out.println();
}
});
// 设置选择模式
jTable.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
// 设置列宽
TableColumn column_1 = jTable.getColumn(titles[0]);
TableColumn column_3 = jTable.getColumn(titles[2]);
column_1.setMinWidth(40);
column_3.setMaxWidth(50);
jf.add(new JScrollPane(jTable));
jf.add(button,BorderLayout.SOUTH);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
private class MyTableModel extends AbstractTableModel {
@Override
public int getRowCount() {
return dataV.size();
}
@Override
public int getColumnCount() {
// TODO 自动生成的方法存根
return titlesV.size();
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
// TODO 自动生成的方法存根
return dataV.get(rowIndex).get(columnIndex);
}
public String getColumnName(int column) {
return (String) titlesV.get(column);
}
public boolean isCellEditable(int rowIndex, int columnIndex) {
return true;
}
}
public static void main(String[] args) {
new TableModelTest().init();
}
}
3.11.3 DefaultTableModel
不仅用户可以扩展AbstractTableModel抽象类,Swing本身也为AbstractTableModel 提供了一个 DefaultTableModel实现类,程序可以通过使用DefaultTableModel实现类来创建|Table对象。通过
DefaultTableModel对象创建Table对象后,就可以调用它提供的方法来添加数据行、插入数据行、删除数据行和移动数据行。DefaultTableModel提供了如下几个方法来控制数据行操作:
addColumn(object columnName)/addColumn(object columnName,Object[] columnData):添加一列 addRow(object[]rowData):添加一行
insertRow(int row,object[] rowData):指定位置处插入一行 removeRow(int row):删除一行
moveRow(int start, int end,int to):移动指定范围内的数据行
public class DefaultTableTest {
JFrame jf = new JFrame("简单表格");
Object[] titles = { "姓名", "年龄", "性别" };
Object[][] data = { { "李清照", 29, "女" }, { "苏格拉底", 56, "男" }, { "李白", 35, "男" }, { "弄玉", 18, "女" },
{ "虎头", 2, "男" }, };
// 创建两个vector数组,表格增删改较方便
private Vector titlesV = new Vector<>();// 存储标题
private Vector<Vector> dataV = new Vector<>();// 存储数据
public void init() {
// 组装视图
for (int i = 0; i < titles.length; i++) {
titlesV.add(titles[i]);
}
for (int i = 0; i < data.length; i++) {
Vector t = new Vector<>();
for (int j = 0; j < data[i].length; j++) {
t.add(data[i][j]);
}
dataV.add(t);
}
DefaultTableModel model = new DefaultTableModel(dataV,titlesV);
JTable jTable = new JTable(model);
JButton addRow = new JButton("添加一行");
JButton addColum = new JButton("添加一列");
JButton deleteRow = new JButton("删除一行");
addRow.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
model.addRow(new Object[] {"柳岩",18,"女"});
}
});
Object[] columnData = {"演员","演员","演员","演员","演员"};
addColum.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
model.addColumn("职业",columnData);
}
});
deleteRow.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int selectedRow = jTable.getSelectedRow();
model.removeRow(selectedRow);
}
});
JPanel panel = new JPanel();
panel.add(addColum);
panel.add(addRow);
panel.add(deleteRow);
jf.add(panel,BorderLayout.SOUTH);
jf.add(new JScrollPane(jTable));
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
public static void main(String[] args) {
new DefaultTableTest().init();
}
}