文档

Java™教程
隐藏目录
如何使用滚动窗格
指南:使用Swing创建GUI
课程:使用Swing组件
章节:如何使用各种组件

如何使用滚动面板

JScrollPane提供了一个组件的可滚动视图。当屏幕空间有限时,使用滚动窗格来显示一个较大的组件或一个尺寸可以动态变化的组件。其他用于节省屏幕空间的容器包括分割窗格选项卡窗格

创建滚动窗格的代码可以很简洁。例如,下面是一个演示程序的图片,它将一个文本区域放在滚动窗格中,因为文本区域的大小会随着文本的添加而动态增长:

ToolBarDemo的快照

下面的代码创建了文本区域,将其设置为滚动窗格的客户端,并将滚动窗格添加到容器中:

//在使用BorderLayout的容器中:
textArea = new JTextArea(5, 30);
...
JScrollPane scrollPane = new JScrollPane(textArea);
...
setPreferredSize(new Dimension(450, 110));
...
add(scrollPane, BorderLayout.CENTER);

加粗的代码行创建了JScrollPane,指定了文本区域作为滚动窗格的客户端。程序不会在JScrollPane对象上调用任何方法,因为滚动窗格会自动处理一切:在必要时创建滚动条,当用户移动滚动条时重新绘制客户端等等。

您可能已经注意到前面的代码设置了滚动窗格容器的首选大小。在Java外观中,这个首选大小恰好比我们创建文本区域时请求的5行所需的高度要小一点,因此滚动条最初会显示一个垂直滚动条。如果我们不限制滚动窗格容器的大小,滚动窗格将足够大,以便文本区域显示使用JTextArea构造函数指定的完整的5行30列。有关使滚动窗格具有所需大小的技巧,请参阅调整滚动窗格的大小

本节的其余部分讨论以下主题:

滚动窗格的工作原理

这是一个使用自定义滚动窗格查看照片的应用程序的快照:

ScrollDemo的快照

这个应用程序中的滚动窗格与之前的演示程序中的滚动窗格非常不同。滚动窗格不显示文本,而是包含了一张图片。滚动窗格还有两个滚动条、一行标题、一列标题和四个角落,其中三个已经自定义。


试试看: 
  1. 点击“Launch”按钮使用 Java™ Web Start 运行ScrollDemo(下载JDK 7或更高版本)。或者,要自己编译和运行示例,请参考示例索引启动ScrollDemo示例

  2. 移动滚动条上的滑块。观察图片的滚动以及水平和垂直标尺的滚动。
  3. 如果你有一个带有滚轮的鼠标(通常在鼠标按钮之间),可以使用鼠标滚轮垂直滚动图片。
  4. 点击滚动窗格左上角的cm切换。行标题和列标题的单位会改变为英寸(或改回为厘米)。
  5. 点击滚动条上的箭头按钮。另外,还可以尝试点击垂直滚动条滑块上方或下方的轨道,或水平滚动条左侧或右侧的轨道。
  6. 将光标移动到图片上并按下光标。继续按下光标,将光标拖动到图片之外的某个位置并暂停。图片的可见区域会向光标移动。这种拖动滚动功能是由滚动窗格和JComponent API启用的,但是它是由显示图片的自定义组件实现的。
  7. 调整窗口大小。当滚动窗格足够大以显示整个图片时,滚动条会消失;当滚动窗格太小以至于无法显示整个图片时,滚动条会重新出现。

在创建滚动窗格时,ScrollDemo程序会为滚动窗格的客户端进行设置:

//在这里声明成员变量:
private ScrollablePicture picture;
...
//创建GUI时:
picture = new ScrollablePicture( ... );
JScrollPane pictureScrollPane = new JScrollPane(picture);

滚动窗格的客户端也被称为视图视口视图。你可以通过调用setViewportView方法来动态更改客户端。请注意,JScrollPane没有对应的getViewportView方法。如果你需要再次引用客户端对象,你可以将其缓存到一个变量中,或者在滚动窗格上调用getViewport().getViewportView()方法。

当用户在滚动窗格中操作滚动条时,客户端的可见区域会相应地改变。下图展示了滚动窗格与其客户端之间的关系,并指示了滚动窗格委托的类:

