经常用 Swing 开发 Java GUI 程序的人一定听过这样的说法 ,Swing 控件是按 MVC结构设计的。更准确地说, Swing是 Model-driven的结构。但不同 Swing控件的 Model,其作用是否相同呢?比如当你在使用 JButton时,你很少需要关心 ButtonModel的存在,但在 JTable使用时,你却总是需要用到 TableModel。更进一步,当你频繁的使用 JTable时 ,你会发现你可能不仅用到了 TableModel,还用到 TableColumnModel, ListSelectionModel。这使我们意识到 ,Model存在不同的种类,不同类型的 Model实现不同的功能。
首先,我们讨论第一种 Model, GUI–State Model 。 GUI-State Model的作用在于标识控件的视觉状态 (visual status)。例如按钮是否被点击,列表中的 Item是否被选中。 Swing的控件会代理对 GUI-State Model的操作,通常我们不要直接操作 GUI–State Model。
最常见的 GUI-State Model是 ButtonModel,属于这个范畴的控件有 JButton , JToggleButton , JCheckBox, JRadioButton, JMenu, JMenuItem, JCheckBoxMenuItem, JRadioButtonMenuItem。 (所有 AbstractButton的子类 )
ButtonModel需要标识的状态有:
显而易见,这些属性都只和显示有关。对于 GUI-State Model,只有以下两种情况我们需要关心它的处在 :( 1)我们想改变控件缺省的视觉行为(假定这种情况很少发生) (2)出于某种显示目的共用 Model,操作一个控件会改变另外一个控件的状态(下面会讨论到这种情况),其他情况下我们可以忽视它。当然还有一种情况我们需要注意,这就是在使用 JRadioButton时。因为使用 JRadioButton时,一组 JRadioButton同时只能有一个被选中 (SELECTED),这当然只有通过操作 ButtonModel的 SELECTED属性来实现。不过, Swing针对这个问题引入了 ButtonGroup类,通过 ButtonGroup.add()方法设置同一个 button group,因此我们同样不需要直接操作 ButtonModel。
另一个常见的 GUI-State Model是 BoundedRangeModel,属于这个范畴的控件有 JProgressBar JScrollBar JSlider。
BoundedRangeModel标识的主要状态有: min,max,value(int),同样的,我们很少直接操作 BoundedRangeModel。使用 JProgressBar 最常见的方式是在构造函数里指定 min,max或是通过 get/set读写 min,max,value。而控件再把这些请求转发给 BoundedRangeModel。
前面提到出于某种显示目的,我们有可能需要直接操作 GUI-State Model。以下是一种可能的情况 (scenario):当我们把一幅面积较大的图像放在 JScrollPane,同时希望通过移动滑杆 (JSlider)来控制显示图像显示在 JScrollPane的部分。常见的做法是监听 BoundedRangeModel的 ChangeEvent事件 (addChangeListener(ChangeListener l)),当 JSlider改变了 Model的值时在 ChangeListener对显示作相应的调整。
TableColumnModel是 JTable特有的 GUI-State Model。 TableColumnModel用于管理 TableColumn。而 TableColumn代表了 JTable中的每一列数据的视觉属性,比如该列对应的 data-model index(这决定了要显示的内容,参见后面叙述) ,该列的宽度是否可变,列的最大、最小、首选宽度;该列的绘制器 TableCellRenderer和编辑器 TableCellEditor(JTable是面向列的,它基于每一列进行绘制和编辑 )
考虑这样一个问题,当使用 JTable时,如何设定从 X行到 Y行处于选择状态呢?
我们可以通过调要 JTable.setRowSelectionInterval(int index0, int index1)来实现。再进一步,如果想实现反转选择 (Toggle Selection),即单击齐数次处于选择状态,偶数次则处于非选择状态, JTable没有提供直接的方法来实现。因为 JTable将选择的工作交由 Selection Model来实现。察看 setRowSelectionInterval的实现
public void setRowSelectionInterval(int index0, int index1) {
selectionModel.setSelectionInterval(boundRow(index0), boundRow(index1));
}
要实现 Toggle Selection就只有直接对 Selection Model进行编程。
Selection Model也属于 GUI- State Model的范畴,因为它标识也是一种视觉的状态,选中的 Item会加亮( high light)。和其他 GUI- State Model一样 ,通常我们不需要直接操作 Selection Model。
Selection Model标识的状态有:
我们分析一下其他需要判断用户选择几种控件
对待 Selection Model的方式和其他 GUI-State Model一样,相应 Jcomponent都提供专门的函数屏蔽我们对它的直接操作。
这类的 Model决定了显示在控件中的内容,因此往往需要我们直接的操作。他们分别是:
Swing首先定义了接口 ListModel
然后定义了抽象类 AbstractListModel实现这个接口。在抽象类里没有定义实际数据的存储方式。因此要实现 AbstractListModel,用户还需要定义这两个函数
因为没有定义实际数据的存储方式,当然没有办法提供这两个函数的实现。
最后 Swing提供缺省类 DefaultListModel实现抽象类,缺省类以 Vector作为存储数据的方式。
构造一个 JList的实例有四种方式:
JList()
JList(final Object[] listData)
JList(final Vector listData)
JList(ListModel dataModel)
前三种构造函数里会分别生成相应的 ListModel。还可以在构造完后 JList还可以用以下的函数来制定 ListModel
void setListData(final Object[] listData)
void setListData(final Vector listData)
void setModel(ListModel model)
JList没有提供编辑其 Item的方法,用户是无法直接编辑其 Item的(这点和 JComboBox不同, JComboBox提供了直接编辑其 Item的方法),要改变 Item的内容需要直接操作 ListModel(用数组和 Vector生成 JList不适合用来显示可变内容的数据)。
要显示可变内容的 JList,最方便的方法是用 DefaultListModel,但由于它用 Vcetor作为其内部的存储数据的方式,决定他们在处理大数据量的显示时是不适宜的。首先 Vector有内部容量的概念,当容量不足以容纳更多的数据时 ,它需要重新分配一块内存,复制原内存的东西,并把原来的内存丢弃,这是非常耗时的动作;其次, Vector是线程安全的容器 (thread-safe collection),所有对容器的操作都需要同步 (synchronized),对于包含大数据量的 collection这也是非常耗时的。
因此对于大数据量的 Application-data,用户如果想用 collection,应该在 ArrayList和 LinkedList(thread-unsafe collection)之间选择:
Swing对 TableModel的处理和 ListModel类似:
首先定义了接口 TableModel,
然后定义了抽象类实现这个接口 AbstractTableModel。在抽象类里没有定义实际数据的存储方式。
要实现 AbstractTableModel,用户还需要定义这三个函数
public String getColumnName(int column) 往往也需要定义,不然在表头将显示为 A,B,C,D …
最后 Swing提供缺省类 DefaultTableModel实现抽象类,缺省类以 Vector作为存储数据的方式。因此对大数据量的操作(比如用 JTable显示数据库查询的结果)同样不适合用 DefaultTableModel。当用 JTable显示数据库查询的结果,最好是扩展
AbstractTableModel,并让数据库每次只返回批量的数据。
JTable的构造函数比起 JList要复杂一些 , 因为它还需要指定 TableColumnModel。但对于 Application-data model 的处理和 JList是一样的。
JComboBox和 JList很相似,都是用来显示一个列表项,并可以接受用户的选择 (JRadioButton也实现这个功能 )。但他们也有本质的不同, JComboBox可以接受用户的输入,并可以编辑已有的选项。通常在使用 JComboBox是把它分成两类:可编辑的和不可编辑的。缺省状态是不可编辑的,通过调用 JComboBox.setEditable(true)可将 JComboBox设置成可编辑。对应这两种状态 JComboBox定义了两类 data-model接口, ComboBoxModel和 MutableComboBoxModel。
ComboBoxModel的定义如下:
public interface ComboBoxModel extends ListModel {
void setSelectedItem(Object anItem);
Object getSelectedItem();
}
它扩展 ListModel并只是定义了用于获取和设置当前选项的办法,这是因为 JComboBox没有 Selection Model。
MutableComboBoxModel,顾名思义,当然是定义修改 data-model的方法,它的定义如下:
public interface MutableComboBoxModel extends ComboBoxModel {
public void addElement( Object obj );
public void removeElement( Object obj );
public void insertElementAt( Object obj, int index );
public void removeElementAt( int index );
}
Swing提供对 MutableComboBoxModel的实现 DefaultComboBoxModel,其内部 Vector来存储数据,当我们想提供自己的现实时,最方便的方法是可以扩展 AbstractListModel, 并选择是实现 ComboBoxModel还是 MutableComboBoxModel。
构造一个 JComboBox实例有四种方法:
public JComboBox()
public JComboBox(final Object items[])
public JComboBox(Vector items)
public JComboBox(ComboBoxModel aModel)
前三种构造函数里会分别生成相应的 DefaultComboBoxModel。 (个人觉得这样构造 JComboBox并不好,由于没有通过构造函数建立不变性 (invariants) ,即该 JComboBox是否可编辑的,当需要修改 data-model时, JComboBox都需要首先判断 data-model是否可编辑,对于不可编辑的 JComboBox调用编辑函数会丢出 RuntimeException ())
TreeModel时最复杂的一种 data-model,参考文献一有详细说明
各类 Text控件是比较独立的主题,这里不再详述。
[1] Understanding the TreeModel :
http://java.sun.com/products/jfc/tsc/articles/jtree/
[2] A Swing Architecture Overview : http://java.sun.com/products/jfc/tsc/articles/architecture/index.html
[3] JGuru Faq:
[4] « Swing » by Matthew Robinson & Pavel Vorobiev