JavaSwing6
3.3.3 使用JLayeredPane、JDesktopPane、JInternalFrame
3.3.3.1 JLayeredPane
JLayeredPane是 一个代表有层 次深度的容器 , 它允许组件在需要 时 互相重叠。当向JLayeredPane容器中添加组件时, 需要为该组件指定一个深度索引 , 其中层次索引较高 的层里的组件位于其他层的组件之上。
JLayeredPane 还将容器的层次深度分成几个默认层 ,程序只是将组件放入相应 的层 ,从而可以更容易地确保组件的正确重叠 , 无须为组件指定具体的深度索引。JLayeredPane 提供了如下几个默认层:
- DEFAULT_LAYER:大多数组件位于标准层,这是最底层;
- PALETTE_LAYER : 调色板层位于默认层之上 。该层对于浮动工具栏和调色板很有用,因此可以位于其他组件之上 。
- MODAL_LAYER: 该层用于显示模式对话框。它们将出现在容器中所有工具栏 、调色板或标准组件的上面 。
- POPUP_LAYER : 该层用于显示右键菜单 , 与对话框 、工具提示和普通组件关联的弹出式窗口将出现在对应的对话框、工具提示和普通组件之上。
- DRAG_LAYER: 该层用于放置拖放过程中的组件(关于拖放操作请看下一节内 容) ,拖放操作中的组件位于所有组件之上 。 一旦拖放操作结束后 , 该组件将重新分配到其所属的正常层。
JLayeredPane 方法:
- moveToBack(Component c):把当前组件c移动到所在层的所有组件的最后一个位置;
- moveToFront(Component c):把当前组件c移动到所在层的所有组件的第一个位置;
- setLayer(Component c, int layer):更改组件c所处的层;
需要注意的是,往JLayeredPane中添加组件,如果要显示,则必须手动设置该组件在容器中显示的位置以及大小。
案例:
使用JLayeredPane完成下图效果:
演示代码:
import cn.itcast.swing.util.ImagePathUtil;
import javax.swing.*;
import java.awt.*;
public class JLayeredPaneTest {
JFrame jf = new JFrame("测试JLayeredPane");
JLayeredPane layeredPane = new JLayeredPane();
//自定义组件,继承JPanel
private class ContentPanel extends JPanel{
public ContentPanel(int xPos,int yPos,String title,String ico){
//设置边框
setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(),title));
JLabel label = new JLabel(new ImageIcon(ImagePathUtil.getRealPath("3\\"+ico)));
add(label);
setBounds(xPos,yPos,160,220);
}
}
public void init(){
//向LayeredPane中添加三个组件,往JLayeredPane中添加组件,都必须手动的设置组件显示的位置和大小,才能显示出来
layeredPane.add(new ContentPanel(10,20,"java自学宝典","java.png"),JLayeredPane.MODAL_LAYER);
layeredPane.add(new ContentPanel(100,60,"Android基础教程","android.png"),JLayeredPane.DEFAULT_LAYER);
layeredPane.add(new ContentPanel(80,100,"轻量级javaEE企业应用","ee.png"),JLayeredPane.DRAG_LAYER);
layeredPane.setPreferredSize(new Dimension(300,400));
jf.add(layeredPane);
jf.pack();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setVisible(true);
}
public static void main(String[] args) {
new JLayeredPaneTest().init();
}
}
3.3.3.2 JDesktopPane和JInternalFrame
JDesktopPane是JLayeredPane的子类,这种容器在开发中会更常用很多应用程序都需要启动多个内部窗口来显示信息(典型的比如IDEA、NotePad++),这些内部窗口都属于同一个外部窗口,当外部窗 口 最小化时, 这些内部窗口都被隐藏起来。在 Windows 环境中,这
种用户界面被称为多文档界面 C Multiple Document Interface, MDI) 。
使用 Swing 可以非常简单地创建出这种 MDI 界面 , 通常,内部窗口有自己的标题栏、标题、图标、三个窗口按钮,并允许拖动改变内部窗口 的大小和位置,但内部窗口不能拖出外部窗口。
JDesktopPane 需要和 JIntemalFrame 结合使用,其中JDesktopPane 代表一 个虚拟桌面 ,而JIntemalFrame则用于创建内部窗口。使用 JDesktopPane 和 JIntemalFrame 创建内部窗口按如下步骤进行即可:
- 创建 一 个 JDesktopPane 对象,代表虚拟桌面
JDesktopPane()
- 使用 JIntemalFrame 创建一个内部窗口
JInternalFrame(String title, boolean resizable, boolean closable, boolean maximizable, boolean iconifiable):
title: 内部窗口标题
resizable:是否可改变大小
closeble: 是否可关闭
maximizable: 是否可最大化
iconifiable:是否可最小化
- 一旦获得了内部窗口之后,该窗口的用法和普通窗口的用法基本相似, 一样可以指定该窗口的布局管理器, 一样可以向窗口内添加组件、改变窗口图标等。
- 将该内部窗口以合适大小、在合适位置显示出来 。与普通窗口类似的是, 该窗口默认大小是 0x0像素,位于0,0 位置(虚拟桌面的左上角处),并且默认处于隐藏状态,程序可以通过如下代码将内部窗口显示出来:
reshape(int x, int y, int width, int height):设置内部窗口的大小以及在外部窗口中的位置;
show():设置内部窗口可见
- 将内部窗口添加到 JDesktopPane 容器中,再将 JDesktopPane 容器添加到其他容器中。
案例:
请使用JDesktopPane和JInternalFrame完成下图效果:
演示代码:
import cn.itcast.swing.util.ImagePathUtil;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
public class JInternalFrameTest {
final int DESKTOP_WIDTH = 480;
final int DESKTOP_HEIGHT = 360;
final int FRAME_DISTANCE = 20;
//创建外部窗口
JFrame jf = new JFrame("测试JInternalFrame");
//创建虚拟桌面
JDesktopPane desktop = new JDesktopPane();
//定义内部窗口为的大小
private int width = 230;
private int height = DESKTOP_HEIGHT;
//定义下一个内部窗口的横轴坐标
private int nextFrameX = 0;
//为外部窗口定义两个菜单
JMenu fileMenu = new JMenu("文件");
//定义Action,用于快捷创建菜单项和工具按钮
Action newAction = new AbstractAction("新建",new ImageIcon(ImagePathUtil.getRealPath("3\\new.png"))) {
@Override
public void actionPerformed(ActionEvent e) {
//创建内部窗口
JInternalFrame iframe = new JInternalFrame("新文档",true,true,true,true);
//往内部窗口中添加一个8行40列的文本框
iframe.add(new JScrollPane(new JTextArea(8,40)));
//将内部窗口添加到虚拟桌面中
desktop.add(iframe);
//设置内部窗口的原始位置
iframe.reshape(nextFrameX,0,width,height);
//使该窗口可见
iframe.show();
//计算下一个内部窗口的位置
nextFrameX+=FRAME_DISTANCE;
if (nextFrameX>DESKTOP_WIDTH-width){
nextFrameX=0;
}
}
};
Action exitAction = new AbstractAction("退出",new ImageIcon(ImagePathUtil.getRealPath("3\\exit.png"))) {
@Override
public void actionPerformed(ActionEvent e) {
//结束当前程序
System.exit(0);
}
};
public void init(){
//为窗口安装菜单条
JMenuBar menuBar = new JMenuBar();
jf.setJMenuBar(menuBar);
menuBar.add(fileMenu);
fileMenu.add(newAction);
fileMenu.add(exitAction);
//设置虚拟桌面的最佳大小
desktop.setPreferredSize(new Dimension(DESKTOP_WIDTH,DESKTOP_HEIGHT));
//将虚拟桌面添加到外部窗口中
jf.add(desktop);
jf.pack();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setVisible(true);
}
public static void main(String[] args) {
new JInternalFrameTest().init();
}
}
3.4 JProcessBar、ProcessMonitor、BoundedRangeModel实现进度条
进度条是图形界面中广泛使用的GUI 组件,当复制一个较大的文件时,操作系统会显示一个进度条,用于标识复制操作完成的比例 : 当启动 Eclipse 等程序时, 因为需要加载较多的资源 , 故而启动速度较慢 , 程序也会在启动过程中显示一个进度条 , 用以表示该软件启动完成的比例 ……
3.4.1 创建进度条
使用JProgressBar创建进度条的步骤:
- 创建JProgressBar对象
public JProgressBar(int orient, int min, int max):
orint:方向
min:最小值
max:最大值
- 设置属性
setBorderPainted(boolean b):设置进度条是否有边框
setIndeterminate(boolean newValue):设置当前进度条是不是进度不确定的进度条,如果是,则将看到一个滑块在进度条中左右移动
setStringPainted(boolean b):设置进度条是否显示当前完成的百分比
- 获取和设置当前进度条的进度状态
setValue(int n):设置当前进度值
double getPercentComplete():获取进度条的完成百分比
String getStrin():返回进度字符串的当前值
案例:
请使用JProgressBar完成下图效果:
演示代码:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class JProgressTest {
JFrame jf = new JFrame("测试进度条");
//创建一个垂直进度条
JProgressBar bar = new JProgressBar(JProgressBar.HORIZONTAL);
JCheckBox indeterminate = new JCheckBox("不确定进度");
JCheckBox noBorder = new JCheckBox("不绘制边框");
public void init(){
Box box = new Box(BoxLayout.Y_AXIS);
box.add(indeterminate);
box.add(noBorder);
jf.setLayout(new FlowLayout());
jf.add(box);
//把进度条添加到jf窗口中
jf.add(bar);
//设置进度条的最大值和最小值
bar.setMinimum(0);
bar.setMaximum(100);
//设置进度条中绘制完成百分比
bar.setStringPainted(true);
//根据选择决定是否绘制进度条边框
noBorder.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
boolean flag = noBorder.isSelected();
bar.setBorderPainted(!flag);
}
});
//根据选择决定是否是不确定进度条
indeterminate.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
boolean flag = indeterminate.isSelected();
bar.setIndeterminate(flag);
//不绘制百分比,因为之前设置了绘制百分比
bar.setStringPainted(!flag);
}
});
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
//通过循环不断改变进度条的完成进度
for (int i = 0; i <= 100; i++) {
//改变进度条的完成进度
bar.setValue(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new JProgressTest().init();
}
}
在刚才的程序中,通过for循环来不断的更新进度条的进度,这仅仅是为了演示而已,实际开发中这样的操作是没有意义的。通常情况下是不断的检测一个耗时任务的完成情况,然后才去更新进度条的进度。下面的代码通过Timer定时器和Runnable接口,对上述代码进行改进,其运行结果没有变化,知识修改到了进度条进度更新的逻辑。
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class JProgressTest2 {
JFrame jf = new JFrame("测试进度条");
//创建一个垂直进度条
JProgressBar bar = new JProgressBar(JProgressBar.HORIZONTAL);
JCheckBox indeterminate = new JCheckBox("不确定进度");
JCheckBox noBorder = new JCheckBox("不绘制边框");
public void init(){
Box box = new Box(BoxLayout.Y_AXIS);
box.add(indeterminate);
box.add(noBorder);
jf.setLayout(new FlowLayout());
jf.add(box);
//把进度条添加到jf窗口中
jf.add(bar);
//开启耗时任务
SimulatedActivity simulatedActivity = new SimulatedActivity(100);
new Thread(simulatedActivity).start();
//设置进度条的最大值和最小值
bar.setMinimum(0);
bar.setMaximum(simulatedActivity.getAmount());
//设置进度条中绘制完成百分比
bar.setStringPainted(true);
//根据选择决定是否绘制进度条边框
noBorder.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
boolean flag = noBorder.isSelected();
bar.setBorderPainted(!flag);
}
});
//根据选择决定是否是不确定进度条
indeterminate.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
boolean flag = indeterminate.isSelected();
bar.setIndeterminate(flag);
//不绘制百分比,因为之前设置了绘制百分比
bar.setStringPainted(!flag);
}
});
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
//通过定时器,不断的读取simulatedActivity中的current值,更新进度条的进度
Timer timer = new Timer(300, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
bar.setValue(simulatedActivity.getCurrent());
}
});
timer.start();
//监听进度条的变化,如果进度完成为100%,那么停止定时器
bar.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
if ( bar.getValue()==bar.getMaximum()){
timer.stop();
}
}
});
}
public static void main(String[] args) {
new JProgressTest2().init();
}
//定义一个线程任务,模拟耗时操作
private class SimulatedActivity implements Runnable{
//内存可见
private volatile int current = 0;
private int amount;
public SimulatedActivity(int amount) {
this.amount = amount;
}
public int getCurrent() {
return current;
}
public void setCurrent(int current) {
this.current = current;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
@Override
public void run() {
//通过循环,不断的修改current的值,模拟任务完成量
while(current<amount){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
current++;
}
}
}
}
之前我们学习过,其实Swing中很多组件的界面与数据都采用了MVC的设计思想:
Swing 组件大都将外观显示和 内部数据分离 , JProgressBar 也不例外, JProgressBar 组件有一个内置的用于保存其状态数据的Model对象 , 这个对象由BoundedRangeModel对象表示,程序调用JProgressBar对象的方法完成进度百分比的设置,监听进度条的数据变化,其实都是通过它内置的BoundedRangeModel对象完成的。下面的代码是对之前代码的改进,通过BoundedRangeModel完成数据的设置,获取与监听。
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class JProgressTest3 {
JFrame jf = new JFrame("测试进度条");
//创建一个垂直进度条
JProgressBar bar = new JProgressBar(JProgressBar.HORIZONTAL);
JCheckBox indeterminate = new JCheckBox("不确定进度");
JCheckBox noBorder = new JCheckBox("不绘制边框");
public void init(){
Box box = new Box(BoxLayout.Y_AXIS);
box.add(indeterminate);
box.add(noBorder);
jf.setLayout(new FlowLayout());
jf.add(box);
//把进度条添加到jf窗口中
jf.add(bar);
//开启耗时任务
SimulatedActivity simulatedActivity = new SimulatedActivity(100);
new Thread(simulatedActivity).start();
//设置进度条的最大值和最小值
bar.getModel().setMinimum(0);
bar.getModel().setMaximum(simulatedActivity.getAmount());
//设置进度条中绘制完成百分比
bar.setStringPainted(true);
//根据选择决定是否绘制进度条边框
noBorder.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
boolean flag = noBorder.isSelected();
bar.setBorderPainted(!flag);
}
});
//根据选择决定是否是不确定进度条
indeterminate.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
boolean flag = indeterminate.isSelected();
bar.setIndeterminate(flag);
//不绘制百分比,因为之前设置了绘制百分比
bar.setStringPainted(!flag);
}
});
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
//通过定时器,不断的读取simulatedActivity中的current值,更新进度条的进度
Timer timer = new Timer(300, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
bar.getModel().setValue(simulatedActivity.getCurrent());
}
});
timer.start();
//监听进度条的变化,如果进度完成为100%,那么停止定时器
bar.getModel().addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
if ( bar.getModel().getValue()==bar.getModel().getMaximum()){
timer.stop();
}
}
});
}
public static void main(String[] args) {
new JProgressTest3().init();
}
//定义一个线程任务,模拟耗时操作
private class SimulatedActivity implements Runnable{
//内存可见
private volatile int current = 0;
private int amount;
public SimulatedActivity(int amount) {
this.amount = amount;
}
public int getCurrent() {
return current;
}
public void setCurrent(int current) {
this.current = current;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
@Override
public void run() {
//通过循环,不断的修改current的值,模拟任务完成量
while(current<amount){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
current++;
}
}
}
}
3.4.2 创建进度对话框
ProgressMonitor的用法与JProgressBa 的用法基本相似,只是ProgressMonitor可以直接创 建一个进度对话框,它提供了下面的构造器完成对话框的创建:
public ProgressMonitor(Component parentComponent,Object message,String note, int min,int max):
parentComponent:对话框的父组件
message:对话框的描述信息
note:对话框的提示信息
min:进度条的最小值
max:进度条的最大值
使用 ProgressMonitor 创建的对话框里包含的进度条是非常固定的,程序甚至不能设置该进度条是否包含边框(总是包含边框) , 不能设置进度不确定,不能改变进度条的方向(总是水平方向) 。
案例:
使用ProgressMonitor完成下图效果:
演示代码:
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ProgressMonitorTest {
Timer timer;
public void init(){
final SimulatedActivity simulatedActivity = new SimulatedActivity(100);
final Thread targetThread= new Thread(simulatedActivity);
targetThread.start();
ProgressMonitor dialog = new ProgressMonitor(null, "等待任务完成", "已完成:", 0, simulatedActivity.getAmount());
timer = new Timer(300, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
dialog.setProgress(simulatedActivity.getCurrent());
if (dialog.isCanceled()){
timer.stop();
targetThread.interrupt();
System.exit(0);
}
}
});
timer.start();
System.out.println("aaa");
}
public static void main(String[] args) {
new ProgressMonitorTest().init();
}
//定义一个线程任务,模拟耗时操作
private class SimulatedActivity implements Runnable{
//内存可见
private volatile int current = 0;
private int amount;
public SimulatedActivity(int amount) {
this.amount = amount;
}
public int getCurrent() {
return current;
}
public void setCurrent(int current) {
this.current = current;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
@Override
public void run() {
//通过循环,不断的修改current的值,模拟任务完成量
while(current<amount){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
current++;
}
}
}
}