在 Java Swing 中,MVC 模式被广泛应用。例如,JTable、JList 等组件都采用了这种模式。通常:
- 模型:实现特定的 Swing 模型接口(如 TableModel、ListModel)。
- 视图:是 Swing 组件本身(如 JTable、JList)。
- 控制器:通常隐含在组件内部,或由开发者实现为事件监听器。
JTableModel 是 Java Swing 中用于管理表格数据的核心接口,它是 MVC(Model-View-Controller)模式在表格组件中的具体实现。JTable 作为视图,负责显示数据;而 JTableModel 作为模型,负责存储和管理数据,并提供数据访问接口。
JTableModel 接口方法
JTableModel 接口定义了以下核心方法:
基本结构方法
int getRowCount()
:返回表格的行数int getColumnCount()
:返回表格的列数String getColumnName(int columnIndex)
:返回指定列的名称Class<?> getColumnClass(int columnIndex)
:返回指定列的数据类型
数据访问方法
Object getValueAt(int rowIndex, int columnIndex)
:获取指定单元格的数据void setValueAt(Object aValue, int rowIndex, int columnIndex)
:设置指定单元格的数据
可选方法
boolean isCellEditable(int rowIndex, int columnIndex)
:指定单元格是否可编辑
实现方式
JTableModel 有三种主要实现方式:
DefaultTableModel
- 最简单的实现,使用 Vector 存储数据和列名
- 缺点:所有单元格数据类型被视为 Object,不支持类型安全
AbstractTableModel
- 抽象基类,提供了事件通知机制
- 通常继承此类并实现必要的方法
自定义 TableModel
- 完全自定义实现,适用于复杂数据结构和特殊需求
案例:自定义 TableModel 实现
下面通过一个案例展示如何实现自定义 TableModel:
Main.java
import javax.swing.*;
import java.awt.*;
public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
// 创建主窗口
JFrame frame = new JFrame("人员信息管理");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(500, 300);
frame.setLocationRelativeTo(null);
// 创建自定义TableModel
PersonTableModel model = new PersonTableModel();
// 添加示例数据
model.addPerson(new Person("张三", 25, false));
model.addPerson(new Person("李四", 30, true));
model.addPerson(new Person("王五", 22, false));
// 创建表格并关联TableModel
JTable table = new JTable(model);
// 添加表格到滚动面板
JScrollPane scrollPane = new JScrollPane(table);
// 添加按钮面板
JPanel buttonPanel = new JPanel();
JButton addButton = new JButton("添加");
JButton deleteButton = new JButton("删除");
// 添加按钮事件处理
addButton.addActionListener(e -> {
String name = JOptionPane.showInputDialog(frame, "请输入姓名:");
if (name != null && !name.isEmpty()) {
String ageStr = JOptionPane.showInputDialog(frame, "请输入年龄:");
if (ageStr != null && !ageStr.isEmpty()) {
try {
int age = Integer.parseInt(ageStr);
String marriedStr = JOptionPane.showInputDialog(frame, "是否已婚(true/false):");
boolean married = Boolean.parseBoolean(marriedStr);
model.addPerson(new Person(name, age, married));
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(frame, "年龄必须是数字!");
}
}
}
});
deleteButton.addActionListener(e -> {
int selectedRow = table.getSelectedRow();
if (selectedRow != -1) {
model.deletePerson(selectedRow);
} else {
JOptionPane.showMessageDialog(frame, "请先选择一行!");
}
});
buttonPanel.add(addButton);
buttonPanel.add(deleteButton);
// 添加组件到窗口
frame.getContentPane().add(scrollPane, BorderLayout.CENTER);
frame.getContentPane().add(buttonPanel, BorderLayout.SOUTH);
// 显示窗口
frame.setVisible(true);
});
}
}
PersonTableModel.java
import javax.swing.table.AbstractTableModel;
import java.util.ArrayList;
import java.util.List;
// 人员类,存储表格中的一行数据
class Person {
private String name;
private int age;
private boolean married;
public Person(String name, int age, boolean married) {
this.name = name;
this.age = age;
this.married = married;
}
public String getName() { return name; }
public int getAge() { return age; }
public boolean isMarried() { return married; }
public void setName(String name) { this.name = name; }
public void setAge(int age) { this.age = age; }
public void setMarried(boolean married) { this.married = married; }
}
// 自定义TableModel实现
public class PersonTableModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
// 列名数组
private final String[] columnNames = {"姓名", "年龄", "已婚"};
// 列数据类型数组
private final Class<?>[] columnTypes = {String.class, Integer.class, Boolean.class};
// 数据列表
private final List<Person> data = new ArrayList<>();
// 添加人员数据
public void addPerson(Person person) {
data.add(person);
// 通知表格数据已插入
fireTableRowsInserted(data.size() - 1, data.size() - 1);
}
// 更新人员数据
public void updatePerson(int row, Person person) {
data.set(row, person);
// 通知表格数据已更新
fireTableRowsUpdated(row, row);
}
// 删除人员数据
public void deletePerson(int row) {
data.remove(row);
// 通知表格数据已删除
fireTableRowsDeleted(row, row);
}
// 获取指定行的人员数据
public Person getPerson(int row) {
return data.get(row);
}
// 返回表格行数
@Override
public int getRowCount() {
return data.size();
}
// 返回表格列数
@Override
public int getColumnCount() {
return columnNames.length;
}
// 返回列名
@Override
public String getColumnName(int column) {
return columnNames[column];
}
// 返回列数据类型
@Override
public Class<?> getColumnClass(int columnIndex) {
return columnTypes[columnIndex];
}
// 返回单元格数据
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
Person person = data.get(rowIndex);
switch (columnIndex) {
case 0: return person.getName();
case 1: return person.getAge();
case 2: return person.isMarried();
default: return null;
}
}
// 设置单元格数据并使单元格可编辑
@Override
public void setValueAt(Object value, int rowIndex, int columnIndex) {
Person person = data.get(rowIndex);
switch (columnIndex) {
case 0:
person.setName((String) value);
break;
case 1:
person.setAge((Integer) value);
break;
case 2:
person.setMarried((Boolean) value);
break;
}
// 通知表格单元格数据已更新
fireTableCellUpdated(rowIndex, columnIndex);
}
// 设置单元格是否可编辑
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return true; // 所有单元格都可编辑
}
}
JTableModel 关键特性
事件通知机制
- AbstractTableModel 提供了事件通知方法:
fireTableDataChanged()
:整个表格数据已更改fireTableStructureChanged()
:表格结构已更改fireTableRowsInserted/Updated/Deleted()
:行数据更改fireTableCellUpdated()
:单元格数据更改
- AbstractTableModel 提供了事件通知方法:
列类型支持
- 通过
getColumnClass()
方法返回列的数据类型 - JTable 会根据列类型自动提供合适的渲染器和编辑器
- 通过
单元格编辑
- 通过
isCellEditable()
方法控制单元格是否可编辑 - 通过
setValueAt()
方法处理编辑后的数据
- 通过