雙列表框交互 (可選項 ←→ 已選項) 是典型 UI 模式。需求:
核心難點:JList 本身不存數據,需配合 ListModel;同步雙向移動時維護數據一致性;最終提取泛型數據並轉型。
JScrollPane + JList (設置 SelectionMode.MULTIPLE_INTERVAL_SELECTION)DefaultListModel (左/右) + 一個 Vector (業務數據備份,動態擴容)import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Vector;
public class DualListBoxDemo extends JFrame {
// 左側:可選項
private final DefaultListModel<String> leftModel = new DefaultListModel<>();
private final JList<String> leftList = new JList<>(leftModel);
// 右側:已選項
private final DefaultListModel<String> rightModel = new DefaultListModel<>();
private final JList<String> rightList = new JList<>(rightModel);
// 業務數據備份 (與 rightModel 同步)
private final Vector<String> selectedData = new Vector<>();
public DualListBoxDemo() {
setTitle("雙列表框動態增刪演示");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(600, 400);
setLayout(new BorderLayout(10, 10));
// 初始化左側數據
String[] items = {"蘋果", "香蕉", "橙子", "葡萄", "西瓜", "草莓", "芒果", "鳳梨"};
for (String s : items) leftModel.addElement(s);
// 左側面板
JPanel leftPanel = new JPanel(new BorderLayout());
leftPanel.add(new JLabel("可選商品 (多選)"), BorderLayout.NORTH);
leftList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
leftPanel.add(new JScrollPane(leftList), BorderLayout.CENTER);
// 右側面板
JPanel rightPanel = new JPanel(new BorderLayout());
rightPanel.add(new JLabel("已選商品 (多選)"), BorderLayout.NORTH);
rightList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
rightPanel.add(new JScrollPane(rightList), BorderLayout.CENTER);
// 中間按鈕欄
JPanel btnPanel = new JPanel(new GridLayout(2, 1, 5, 5));
JButton btnAdd = new JButton(">>");
JButton btnRemove = new JButton("<<");
btnPanel.add(btnAdd);
btnPanel.add(btnRemove);
// 組裝主面板
JPanel center = new JPanel(new GridLayout(1, 3, 10, 0));
center.add(leftPanel);
center.add(btnPanel);
center.add(rightPanel);
add(center, BorderLayout.CENTER);
// 底部確定按鈕
JButton btnConfirm = new JButton("確定獲取選中值");
add(btnConfirm, BorderLayout.SOUTH);
// ===== 事件邏輯 =====
btnAdd.addActionListener(e -> moveSelected(leftList, leftModel, rightModel, selectedData, true));
btnRemove.addActionListener(e -> moveSelected(rightList, rightModel, rightModel, selectedData, false));
btnConfirm.addActionListener(e -> {
String[] result = selectedData.toArray(new String[0]);
JOptionPane.showMessageDialog(this, "已選商品 (" + result.length + "):\n" + String.join(", ", result));
// 此處可將 result 傳給業務層
});
setLocationRelativeTo(null);
}
/**
* 通用移動邏輯
* @param sourceList 來源列表
* @param sourceModel 來源模型
* @param targetModel 目標模型 (右側模型)
* @param dataVector 業務數據向量 (僅同步右側)
* @param toRight true=左→右, false=右→左
*/
private void moveSelected(JList<String> sourceList, DefaultListModel<String> sourceModel,
DefaultListModel<String> targetModel, Vector<String> dataVector, boolean toRight) {
int[] indices = sourceList.getSelectedIndices();
if (indices.length == 0) return;
// 必須倒序刪除,避免索引錯位
for (int i = indices.length - 1; i >= 0; i--) {
String val = sourceModel.getElementAt(indices[i]);
sourceModel.remove(indices[i]);
targetModel.addElement(val);
if (toRight) {
dataVector.addElement(val); // 同步業務數據
} else {
dataVector.removeElement(val); // 移除業務數據
}
}
sourceList.clearSelection();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new DualListBoxDemo().setVisible(true));
}
}
| 技術點 | 說明 |
|---|---|
DefaultListModel | 可變列表模型,支援 addElement/removeElement/insertElementAt,自動觸發 JList 重繪 |
SelectionMode.MULTIPLE_INTERVAL_SELECTION | 支援 Ctrl/Shift 多選,getSelectedIndices() 返回所有選中索引 |
| 倒序遍歷刪除 | 從最大索引向 0 刪除,防止前序刪除導致後序索引偏移 |
Vector 同步備份 | 線程安全、動態擴容、toArray() 直接轉型為強類型數組 |
泛型 <String> | 編譯期類型安全,避免運行時 ClassCastException |
String 替換為 Product 等領域對象,JList 配合 ListCellRenderer 顯示名稱,Vector<Product> 直接攜帶完整業務數據。Vector,實現真正的雙向數據綁定。TransferHandler 支持鼠標拖拽移動,體驗更佳。JList 不操作 Model → 界面不更新Vector 與 Model 同步 → 確定時取不到最新數據SelectionMode → 默認單選,無法多選