JavaGUI编程:Swing可分离模型架构与自定义渲染器
立即解锁
发布时间: 2025-08-21 00:56:26 阅读量: 3 订阅数: 12 


Java编程艺术:从初学者到大师的进阶指南
### Java GUI编程:Swing可分离模型架构与自定义渲染器
#### 1. Swing的可分离模型架构
在这一部分,我们将深入探讨Swing的可分离模型架构,并通过一个示例程序展示其在实际应用中的优势。
##### 1.1 AWT与Swing的GUI架构差异
AWT和Swing在GUI架构上存在显著差异。Swing采用了可分离模型架构,而AWT则没有。以`java.awt.List`和`javax.swing.JList`为例,`java.awt.List`提供了许多管理其包含数据的方法,如添加、删除和替换项目,但要求项目必须是字符串。这导致`List`与其数据之间存在紧密耦合,如果在程序中使用`List`,就必须将数据表示为字符串列表,并将数据的唯一所有权交给`List`。
例如,在我们的应用程序中,数据是一个`Garments`列表。如果使用`List`,应用程序不仅要维护`Garments`列表,还要为`List`维护一个字符串列表。每当`Garments`列表发生变化时,应用程序必须确保`List`的字符串列表也相应更改,否则应用程序数据和视图将不同步,这会给维护带来麻烦。
而`JList`则不提供管理数据的方法,它只维护视图功能,将数据管理委托给一个单独的对象`ListModel`,这就是所谓的可分离模型。`ListModel`是一个接口,它定义了管理只读数据列表和一组`ListDataListeners`的最少方法。与`List`不同,`ListModel`只要求数据由对象组成,从而使应用程序可以根据需要自由管理其数据。任何实现`ListModel`接口的对象都可以通过`JList`的`getModel()`和`setModel()`方法与任意数量的`JList`共享。此外,由于`JList`会自动向其`ListModel`注册一个`ListDataListener`,因此每当调用`ListModel`的方法更改数据时,`JList`都会收到通知。
以下是`ListModel`接口定义的方法:
| 方法名称 | 用途 |
| --- | --- |
| `public int getSize()` | 返回列表模型中的元素数量。 |
| `public Object getElementAt(int index)` | 返回列表模型中指定索引处的元素。 |
| `public void addListDataListener(ListDataListener l)` | 当此列表模型成为组件的数据模型时自动调用。 |
| `public void removeListDataListener(ListDataListener l)` | 当此列表模型不再是组件的数据模型时自动调用。 |
以下是`JList`用于操作其`ListModel`的方法:
| 方法名称 | 用途 |
| --- | --- |
| `public ListModel getModel()` | 获取当前的`ListModel`。 |
| `public void setModel(ListModel model)` | 安装指定的`ListModel`。 |
| `public void setListData(Object[] listData)` | 安装一个包含指定数据的新`ListModel`。 |
| `public void setListData(Vector listData)` | 安装一个包含指定数据的新`ListModel`。 |
使用`JList`的好处在于,我们的应用程序可以将`Garments`列表包装在`ListModel`接口中,并与任意数量的`JList`共享。这意味着无需额外努力,所有`JList`都能保证与底层数据同步,这显然是一个更好的方案。
##### 1.2 MainFrame类的实现
在`gui1`包中,`MainFrame`类负责创建、初始化和操作`JList`的`ListModel`。具体实现如下:
```java
package chap14.gui1;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Image;
import java.awt.Point;
import java.util.Collections;
import java.util.Vector;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.ListModel;
import javax.swing.border.BevelBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import utils.ResourceUtils;
import chap14.gui0.DressingBoard;
import chap14.gui0.Garment;
public class MainFrame extends JFrame implements ListSelectionListener {
private DressingBoard dressingBoard;
private JList garmentList;
public MainFrame() {
setTitle(getClass().getName());
setSize(600, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
dressingBoard = new DressingBoard();
contentPane.add("Center", dressingBoard);
garmentList = new JList();
garmentList.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
garmentList.addListSelectionListener(this);
contentPane.add("West", garmentList);
initList();
}
private void initList() {
String[] names = {
"T-Shirt",
"Briefs",
"Left Sock",
"Right Sock",
"Shirt",
"Pants",
"Belt",
"Tie",
"Left Shoe",
"Right Shoe"
};
Point[] points = {
new Point(75, 125),
new Point(86, 197),
new Point(127, 256),
new Point(45, 260),
new Point(69, 118),
new Point(82, 199),
new Point(88, 203),
new Point(84, 124),
new Point(129, 258),
new Point(40, 268)
};
Vector garments = new Vector(names.length);
for (int i = 0; i < names.length; ++i) {
Image image = ResourceUtils.loadImage("chap14/images/" + names[i] + ".gif", this);
Garment garment = new Garment(image, points[i].x, points[i].y, names[i]);
garments.add(garment);
}
Collections.shuffle(garments);
garmentList.setListData(garments);
}
private void redrawBoy() {
ListModel lm = garmentList.getModel();
int stop = lm.getSize();
Vector order = new Vector();
for (int i = 0; i < stop; ++i) {
Garment garment = (Garment)lm.getElementAt(i);
if (garment.isWorn()) {
order.add(garment);
}
}
dressingBoard.setOrder((Garment[])order.toArray(new Garment[0]));
}
public static void main(String[] arg) {
new MainFrame().setVisible(true);
}
public void valueChanged(ListSelectionEvent e) {
ListModel lm = garmentList.getModel();
for (int i = lm.getSize() - 1; i >= 0; --i) {
Garment garment = (Garment)lm.getElementAt(i);
garment.setWorn(false);
}
Object[] selectedGarments = garmentList.getSelectedValues();
for (int i = 0; i < selectedGarments.length; ++i) {
Garment selectedGarment = (Garment)selectedGarments[i];
selectedGarment.setWorn(true);
}
redrawBoy();
}
}
```
在`initList`方法中,我们创建了一个`Garments`的`Vector`,对其进行洗牌操作,然后将其传递给`JList`,`JList`会为自己创建一个包含洗牌后`Garments`的`ListModel`。
`MainFrame`还实现了`ListSelectionListener`接口,并向`JList`注册自己,这样`JList`中所选项目的任何更改都会触发调用`MainFrame`的`valueChanged()`方法。`valueChanged()`方法将`JList`的选择状态转换为`ListModel`的数据,即只有当`ListModel`中的`Garments`在`JList`中被选中时,才将其设置为已穿戴。接下来的逻辑步骤被封装到一个新方法`redrawBoy()`中,该方法不依赖于`JList`的选择状态,这样如果我们出于其他原因需要重新绘制男孩,就有一个方便的方法,而不需要`ListSelectionEvent`参数。`redrawBoy()`从`ListModel`中获取所有服装,并创建一个已穿戴服装的数组,将其传递给`DressingBoard.setOrder()`。
##### 1.3 编译和运行示例
使用以下命令编译和执行示例:
```
javac –d classes -sourcepath src src/chap14/gui1/MainFrame.java
java –cp classes chap14.gui1.MainFrame
```
运行程序后,你可以进行单选择和多选择操作,观察每个选择对男孩绘图的影响。现在注释掉`Garment`的`toString()`方法,重新编译并再次运行程序,观察这对程序的影响。默认情况下,`JList`使用`JLabel`来渲染每个列表项,并将`JLabel`的文本设置为`toString()`返回的内容。
#### 2. 编写自定义渲染器
在这一部分,我们将学习如何自定义`JList`显示其项目的方式。
##### 2.1 使用渲染器的原因
由于该应用程序最终将支持拖动操作,`JList`中的鼠标点击将用于两个目的:
- 选择
0
0
复制全文
相关推荐