滚动窗格的架构

滚动窗格使用一个JViewport实例来管理客户端的可见区域。视口负责根据滚动条的位置来定位和调整客户端的大小,并将其显示出来。

滚动窗格可以使用两个独立的JScrollBar实例作为滚动条。滚动条为用户提供了操作可见区域的接口。下图显示了滚动条的三个区域:滑块(有时称为拇指)、(箭头)按钮和轨道。

滚动条的组成部分

当用户在垂直滚动条上上下移动滑块时,客户端的可见区域也会相应地上下移动。类似地,当用户在水平滚动条上左右移动滑块时,客户端的可见区域也会相应地左右移动。滑块相对于轨道的位置与可见区域相对于客户端的位置成比例。在Java外观和其他一些外观中,滑块的大小可以提供有关客户端可见区域的视觉线索。

通过点击箭头按钮,用户可以按单位增量进行滚动。通过在轨道内点击,用户可以按块增量进行滚动。如果用户使用带有滚轮的鼠标,那么用户可以使用鼠标滚轮垂直滚动。鼠标滚轮滚动的量因平台而异。例如,默认情况下,在Windows XP上,鼠标滚轮每次滚动三个单位增量;鼠标控制面板允许您指定不同数量的单位增量或使用块增量。有关单位和块增量的更多信息,请参见实现可滚动的客户端

典型的程序不会直接实例化或调用视口(viewport)或滚动条的方法。相反,程序使用JScrollPane API和实现可滚动客户端中讨论的API来实现滚动行为。一些具有滚动功能的组件,如JList、JTable和JTree,还提供其他API来帮助您影响它们的滚动行为。

设置滚动条策略

ScrollDemo应用程序中,滚动窗格在启动时有两个滚动条。如果将窗口放大,两个滚动条都会消失,因为它们不再需要。如果然后将窗口的高度缩小而不改变其宽度,垂直滚动条会重新出现。进一步的实验将显示,在此应用程序中,两个滚动条根据需要出现和消失。这个行为由滚动窗格的滚动条策略控制,实际上是两个策略:每个滚动条都有自己的策略。

ScrollDemo不明确设置滚动窗格的滚动条策略-它使用默认值。您可以在创建滚动窗格时设置策略,也可以在运行时动态更改它们。

JScrollPane提供的构造函数中,这两个构造函数可以在创建滚动窗格时设置滚动条策略:

JScrollPane(Component, int, int)
JScrollPane(int, int)

第一个int指定垂直滚动条的策略;第二个指定水平滚动条的策略。您还可以使用setHorizontalScrollBarPolicysetVerticalScrollBarPolicy方法动态设置策略。无论是使用构造函数还是方法,都可以使用JScrollPane实现的ScrollPaneConstants接口中定义的以下常量:

策略 描述
VERTICAL_SCROLLBAR_AS_NEEDED
HORIZONTAL_SCROLLBAR_AS_NEEDED
默认值。当视口小于客户端时,滚动条出现;当视口大于客户端时,滚动条消失。
VERTICAL_SCROLLBAR_ALWAYS
HORIZONTAL_SCROLLBAR_ALWAYS
始终显示滚动条。如果视口足够大以显示整个客户端,滚动条的滑块消失。
VERTICAL_SCROLLBAR_NEVER
HORIZONTAL_SCROLLBAR_NEVER
从不显示滚动条。如果您不希望用户直接控制所显示的客户端的部分,或者如果您只希望他们使用非滚动条技术(如拖动),请使用此选项。

提供自定义装饰

滚动窗格绘制的区域由最多九个部分组成:中心、四个边和四个角。中心是所有滚动窗格中唯一总是存在的组件。除了滚动条,边可以包含列和行标题。只有当两个相交的边都包含可见组件时,角组件才可见。

滚动窗格上的装饰

如图所示,ScrollDemo中的滚动窗格具有自定义的行和列标题。此外,由于所有四个边都被填充,四个角都存在。程序自定义了三个角 — 其中两个只是用与Rule相同的颜色填充它们的区域,另一个包含一个切换按钮。第四个角,也就是右下角,是滚动窗格默认提供的。请注意,因为在这个示例中行和列标题总是存在,切换按钮也总是存在。

