仰る通り、追加修正等の度にダイアログが出てくるのは結構面倒です。ですので EXCEL シートのように直接書き込めた方が「使う側にとっては簡単」です。
そしてダイアログを出すほうが「コードを作る側として簡単そう」というのもその通りです。
そこから逆に言いますと、使う側にとっては簡単になる「直接書き込み」はコードを作る側にとっては(コード的にも、データの整合性的にも)「簡単では無く」なります。
例えば JTable のセルの値を更新するテーブルモデルの実装方法 のような記事をお読みになってみては如何でしょうか。
テーブルモデルを AbstractTableModel から作り、編集可能としてみました。(編集可能状態になるだけで、実際には編集・追加の処理が入っていませんので、編集・追加は出来ません。)
"Name" の列は OmittableString.class とすることでレンダラを使うようにし、編集可能とする為にエディタを設定しています。(name の実体は String なので特別なことはしていません。)
グループ化・ソートをオリジナル(?)に合わせる為に、 Duration・Book クラスを作って name を参照できるようにしました。
その為、全列にレンダラ・エディタが必要になりました・・・。
大きくなってきたので Row クラスを別ファイルとしました。
Insert キー / Delete キーで 行の追加・削除をしてみました。
RowGroupInTableTest2.java
java
1public class RowGroupInTableTest2 extends JFrame {
2 public static void main(String[] args) {
3 EventQueue.invokeLater(() -> new RowGroupInTableTest2().setVisible(true));
4 }
5
6 RowGroupInTableTest2() {
7 super("RowGroupInTableTest2");
8 setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
9 setSize(320, 240);
10 setLocationRelativeTo(null);
11
12 MyTableModel model = new MyTableModel();
13 model.add(new MyData("Tom", 17, "Book1"));
14 model.add(new MyData("Tom", 23, "Book2"));
15 model.add(new MyData("Tom", 25, "Book3"));
16 model.add(new MyData("Polazzo", 41, "Book1"));
17 model.add(new MyData("Polazzo", 45, "Book2"));
18 model.add(new MyData("Polazzo", 12, "Book3"));
19 model.add(new MyData("Anna", 1, "Book3"));
20 model.add(new MyData("Anna", 33, "Book5"));
21
22 JTable table = new JTable(model);
23 table.setFillsViewportHeight(true);
24 table.setDefaultRenderer(Row.Name.class, new Row.NameRenderer());
25 table.setDefaultEditor(Row.Name.class, new DefaultCellEditor(new JTextField()));
26 table.setDefaultRenderer(Row.Duration.class, new Row.DurationRenderer());
27 table.setDefaultEditor(Row.Duration.class, new Row.DurationEditor());
28 table.setDefaultRenderer(Row.Book.class, new Row.BookRenderer());
29 table.setDefaultEditor(Row.Book.class, new Row.BookEditor());
30
31 table.setRowSorter(new TableRowSorter<TableModel>(model));
32
33 setAction(table, KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, 0), new AddAction());
34 setAction(table, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), new DeleteAction());
35
36 getContentPane().add(new JScrollPane(table));
37 }
38
39 private static void setAction(JTable table, KeyStroke key, Action a) {
40 table.getActionMap().put(a, a);
41
42 InputMap im;
43 im = table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
44 im.put(key, a);
45 im = table.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
46 im.put(key, a);
47 }
48
49 static class AddAction extends AbstractAction {
50 public AddAction() {
51 super("table-row-add");
52 }
53
54 @Override
55 public void actionPerformed(ActionEvent e) {
56 JTable table = (JTable)e.getSource();
57 MyTableModel model = (MyTableModel)table.getModel();
58 model.insert(table.getRowCount(), new MyData("", 0, ""));
59 }
60 }
61
62 public class DeleteAction extends AbstractAction {
63 public DeleteAction() {
64 super("table-row-del");
65 }
66
67 @Override
68 public void actionPerformed(ActionEvent e) {
69 JTable table = (JTable)e.getSource();
70 int[] rows = table.getSelectedRows();
71 for(int i=0; i<rows.length; i++) {
72 rows[i] = table.convertRowIndexToModel(rows[i]);
73 }
74
75 MyTableModel model = (MyTableModel)table.getModel();
76 for(int i=rows.length-1; i>=0; i--) {
77 model.remove(rows[i]);
78 }
79 }
80 }
81}
82
83class MyData {
84 final String name;
85 final int duration;
86 final String book;
87
88 MyData(String name, int duration, String book) {
89 this.name = name;
90 this.duration = duration;
91 this.book = book;
92 }
93}
94
95class MyTableModel extends AbstractTableModel {
96 abstract private class Column {
97 final String title;
98 Column(String title) {
99 this.title = title;
100 }
101 abstract Object getValueAt(Row row);
102 abstract Class<?> getColumnClass();
103 abstract Row setRowAt(Row row, Object aValue);
104 }
105 private Column[] columns = {
106 new Column("Name") {
107 Object getValueAt(Row row) { return row.name; }
108 Class<?> getColumnClass() { return Row.Name.class; }
109 Row setRowAt(Row row, Object aValue) { return row.setName((String)aValue); }
110 },
111 new Column("Last job duration") {
112 Object getValueAt(Row row) { return row.duration; }
113 Class<?> getColumnClass() { return Row.Duration.class; }
114 Row setRowAt(Row row, Object aValue) { return row.setDuration((Integer)aValue); }
115 },
116 new Column("Book #") {
117 Object getValueAt(Row row) { return row.book; }
118 Class<?> getColumnClass() { return Row.Book.class; }
119 Row setRowAt(Row row, Object aValue) { return row.setBook((String)aValue); }
120 }
121 };
122
123 private List<Row> rowList = new ArrayList<>();
124
125 void add(MyData data) {
126 insert(rowList.size(), data);
127 }
128
129 void insert(int rowIndex, MyData data) {
130 rowList.add(rowIndex, new Row(data.name, data.duration, data.book));
131 fireTableRowsInserted(rowIndex, rowIndex);
132 }
133
134 void remove(int rowIndex) {
135 rowList.remove(rowIndex);
136 fireTableRowsDeleted(rowIndex, rowIndex);
137 }
138
139 List<MyData> getList() {
140 List<MyData> list = new ArrayList<>();
141 for(Row row : rowList) list.add(row.getMyData());
142 return list;
143 }
144
145 @Override
146 public String getColumnName(int column) {
147 return columns[column].title;
148 }
149
150 @Override
151 public int getColumnCount() {
152 return columns.length;
153 }
154
155 @Override
156 public int getRowCount() {
157 return rowList.size();
158 }
159
160 @Override
161 public Object getValueAt(int rowIndex, int columnIndex) {
162 return columns[columnIndex].getValueAt(rowList.get(rowIndex));
163 }
164
165 @Override
166 public Class<?> getColumnClass(int columnIndex) {
167 return columns[columnIndex].getColumnClass();
168 }
169
170 @Override
171 public boolean isCellEditable(int rowIndex, int columnIndex) {
172 return true;
173 }
174
175 @Override
176 public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
177 rowList.set(rowIndex, columns[columnIndex].setRowAt(rowList.get(rowIndex), aValue));
178 }
179}
Row.java
java
1class Row {
2 final String name;
3 final Duration duration;
4 final Book book;
5
6 Row(String name, int duration, String book) {
7 this.name = name;
8 this.duration = new Duration(duration);
9 this.book = new Book(book);
10 }
11
12 Row setName(String name) {
13 return new Row(name, duration.value, book.value);
14 }
15 Row setDuration(int duration) {
16 return new Row(name, duration, book.value);
17 }
18 Row setBook(String book) {
19 return new Row(name, duration.value, book);
20 }
21 MyData getMyData() {
22 return new MyData(name, duration.value, book.value);
23 }
24
25 class Name {
26 }
27 static class NameRenderer extends DefaultTableCellRenderer {
28 @Override
29 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
30 //直前の行と同じ内容なら省略する
31 value = row>0 && Objects.equals(table.getValueAt(row-1, column), value) ? "" : value;
32
33 return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
34 }
35 }
36
37 class Duration implements Comparable<Duration> {
38 final Integer value;
39 private Duration(int value) {
40 this.value = value;
41 }
42 private String getName() { return Row.this.name; }
43 @Override
44 public int compareTo(Duration o) {
45 int result = getName().compareTo(o.getName());
46 return result == 0 ? value.compareTo(o.value) : result;
47 }
48 }
49
50 static class DurationRenderer extends DefaultTableCellRenderer {
51 DurationRenderer() {
52 super();
53 setHorizontalAlignment(SwingConstants.RIGHT);
54 }
55 @Override
56 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
57 return super.getTableCellRendererComponent(table, ((Duration)value).value, isSelected, hasFocus, row, column);
58 }
59 }
60 static class DurationEditor extends AbstractCellEditor implements TableCellEditor {
61 private JTextField component = new JTextField();
62 DurationEditor() {
63 component.setHorizontalAlignment(JTextField.RIGHT);
64 }
65 @Override
66 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
67 Duration duration = (Duration)value;
68 component.setText(""+duration.value);
69 return component;
70 }
71 @Override
72 public Object getCellEditorValue() {
73 try {
74 return Integer.parseInt(component.getText());
75 } catch(NumberFormatException e) {
76 return 0;
77 }
78 }
79 }
80
81 class Book implements Comparable<Book> {
82 final String value;
83 private Book(String value) {
84 this.value = value;
85 }
86 private String getName() { return Row.this.name; }
87 @Override
88 public int compareTo(Book o) {
89 int result = getName().compareTo(o.getName());
90 return result == 0 ? value.compareTo(o.value) : result;
91 }
92 }
93
94 static class BookRenderer extends DefaultTableCellRenderer {
95 @Override
96 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
97 return super.getTableCellRendererComponent(table, ((Book)value).value, isSelected, hasFocus, row, column);
98 }
99 }
100
101 static class BookEditor extends AbstractCellEditor implements TableCellEditor {
102 private JTextField component = new JTextField();
103 @Override
104 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
105 Book book = (Book)value;
106 component.setText(book.value);
107 return component;
108 }
109 @Override
110 public Object getCellEditorValue() {
111 return component.getText();
112 }
113 }
114}