Страницы

Поиск по вопросам

суббота, 1 июня 2019 г.

Как правильно использовать AbstractTableModel и DefaultTableColumnModel

Имеется задача, построить таблицу, столбцы которой являются полями в классе CustomItem
package com.kolos.Table;
import java.util.Date;
public class CustomItem { public Integer id; public String name; public Date date; public CustomItem(Integer id, String name, Date date) { this.id = id; this.name = name; this.date = date; } }
Класс CustomColumnModel
package com.kolos.Table;
import javax.swing.table.DefaultTableColumnModel; import javax.swing.table.TableColumn; import java.lang.reflect.Field;
public class CustomColumnModel extends DefaultTableColumnModel{ public CustomColumnModel(Class tClass) { for (Field tField : tClass.getDeclaredFields()){ TableColumn tColumn = new TableColumn(); tColumn.setHeaderValue(tField.getName()); tColumn.setModelIndex(getColumnCount()); tColumn.setIdentifier(tField); tField.setAccessible(true); this.addColumn(tColumn); } } }
Модель таблицы:
package com.kolos.Table;
import javax.swing.table.AbstractTableModel; import java.lang.reflect.Field; import java.util.Date;
public class CustomDataModel extends AbstractTableModel{
private CustomColumnModel columnModel; private CustomItem[] dataList = new CustomItem[3];
public CustomDataModel(CustomColumnModel columnModel) { this.dataList = new CustomItem[3]; this.dataList[0] = new CustomItem(1, "Fist", new Date()); this.dataList[1] = new CustomItem(2, "Second", new Date()); this.dataList[2] = new CustomItem(3, "Third", new Date()); this.columnModel = columnModel; }
@Override public int getRowCount() { return this.dataList.length; }
@Override public int getColumnCount() { return this.columnModel.getColumnCount(); }
@Override public Object getValueAt(int rowIndex, int columnIndex) { try { Field tField = ((Field) columnModel.getColumn(columnIndex).getIdentifier()); return tField.get(dataList[rowIndex]); } catch (IllegalAccessException e) { return "IllegalAccessExeption"; } } }
Класс CustomTable
package com.kolos.Table;
import javax.swing.*;
public class CustomTable extends JTable{ private CustomDataModel dataModel; private CustomColumnModel columnModel; public CustomTable() { setAutoCreateColumnsFromModel(false); columnModel = new CustomColumnModel(CustomItem.class); setColumnModel(columnModel); dataModel = new CustomDataModel(columnModel); setModel(dataModel); } }
Фрейм:
package com.kolos;
import com.kolos.Table.CustomTable; import javax.swing.*; import java.awt.*;
public class MainFrame extends JFrame{ public MainFrame() { this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); this.setSize(new Dimension(800, 600)); this.setLayout(new GridBagLayout());
//create table JScrollPane scrollPane = new JScrollPane(); CustomTable table = new CustomTable(); scrollPane.setViewportView(table);
this.add(scrollPane, new GridBagConstraints( 0, 0, 1, 1, 1, 1, GridBagConstraints.NORTH, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0 ));
} public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { MainFrame mainFrame = new MainFrame(); mainFrame.setVisible(true); } }); }
}
Проблема в том что при перетаскивании столбцов (например 2 -> 0) заголовки переносятся, а значения из модели (получаемые методом getValueAt ) переносятся непонятным образом.

Собственно почему так и как исправить?


Ответ

Дело в том, что когда вы перемещаете столбцы, они реально меняются местами в списке столбцов ColumnModel, при этом в TableModel.getValueAt координаты ячейки приходят уже пересчитанными в пространство модели (значение поля TableColumn.modelIndex) Поэтому когда вы ставите третью колонку на место первой, для показа первой колонки вызывается getValueAt( rowIndex, 2 ), вы берете у модели столбцов колонку по индексу 2, но туда уже сдвинулась средняя колонка, которая отвечала за вывод имени.
Мне кажется, лучше всего держать получение данных по номеру столбца прямо в модели таблицы. Заведите список Field для доступа к данным, заполните его в конструкторе модели, как вы сейчас делаете в CustomColumnModel, и возвращайте имя колонки в TableModel.getColumnName(int). А TableColumnModel вообще не трогать.
public class CustomDataModel extends AbstractTableModel {
private CustomItem[] dataList = new CustomItem[3];
private List fields = new ArrayList<>();
public CustomDataModel( Class tClass ) { this.dataList = new CustomItem[3]; this.dataList[0] = new CustomItem(1, "Fist", new Date()); this.dataList[1] = new CustomItem(2, "Second", new Date()); this.dataList[2] = new CustomItem(3, "Third", new Date());
for (Field tField : tClass.getDeclaredFields()){ tField.setAccessible(true); fields.add( tField ); } }
@Override public int getRowCount() { return this.dataList.length; }
@Override public int getColumnCount() { return this.fields.size(); }
@Override public String getColumnName( int column ) { return fields.get( column ).getName(); }
// можно возвращать тип столбца, тогда JTable будет выбирать соотв. TableCellRenderer // @Override // public Class getColumnClass( int column ) { // return fields.get( column ).getType(); // }
@Override public Object getValueAt(int rowIndex, int columnIndex) { try { Field tField = fields.get( columnIndex );
return tField.get(dataList[rowIndex]); } catch (IllegalAccessException e) { return "IllegalAccessExeption"; } } }
public class CustomTable extends JTable{ private CustomDataModel dataModel; public CustomTable() { setAutoCreateColumnsFromModel( true ); // не забудьте вернуть автосоздание dataModel = new CustomDataModel( CustomItem.class ); setModel( dataModel ); } }
Либо вы можете в getValueAt искать нужную колонку по совпадению columnIndex и tableColumn.getModelIndex(), или воспользоваться методами jTable.convertColumnIndexToView( columnIndex ) (но нужно держать в модели ссылку на таблицу), или sun.swing.SwingUtilities2.convertColumnIndexToView( columnModel, columnIndex ), которые делают то же самое.

Комментариев нет:

Отправить комментарий