如果一个角包含用户需要始终访问的控件,请确保相交在角上的边始终存在。例如,如果该应用程序将切换按钮放在滚动条相交的右下角,则在用户调整窗口大小并且其中一个滚动条消失时,切换按钮将消失。

滚动窗格的行和列标题由自定义的JComponent子类Rule提供,它以厘米或英寸为单位绘制标尺。以下是创建和设置滚动窗格的行和列标题的代码:

//在成员变量定义的位置:
private Rule columnView;
private Rule rowView;
...
//在初始化GUI的位置:
ImageIcon bee = createImageIcon("images/flyingBee.jpg");
...
//创建行和列标题。
columnView = new Rule(Rule.HORIZONTAL, true);
rowView = new Rule(Rule.VERTICAL, true);

...
pictureScrollPane.setColumnHeaderView(columnView);
pictureScrollPane.setRowHeaderView(rowView);

您可以使用任何组件作为滚动窗格的行和列标题。滚动窗格将行和列标题放在它们自己的JViewPort中。因此,当水平滚动时,列标题会跟随移动,而当垂直滚动时,行标题会跟随移动。确保行和列具有与视图相同的宽度和高度,因为JScrollPane不强制这些值具有相同的大小。如果宽度和高度不同,可能无法获得所需的行为。

作为JComponent的子类,我们自定义的Rule类将其渲染代码放在paintComponent方法中。Rule的渲染代码确保只在当前剪切边界内绘制,以确保快速滚动。您自定义的行和列标题也应该这样做。

您还可以使用任何组件作为滚动窗格的角落。 ScrollDemo通过在左上角放置一个切换按钮以及在右上角和左下角放置自定义Corner对象来说明这一点。下面是创建Corner对象并调用setCorner来放置它们的代码:

//创建角落。
JPanel buttonCorner = new JPanel(); //使用FlowLayout
isMetric = new JToggleButton("cm", true);
isMetric.setFont(new Font("SansSerif", Font.PLAIN, 11));
isMetric.setMargin(new Insets(2,2,2,2));
isMetric.addItemListener(this);
buttonCorner.add(isMetric);
...
//设置角落。
pictureScrollPane.setCorner(JScrollPane.UPPER_LEFT_CORNER,
                            buttonCorner);
pictureScrollPane.setCorner(JScrollPane.LOWER_LEFT_CORNER,
                            new Corner());
pictureScrollPane.setCorner(JScrollPane.UPPER_RIGHT_CORNER,
                            new Corner());

请记住,每个角的大小取决于相交的边的大小。对于某些组件,您必须确保组件的特定实例适合其角。例如,该程序在切换按钮上设置字体和边距,以使其适应标题所建立的空间。对于Corner类来说,这不是问题,因为该类使用纯色填充其整个边界。

从代码中可以看出,常量指示了角的位置。以下是每个位置的常量:

Corner constants

这些常量在JScrollPane实现的ScrollPaneConstants接口中定义。

实现具有滚动功能的客户端

要自定义客户端组件与其滚动窗格的交互方式,您可以使组件实现Scrollable接口。通过实现Scrollable,客户端可以指定用于查看它的视口的大小以及点击滚动条上不同控件时滚动的量。您还可以指定视图是否应跟踪视口的大小。这通常用于当视口大于视图时,但视图应填充可用空间的情况。


注意: 如果您无法或不想实现可滚动的客户端,可以使用setUnitIncrementsetBlockIncrement方法来指定单位和块增量,这些方法属于JScrollBar类。例如,以下代码将垂直滚动的单位增量设置为10个像素:
scrollPane.getVerticalScrollBar().setUnitIncrement(10);

这里再次介绍滚动条的三个控制区域:旋钮、按钮和轨道。

滚动条的组成部分

