Java图形界面开发:高级Swing容器(二)
11.3 JTabbedPane类
JTabbedPane类表示曾经流行的属性页来支持在一个窗口中多个容器的输入或输出,其中每次只显示一个面板。使用JTabbedPane类似于使用CardLayout管理器,所不同的是添加到修改内建卡片的支持。然而CardLayout是一个LayoutManager,而JTabbedPane是一个完全功能的Container。如果我们不熟悉属性页,标签对话框或是标签面板(所有都是相同的事物的不同名字),图11-10显示了一个JDK 1.2版本所带的原始SwingSet Demo中的标签集合。
为了有助于JTabbedPane管理哪一个Component被选中,容器的模型是一个SingleSelectionModel接口的实现,或者更确切的说,是一个DefaultSingleSelectionModel实例。(SingleSelectionModel与DefaultSingleSelectionModel在第6章中进行了描述。)
11.3.1 创建JTabbedPane
JTabbedPane只有三个构造函数:
public JTabbedPane() JTabbedPane tabbedPane = new JTabbedPane(); public JTabbedPane(int tabPlacement) JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.RIGHT); public JTabbedPane(int tabPlacement, int tabLayoutPolicy) JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.RIGHT, JTabbedPane.SCROLL_TAB_LAYOUT);
可配置的选项是用来修改显示哪一个组件的标签位置与当在一个虚拟行中有多个标签时的标签布局策略。默认情况下,标签位于容器的顶部,并且标签数量超过容器宽度时会进行回环形成多行。然而,我们可以使用JTabbedPane的下列常量之一来显式的指定位置:TOP, BOTTOM, LEFT或RIGHT,或者是使用SCROLL_TAB_LAYOUT或WRAP_TAB_LAYOUT来配置布局策略。图11-11使用其他三个标签位置显示了图11-10的屏幕显示。图11-12显示了带有滚动标签布局的屏幕。
11.3.2 添加与移除标签
一旦我们创建了基本的JTabbedPane容器,我们需要添加构成JTabbedPane页的面板。我们可以使用两种基本方法来添加面板。
如果我们使用JBuilder或是Eclipse的可视化工具来创建我们的界面,用户界面构建器将会使用我们所熟悉的Container的add()方法来添加Component。所添加的面板使用component.getName()作为默认标题。然而,如果我们手动编程我们不应使用各种add()方法。
添加组件或是面板来创建标签更为合适的方法是使用下面列出的addTab()或是insertTab()方法。insertTab()方法中除了组件与位置索引以外,其他的参数可以为空。(传递null作为Component参数会在运行时抛出NullPointerException。)显示的图标与工具提示设置并没有默认值。
• public void addTab(String title, Component component) • public void addTab(String title, Icon icon, Component component) • public void addTab(String title, Icon icon, Component component, String tip) • public void insertTab(String title, Icon icon, Component component, String tip, int index)
当使用addTab()时,标签被添加到末尾,也就是对于顶部或是底部标签集合来说是最右边的位置,或是对于在左边或右边放置的标签时位于底部,依据组件的方向,也可以是相反的一边。
在创建面板之后,我们可以通过setXXXAt()方法修改一个特定标签的标题,图标,热键,工具提示或是组件:
• public void setTitleAt(int index, String title) • public void setIconAt(int index, Icon icon) • public void setMnemonicAt(int index, int mnemonic) • public void setDisplayedMnemonicIndexAt(int index, int mnemonicIndex) • public void setToolTipTextAt(int index, String text) • public void setComponentAt(int index, Component component)
提示,显示的热键索引指向标题中哪一个字符应高亮。例如,如果我们希望title中第二t高亮显示热键,我们可以使用setMnemonicAt()方法将热键字符设置为KeyEvent.VK_T,并使用setDisplayedMnemonicIndexAt()将热键索引设置为2。
另外,我们可以修改一个特定标签的背景色或前景色,允许或是禁止一个特定的标签,或是使用setXXXAt()方法设置不同的禁止图标:
• public void setBackgroundAt(int index, Color background) • public void setForegroundAt(int index, Color foreground) • public void setEnabledAt(int index, boolean enabled) • public void setDisabledIconAt(int index, Icon disabledIcon)
要移除一个标签,我们可以使用removeTabAt(int index), remove(int index)或是remove(Component component)来移除一个特定的标签。另外,我们可以使用removeAll()移除所有的标签。
13.3.3 JTabbedPane属性
表11-4显示了JTabbedPane的11个属性。因为JTabbedPane的许多setter/getter方法都指定了一个索引参数,事实上他们并不是真正的属性。
我们可以通过selectedComponent或是selectedIndex属性来编程修改显示的标签。
tabRunCount属性表示显示所有的标签所必须的行数(对于顶部或底部标签位置)或是列数(对于左边或是右边位置)。
注意,当要显示容器时修改JTabbedPane的LayoutManager将会抛出异常。换句话说,不要那样做。
11.3.4 监听修改标签选中
如果我们对确定何时选中的标签变化感兴趣,我们需要监听选中模型的变化。这是通过我们将一个ChangeListener关联到JTabbedPane(或是直接关联到SingleSelectionModel)来实现的。注册的ChangeListener报告何时选中模型发生变化,以及选中的面板变化时模型的变化。
显示在列表11-3中的程序演示了监听选中标签的变化并且显示了新选中标签的标题。
package swingstudy.ch11; import java.awt.BorderLayout; import java.awt.Color; import java.awt.EventQueue; import java.awt.event.KeyEvent; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JTabbedPane; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import swingstudy.ch04.DiamondIcon; public class TabSample { static Color colors[] = {Color.RED, Color.ORANGE, Color.YELLOW, Color.GREEN, Color.BLUE, Color.MAGENTA}; static void add(JTabbedPane tabbedPane, String label, int mnemonic) { int count = tabbedPane.getTabCount(); JButton button = new JButton(label); button.setBackground(colors[count]); tabbedPane.addTab(label, new DiamondIcon(colors[count]), button, label); tabbedPane.setMnemonicAt(count, mnemonic); } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Tabbed Pane Sample"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JTabbedPane tabbedPane = new JTabbedPane(); tabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); String titles[] = {"General", "Security", "Content", "Connection", "Programs", "Advanced"}; int mnemonics[] = {KeyEvent.VK_G, KeyEvent.VK_S, KeyEvent.VK_C, KeyEvent.VK_0, KeyEvent.VK_P, KeyEvent.VK_A}; for(int i=0, n=titles.length; i<n; i++) { add(tabbedPane, titles[i], mnemonics[i]); } ChangeListener changeListener = new ChangeListener() { public void stateChanged(ChangeEvent event) { JTabbedPane sourceTabbedPane = (JTabbedPane)event.getSource(); int index = sourceTabbedPane.getSelectedIndex(); System.out.println("Tab changed to: "+sourceTabbedPane.getTitleAt(index)); } }; tabbedPane.addChangeListener(changeListener); frame.add(tabbedPane, BorderLayout.CENTER); frame.setSize(400, 150); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
11.3.5 自定义JTabbedPane观感
每一个可安装的Swing观感都提供了不同的JTabbedPane外观以及JTabbedPane组件的默认UIResource值集合。图11-13显示了JTabbedPane容器在预安装的观感类型Motif,Windows以及Ocean下的外观。某些项目是特定观感的:当可用的标签集合对于显示过度时JTabbedPane如何显示,当用户在后一行选择标签时如何响应,如何显示工具提示,以及如何显示滚动标签布局。
JTabbedPane可用的UIResource相关的属性集合显示在表11-5中。对于JTabbedPane组件,有34个不同的属性。
11.4 JScrollPane类
Swing的JScrollPane容器通过滚动支持(如果需要)来使得当前部分不可见从而为在较小的显示区域内显示大组件提供支持。图11-4显示了一个实现,其中大组件是一个具有ImageIcon的JLabel。
可以使用两种方示来标识要滚动的组件。我们不需要将要滚动的组件直接添加到JScrollPane容器中,我们可以将组件添加到已经包含在滚动面板中的另一个组件,JViewport。相对应的,我们可以通过将其传递给构造函数,在构造时标识组件。
Icon icon = new ImageIcon("dog.jpg"); JLabel label = new JLabel(icon); JScrollPane jScrollPane = new JScrollPane(); jScrollPane.setViewportView(label); // or JScrollPane jScrollPane2 = new JScrollPane(label);
一旦我们将组件添加到JScrollPane中,用户可以使用滚动条来查看在JScrollPane的内部区域不可见的大组件部分。
除了为我们提供了设置JScrollPane可滚动组件的方法,显示策略可以决定是否以及何时在JScrollPane周围显示滚动条。Swing的JScrollPane为水平以及垂直滚动条维度了单独的显示策略。
除了使得我们为滚动添加JViewport以及两个JScrollBar组件以外,JScrollPane同时允许我们提供另外两个JViewport对象用于行与列头以及在滚动面板四个角中显示的四个Component对象。这些组件的放置是通过在第10章介绍进行全面描述的ScrollPaneLayout管理器来管理的。JScrollPane实现所用的JScrollBar组件是一个名为JScrollPane.ScrollBar的JScrollBar子类。他们被用来替换通常的JScrollBar,从而在组件实现了Scrollable接口时正确处理JViewport中的滚动组件。
为了帮助我们理解这些组件如何放置在JScrollPane中,图11-15演示了ScrollPaneLayout如何放置各种对象。
注意,JScrollPane组件只支持轻量级组件的滚动。我们不应该向容器添加通常的,重量级AWT组件。
11.4.1 创建JScrollPane
JScrollPane有四个构造函数:
public JScrollPane() JScrollPane scrollPane = new JScrollPane(); public JScrollPane(Component view) Icon icon = new ImageIcon("largeImage.jpg"); JLabel imageLabel = new JLabel(icon); JScrollPane scrollPane = new JScrollPane(imageLabel); public JScrollPane(int verticalScrollBarPolicy, int horizontalScrollBarPolicy) JScrollPane scrollPane = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); public JScrollPane(Component view, int verticalScrollBarPolicy, int horizontalScrollBarPolicy) JScrollPane scrollPane = new JScrollPane(imageLabel, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
这些构造函数提供了预安装滚动组件以及配置单独滚动条滚动策略的选项。默认情况下,滚动条只在需要的时候显示。表11-16显示了用来为每一个滚动条显示设置策略的JScrollPane常量。使用其他不正确的设置会导致抛出IllegalArgumentException。
下面的章节将会解释如何在创建JScrollPane之后添加或修改组件。
11.4.2 修改Viewport View
如果我们使用合适的组件创建JScrollPane,我们只需要添加JScrollPane来显示。然而,如果我们并没有在创建时关联组件,或者是希望在稍后进行修改,有两种方法可以为滚动关联一个新的组件。首先,我们可以通过设置viewportView属性直接修改组件:
scrollPane.setViewportView(dogLabel);
修改滚动组件另一种方法就是将JViewport放在JScrollPane的中间,然后修改其view属性:
scrollPane.getViewport().setView(dogLabel);
我们将会在本章稍后的“JViewport类”一节中了解到更多关于JViewport组件的内容。
11.4.3 Scrollable接口
不同于AWT组件,例如List会在一次显示的选项过多时自动提供可滚动区域,Swing组件JList,JTable,JTextComponent,以及JTree并不会自动提供滚动支持。我们必须创建组件,将其添加到JScrollPane,然后将滚动面板添加到屏幕。
JList list = new JList(...); JScrollPane scrollPane = new JScrollPane(list); aFrame.add(scrollPane, BorderLayout.CENTER);
将组件添加到JScrollPane起作用的原因在于每一个也许对于屏幕过大的Swing组件(并且需要滚动支持)实现了Scrollable接口。通过实现这个接口,当我们移动与JScrollPane相关联的滚动条时,JScrollPane会查询容器内Scrollable组件的尺寸信息从而基于当前的滚动条位置正确的定位组件。
我们唯一需要担心Scrollable接口的时机就是当我们创建一个需要滚动支持的自定义组件的时候。下面是Scrollable接口的定义。
public interface Scrollable { public Dimension getPreferredScrollableViewportSize(); public boolean getScrollableTracksViewportHeight(); public boolean getScrollableTracksViewportWidth(); public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction); public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction); }
如果我们创建一个自定义的Scrollable组件,然后将放在JScrollPane中,当JScrollPane的滚动条或是鼠标滚轮移动时,他就会正确的响应。
11.4.4 JScrollPane属性
表11-7显示了JScrollPane的19个属性。
尝试着JScrollPane的布局属性修改为除了ScrollPaneLayout以外的值或是null将会在运行时抛出ClassCastException,因为JScrollPane所用的布局管理器必须为ScrollPaneLayout。
使用ScrollPaneLayout JScrollPane依赖ScrollPaneLayout管理器对容器内的组件进行放置。然而大多数的布局管理器被设置用来布局所有的组件类型,但是ScrollPaneLayout的四个区域只接受特定类型的组件。表11-8显示了可以放置在图11-15中所示区域中显示的组件类型。
注意,区域角有两个常量集合。对于国际化支持,我们可以使用LOWER_LEADING_CORNER, LOWER_TRAILING_CORNER, UPPER_LEADING_CORNER与UPPER_TRAILING_CORNER,这些常量可以为我们处理组件方向。对于由左到右的组件方向,起始是左边,而结束是右边。
正如设计要求,布局管理器描述支持对于可用空间过大的主内容区域(VIEWPORT)所必须的屏幕布局。用于在区域中浏览的滚动条可以被设置在内容区域的右边(VERTICAL_SCROLLABAR)或是下边(HORIZONTAL_SCROLLBAR)。不滚动的固定头可以被放置在内容区域的上部(COLUMN_HEADER)或是其左边(ROW_HEADER)。四个角(*_CORNER)可以配置来显示任意的组件类型,通常是带有图片的标签;然则 ,在其中可以放置任意的组件。
注意,一些开发者会认为ScrollPaneLayout是一个带有自定义约束的GridBagLayout。在通常情况下,大多数开发者并不会在JScrollPane之外使用ScrollPaneLayout。
使用JScrollPane头与角
如图11-15与表11-8所示,在JScrollPane存在多个不同的区域。通常,我们只使用中间的视图,并使用两个滚动条完成相应的任务。另外,当使用JTable组件时,当放置在JScrollPane中时,表会自动将列头放置在列头区域。
我们也可以手动添加或是修改JScrollPane的列头或是行头。尽管我们可以在这里区域完全替换JViewport,但是为此区域中的Component设置columnHeaderView或是rowHeaderView属性更为简单。这一操作可以为我们将组件放置在JViewport中。
要将组件放置在JScrollPane的一个角中,我们需要调用setCorner(String key, Component corner)方法,其中key是JScrollPane中的下列常量之一:LOWER_LEFT_CORNER, LOWER_RIGHT_CORNER, UPPER_LEFT_CORNER,或是UPPER_RIGHT_CORNER。
角区域的使用比较有技巧。只有当两个位于角落右边角的组件是当前显示时,角落组件才会被显示。例如,假如我们要在右下角落放置一个具有公司logo的标签,而两个滚动条的滚动策略只有在必需的时才会显示。在这种情况下,如果一个滚动条不需要,角落中的logo也不会被显示。作为另一个盒子,如果一个JScrollPane具有一个列头显示,但是并没有行头,左上角中的组件也不会被显示。
所以,仅仅因为我们将角落设置为一个组件(例如scrollPane.setCorner(JScrollPane.UPPER_LEFT_CORNER, logLabel)),不要期望组件总是或是自动显示。而且,如图11-16所示,相邻的区域控制角落的尺寸。不要认为角落组件会按需要大小显示。这是因为其最小尺寸,最优尺寸与最大尺寸被完全被忽略了。在图11-16中,用来创建角落组件的实际图片要大于所用的空间。
注意,修改JScrollPane的一个角落类似于边界属性,其中属性名是表11-8中所列的角落键值。
重设视图域位置
有时,我们也许会希望将内部视图的内容向JScrollPane的左上角移动。这种变化也许是需要的,因为视图发生了变化,或者是某些事情的发生要求视图域组件返回到JScrollPane的原始位置。移动视图最简单的方法就是JScrollPane的滚动条位置。将每一个滚动条设置为其最小值就有效的将组件视图移动到了组件的左上角区域。列表11-4中所显示的ActionListener可以关联到屏幕中的按钮或是JScrollPane的角落,从而使得JScrollPane的内容返回到原始位置。
package swingstudy.ch11; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JScrollBar; import javax.swing.JScrollPane; public class JScrollPaneToTopAction implements ActionListener { JScrollPane scrollPane; public JScrollPaneToTopAction(JScrollPane scrollPane) { if(scrollPane == null) { throw new IllegalArgumentException("JScrollPaneToTopAction: null JScrollPane"); } this.scrollPane = scrollPane; } @Override public void actionPerformed(ActionEvent event) { // TODO Auto-generated method stub JScrollBar verticalScrollBar = scrollPane.getVerticalScrollBar(); JScrollBar horizontalScrollBar = scrollPane.getHorizontalScrollBar(); verticalScrollBar.setValue(verticalScrollBar.getMinimum()); horizontalScrollBar.setValue(horizontalScrollBar.getMinimum()); } }
11.4.5 自定义JScrollPane观感
每一个可安装的观感都提供了不同的JScrollPane外观以及默认的组件UIResource值集合。图11-17显示了JScrollPane组件在预安装的观感类型集合下的外观显示。对于JScrollPane,观感类型之间的主要区别与滚动条的外观以及视图周围的边框有关。
表11-9显示了JScrollPane可用的UIResource相关属性集合。对于JScrollPane组件,有十个不同的属性。当滚动条在JScrollPane内可见时,修改与JScrollBar的相关属性会影响其外观。