I have a JTable populated with a custom DataModel (pasted below) and when I call the populate() method, it appears to populate the table with duplicate data - each row is filled with the same value over and over again. However, on closer inspection (by simply println()ing the 'data' field), the data model isn't at fault - it holds correct data, in the format I expect. What gives?
import java.util.ArrayList;
import javax.swing.table.AbstractTableModel;
#SuppressWarnings("serial") // we don't expect this app to ever use serialized classes. EVER.
public class CollectionDataModel extends AbstractTableModel {
private ArrayList<ArrayList<String>> data;
public CollectionDataModel() {
data = new ArrayList<ArrayList<String>>();
}
#Override
public int getColumnCount() {
if(data.isEmpty()) return 0;
return data.get(0).size();
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
if(rowIndex > getRowCount()) return null;
if(columnIndex > getColumnCount()) return null;
return data.get(rowIndex).get(columnIndex);
}
public void populate(Collection c) {
data.clear();
for(Item i : c.getItems()) {
ArrayList<String> row = new ArrayList<String>();
for(Property p : i.getProperties().values()) {
row.add(p.toString());
}
data.add(row);
}
fireTableDataChanged();
}
}
Here's a complete example that may prove helpful. As the sample Map is unmodifiable, I refer you to #mKorbel's example on how to override isCellEditable() and setValueAt().
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.util.Map;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
/** #see https://stackoverflow.com/questions/9132987 */
public class EnvTableTest extends JPanel {
public EnvTableTest() {
this.setLayout(new GridLayout());
this.add(new JScrollPane(new JTable(new EnvDataModel())));
}
private static class EnvDataModel extends AbstractTableModel {
private Map<String, String> data = System.getenv();
private String[] keys;
public EnvDataModel() {
keys = data.keySet().toArray(new String[data.size()]);
}
#Override
public String getColumnName(int col) {
if (col == 0) {
return "Key";
} else {
return "Value";
}
}
#Override
public int getColumnCount() {
return 2;
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public Object getValueAt(int row, int col) {
if (col == 0) {
return keys[row];
} else {
return data.get(keys[row]);
}
}
}
private void display() {
JFrame f = new JFrame("EnvTableTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new EnvTableTest().display();
}
});
}
}
You could try to make the changes of populate more atomic.
public void populate(Collection c) {
ArrayList<ArrayList<String>> data2 = new ArrayList<ArrayList<String>>();
for(Item i : c.getItems()) {
ArrayList<String> row = new ArrayList<String>();
for(Property p : i.getProperties().values()) {
row.add(p.toString());
}
data2.add(row);
}
data = data2;
fireTableDataChanged();
}
I am guessing that populate is called again before a prior populate call finished. And probably c is changed during its iteration.
1) your TableModel is un_completed, I miss there lots or required methods for JTable's life_cycle, starting with TableHeader etc.
2) since there are lots of AbstactTableModels based on HashMap, I'd suggest to return arrays type implemented in API directly
Vector<Vector<Object or String>> data;
String[][] or Object[][]
instead of
ArrayList<ArrayList<String>> data;
simple explanations is that XxxList returs column and Vector or String[] returns Row
3) I'd suggest to use DefaultTableModel directly then you'll never need to solve duplicates or missed column/row
Related
I've done JTable for simple schedule with visits.
It contains custom AbstractTableModel which shows three columns shown below.
The problem is that it is possible to initializate Table and to get desired look - but after data change there is no change in appearance of Table. Each button click takes data from database and sets fields in columns TYPE and STATE - depending on given hour and date of reservation.
What is more I'am able to insert new row at the end of table but cannot make visible existing value update.
I already read few similar topics but nothing helps in my case.
Thanks in advance for every suggestions.
[UPDATED] Working code illustrating the problem:
import java.awt.EventQueue;
import java.util.Vector;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class ModelTest {
private JFrame frame;
private JTable tablePendingVisits;
private PendingVisitModel pendingVisitModel;
private JScrollPane scrollPanePendingVisits;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
ModelTest window = new ModelTest();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public ModelTest() {
frame = new JFrame();
frame.setBounds(100, 100, 450, 407);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(null);
JButton btnChangeValue = new JButton("Change value at 9:00");
btnChangeValue.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
refreshTableModel();
}
});
btnChangeValue.setBounds(63, 308, 305, 23);
frame.getContentPane().add(btnChangeValue);
tablePendingVisits = new JTable();
scrollPanePendingVisits = new JScrollPane();
pendingVisitModel = new PendingVisitModel();
tablePendingVisits.setModel(pendingVisitModel);
scrollPanePendingVisits.setBounds(63, 36, 305, 246);
scrollPanePendingVisits.setViewportView(tablePendingVisits);
frame.getContentPane().add(scrollPanePendingVisits);
}
public void refreshTableModel(){
String[] sampleString = {"9:00", "Bobby", "Tables"};
// search for row with 9:00 and replace values in given columns
for (int i = 0; i < pendingVisitModel.getRowCount(); i++) {
if( sampleString[0].equals(pendingVisitModel.getValueAt(i, 0)) ) { // Change row values when both hours are equal
pendingVisitModel.setValueAt(sampleString[1], i, 1); // Change at type column
pendingVisitModel.setValueAt(sampleString[2], i, 2); // Change at status column
}
}
}
}
// Custom TableModel
class PendingVisitModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
private String[] columnNames = {"HOUR", "TYPE", "STATE"};
private Vector<String[]> data = new Vector<String[]>();
public PendingVisitModel() {
for(int i = 8; i<15; i++) {
data.add(new String[]{i+":00", "-", "Free"} );
data.add(new String[]{i+":15", "-", "Free"} );
data.add(new String[]{i+":30", "-", "Free"} );
data.add(new String[]{i+":45", "-", "Free"} );
}
}
public int getColumnCount() {
return columnNames.length;
}
public int getRowCount() {
return data.size();
}
public String getColumnName(int col) {
return columnNames[col];
}
public String getValueAt(int row, int col) {
String[] temp = data.get(row);
if(temp.length > 0 && col < 3)
return temp[col];
else
return null;
}
public void setValueAt(String[] value, int row, int col) {
String[] temp = data.get(row);
temp[col] = value[col];
data.set(row, temp);
fireTableRowsUpdated(row, row);
}
public void insertRow(String[] value) {
data.add(value);
fireTableRowsInserted(data.size(), data.size());
}
public void clearRows(){
data.clear();
fireTableDataChanged();
}
public void removeAllEntry(){
data.clear();
fireTableDataChanged();
}
public boolean isCellEditable(int row, int col) {
return false;
}
#Override
public Class<String> getColumnClass(int colNum) {
return String.class;
}
}
PendingVisitModel pendingVisitModel = new PendingVisitModel();
It looks to me like the pendingVisitModel is defined as a local variable. This is the model you add to the table.
The refreshTableModel() method is referencing a pendingVisitModelvariable, but I would guess this is an instance variable that is NOT used by the table.
Get rid of local instance of your pendingVisitModel.
fireTableDataChanged();
Also don't keep using firTableDataChanged in all your TableModel methods. The API provides other methods that are more appropriate for different events.
Edit:
public void setValueAt(String[] value, int row, int col) {
You did not override the setValueAt(...) method. The value parameter is an Object not a String array.
Whenever you override a method of a class you should use the #Override annotation before the method. This way the compiler will give you an error if you override a method incorrectly (saving hours of frustration...).
#Override
public void setValueAt(Object value, int row, int col) {
I would like to populate a JTable during runtime with many rows (lets say 10000). But all my attempts are very poor and inefficient.
Starting point is the addData method which gets a List of Objects representing a row. I tried to fill the table via a SwingWorker but this only works for small data for me.
Another attempt was setting the data directly without using any kind of thread, but this is also very slow, at least the UI isn't blocked like its the case with the SwingWorker.
So how do you do this is general? The table should be filled row by row or chunkwise but not all by one and the vertical scrollbar should be scrollable meanwhile.
My TableModel:
public class MyTableModel extends AbstractTableModel {
/**
*
*/
private static final long serialVersionUID = 1L;
String[] columnNames;
public Map<Long, ErrorMessage> data = new LinkedHashMap<Long, ErrorMessage>();
public MyTableModel(String[] header) {
columnNames = header;
}
public String getColumnName(int col) {
return columnNames[col].toString();
}
#Override
public int getColumnCount() {
return columnNames.length;
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public Object getValueAt(int row, int col) {
.
.
return value;
}
public void addRow(long id, MyDataObject o) {
data.put(id, m);
fireTableRowsInserted(0,nqm_messages.size()-1);
}
}
SwingWorker implementation:
class TableSwingWorker extends SwingWorker<MyTableModel, MyDataObject> {
private final MyTableModel tableModel;
List<MyDataObject> messages;
public TableSwingWorker(MyTableModel tableModel, List<MyDataObject> dataList) {
this.tableModel = tableModel;
this.messages = new LinkedList<MyDataObject>(mm);
}
#Override
protected MyTableModel doInBackground() throws Exception {
for(MyDataObject s : messages) {
publish(s);
}
return tableModel;
}
#Override
protected void process(List<MyDataObject> chunks) {
for(MyDataObject row : chunks){
Long l = Long.parseLong(row.getId());
tableModel.addRow(l, row);
}
}
}
Add Objects to JTable:
public void addData(List<MyDataObject> o) {
MyTableModel m = (MyTableModel)table.getModel();
(new TableSwingWorker(m,o)).execute();
//for(int i=0; i < mm.size();i++) {
// long l = Long.parseLong(mm.get(i).getId());
// m.addRow(l, mm.get(i));
//}
}
So, a number of things have being identified from the comments...
You need to correctly fire the row inserted method, indicating only those rows that have being added and where they have being updated. This very important, as the the table has being optimised for speed
You should provide batch add method for your table model, allowing you to more easily add multiple rows in a single or as few steps as possible
You should have the SwingWorker periodically sleep or yield, to allow it time to publish the results.
So, in this example, I'm adding 1, 000, 000 rows. In my test it took slightly under 1 second...
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
public class TestTableLoad01 {
public static void main(String[] args) {
new TestTableLoad01();
}
public TestTableLoad01() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
MyTableModel model = new MyTableModel();
JTable table = new JTable(model);
table.setDefaultRenderer(Date.class, new TimeCellRenderer());
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
TableSwingWorker worker = new TableSwingWorker(model);
worker.execute();
}
});
}
public class TimeCellRenderer extends DefaultTableCellRenderer {
private DateFormat df;
public TimeCellRenderer() {
df = new SimpleDateFormat("HH:mm:ss");
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (value instanceof Date) {
value = df.format(value);
}
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
return this;
}
}
public class MyTableModel extends AbstractTableModel {
private String[] columnNames = new String[]{"Date", "Row"};
private List<RowData> data;
public MyTableModel() {
data = new ArrayList<>(25);
}
#Override
public Class<?> getColumnClass(int columnIndex) {
return columnIndex == 0 ? Date.class : Integer.class;
}
#Override
public String getColumnName(int col) {
return columnNames[col];
}
#Override
public int getColumnCount() {
return columnNames.length;
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public Object getValueAt(int row, int col) {
RowData value = data.get(row);
return col == 0 ? value.getDate() : value.getRow();
}
public void addRow(RowData value) {
int rowCount = getRowCount();
data.add(value);
fireTableRowsInserted(rowCount, rowCount);
}
public void addRows(RowData... value) {
addRows(Arrays.asList(value));
}
private void addRows(List<RowData> rows) {
int rowCount = getRowCount();
data.addAll(rows);
fireTableRowsInserted(rowCount, getRowCount() - 1);
}
}
public class RowData {
private Date date;
private int row;
public RowData(int row) {
this.date = new Date();
this.row = row;
}
public Date getDate() {
return date;
}
public int getRow() {
return row;
}
}
public class TableSwingWorker extends SwingWorker<MyTableModel, RowData> {
private final MyTableModel tableModel;
public TableSwingWorker(MyTableModel tableModel) {
this.tableModel = tableModel;
}
#Override
protected MyTableModel doInBackground() throws Exception {
// This is a deliberate pause to allow the UI time to render
Thread.sleep(2000);
System.out.println("Start polulating");
for (int index = 0; index < 1000000; index++) {
RowData data = new RowData(index);
publish(data);
Thread.yield();
}
return tableModel;
}
#Override
protected void process(List<RowData> chunks) {
System.out.println("Adding " + chunks.size() + " rows");
tableModel.addRows(chunks);
}
}
}
I have a subclass of AbstractTableModel and a JFrame to display the data from my table, ran over and the only error that appears instead of the column names appear A, B, C
What am I doing wrong?
Here are my classes
package Biblioteca;
import java.util.ArrayList;
import javax.swing.table.AbstractTableModel;
public class TabelaAlunos extends AbstractTableModel {
private ArrayList linhas = null;
private String[] colunas = {"id_aluno", "nome_aluno", "matricula", "telefone", "email", "sexo"};
public TabelaAlunos(ArrayList lin, String[] col) {
setColunas(col);
setLinhas(lin);
}
public ArrayList getLinhas() {
return linhas;
}
public void setLinhas(ArrayList dados) {
linhas = dados;
}
public String[] getColunas() {
return colunas;
}
public void setColunas(String[] nomes) {
colunas = nomes;
}
#Override
public int getColumnCount() {
return colunas.length;
}
#Override
public int getRowCount() {
return linhas.size();
}
#Override
public String getColumnName(int columnIndex) {
return colunas[columnIndex];
}
#Override
public Object getValueAt(int numLin, int columnIndex) {
Object[] linha = (Object[]) getLinhas().get(numLin);
return linha[columnIndex];
}
};
There is a typo in this method:
public String getColomnName(int numCol) {
return colunas[numCol];
}
It should be (note the u instead of o):
#Override
public String getColumnName(int numCol) {
return colunas[numCol];
}
This is why #Override annotation is important when we are subclassing and overriding methods. If you include this annotation in your actual code it shouldn't compile because getColomnName(...) is not defined in parent class.
The same principle applies for all these methods:
getColumnCount()
getRowCount()
getValueAt(int row, int column)
Edit
Based on your update your table model looks just fine. I've made an MCVE using your table model and it all worked as expected. Check out the questions' third revision, you don't use TabelaAlunos table model but TabelaLivros instead, so it's probably the issue is in that table model.
You might also consider wrap your data using POJO's to model business data and implement a table model like exemplified here. There are advanced alternatives shown here and here. Also see Table From Database by Rob Camick.
Finally, please see the example below:
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
public class Demo {
private void createAndShowGUI() {
String[] header = {"id_aluno", "nome_aluno", "matricula", "telefone", "email", "sexo"};
ArrayList<Object[]> data = new ArrayList<>();
data.add(new Object[] {1, "Fernando", "1234567890", "1234-567890", "email#example.com", "M"});
TableModel model = new TabelaAlunos(data, header);
JTable table = new JTable(model);
JFrame frame = new JFrame("Demo");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Demo().createAndShowGUI();
}
});
}
public class TabelaAlunos extends AbstractTableModel {
private ArrayList linhas;
private String[] colunas;
public TabelaAlunos(ArrayList lin, String[] col) {
setColunas(col);
setLinhas(lin);
}
public ArrayList getLinhas() {
return linhas;
}
public void setLinhas(ArrayList dados) {
linhas = dados;
}
public String[] getColunas() {
return colunas;
}
public void setColunas(String[] nomes) {
colunas = nomes;
}
#Override
public int getColumnCount() {
return colunas.length;
}
#Override
public int getRowCount() {
return linhas.size();
}
#Override
public String getColumnName(int columnIndex) {
return colunas[columnIndex];
}
#Override
public Object getValueAt(int numLin, int columnIndex) {
Object[] linha = (Object[]) getLinhas().get(numLin);
return linha[columnIndex];
}
}
}
Screenshot
I would like to populate a JTable during runtime with many rows (lets say 10000). But all my attempts are very poor and inefficient.
Starting point is the addData method which gets a List of Objects representing a row. I tried to fill the table via a SwingWorker but this only works for small data for me.
Another attempt was setting the data directly without using any kind of thread, but this is also very slow, at least the UI isn't blocked like its the case with the SwingWorker.
So how do you do this is general? The table should be filled row by row or chunkwise but not all by one and the vertical scrollbar should be scrollable meanwhile.
My TableModel:
public class MyTableModel extends AbstractTableModel {
/**
*
*/
private static final long serialVersionUID = 1L;
String[] columnNames;
public Map<Long, ErrorMessage> data = new LinkedHashMap<Long, ErrorMessage>();
public MyTableModel(String[] header) {
columnNames = header;
}
public String getColumnName(int col) {
return columnNames[col].toString();
}
#Override
public int getColumnCount() {
return columnNames.length;
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public Object getValueAt(int row, int col) {
.
.
return value;
}
public void addRow(long id, MyDataObject o) {
data.put(id, m);
fireTableRowsInserted(0,nqm_messages.size()-1);
}
}
SwingWorker implementation:
class TableSwingWorker extends SwingWorker<MyTableModel, MyDataObject> {
private final MyTableModel tableModel;
List<MyDataObject> messages;
public TableSwingWorker(MyTableModel tableModel, List<MyDataObject> dataList) {
this.tableModel = tableModel;
this.messages = new LinkedList<MyDataObject>(mm);
}
#Override
protected MyTableModel doInBackground() throws Exception {
for(MyDataObject s : messages) {
publish(s);
}
return tableModel;
}
#Override
protected void process(List<MyDataObject> chunks) {
for(MyDataObject row : chunks){
Long l = Long.parseLong(row.getId());
tableModel.addRow(l, row);
}
}
}
Add Objects to JTable:
public void addData(List<MyDataObject> o) {
MyTableModel m = (MyTableModel)table.getModel();
(new TableSwingWorker(m,o)).execute();
//for(int i=0; i < mm.size();i++) {
// long l = Long.parseLong(mm.get(i).getId());
// m.addRow(l, mm.get(i));
//}
}
So, a number of things have being identified from the comments...
You need to correctly fire the row inserted method, indicating only those rows that have being added and where they have being updated. This very important, as the the table has being optimised for speed
You should provide batch add method for your table model, allowing you to more easily add multiple rows in a single or as few steps as possible
You should have the SwingWorker periodically sleep or yield, to allow it time to publish the results.
So, in this example, I'm adding 1, 000, 000 rows. In my test it took slightly under 1 second...
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
public class TestTableLoad01 {
public static void main(String[] args) {
new TestTableLoad01();
}
public TestTableLoad01() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
MyTableModel model = new MyTableModel();
JTable table = new JTable(model);
table.setDefaultRenderer(Date.class, new TimeCellRenderer());
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
TableSwingWorker worker = new TableSwingWorker(model);
worker.execute();
}
});
}
public class TimeCellRenderer extends DefaultTableCellRenderer {
private DateFormat df;
public TimeCellRenderer() {
df = new SimpleDateFormat("HH:mm:ss");
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (value instanceof Date) {
value = df.format(value);
}
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
return this;
}
}
public class MyTableModel extends AbstractTableModel {
private String[] columnNames = new String[]{"Date", "Row"};
private List<RowData> data;
public MyTableModel() {
data = new ArrayList<>(25);
}
#Override
public Class<?> getColumnClass(int columnIndex) {
return columnIndex == 0 ? Date.class : Integer.class;
}
#Override
public String getColumnName(int col) {
return columnNames[col];
}
#Override
public int getColumnCount() {
return columnNames.length;
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public Object getValueAt(int row, int col) {
RowData value = data.get(row);
return col == 0 ? value.getDate() : value.getRow();
}
public void addRow(RowData value) {
int rowCount = getRowCount();
data.add(value);
fireTableRowsInserted(rowCount, rowCount);
}
public void addRows(RowData... value) {
addRows(Arrays.asList(value));
}
private void addRows(List<RowData> rows) {
int rowCount = getRowCount();
data.addAll(rows);
fireTableRowsInserted(rowCount, getRowCount() - 1);
}
}
public class RowData {
private Date date;
private int row;
public RowData(int row) {
this.date = new Date();
this.row = row;
}
public Date getDate() {
return date;
}
public int getRow() {
return row;
}
}
public class TableSwingWorker extends SwingWorker<MyTableModel, RowData> {
private final MyTableModel tableModel;
public TableSwingWorker(MyTableModel tableModel) {
this.tableModel = tableModel;
}
#Override
protected MyTableModel doInBackground() throws Exception {
// This is a deliberate pause to allow the UI time to render
Thread.sleep(2000);
System.out.println("Start polulating");
for (int index = 0; index < 1000000; index++) {
RowData data = new RowData(index);
publish(data);
Thread.yield();
}
return tableModel;
}
#Override
protected void process(List<RowData> chunks) {
System.out.println("Adding " + chunks.size() + " rows");
tableModel.addRows(chunks);
}
}
}
I've a subclass of JTable that uses a custom table model (an implementation of AbstractTableModel) to manage data.
The problem occurs when I try to delete a row with the method deleteRow. The row in my table is replaced by a blank string but the row is not deleted.
Here is the code:
public class LiveSearchTableModel extends AbstractTableModel
{
private List<String> columnNames = new ArrayList<String>();
private Map<Point, Object> displayData = new HashMap<Point, Object>();
private Map<Point, Object> originalData = new HashMap<Point, Object>();
public LiveSearchTableModel(List<String> columnNames,
Map<Point, Object> tableData)
{
this.columnNames = columnNames;
this.displayData = tableData;
this.originalData.putAll(tableData);
}
#Override
public int getColumnCount() {
return columnNames.size();
}
#Override
public int getRowCount() {
return displayData.size() / columnNames.size();
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
return displayData.get(new Point(rowIndex, columnIndex));
}
public void deleteRow (int row)
{
for (int cont = 0; cont < columnNames.size();cont++)
{
displayData.remove(new Point (row,cont));
}
this.fireTableRowsDeleted(row, row);
}
#Override
public void setValueAt(Object value, int rowIndex, int columnIndex)
{
this.displayData.put(new Point(rowIndex, columnIndex), value);
this.fireTableDataChanged();
}
}
The sscce below illustrates one potential problem: The keys of a Map are not ordered, while the rows of a table are. In the approach shown, an array of keys must by updated with each change to the data. See also this related example. If required, you could extend Point to implement Comparable, as shown here for Value.
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
/**
* #see https://stackoverflow.com/q/12330167/230513
*/
public class TableModelTest extends JPanel {
public TableModelTest() {
super(new BorderLayout());
final MyModel model = new MyModel();
JTable table = new JTable(model);
this.add(new JScrollPane(table));
this.add(new JButton(new AbstractAction("Delete") {
#Override
public void actionPerformed(ActionEvent e) {
model.remove(0);
}
}), BorderLayout.SOUTH);
}
public class MyModel extends AbstractTableModel {
private List<String> names = new ArrayList<String>();
private Map<Point, Object> data = new HashMap<Point, Object>();
private Point[] keys;
public MyModel() {
this.names = Arrays.asList("Point", "Name");
data.put(new Point(1, 1), "One");
data.put(new Point(2, 2), "Two");
data.put(new Point(3, 3), "Three");
this.keys = data.keySet().toArray(new Point[data.size()]);
}
public void remove(int row) {
data.remove(keys[row]);
keys = data.keySet().toArray(new Point[data.size()]);
this.fireTableRowsDeleted(row, row);
}
#Override
public int getColumnCount() {
return names.size();
}
#Override
public String getColumnName(int col) {
return names.get(col);
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public Object getValueAt(int row, int col) {
if (col == 0) {
return keys[row];
} else {
return data.get(keys[row]);
}
}
}
private void display() {
JFrame f = new JFrame("TableModelTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new TableModelTest().display();
}
});
}
}