您可能已经注意到,在ScrollDemo中操作滚动条时,点击按钮会将图像滚动到刻度边界。您还可能注意到,点击轨道会将图片滚动一个"屏幕"的大小。更一般地说,按钮按单位增量滚动可见区域,而轨道按块增量滚动可见区域。您在示例中看到的行为不是滚动窗格的默认行为,而是客户端在实现Scrollable接口时指定的行为。

ScrollDemo程序的客户端是ScrollablePictureScrollablePictureJLabel的子类,提供了所有五个Scrollable方法的实现:

ScrollablePicture主要通过实现Scrollable接口来影响单位和块增量。然而,它必须为所有五个方法提供实现。因此,它为其他三个方法提供了合理的默认值,您可能希望将其复制到您的滚动类中。

滚动窗格在用户点击滚动条上的按钮时调用客户端的getScrollableUnitIncrement方法。只要客户端实现了Scrollable接口,就会调用该方法。该方法返回要滚动的像素数。该方法的一个明显实现是返回标头刻度之间的像素数。ScrollablePicture则执行了不同的操作:它返回将图像定位在刻度边界上所需的值。以下是其实现:

public int getScrollableUnitIncrement(Rectangle visibleRect,
                                      int orientation,
                                      int direction) {
    //获取当前位置
    int currentPosition = 0;
    if (orientation == SwingConstants.HORIZONTAL) {
        currentPosition = visibleRect.x;
    } else {
        currentPosition = visibleRect.y;
    }

    //返回当前位置到最近的刻度线之间的像素数
    if (direction < 0) {
        int newPosition = currentPosition -
                         (currentPosition / maxUnitIncrement)
                          * maxUnitIncrement;
        return (newPosition == 0) ? maxUnitIncrement : newPosition;
    } else {
        return ((currentPosition / maxUnitIncrement) + 1)
                 * maxUnitIncrement
                 - currentPosition;
    }
}

如果图像已经在刻度线边界上,此方法返回刻度线之间的像素数。否则,返回从当前位置到最近刻度线的像素数。

同样,滚动窗格在每次用户点击轨道时调用客户端的getScrollableBlockIncrement方法,但前提是客户端实现了Scrollable接口。下面是ScrollablePicture对该方法的实现:

public int getScrollableBlockIncrement(Rectangle visibleRect,
                                       int orientation,
                                       int direction) {
    if (orientation == SwingConstants.HORIZONTAL)
        return visibleRect.width - maxUnitIncrement;
    else
        return visibleRect.height - maxUnitIncrement;
}

此方法返回可见矩形的高度减去一个刻度线。这是典型的行为,但在垂直滚动时为真,否则为宽度。块增量应略小于视口,以留下一些前一个可见区域的上下文。例如,文本区域可以保留一两行文本作为上下文,表格可以保留一行或一列(取决于滚动方向)。

ScrollablePicture.java还包含了一个不是Scrollable接口所必需的代码,但在可滚动组件中很常见:一个鼠标移动监听器,允许用户通过从中拖动来滚动图片。下面代码片段中的加粗代码实现了拖动滚动:

public class ScrollablePicture extends JLabel
                               implements Scrollable,
                                          MouseMotionListener {
    ...
    public ScrollablePicture(...) {
        ...
        setAutoscrolls(true); //启用合成拖动事件
        addMouseMotionListener(this); //处理鼠标拖动
    }
    ...
    public void mouseDragged(MouseEvent e) {
        //用户正在拖动,所以滚动!
        Rectangle r = new Rectangle(e.getX(), e.getY(), 1, 1);
        scrollRectToVisible(r);
    }
...
}

该片段在用户将图片从图片拖到图片外部的位置并暂停时滚动图片。 setAutoscrolls 方法由 JComponent 定义,用于辅助滚动拖动,但不实现它。将 autoscrolls 属性设置为 true 使组件在鼠标不移动时(因为它在组件外停止拖动)触发合成的鼠标拖动事件。组件的鼠标动作监听器需要监听这些事件并做出相应的反应。

设置滚动窗格的大小

除非显式设置滚动窗格的首选大小,否则滚动窗格会根据其九个组件的首选大小(视口和如果存在的话,两个滚动条、行和列标题以及四个角落)计算它的首选大小。最大的因素,也是大多数程序员关心的因素,是用于显示客户端的视口的大小。

