尽管JScrollBar对于屏幕滚动区域十分有用,但是他并不适用于使得用户在一个范围内进行输入。对于这个目的,Swing提供了JSlider组件。除了提供了类似JScrollBar组件所提供的可拖动滑块以外,JSlider同时提供了可视化的标记以及标签来辅助显示当前的设置并且选择新的设置。图12-5显示了几个JSlider组件的示例。
JSlider是由几部分组成的。我们所熟悉的BoundedRangeModel存储组件的数据模型,而Dictionary存储用于标记的标签。用户界面委托是SliderUI。
现在我们已经了解了JSlider组件的不同部分,下面我们来探讨如何使用JSlider。
JSlider有六个构造函数:
使用无参数的构造函数会使用默认的数据模型创建一个水平滑动器。模型的初始值为50,最小值为0,最大值为100,而扩展值为0。我们也可以使用JSlider.HORIZONTAL或是JSlider.VERTICAL显示设置方向,使用各种构造函数设置特定的模型属性。另外,我们也可以显式设置组件的数据模型。
如果我们使用一个预配置的BoundedRangeModel,记住当创建模型时要将扩展值设置为0。如果extent属性大于0,value属性的最大设置就会减少相应的值,而且value设置绝不会达到maximum属性设置。
注意,将方向初始化为VERTICAL与HORIZONTAL以外的值会抛出IllegalArgumentException。如果范围与初始值不遵循BoundedRangeModel的规则,则实始化数据模型的所有构造函数会抛出IllegalArgumentException。
我们可以使用ChangeListener监听JSlider的变化。与JScrollBar不同,JSlider并没有AdjustmentListener。可以将前面JScrollBar示例中的BoundedChangeListener添加到JSlider的数据模型,然后当模型发生变化时我们就可以得到通知。
除了将ChangeListener关联到模型,我们还可以将ChangeListener直接关联到JSlider本身。这可以使得我们在视图与独立监听变化之间共享数据模型。这需要我们略微修改前面的监听器,因为现在变化的事件源是JSlider,而不是BoundedRangeModel。更新的BoundedChangeListener显示在列表12-3中,可以适用于两种情况下的关联。在下面的列表中变化的部分以粗体标出。
与滑动器的关联可以直接进行,而无需通过模型间接进行。
aJSlider.addChangeListener(changeListener);
在我们创建了JSlider之后,我们也许希望修改其底层数据模型。类似于JScrollBar,我们可以使用public BoundedRangeModel getModel()方法获取模型,然后直接修改模型。我们也可以直接调用组件的方法:
类似于JScrollBar,这些方法只是作为代理,并且将方法调用重定向到对应的模型方法。
表12-3显示了JSlider的19个属性。
在JSlider中显示刻度标记
JSlider组件允许我们可以在水平滑动器的下边或是垂直滑动器的右边添加刻度标记。这些标记可以使得用户大致估计滑动器的值与尺度。可以具有主标记与次标记;主标记的绘制要略微长些。可以显示一个或是两个同时显示,也可以都不显示,而这则是默认设置。
注意,由技术上来说,自定义的观感可以将标记放置在任何地方。然而,系统提供的观感类型将标记放置在下边或是右边。
要显示刻度标记,我们需要使用public void setPaintTicks(boolean newValue)方法允许刻度绘制。当使用true设置来调用时,该方法会以允许次标记与主标记的绘制。默认情况下,两种刻度标记类型的刻度空间被设置为0。当有一个设置为0时,则该刻度类型不会显示。因为两个都初始为0,我们必须修改一个刻度空间的值来查看刻度。public void setMajorTickSpacing(int newValue)与public void setMinorTickSpacing(int newValue)方法都支持这种修改。
为了进行演示,图12-6显示了四个滑动器。这有利于主刻度空间是次刻度空间整数倍的情况。另外,刻度空间不应太窄,因而使得刻度看起来像是一个块。
图12-6中的示例源码显示在列表12-4中。顶部的滑动器没有刻度。底部的滑动器具有主刻度空间与次刻度空间,次刻度为5个单位,而主刻度为25个单位。左边的滑动器显示了一个糟糕的刻度空间,次刻度为6个单位,而主刻度为25个单位。右边的滑动器次刻度为单个单元,结果导致空间过于紧凑。
停靠JSlider滑块位置
另一个与刻度标记相关的JSlider属性是通过public void setSnapToTicks(boolean newValue)方法设置的snapToTicks属性。当这个属性为true且显示在刻度标记时,在我们移动了滑块之后,滑块只停留在刻度处。例如,滑动器的滑块范围为0到100,并且每一个刻度为10个单位,如果我们将滑块拖动到33刻度处,滑块就会停靠30刻度处。如果刻度标记没有显示,属性设置没有影响,包括没有刻度标记显示标签时。
标记JSlider位置
如图12-5所示,我们可以使用Component标记JSlider中的位置。当一个位置被标记,组件会相邻显示。标记存储在一个派生自Dictionary类的查询表中,其中键值是Integer位置,而值则是Component。任何的AWT Component都可以是标签;然而,JLabel最合适这一角色。图12-7显示了图12-5中右边滑动器的词典的样子。
通常,Dictionary将标签存储在一个Hashtable中。然而,扩展自Dictionary类并且使用Integer键值的类也可以。在我们创建了我们的标签词典以后,我们可以使用public void setLabelTable(Dictionary newValue)方法将词典与滑动器相关联。下面的代码创建与一个与图12-7相关联的标签查询表。
注意,请记住在J2SE 5.0中,编译器会自动将一个int参数装箱为一个Integer。
简单的标签表与滑动器相关联并不会显示标签。为了能够绘制标签,我们必须使用参数true来调用public void setPaintLabels(boolean newValue)方法。如果我们没有手动创建标签表,系统会使用反映主标记空间的内部值来为我们创建一个。例如,图12-5中左边的滑动器的范围为0到100,而主标记空间为10。当在这个滑动器上调用stPaintLabels(true)方法,标签创建在0,10,20等处,直到100。次标记空间与标签的自动生成无关。而且为了标签显示标记并不需要绘制;getPaintTicks()方法可以返回false。
标签的自动创建是通过public Hashtable createStandardLabels(int increment)方法实现的,其中increment是主标记空间。我们并不需要直接调用这个方法。如果我们希望并不由最小值创建标签,我们可以调用重载的public Hashtable createStandardLabels(int increment, int start)方法 ,并且将哈希表关联到滑动器。
每一个可安装的Swing观感都提供了不同的JSlider外观以及默认的UIResource值集合。图12-8显示了预安装的观感类型集合下的JSlider组件的外观。
两个观感类型相关的属性是JSlider类定义的一部分。默认情况下,水平滑动器的最小滑块值位于左边;对于垂直滑动器,则是在下边。要修改滑动器的方向,使用参数true调用public void setInverted(boolean newValue)方法。另外,滑块沿着移动的轨道默认显示。我们可以通过public void setPaintTrack(boolean newValue)方法来关闭显示。false值会关闭轨道显示。图12-9显示了JSlider轨道的样子并且标出了常规与相反滑动器的最小值与最大值位置。
表12-4显示了JSlider的30个UIResource相关的属性。
JSlider资源允许我们自定义通过JSlider或是SliderUI方法不能访问的元素。例如,要自定义我们程序的的JSlider外观,我们也许希望修改可拖动的滑块的图标。通过很少的几行代码,我们可以使用任意的图标作为我们程序中滑动器的滑动图标。
图12-10显示了结果。类似于所有的UIResource属性,这种修改将会影响在设置属性之后所创建的所用的JSlider组件。
注意,图标的高度与宽度受限于滑动器的维度。修改icon属性并不会影响滑动器尺寸。
默认情况下,对于Metal观感,当轨道可见时,当滑块在其上移动时轨道并不会变化。而且,我们可以允许将会通知滑块填充滑块所移动过的直到当前的值的轨道部分的客户属性。这个属性名为JSlider.isFilled,而Boolean对象表示当前的设置。默认情况下,这个设置为Boolean.FALSE。图12-11演示了Boolean.TRUE与Boolean.FALSE设置;代码片段如下:
这个设置只在Metal观感下起作用。Metal观感的Ocean主题会忽略这一设置,总是绘制填充的轨道。要获得这一行为,我们需要将系统属性swign.metalTheme设置为steel,例如java -Dswing.metalTheme=steel ClassName。
Swing的JProgressBar不同于其他的BoundedRangeModel组件。其主要目的并不是由用户获取输入,而是展示输出。输出以过程完成百分比的方式进行显示。当百分比增加时,在组件上会显示一个过程栏,直接工作完成并且过程栏被填满。过程栏的运动通常是某些多线程任务的一部分,从而避免影响程序的其他部分。
图12-12显示了一些示例JProgressBar组件。顶部的过程栏使用所有的显示特性。底部的过程栏在组件的周围添加了一个边框,并且显示完成百分比。右边的过程栏移除了边框,而左边的过程栏具有一个固定的字符串表示来替换完成百分比。
由面向对象的角度来看,JProgressBar有两个主要部分:我们所熟悉的BoundedRangeModel存储组件的数据模型,而ProgressUI是用户界面委托。
注意,在对话框中显示一个过程栏,使用在第9章所讨论的ProgressMonitor类。
JProgressBar有五个不同的构造函数:
使用无参数的构造函数创建JProgressBar时会使用默认的数据模型创建一个水平的过程栏。模型的初始值为0,最小值为0,而最大值为100,扩展值为0。过程栏有一个扩展,但是不使用,尽管他是数据模型的一部分。
我们可以使用JProgressBar.HORIZONTAL或是JProgressBar.VERTICAL显示设置方向,同时可以使用不同的构造函数设置任意的特定模型属性。另外,我们可以为组件显示设置数据模型。
注意,将方向设置为VERTICAL或HORIZONTAL以外的值会抛出IllegalArgumentException。
由BoundedRangeModel创建JProgressBar有一些笨拙,因为过程栏会忽略一个设置并且初始值被初始化为最小值。假定我们希望JProgressBar向用户所期望的样子启动,我们需要记住当创建模型时要将扩展设置为0,并且将值设置最小值。如果我们增加extent属性,value属性的最大设置就会减少相应的量,从而value设置不会达到maximum属性的设置。
在我们创建了JProgressBar之后,我们需要对其进行修改。表12-5显示了JProgressBar的14个属性。
绘制JProgressBar边框
所有的JComponent子类默认都有一个border属性,而JProgressBar有一个特殊的borderPainted属性可以很容易的允许或是禁止边框的绘制。使用参数false调用public void setBorderPainted(boolean newValue)方法可以关闭过程栏边框的绘制。图12-12右侧的过程栏就关闭了其边框。其初始化代码如下:
标识JProgressBarJProgressBar支持在组件中间显示文本。这种标签有三种形式:
图12-12的左边与底部过程栏分别显示了固定标签与百分比标签。创建这两个过程栏的代码如下:
使用不确定的JProgressBar
某些过程并没有固定的步骤数目,或者是他们具有固定的步骤数目,但是我们并不知道在所有的步骤完成之间的数目。对于这种操作类型,JProgressBar提供了一种不确定模式,在这种模式中依据过程栏的方向,JProgressBar中的过程栏会由一边到另一边不断运行,或是由上到下不断运动。要允许这种模式,只需要使用true值为调用public void setIndeterminate(boolean newValue)方法。图12-13显示了不确定过程栏在不同时刻的样子。滑动块的长度是可用空间的六分之一,并且是不可设置的。
沿着JProgressBar步进
JProgressBar的主要用法显示我们在一系列操作中前进的过程。通常情况下,我们将过程栏最小值设置为0,而最大值设置为要执行的步骤数目。由value属性值0开始,当我们执行每一个步骤时向增加值向最大值靠近。所有这些操作意味着多线程,事实上,这是绝对必需的。另外,当更新过程栏的值时,我们需要记住只能在事件分发线程中更新(借助于EventQueue.invokeAndWait()方法)。
过程栏在一个范围内前进的过程如下:
1、初始化。这是使用所需要的方向与范围创建JProgressBar的基本过程。另外,在这个过程中执行边框与标签操作。
2、启动线程执行所需要的步骤。也许是作为在屏幕上执行动作的结果,我们需要启动线程来完成过程栏的工作。我们需要启动一个新线程,从而用户界面可以保持响应。
3、执行步骤。忽略更新过程栏,而是编写相应的代码来执行每一个步骤。
4、对于每一个步骤,使得线程在事件线程内更新过程栏。在for循环之外只创建一次Runnable类。没有必要为每一个步骤创建一个。
在循环之内,通知runner更新过程栏。这个更新必须使用特殊的EventQueue方法invokeLater()或是invokeAndWait()在事件线程内完成,因为我们正在更新JProgressBar的属性。
完整的运行示例显示在列表12-5中。
通过简单的将列表12-5中的sleep动作修改为所需要的操作,这个示例提供了一个合适的重用框架。
注意,要使得过程栏填充相反的方向,使得值由最大值开始并且在每一个步骤减小。也许我们并不希望显示完成的百分比字符串,因为他将由100%开始减小到0%。
由技术上来说,JProgressBar类通过ChangeListener支持数据模型变化的通知。另外,我们可以将ChangeListener关联到其数据模型。因为过程栏更多的意味着提供可视化的输出而不是获得输入,我们通常并会对其使用ChangeListener。然而,有时这却是适用的。要重用本章前面列表12-3中的BoundedRangeChangeListener,对其进行修改(列表12-6中以粗体显示),因为这些变化事件的源是JProgressBar。
每一个可安装的Swing观感都提供了不同的JProgressBar外观以及默认的UIResource值集合。图12-5显示了JProgressBar组件在预安装的观感类型集合下的外观。
表12-6显示了JProgressBar可用的UIResource相关属性的集合。他具有15个不同的属性。
JTextField组件并不是一个技术上的bounded-range组件,但是,他却使用BoundedRangeModel。当组件内容的宽度超出其可见的水平空间时,内建在JTextField内部的是一个可滚动的区域。BoundedRangeModel控制这个滚动区域。我们将会在第15章更详细的探讨JTextField组件。在这里我们可以了解JSrollBar如何跟踪JTextFiled的滚动区域。图12-16显示了一个实际的例子,而列表12-7显示了源码。
通常情况下,JTextField并没有相关联的滚动条。事实上,大多数的观感类型并不提供。然而,如果这个组件是我们希望进行交互的组件,我们可以在我们的程序中进行重用。大量的访问方法可以重用得很简单,而且我们可以避免直接访问内部成员的需要。
在本章中,我们了解了如何使用Swing的JScrollBar,JSlider与JProgressBar组件。我们了解了每一个组件如何使用BoundedRangeModel接口来控制操作这些组件所必需的内部数据,以及DefaultBoundedRangeModel类如何为这个数据模型提供了一个默认实现。
现在我们知道了如何使用各种具有边界范围的组件,我们可以继续进入第13章,在那里我们将会了解提供数据选择的控件:JList与JComboBox。