Как сделать Компоненты внутри JPanel, которая находится внутри editabe JTable? - PullRequest
2 голосов
/ 14 апреля 2020

Я пытаюсь отобразить список данных в виде таблицы с информацией для каждого элемента в списке, чтобы быть в одной строке. Я также хочу, чтобы внешний вид каждой «ячейки» выглядел лучше, чем JTable по умолчанию, и хочу, чтобы данные были редактируемыми.

Сейчас у меня есть JTable с одним столбцом, а каждая строка - JPanel. который содержит список JTextfields. Это выглядит так, как я хочу, но я не могу редактировать поля JText.

Как мне сделать поля JText доступными для редактирования? Вот код, который у меня сейчас есть:

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

Ответы [ 2 ]

3 голосов
/ 17 апреля 2020

Опять же, я настоятельно призываю вас отделить модель от вида. Модель должна храниться в виде чистых данных, должна рассматриваться многими различными способами, если это необходимо, и поэтому не должна быть так тесно связана с представлением.

Итак, предполагая схожие данные, ...

    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, который содержит 3 JTextFields (как это выглядит по вашему желанию), поэтому работающий рендерер может быть:

@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 в конечном итоге будет привязан к базе данных, и используя более чистая модель сделает это намного проще, чище и безопаснее, включая передачу данных из базы данных в модель и из модели в базу данных. Опять же, это просто имеет смысл.

0 голосов
/ 16 апреля 2020

Ну, это был полный беспорядок, и я не рекомендовал бы этот подход, поскольку @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 с BoxLayout вдоль Y_AXIS для отображения ваших строк

...