如果客户端不具备滚动功能,则滚动窗格会自动调整大小,以便客户端以其首选大小显示。对于典型的不具备滚动功能的客户端,这使得滚动窗格变得多余。也就是说,滚动窗格没有滚动条,因为客户端的首选大小足够大,可以显示整个客户端。在这种情况下,如果客户端不会动态改变大小,您应该通过设置滚动窗格的首选大小或其容器的首选大小来限制滚动窗格的大小。

如果客户端具备滚动功能,则滚动窗格使用客户端的 getPreferredScrollableViewportSize 方法返回的值来计算其视口的大小。该方法的实现通常会为滚动返回一个较小的首选大小,该大小小于组件的标准首选大小。例如,默认情况下,JListgetPreferredScrollableViewportSize 方法返回的值刚好足以显示八行。

具备滚动功能的类,如列表表格文本组件,通常提供一个或多个方法,允许程序员影响 getPreferredScrollableViewportSize 返回的大小。例如,您可以通过调用 setVisibleRowCount 方法来设置列表或树中可见行的数量。列表或树会自动计算出需要显示该行数的大小。

有关与滚动相关的方法,请参阅与滚动相关的其他类中的方法,这些方法由 JScrollPane 以外的类提供。还记住,如果不喜欢 getPreferredScrollableViewportSize 返回的值,始终可以设置滚动窗格或其容器的首选大小。

动态更改客户端的大小

更改滚动窗格的客户区大小是一个两步过程。首先,设置客户区的首选大小。然后,在客户区上调用revalidate,让滚动窗格知道它应该更新自身和滚动条。让我们看一个例子。

下面是一个应用程序的图片,当用户放置一个圆形,其边界超出当前客户区的边界时,会改变客户区的大小。当用户清除绘图区域时,程序也会改变客户区的大小:

ScrollDemo2的快照

您可以在ScrollDemo2.java中找到此示例的完整源代码,该示例基于教程读者John Vella提供的示例。您可以运行ScrollDemo2下载JDK 7或更高版本)。

下面是在需要时更改绘图区域大小的代码:

if (changed) {
    //更新客户区的首选大小,因为图形所占用的区域已经变大或变小(如果已清除)。
    drawingArea.setPreferredSize(/* 新大小 */);

    //让滚动窗格知道要更新自身和滚动条。
    drawingArea.revalidate();
}

请注意,当客户区大小更改时,滚动条会相应调整。滚动窗格不会重新调整大小,视口也不会。

请参考SplitPaneDemo,了解另一个客户对象大小更改的示例。

滚动窗格API

以下表格列出了常用的滚动相关构造函数和方法。您最有可能在JScrollPane对象上调用的其他方法是其超类提供的setPreferredSize等方法。请参阅JComponent API以获取常用继承方法的表格。

使用滚动窗格的API可分为以下几类:

