如何在 JTable editabe 中的 JPanel 中制作组件?



我试图以类似表格的方式显示数据列表,列表中每个项目的信息都在一行中。我还希望每个"单元格"的外观看起来比默认的JTable更好,并且希望数据是可编辑的。

我现在拥有的是一个有一列的JTable,每一行都是一个包含JTextfield列表的JPanel。它看起来是我想要的样子,但我不能编辑JText字段。

如何使JTextfields变成可编辑的?这是我现在的代码:

import javax.swing.*;
import javax.swing.border.TitledBorder;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
public class JTableWithJPanelExample {
public static void main(String[] args) {
JFrame frame = new JFrame();
JPanel panel = new JPanel();
panel.setBorder(BorderFactory.createTitledBorder( BorderFactory.createEtchedBorder(), "ODI Rankings", TitledBorder.CENTER, TitledBorder.TOP));
String[][] data = {
{ "1", "Steve", "AUS" },
{ "2", "Virat", "IND" },
{ "3", "Kane", "NZ" },
{ "4", "David", "AUS" },
{ "5", "Ben", "ENG" },
{ "6", "Eion", "ENG" },
};
JTable table = new JTable();
table.setModel(new JPanelTableModel());
table.setDefaultRenderer(JPanel.class, new JPanelRowRenderer());
table.setRowHeight(30);
JPanelTableModel model = (JPanelTableModel) table.getModel();
// Add rows
for (String[] row : data) {
JPanel rowPanel = new JPanel();
for (String element : row) {
rowPanel.add(new JTextField(element));
}
model.addRow(rowPanel);
}
panel.add(new JScrollPane(table));
frame.add(panel);
frame.setSize(550, 400);
frame.setVisible(true);
}

private static class JPanelTableModel extends AbstractTableModel {
private List<JPanel> rows = new ArrayList<>();
public Class getColumnClass(int columnIndex) {
return JPanel.class;
}
public int getColumnCount() {
return 1;
}
public String getColumnName(int columnIndex) {
return "";
}
public int getRowCount() {
return (rows == null) ? 0 : rows.size();
}
public Object getValueAt(int rowIndex, int columnIndex) {
return (rows == null) ? null : rows.get(rowIndex);
}
public boolean isCellEditable(int columnIndex, int rowIndex) {
return true;
}
public void addRow(JPanel panel) {
rows.add(panel);
}
public void clear() {
rows.clear();
}
}
private static class JPanelRowRenderer implements TableCellRenderer {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
return (JPanel) value;
}
}
}

再次强烈建议您将模型和视图分离。模型应该作为纯数据存储,如果需要,应该以多种不同的方式查看,因此不应该与视图紧密联系。

所以,假设有类似的数据。。。

String[][] data = { 
{ "1", "Steve", "AUS" }, 
{ "2", "Virat", "IND" }, 
{ "3", "Kane", "NZ" },
{ "4", "David", "AUS" }, 
{ "5", "Ben", "ENG" }, 
{ "6", "Eion", "ENG" } };

让我们建立一个更好的模型

让我们创建一个类来保存1行的数据:

