在本章中,我们将会了解这些Swing组件的类似与不同之处。我们的讨论由共享的数据模型BoundedRangeModel开始。
BondedRangeModel接口是本章中描述的组件所共享的MVC数据模型。这个接口包含了描述范围值minimum, maximum, value, extent所必须的四个交互关联的属性。
minimum与maximum属性定义了模型值的范围。value属性定义了我们所认为的模型的当前设置,而value属性的最大值并不一定是模型的maximum属性值。相反,value属性可以使用的最大设置是小于extent属性的maximum属性。为了有助于我们理解这些属性,图12-1显示了这些设置与JScollBar的关系。extent属性的其他目的依赖于作为模型视图的属性。
四个属性的设置必须满足下列关系:
minimum <= value <= value+extent <= maximum
当一个设置发生变化时,也许就会触发其他设置的变化,以满足上面的大小关系。例如,将minimum修改为当前value加上extent与maximum之间的一个设置会使得减少extent并且增加value来保持上面的大小关系。另外,原始属性的变化会导致修改为一个新设置而不是所请求的设置。例如,尝试将value设置为小于minimum或是maximum会将value设置为最接近范围极限的值。
BoundedRangeModel接口的定义如下:
public interface BoundedRangeModel { // Properties public int getExtent(); public void setExtent(int newValue); public int getMaximum(); public void setMaximum(int newValue); public int getMinimum(); public void setMinimum(int newValue); public int getValue(); public void setValue(int newValue); public boolean getValueIsAdjusting(); public void setValueIsAdjusting(boolean newValue); // Listeners public void addChangeListener(ChangeListener listener); public void removeChangeListener(ChangeListener listener); // Other Methods public void setRangeProperties(int value, int extent, int minimum, int maximum, boolean adjusting); }
尽管模型可用的不同属性设置是JavaBean属性,当属性设置发生变化时,接口使用Swing的ChangeListener方法而不是java.beans.PropertyChangeListener。
当用户正在执行一系列的模型快速变化时,也许是在屏幕上拖动滑块所造成的,模型的valueIsAdjusing属性就派上用场了。对于某些只对何时设置模型的最终值感兴趣的人,在getValueIsAdjusting()返回false之前监听器会忽略所有的改变。
实际实现BoundedRangeModel接口的Swing类是DefaultBoundedRangeModel。这个类会小心处理为了保证不同属性值的相应顺序所必需的调整。他同时管理ChangeListener列表在模型发生变化时通知监听器。
DefaultBoundedRangeModel有两个构造函数:
public DefaultBoundedRangeModel() public DefaultBoundedRangeModel(int value, int extent, int minimum, int maximum)
无参数版本会将模型的minimum, value与extent属性设置为0。余下的maximum属性设置为100。
第二个构造函数版本需要四个整形参数,显式设置四个属性。对于这两个构造函数,valuesAdjusting属性的初始值均为false,因为模型值在初始值之外并没有发生变化。
注意,除非我们在多个组件之间共享模型,通常并没有必要创建BoundedRangeMode。如果我们要在多个组件之间共享模型,我们可以创建第一个组件,然后获取其BoundedRangeModel模型进行共享。
类似于通常的管理其监听器列表的所有类,我们可以向DefaultBoundedRangeModel查询赋给他的监听器。这里我们可以使用getListeners(ChangeListener.class)方法获取模型的ChangeListener列表。这会返回一个EventListener对象数组。
最简单的边界范围组件是JScrollBar。JScrollBar组件用在我们在第11章所描述的JScrollPane容器中来控制滚动区域。我们也可以将这个组件用在我们自己的容器中,尽管由于JScrollPane的灵活性,通常并不必需这样做。然而关于JScrollBar我们需要记住的一点就是JScrollPane并用于值,而是用于屏幕的滚动区域。对于值,我们要使用在下节将要讨论的JSlider组件。
注意,JScrollPane中的JScrollBar实际是上是JScrollBar的一个特殊子类,他能够正确处理实现了Scrollable接口的可滚动组件。尽管我们可以修改JScrollPane的滚动条,但是通常并不需要这样做,而且所需要工作比我们认为要多得多。
如图12-2所示,水平的JScrollBar由几部分组成。由中间开始向前,我们可以看到滚动条的滑块。滑块的宽度是BoundedRangeModel的extent属性。滚动条的当前值是滑块的左边。滑块的左边与右边是块翻页区域。点击滑块的左边并减少滚动条的值,而点击右边会增加滚动条的值。滑块增加或减少的数量值是滚动条的blockIncrement属性。
在滚动条的左边与右边是箭头按钮。当点击左箭头时,滚动条会减少一个单元。滚动条的unitIncrement属性指定了这个单元。通常情况,这个值为1,尽管并不是必须这样。在左箭头的右边是滚动条的最小值与模型。除了用左箭头减少值以外,点击右箭头会使得滚动条增加一个单元。右箭头的左边是滚动条的最大范围。最大值实际上略远于左边,这里略远的距离是由模型的extent属性来指定的。当滑块紧邻右箭头时,这会将滚动条的滚动动条值放在滑块的左边,对于所有其他的位置也是如此。
垂直JScrollBar是由与水平JScrollBar相同的部分组成的,最小值与减少部分位顶部,而且值是由滚动条滑块的上边决定的。最小值与增加部分位于底部。
正如前面所提到的,JScrollBar模型是BoundedRangeModel。用户界面的委托是ScrollBarUI。
现在我们已经了解了JSrollBar的不同部分,现在我们来了解一下如何使用。
JScrollBar有三个构造函数:
public JScrollBar() JScrollBar aJScrollBar = new JScrollBar(); public JScrollBar(int orientation) // Vertical JScrollBar aJScrollBar = new JScrollBar(JScrollBar.VERTICAL); // Horizontal JScrollBar bJScrollBar = new JScrollBar(JScrollBar.HORIZONTAL); public JScrollBar(int orientation, int value, int extent, int minimum, int maximum) // Horizontal, initial value 500, range 0-1000, and extent of 25 JScrollBar aJScrollBar = new JScrollBar(JScrollBar.HORIZONTAL, 500, 25, 0, 1025);
使用无参数的构造函数会使用默认的数据模型创建一个垂直滚动条。模型的初始值为0,最小值为0,最大值为100,而扩展值为10。这个默认模型只提供了0到90的范围。我们可以显示的将方向设置为JScrollBar.HORIZONTAL或是JScrollBar.VERTICAL。如果我们不喜欢其他两个构造函数所提供的初始模型设置,我们需要自己进行显示的设置。如果数据元素没有正确的进行约束,正如前面关于BoundedRangeModel所描述的,则会抛出一个IllegalArgumentException,使得JScrollBar构造中断。
很奇怪没有出现在构造函数列表中的接受BoundedRangeModel参数的构造函数。如果我们有一个模型实例,我们可以在创建了滚动条之后调用setModel(BoundedRangeModel newModel)方法或是在创建构造函数时由模型获取单个属性,如下所示:
JScrollBar aJScrollBar = new JScrollBar (JScrollBar.HORIZONTAL, aModel.getValue(), aModel.getExtent(), aModel.getMinimum(), aModel.getMaximum())
由J2SE平台的1.3版本开始,滚动条不再参与焦点遍历。
一旦我们创建了JScrollBar,如果我们对滚动条的值何时发生变化感兴趣,则我们需要监听这些变化。有两种监听的方法:AWT 1.1事件模型方法以及Swing MVC方法。AWT方法涉及到将AdjustmentListener关联到JScrollBar。MVC方法涉及到将ChangeListener关联到数据模型。每一种方法都可以工作得很好,如果模型通过编程变化或是用户拖动滚动条滑块,两种方法都会得到通知。后一种方法提供了更多的灵活性,因而是一个不错的选择,除非我们是在多个组件之间共享数据模型并且需要知道哪一个组件修改了模型。
使用AdjustmentListsener监听滚动事件
将AdjustmentListener关联到JScrollBar使得我们可以监听用户修改滚动条设置。下面的代码片段,将会用在稍后的列表12-3中,显示了为了监听用户修改JScrollBar的值,我们如何将AdjustmentListsener关联到JScrollBar。
首先,定义简单输出滚动条当前值的AdjustmentListsener:
AdjustmentListener adjustmentListener = new AdjustmentListener() { public void adjustmentValueChanged (AdjustmentEvent adjustmentEvent) { System.out.println ("Adjusted: " + adjustmentEvent.getValue()); } };
在我们创建了监听器之后,我们可以创建组件并且关联监听器:
JScrollBar oneJScrollBar = new JScrollBar (JScrollBar.HORIZONTAL); oneJScrollBar.addAdjustmentListener(adjustmentListener);
这种监听修改事件的方法可以工作得很完美。然而,我们也许会更喜欢将ChangeListener关联到数据模型。
使用ChangeListener监听滚动事件
将ChangeListener关联到JScrollBar数据模型会在我们的程序设计中提供更多的灵活性。使用AWT AdjustmentListener,只有滚动条的值发生变化时监听器才会得到通知。另一方面,当最小值,最大值,当前值,以及扩展值发生变化时,所关联的ChangeListener会得到通知。另外,由于模型有一个valueIsAdjusting属性,我们可以选择忽略即时变化事件-一些我们可以使用AdjustmentListener,通过Adjustment中相同名的属性处理的事件。
为了进行演示,定义了一个当模型完成调整时输出滚动条当前值的ChangeListener,如列表12-1所示。我们可以通过本章来加强BoundedChangeListener类。
package swingstudy.ch11; import javax.swing.BoundedRangeModel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; public class BoundedChangeListener implements ChangeListener { @Override public void stateChanged(ChangeEvent event) { // TODO Auto-generated method stub Object source = event.getSource(); if(source instanceof BoundedRangeModel) { BoundedRangeModel aModel = (BoundedRangeModel)source; if(!aModel.getValueIsAdjusting()) { System.out.println("Changed: "+aModel.getValue()); } } else { System.out.println("Something changed: "+source); } } }
一旦我们创建了监听器,我们也可以创建组件并且关联监听器。在这个特定的例子中,我们需要将监听器关联到组件的数据模型,而不是直接关联到组件。
ChangeListener changeListener = new BoundedChangeListener(); JScrollBar anotherJScrollBar = new JScrollBar (JScrollBar.HORIZONTAL); BoundedRangeModel model = anotherJScrollBar.getModel(); model.addChangeListener(changeListener);
列表12-2显示了测试程序的源码。
package swingstudy.ch11; import java.awt.BorderLayout; import java.awt.EventQueue; import java.awt.event.AdjustmentEvent; import java.awt.event.AdjustmentListener; import javax.swing.BoundedRangeModel; import javax.swing.JFrame; import javax.swing.JScrollBar; import javax.swing.event.ChangeListener; public class ScrollBarSample { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Runnable runner = new Runnable() { public void run() { AdjustmentListener adjustmentListener = new AdjustmentListener() { public void adjustmentValueChanged(AdjustmentEvent event) { System.out.println("Adjusted: "+event.getValue()); } }; JScrollBar oneJScrollBar = new JScrollBar(JScrollBar.HORIZONTAL); oneJScrollBar.addAdjustmentListener(adjustmentListener); ChangeListener changeListener = new BoundedChangeListener(); JScrollBar anotherJScrollBar = new JScrollBar(JScrollBar.HORIZONTAL); BoundedRangeModel model = anotherJScrollBar.getModel(); model.addChangeListener(changeListener); JFrame frame = new JFrame("ScrollBars R US"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(oneJScrollBar, BorderLayout.NORTH); frame.add(anotherJScrollBar, BorderLayout.SOUTH); frame.setSize(300, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
当运行这个程序时,他会显示两个水平滚动条,如图12-3所示。移动滚动条的输出发送到终端容器。
在我们创建了JScrollBar之后,修改其底层数据模型就变得必要了。我们可以使用public BoundedRangeModel getModel()方法获得模型,然后直接修改模型。更可能的,我们仅需要调用组件的相应方法:
• setValue(int newValue), setExtent(int newValue), setMinimum(int newValue) • setMaximum(int newValue)
注意,尽管支持,但是并不推荐在显示组件之后修改JScrollBar方向。这会极大的影响用户的满意度并且使得用户去寻找其他的解决方案。
除了数据模型属性,表12-1显示了JScrollBar的16个属性。
每一个可安装的Swing观感都提供了不同的JScrollBar外观以及默认的UIResource值集合。图12-4显示了预安装的观感类型Motif,Windows,以及Ocean的JScrollBar组件的外观。
表12-2显示了JScrollBar的UIResource相关属性集合。有28个不同的属性。