首页 > 解决方案 > 如何在 JTable editabe 内的 JPanel 内制作组件?

问题描述

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

我现在拥有的是一个带有一列的 JTable,每一行都是一个 JPanel,其中包含一个 JTextfields 列表。它看起来像我想要的样子,但我无法编辑 JTextfields。

我怎样才能让 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;
        }
    }
}

标签: javaswingmodeljtable

解决方案


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

因此,假设类似的数据,...

    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 并将单行数据作为模型中的单行保存。这实际上是一维数据集合:

@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;
        }
    }
}

现在解决问题的关键是创建一个渲染器和一个编辑器,它将在一个 JPanel 中显示数据,该 JPanel 包含 3 个 JTextField(如您所愿),因此可以使用的渲染器可能是:

@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 最终会绑定到数据库中,使用更纯粹的模型会使这样做更容易、更干净、更安全,包括将数据从数据库传输到模型以及从模型传输到数据库。同样,这很有意义。


推荐阅读