public class RowData {
private int id;
private String name;
private String countryCode;
public RowData(int id, String name, String countryCode) {
super();
this.id = id;
this.name = name;
this.countryCode = countryCode;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCountryCode() {
return countryCode;
}
public void setCountryCode(String countryCode) {
this.countryCode = countryCode;
}
@Override
public String toString() {
return "RowData [id=" + id + ", name=" + name + ", countryCode=" + countryCode + "]";
}
}

然后使用它来创建一个表模型。我扩展了DefaultTableModel,并将单行的数据作为单行保存在模型中。事实上,这是一个1D数据集:

@SuppressWarnings("serial")
class MyTableModel extends DefaultTableModel {
private static final Object[] COLUMN_NAMES = new Object[] { "ODI Rankings" };
public MyTableModel() {
super(COLUMN_NAMES, 0);
}
public void addRow(RowData rowData) {
super.addRow(new Object[] { rowData });
}
@Override
public Class<?> getColumnClass(int columnIndex) {
if (columnIndex != 0) {
String text = "for column index: " + columnIndex;
throw new IllegalArgumentException(text);
} else {
return RowData.class;
}
}
}

现在,解决问题的关键是创建一个渲染器和一个编辑器,它将在一个包含3个JTextFields的JPanel中显示数据(正如您所希望的那样(,因此一个有效的渲染器可以是:

@SuppressWarnings("serial")
class MyCellRenderer extends JPanel implements TableCellRenderer {
private JTextField idField = new JTextField(10);
private JTextField nameField = new JTextField(10);
private JTextField countryCodeField = new JTextField(10);
public MyCellRenderer() {
add(idField);
add(nameField);
add(countryCodeField);
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
int row, int column) {
RowData rowData = (RowData) value;
if (rowData != null) {
idField.setText("" + rowData.getId());
nameField.setText(rowData.getName());
countryCodeField.setText(rowData.getCountryCode());
} else {
idField.setText("");
nameField.setText("");
countryCodeField.setText("");
}
if (isSelected) {
this.setBorder(BorderFactory.createLineBorder(Color.RED, 1));
} else {
this.setBorder(BorderFactory.createLineBorder(Color.black, 1));
}
return this;
}
}

和一个编辑器(建立在默认单元格编辑器的基础上(:

@SuppressWarnings("serial")
class MyCellEditor extends AbstractCellEditor implements TableCellEditor {
private JPanel mainPanel = new JPanel();
private JTextField idField = new JTextField(10);
private JTextField nameField = new JTextField(10);
private JTextField countryCodeField = new JTextField(10);
private RowData currentData;
public MyCellEditor() {
mainPanel.add(idField);
mainPanel.add(nameField);
mainPanel.add(countryCodeField);
}
@Override
public Object getCellEditorValue() {
int id = 0;
try {
id = Integer.parseInt(idField.getText());
} catch (NumberFormatException nfe) {
return currentData;
// warn user here
}
String name = nameField.getText();
String countryCode = countryCodeField.getText();
RowData rowData = new RowData(id, name, countryCode);
return rowData;
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
currentData = (RowData) value;
if (currentData != null) {
idField.setText("" + currentData.getId());
nameField.setText(currentData.getName());
countryCodeField.setText(currentData.getCountryCode());
}
return mainPanel;
}
}


导入的剩余驱动代码,加上一个扩展JTable以解决行高问题的类:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import javax.swing.AbstractCellEditor;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.border.TitledBorder;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
@SuppressWarnings("serial")
public class JTableWithJPanelExample2 extends JPanel {
private MyTableModel tableModel = new MyTableModel();
private MyTable table = new MyTable(tableModel);
public JTableWithJPanelExample2() {
MyCellRenderer rendererEditor = new MyCellRenderer();
table.setDefaultRenderer(RowData.class, rendererEditor);
table.setDefaultEditor(RowData.class, new MyCellEditor());
setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "ODI Rankings",
TitledBorder.CENTER, TitledBorder.TOP));
setLayout(new BorderLayout());
JScrollPane scrollPane = new JScrollPane(table);
add(scrollPane);
}
public void addRow(RowData rowData) {
tableModel.addRow(rowData);
}
public static void main(String[] args) {
String[][] data = { 
{ "1", "Steve", "AUS" }, 
{ "2", "Virat", "IND" }, 
{ "3", "Kane", "NZ" },
{ "4", "David", "AUS" }, 
{ "5", "Ben", "ENG" }, 
{ "6", "Eion", "ENG" } };
JTableWithJPanelExample2 example = new JTableWithJPanelExample2();
for (String[] datum : data) {
int id = Integer.parseInt(datum[0]);
String name = datum[1];
String countryCode = datum[2];
RowData rowData = new RowData(id, name, countryCode);
example.addRow(rowData);
}
JFrame gui = new JFrame("GUI");
gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gui.add(example);
gui.pack();
gui.setLocationRelativeTo(null);
gui.setVisible(true);
}
}
@SuppressWarnings("serial")
class MyTable extends JTable {
public MyTable(MyTableModel tableModel) {
super(tableModel);
}
// a kludge to make the cells high enough to display properly
@Override
public int getRowHeight() {
return 2 * super.getRowHeight();
}
}

我认为GUI最终会绑定到数据库中,使用更纯的模型会使它更容易、更干净、更安全,包括将数据从数据库传输到模型,以及从模型传输到数据库。再说一遍,这是有道理的。

这真是一团糟,我不推荐这种方法,因为@Hovercraft Full Of Eels说"你不能把你的视图和模型混合在一起"。但这是行之有效的。

步骤1
确保TableModel和编辑器共享相同的行(这只是我的建议(。

private static class JPanelTableModel extends AbstractTableModel 
{
private List<JPanel> rows = new ArrayList<>();
public Editor editor(){return new Editor(rows);}
}

步骤2

为JPanel类创建自己的单元格编辑器。

private static class Editor extends AbstractCellEditor implements TableCellEditor 
{
private List<JPanel> rows;
private int row;

private Editor(List<JPanel> rows){this.rows=rows;}

public Object getCellEditorValue(){return rows.get(row);}//Return the value of the current row you are editing

@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column)
{
this.row=row;

return rows.get(row);//Simple Return that exact same row you passed to your tableRenderer so that Your table makes it ready to receive user focus and input
}
} 

步骤3使其成为表的默认编辑器。

JPanelTableModel model=new JPanelTableModel();
table.setModel(model);
table.setDefaultRenderer(JPanel.class, new JPanelRowRenderer());
table.setDefaultEditor(JPanel.class,model.editor());//This is what matters
table.setRowHeight(30);

虽然不干净,但确实可以。我强烈建议你重构代码,只返回"轻量级子组件",而不是"重型容器"作为单独的单元组件。

出于您的目的,由于表只有一列,我建议只使用一个JPanel,其中BoxLayoutY_AXIS一起显示您的行。

最新更新