12.3.3 使用JLayeredPane、 JDesktopPane和JInternalFrame
JLayeredPane 分层容器
JLayeredPane
是一个代表有层次深度的容器,它允许组件在需要时互相重叠。当向JLayeredPane
容器中添加组件时,需要为该组件指定一个深度索引,其中层次索引较高的层里的组件位于其他层的组件之上。
JLayeredPane默认层
JLayeredPane
还将容器的层次深度分成几个默认层,程序只是将组件放入相应的层,从而可以更容易地确保组件的正确重叠,无须为组件指定具体的深度索引。JLayeredPane
提供了如下几个默认层。
默认层 |
描述 |
static Integer DEFAULT_LAYER |
大多数组件位于的标准层。这是最底层 |
static Integer PALETTE_LAYER |
调色板层位于默认层之上。该层对于浮动工具栏和调色板很有用,因此可以位于其他组件之上。 |
static Integer MODAL_LAYER |
该层用于显示模式对话框。它们将出现在容器中所有工具栏、调色板或标准组件的上面。 |
static Integer POPUP_LAYER |
该层用于显示右键菜单,与对话框、工具提示和普通组件关联的弹出式窗口将出现在对应的对话框、工具提示和普通组件之上。 |
static Integer DRAG_LAYER |
该层用于放置拖放过程中的组件(关于拖放操作请看下一节内容),拖放操作中的组件位于所有组件之上。一旦拖放操作结束后,该组件将重新分配到其所属的正常层 |
static Integer FRAME_CONTENT_LAYER |
Convenience object defining the Frame Content layer. |
static String LAYER_PROPERTY |
Bound property |
每一层都是一个不同的整数。可以在调用add()
方法的过程中通过Integer
参数指定该组件所在的层。也可以传入上面几个静态常量,它们分别等于0,100,200,300,400等值。
除此之外,也可以使用JLayeredPane
的moveToFront()
、moveToBack()
和setPosition()
方法在组件所在层中对其进行重定位,还可以使用setLayer()
方法更改该组件所属的层。
添加到JLayeredPane中的组件大小和位置必须确定
向JLayeredPane
中添加组件时,必须显式设置该组件的大小和位置,否则该组件不能显示出来
程序 JLayeredPane分层容器
下面程序简单示范了JLayeredPane
容器的用法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import java.awt.*; import javax.swing.*;
public class JLayeredPaneTest { JFrame jf = new JFrame("测试JLayeredPane"); JLayeredPane layeredPane = new JLayeredPane();
public void init() { layeredPane.add(new ContentPanel(10, 20, "疯狂Java讲义", "ico/java.png"), JLayeredPane.MODAL_LAYER); layeredPane.add(new ContentPanel(100, 60, "疯狂Android讲义", "ico/android.png"), JLayeredPane.DEFAULT_LAYER); layeredPane.add(new ContentPanel(190, 100, "轻量级Java EE企业应用实战", "ico/ee.png"), 4); layeredPane.setPreferredSize(new Dimension(400, 300)); layeredPane.setVisible(true); jf.add(layeredPane); jf.pack(); jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jf.setVisible(true); }
public static void main(String[] args) { new JLayeredPaneTest().init(); } }
class ContentPanel extends JPanel { private static final long serialVersionUID = 4535201982849108665L;
public ContentPanel(int xPos, int yPos, String title, String ico) { setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), title)); JLabel label = new JLabel(new ImageIcon(ico)); add(label); setBounds(xPos, yPos, 160, 220); } }
|
上面程序中粗体字代码向JLayeredPane
中添加了三个Panel
组件,每个Panel
组件都必须显式设置大小和位置(程序中①处代码设置了Panel
组件的大小和位置),否则该组件不能被显示出来。
运行上面程序,会看到如图12.17所示的运行效果。
JDesktopPane 内部窗口
JLayeredPane
的子类JDesktopPane
容器更加常用,很多应用程序都需要启动多个内部窗口来显示信息(典型的如Eclipse
、EditPlus
都使用了这种内部窗口来分别显示每个Java
源文件),这些内部窗口都属于同一个外部窗口,当外部窗口最小化时,这些内部窗口都被隐藏起来。在Windows
环境中,这种用户界面被称为多文档界面(Multiple Document Interface,MDI
)。
使用Swing
可以非常简单地创建岀这种MDI
界面,通常,内部窗口有自己的标题栏、标题、图标、三个窗口按钮,并允许拖动改变内部窗口的大小和位置,但内部窗口不能拖出外部窗口。
内部窗口与外部窗口表现方式上的唯一区别在于:
- 外部窗口的桌面是实际运行平台的桌面,
- 而内部窗口以外部窗口的指定容器作为桌面。
就其实现机制来看,外部窗口和内部窗口则完全不同,
- 外部窗口需要部分依赖于本地平台的
GUI
组件,属于重量级组件;
- 而内部窗口则采用100%的
Java
实现,属于轻量级组件。
JDesktopPane
需要和JInternalFrame
结合使用,其中JDesktopPane
代表一个虚拟桌面,而JInternalFrame
则用于创建内部窗口。
创建内部窗口的步骤
使用JDesktopPane
和JInternalFrame
创建内部窗口按如下步骤进行即可。
- 创建一个
JDesktopPane
对象。JDesktopPane
类仅提供了一个无参数的构造器,通过该构造器创建JDesktopPane
对象,该对象代表一个虚拟桌面。
- 使用
JInternalFrame
创建一个内部窗口。创建内部窗口与创建JFrame
窗口有一些区别,创建JInternalFrame
对象时除可以传入一个字符串作为该内部窗口的标题之外,还可以传入4个boolean
值,用于指定该内部窗口是否允许改变窗口大小、是否允许关闭窗口、是否允许最大化窗口、是否允许最小化窗口。
例如,下面代码可以创建一个内部窗口:1 2 3 4 5 6
| final JInternalFrame iframe = new JInternalFrame("新文档", true, true, true, true);
|
- 一旦获得了内部窗口之后,该窗口的用法和普通窗口的用法基本相似,一样可以指定该窗口的布局管理器,一样可以向窗口内添加组件、改变窗口图标等。关于操作内部窗口具体存在哪些方法,请参阅
JInternalFrame
类的API
文档。
- 将该内部窗口以合适大小、在合适位置显示出来。与普通窗口类似的是,该窗口默认大小是0×0像素,位于0.0位置(虚拟桌面的左上角处),并且默认处于隐藏状态,程序可以通过如下代码将内部窗口显示出来。
1 2 3 4
| iframe.reshape(20, 20, 300, 400);
iframe.show();
|
- 将内部窗口添加到
JDesktopPane
容器中,再将JDesktopPane
容器添加到其他容器中
外部窗口的show方法过时了
外部窗口的show()
方法已经过时了,不再推荐使用。但内部窗口的show()
方法没有过时,该方法不仅可以让内部窗口显示出来,而且可以让该窗口处于选中状态。
JDesktopPane不能独立存在
JDesktopPane
不能独立存在,必须将JDesktopPane
添加到其他顶级容器中才可以正常使用。
程序 使用JDesktopPane
和JInternalFrame
创建多文档页面
下面程序示范了如何使用JDesktopPane
和JInternalFrame
来创建MDI
界面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
| import java.beans.*; import java.awt.*; import java.awt.event.*; import javax.swing.*;
public class JInternalFrameTest { final int DESKTOP_WIDTH = 480; final int DESKTOP_HEIGHT = 360; final int FRAME_DISTANCE = 30; JFrame jf = new JFrame("MDI界面"); private MyJDesktopPane desktop = new MyJDesktopPane(); private int nextFrameX; private int nextFrameY; private int width = DESKTOP_WIDTH / 2; private int height = DESKTOP_HEIGHT / 2; JMenu fileMenu = new JMenu("文件"); JMenu windowMenu = new JMenu("窗口"); Action newAction = new AbstractAction("新建", new ImageIcon("ico/new.png")) { private static final long serialVersionUID = 7504047155791213931L;
public void actionPerformed(ActionEvent event) { final JInternalFrame iframe = new JInternalFrame("新文档", true, true, true, true); iframe.add(new JScrollPane(new JTextArea(8, 40))); desktop.add(iframe); iframe.reshape(nextFrameX, nextFrameY, width, height); iframe.show(); nextFrameX += FRAME_DISTANCE; nextFrameY += FRAME_DISTANCE; if (nextFrameX + width > desktop.getWidth()) nextFrameX = 0; if (nextFrameY + height > desktop.getHeight()) nextFrameY = 0; } }; Action exitAction = new AbstractAction("退出", new ImageIcon("ico/exit.png")) { private static final long serialVersionUID = -254393382364931601L;
public void actionPerformed(ActionEvent event) { System.exit(0); } };
public void init() { JMenuBar menuBar = new JMenuBar(); JToolBar toolBar = new JToolBar(); jf.setJMenuBar(menuBar); menuBar.add(fileMenu); fileMenu.add(newAction); fileMenu.add(exitAction); toolBar.add(newAction); toolBar.add(exitAction); menuBar.add(windowMenu); JMenuItem nextItem = new JMenuItem("下一个"); nextItem.addActionListener(event -> desktop.selectNextWindow()); windowMenu.add(nextItem); JMenuItem cascadeItem = new JMenuItem("级联"); cascadeItem.addActionListener(event -> desktop.cascadeWindows(FRAME_DISTANCE, 0.75)); windowMenu.add(cascadeItem); JMenuItem tileItem = new JMenuItem("平铺"); tileItem.addActionListener(event -> desktop.tileWindows()); windowMenu.add(tileItem); final JCheckBoxMenuItem dragOutlineItem = new JCheckBoxMenuItem("仅显示拖动窗口的轮廓"); dragOutlineItem.addActionListener(event -> desktop.setDragMode( dragOutlineItem.isSelected() ? JDesktopPane.OUTLINE_DRAG_MODE : JDesktopPane.LIVE_DRAG_MODE)); windowMenu.add(dragOutlineItem); desktop.setPreferredSize(new Dimension(480, 360)); jf.add(desktop); jf.add(toolBar, BorderLayout.NORTH); jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jf.pack(); jf.setVisible(true); }
public static void main(String[] args) { new JInternalFrameTest().init(); } }
class MyJDesktopPane extends JDesktopPane { public void cascadeWindows(int offset, double scale) { int width = (int) (getWidth() * scale); int height = (int) (getHeight() * scale); int x = 0; int y = 0; for (JInternalFrame frame : getAllFrames()) { try { frame.setMaximum(false); frame.setIcon(false); frame.reshape(x, y, width, height); x += offset; y += offset; if (x + width > getWidth()) x = 0; if (y + height > getHeight()) y = 0; } catch (PropertyVetoException e) { } } }
public void tileWindows() { int frameCount = 0; for (JInternalFrame frame : getAllFrames()) { frameCount++; } int rows = (int) Math.sqrt(frameCount); int cols = frameCount / rows; int extra = frameCount % rows; int width = getWidth() / cols; int height = getHeight() / rows; int x = 0; int y = 0; for (JInternalFrame frame : getAllFrames()) { try { frame.setMaximum(false); frame.setIcon(false); frame.reshape(x * width, y * height, width, height); y++; if (y == rows) { y = 0; x++; if (extra == cols - x) { rows++; height = getHeight() / rows; } } } catch (PropertyVetoException e) { } } }
public void selectNextWindow() { JInternalFrame[] frames = getAllFrames(); for (int i = 0; i < frames.length; i++) { if (frames[i].isSelected()) { int next = (i + 1) % frames.length; while (next != i) { if (!frames[next].isIcon()) { try { frames[next].setSelected(true); frames[next].toFront(); frames[i].toBack(); return; } catch (PropertyVetoException e) { } } next = (next + 1) % frames.length; } } } } }
|
上面程序中示范了创建JDesktopPane
虚拟桌面创建JInternatFrame
内部窗口,并将内部窗口添加到虚拟桌面中,最后将虚拟桌面添加到顶级JFrame
容器中的过程。
运行上面程序,会看到如图12.18所示的内部窗口效果。
该变内部窗口的拖动模式
在默认情况下,当用户拖动窗口时,内部窗口会紧紧跟随用户鼠标的移动,这种操作会导致系统不断重绘虚拟桌面的内部窗口,从而引起性能下降。为了改变这种拖动模式,可以设置当用户拖动内部窗口时,虚拟桌面上仅绘出该内部窗口的轮廓。可以通过调用JDesktopPane
的setDragMode()
方法来改变内部窗口的拖动模式
方法 |
描述 |
void setDragMode(int dragMode) |
Sets the “dragging style” used by the desktop pane. |
该方法接收如下两个参数值。
dragMode参数值 |
描述 |
JDesktopPane.OUTLINE_DRAG_MODE |
拖动过程中仅显示内部窗口的轮廓。 |
JDesktopPane.LIVE_DRAG_MODE |
拖动过程中显示完整窗口,这是默认选项。 |
上面程序中①处代码允许用户根据CheckBoxMenuitem
的状态来决定窗口采用哪种拖动模式。
读者可能会发现,程序创建虚拟桌面时并不是直接创建JDesktopPane
对象,而是先扩展JDesktopPane
类,为该类增加了如下三个方法。
cascadeWindows()
:级联显示所有的内部窗口。
tileWindows()
:平铺显示所有的内部窗口。
selectNextWindow()
:选中当前窗口的下一个窗口。
JDesktopPane
没有提供这三个方法,但这三个方法在MDI
应用里又是如此常用,以至于开发者总需要自己来扩展JDesktopPane
类,而不是直接使用该类。这是一个非常有趣的地方:Oracle
似乎认为这些方法太过简单,不屑为之,于是开发者只能自己实现,这给编程带来一些麻烦。
级联 显示窗口
级联显示窗口其实很简单,先根据内部窗口与JDesktopPane
的大小比例计算出每个内部窗口的大小,然后以此重新排列每个窗口,重排之前让相邻两个窗口在横向、纵向上产生一定的位移即可。
平铺 显示窗口
平铺显示窗口相对复杂一点,程序先计算需要几行、几列可以显示所有的窗口,如果还剩下多余(不能整除)的窗口,则依次分布到最后几列中。图12.19显示了平铺窗口的效果。
程序 弹出内部对话框
前面介绍JOptionPane
时提到该类包含了多个重载的showInternalXxxDialog()
方法,这些方法用于弹出内部对话框,当使用该方法来弹出内部对话框时通常需要指定一个父组件,这个父组件既可以是虚拟桌面(JDesktopPane
对象),也可以是内部窗口(JInternalFrame
对象)。下面程序示范了如何弹出内部对话框
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| import java.awt.BorderLayout; import java.awt.Dimension; import javax.swing.JButton; import javax.swing.JDesktopPane; import javax.swing.JFrame; import javax.swing.JInternalFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea;
public class InternalDialogTest { private JFrame jf = new JFrame("测试内部对话框"); private JDesktopPane desktop = new JDesktopPane(); private JButton internalBn = new JButton("内部窗口的对话框"); private JButton deskBn = new JButton("虚拟桌面的对话框"); private JInternalFrame iframe = new JInternalFrame("内部窗口");
public void init() { iframe.add(new JScrollPane(new JTextArea(8, 40))); desktop.setPreferredSize(new Dimension(400, 300)); jf.add(desktop); iframe.reshape(0, 0, 300, 200); iframe.show(); desktop.add(iframe); JPanel jp = new JPanel(); deskBn.addActionListener(event -> JOptionPane.showInternalMessageDialog(desktop, "属于虚拟桌面的对话框")); internalBn.addActionListener(event -> JOptionPane.showInternalMessageDialog(iframe, "属于内部窗口的对话框")); jp.add(deskBn); jp.add(internalBn); jf.add(jp, BorderLayout.SOUTH); jf.pack(); jf.setVisible(true); }
public static void main(String[] args) { new InternalDialogTest().init(); } }
|
上面程序中两个按钮可以弹出两个内部对话框,这两个对话框一个以虚拟桌面作为父窗口,一个以内部窗口作为父组件。运行上面程序会看到如图12.20所示的内部窗口的对话框