设置滚动窗格
(JScrollPane 构造函数和方法)
方法或构造函数 目的
JScrollPane()
JScrollPane(Component)
JScrollPane(int, int)
JScrollPane(Component, int, int)
创建一个滚动窗格。当存在 Component 参数时,设置滚动窗格的客户端。当存在两个 int 参数时,分别设置垂直和水平滚动条的策略。
void setViewportView(Component) 设置滚动窗格的客户端。
void setVerticalScrollBarPolicy(int)
int getVerticalScrollBarPolicy()
设置或获取垂直滚动策略。 ScrollPaneConstants 定义了三个值来指定这个策略: VERTICAL_SCROLLBAR_AS_NEEDED(默认值)、 VERTICAL_SCROLLBAR_ALWAYSVERTICAL_SCROLLBAR_NEVER
void setHorizontalScrollBarPolicy(int)
int getHorizontalScrollBarPolicy()
设置或获取水平滚动策略。 ScrollPaneConstants 定义了三个值来指定这个策略: HORIZONTAL_SCROLLBAR_AS_NEEDED(默认值)、 HORIZONTAL_SCROLLBAR_ALWAYSHORIZONTAL_SCROLLBAR_NEVER
void setViewportBorder(Border)
Border getViewportBorder()
设置或获取视口周围的边框。这比在组件上设置边框更好。
boolean isWheelScrollingEnabled() 设置或获取鼠标滚轮滚动时是否发生滚动。默认情况下启用鼠标滚轮滚动。
装饰滚动窗格
(JScrollPane 方法)
方法 目的
void setColumnHeaderView(Component)
void setRowHeaderView(Component)
为滚动窗格设置列或行标题。
void setCorner(String, Component)
Component getCorner(String)
设置或获取指定的角落。int 参数指定了哪个角落,并且必须是 ScrollPaneConstants 中定义的以下常量之一:UPPER_LEFT_CORNER、UPPER_RIGHT_CORNER、LOWER_LEFT_CORNER、LOWER_RIGHT_CORNER、LOWER_LEADING_CORNER、LOWER_TRAILING_CORNER、UPPER_LEADING_CORNER 和 UPPER_TRAILING_CORNER。
实现可滚动的客户端
方法 目的
int getScrollableUnitIncrement(Rectangle, int, int)
int getScrollableBlockIncrement(Rectangle, int, int)
(Scrollable接口要求)
获取单位或块的滚动增量(以像素为单位)。Rectangle参数是当前可见矩形的边界。第一个int参数要么是SwingConstants.HORIZONTAL,要么是SwingConstants.VERTICAL,取决于用户点击的滚动条。第二个int参数指示滚动的方向。小于0的值表示向上或向左滚动。大于0的值表示向下或向右滚动。
Dimension getPreferredScrollableViewportSize()
(Scrollable接口要求)
获取视口的首选大小。这允许客户端影响其显示的视口的大小。如果视口大小不重要,请实现此方法返回getPreferredSize
boolean getScrollableTracksViewportWidth()
boolean getScrollableTracksViewportHeight()
(Scrollable接口要求)
获取滚动面板是否应强制客户端与视口具有相同的宽度或高度。这两个方法中的任何一个返回true都会有效地禁止水平或垂直滚动。
void setAutoscrolls(boolean)
(在JComponent中)
设置是否在用户将鼠标拖到组件外部并停止时生成合成的鼠标拖动事件;这些事件对于通过拖动来进行滚动是必要的。默认情况下,该值为false,但许多可滚动的组件(如JTable和自定义组件)会将该值设置为true
与滚动相关的其他类中的方法void scrollRectToVisible(Rectangle)
(在 JComponent 中)void setVisibleRowCount(int)
int getVisibleRowCount()
(在 JList 中)getPreferredScrollableViewportSizevoid ensureIndexIsVisible(int)
(在 JList 中)scrollRectToVisiblevoid setVisibleRowCount(int)
int getVisibleRowCount()
(在 JTree 中)getPreferredScrollableViewportSizevoid scrollPathToVisible(TreePath)
void scrollRowToVisible(int)
(在 JTree 中)scrollRectToVisiblevoid setScrollsOnExpand(boolean)
boolean getScrollsOnExpand()
(在 JTree 中)void setPreferredScrollableViewportSize(Dimension)
(在 JTable 中)getPreferredScrollableViewportSize

使用滚动窗格的示例

这个表格展示了使用JScrollPane的示例以及这些示例所在的位置。

示例 所在位置 备注
ToolBarDemo 本节,
如何使用工具栏
展示了一个简单但典型的滚动窗格的用法。
ScrollDemo 本节 使用了滚动窗格的许多功能。
ScrollDemo2 本节 展示了如何改变客户端的大小。
SplitPaneDemo 如何使用分割窗格,
如何使用列表
在一个滚动窗格中放置了一个列表和一个标签。同时,展示了如何处理滚动窗格的客户端大小变化的情况。
TableDemo 如何使用表格 在一个滚动窗格中放置了一个表格。
TextSamplerDemo 使用文本组件 分别在一个滚动窗格中放置了一个文本区域、一个编辑器面板和一个文本面板。
TreeDemo 如何使用树 在一个滚动窗格中放置了一个树。

如果您正在使用JavaFX,请参考滚动窗格


上一页:如何使用Root Panes
下一页:如何使用分隔符