12.9.2 不强制存储列表项的ListModel和ComboBoxModel

12.9.2 不强制存储列表项的ListModel和ComboBoxModel

正如前面提到的,Swing的绝大部分组件都采用了MVC的设计模式,其中JListJComboBox都只负责组件的外观显示,而组件底层的状态数据维护则由对应的Model负责。
JList对应的ModelListModel接口,
JCombobox对应的ModelComboBoxModel接口.

这两个接口负责维护JListJCombobox组件里的列表项。

ListModel接口

ListModel接口的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
package javax.swing;
import javax.swing.event.ListDataListener;
public interface ListModel<E>{
//返回列表项的数量
int getSize();
// 返回指定索引处的列表项
E getElementAt(int index);
// 为列表项添加一个监听器,当列表项发生变化时将触发该监听器
void addListDataListener(ListDataListener l);
// 删除列表项上的指定监听器
void removeListDataListener(ListDataListener l);
}

从上面接口来看,这个ListModel不管JList里的所有列表项的存储形式,它甚至不强制存储所有的列表项,只要ListModel的实现类提供了getSize()getElementAt()两个方法,JList就可以根据该ListModel对象来生成列表框

ComboBoxModel接口

ComboBoxModel继承了ListModel,它添加了“选择项”的概念,选择项代表JComboBox显示区域内可见的列表项。ComboBoxModel为“选择项”提供了两个方法,下面是ComboModel接口的代码。

1
2
3
4
5
6
7
package javax.swing;
public interface ComboBoxModel<E> extends ListModel<E> {
// 设置选中“选择项”
void setSelectedItem(Object anItem);
// 获取“选择项”的值
Object getSelectedItem();
}

创建ListModel实现类

因为ListModel不强制保存所有的列表项,因此可以为它创建一个实现类:NumberListModel,这个实现类只需要传入数字上限、数字下限和步长,程序就可以自动为之实现上面的getsize()方法和getElementAt()方法,从而允许直接使用一个数字范围来创建JList对象。

实现getSize()方法

实现getSize()方法的代码如下

1
2
3
public int getsize (){
return (int) Math.floor(end.subtract(start).divide(step).doublevalue())+ 1;
}

用“**(上限-下限)÷步长+1**”即得到该ListModel中包含的列表项的个数
程序使用BigDecimal变量来保存上限、下限和步长,而不是直接使用double变量来保存这三个属性,主要是为了实现对数值的精确计算,所以上面程序中的endstartstep都是BigDecimal类型的变量。

实现getElementAt方法

实现getElementAt()方法也很简单,“下限+步长×索引”就是指定索引处的元素,该方法的具体实现请参考ListModelTest.java

程序 创建ListModel的实现类 创建ComboBoxModel的实现类

下面程序为ListModel提供了NumberListModel实现类,并为ComboBoxModel提供了NumberComboBoxModel实现类,这两个实现类允许程序使用数值范围来创建JListJComboBox对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import java.math.BigDecimal;
import java.util.*;
import java.awt.BorderLayout;
import javax.swing.*;

public class ListModelTest {
private JFrame mainWin = new JFrame("测试ListModel");
// 根据NumberListModel对象来创建一个JList对象
private JList<BigDecimal> numScopeList = new JList<>(new NumberListModel(1, 21, 2));
// 根据NumberComboBoxModel对象来创建JComboBox对象
private JComboBox<BigDecimal> numScopeSelector = new JComboBox<>(new NumberComboBoxModel(0.1, 1.2, 0.1));
private JTextField showVal = new JTextField(10);

public void init() {
// JList的可视高度可同时显示4个列表项
numScopeList.setVisibleRowCount(4);
// 默认选中第三项到第五项(第一项的索引是0)
numScopeList.setSelectionInterval(2, 4);
// 设置每个列表项具有指定的高度和宽度。
numScopeList.setFixedCellHeight(30);
numScopeList.setFixedCellWidth(90);
// 为numScopeList添加监听器
numScopeList.addListSelectionListener(e -> {
// 获取用户所选中的所有数字
List<BigDecimal> nums = numScopeList.getSelectedValuesList();
showVal.setText("");
// 把用户选中的数字添加到单行文本框中
for (BigDecimal num : nums) {
showVal.setText(showVal.getText() + num.toString() + ", ");
}
});
// 设置列表项的可视高度可显示5个列表项
numScopeSelector.setMaximumRowCount(5);
Box box = new Box(BoxLayout.X_AXIS);
box.add(new JScrollPane(numScopeList));
JPanel p = new JPanel();
p.add(numScopeSelector);
box.add(p);
// 为numScopeSelector添加监听器
numScopeSelector.addItemListener(e -> {
// 获取JComboBox中选中的数字
Object num = numScopeSelector.getSelectedItem();
showVal.setText(num.toString());
});
JPanel bottom = new JPanel();
bottom.add(new JLabel("您选择的值是:"));
bottom.add(showVal);
mainWin.add(box);
mainWin.add(bottom, BorderLayout.SOUTH);
mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWin.pack();
mainWin.setVisible(true);
}

public static void main(String[] args) {
new ListModelTest().init();
// ComboBoxModel
}
}

class NumberListModel extends AbstractListModel<BigDecimal> {
protected BigDecimal start;
protected BigDecimal end;
protected BigDecimal step;

public NumberListModel(double start, double end, double step) {
this.start = BigDecimal.valueOf(start);
this.end = BigDecimal.valueOf(end);
this.step = BigDecimal.valueOf(step);
}

// 返回列表项的个数
public int getSize() {
return (int) Math.floor(end.subtract(start).divide(step).doubleValue()) + 1;
}

// 返回指定索引处的列表项
public BigDecimal getElementAt(int index) {
return BigDecimal.valueOf(index).multiply(step).add(start);
}
}

class NumberComboBoxModel extends NumberListModel implements ComboBoxModel<BigDecimal> {
// 用于保存用户选中项的索引
private int selectId = 0;

public NumberComboBoxModel(double start, double end, double step) {
super(start, end, step);
}

// 设置选中“选择项”
public void setSelectedItem(Object anItem) {
if (anItem instanceof BigDecimal) {
BigDecimal target = (BigDecimal) anItem;
// 根据选中的值来修改选中项的索引
selectId = target.subtract(super.start).divide(step).intValue();
}
}

// 获取“选择项”的值
public BigDecimal getSelectedItem() {
// 根据选中项的索引来取得选中项
return BigDecimal.valueOf(selectId).multiply(step).add(start);
}
}

上面程序中分别使用NumberListModelNumberComboBoxModel创建了一个JListJComboBox对象,创建这两个列表框时无须指定每个列表项,只需给出数值的上限、下限和步长即可。运行上面程序,会看到如图12.35所示的窗口。