AbstractTableModel GUI display issue
Because database access is inherently asynchronous, you'll surely want to retrieve rows in the background to avoid blocking the event dispatch thread; SwingWorker
makes this relatively easy. Fetch rows in your implementation of doInBackground()
, publish()
interim results, and add them to the table model in your implementation of process()
. A complete example that outlines the attendant benefits is shown here. The example loops through a file, but you can substitute your ResultSet
operations.
while (rs.next()) {
//collect row data
publish(rowData);
}
Defer tableData.add()
to your implementation of process()
.
Focusing on the interaction between the custom TableModel
and its contained SwingWorker
, the following complete example creates a test database having N
rows and displays a JTable
showing the results of a query of that table. In particular,
JDBCModel
extendsAbstractTableModel
. For simplicity, the model'sdata
is stored in aList<Row>
, andResultSetMetaData
is used for the column names. As a more abstract alternative, see Apache CommonsDbUtils
, which uses Class Literals as Runtime-Type Tokens andResultSetMetaData
to safely create instances of row data.JDBCModel
delegates row retrieval to a privateJDBCWorker
; it invokespublish()
on each row retrieved from theResultSet
; becauseprocess()
runs on the EDT, the worker can optimize the number of table model events that it fires on behalf of the parent model usingfireTableRowsInserted()
.Similarly, your implementation of
delete()
should reside inJDBCModel
, not the GUI; it shouldfireTableRowsDeleted()
after the row is successfully deleted from the database and removed fromdata
.Add
Thread.sleep()
to the worker's background loop to see the effect of artificially increasing latency.Use
setProgress()
and aPropertyChangeListener
, shown here, to display progress; aJOptionPane
whendone()
may be superfluous.Override
getPreferredScrollableViewportSize()
to customize the size of the table's enclosingJScrollPane
.Avoid class names, e.g.
TableModel
, that collide with common API names.A variation that implements live filtering in the view is examined here.
import java.awt.Dimension;
import java.awt.EventQueue;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingWorker;
import javax.swing.table.AbstractTableModel;
/**
* @see https://stackoverflow.com/a/34742409/230513
* @see https://stackoverflow.com/a/24762078/230513
*/
public class WorkerTest {
private static final int N = 1_000;
private static final String URL = "jdbc:h2:mem:test";
private static final Random r = new Random();
private void display() {
JFrame f = new JFrame("WorkerTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
createTestDatabase(N);
JDBCModel model = new JDBCModel(getConnection(), "select * from city");
f.add(new JScrollPane(new JTable(model) {
@Override
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(320, 240);
}
}));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static class Row {
int ID;
String name;
}
private static class JDBCModel extends AbstractTableModel {
private final List<Row> data = new ArrayList<>();
private ResultSet rs = null;
private ResultSetMetaData meta;
public JDBCModel(Connection conn, String query) {
try {
Statement s = conn.createStatement();
rs = s.executeQuery(query);
meta = rs.getMetaData();
JDBCWorker worker = new JDBCWorker();
worker.execute();
} catch (SQLException e) {
e.printStackTrace(System.err);
}
}
@Override
public int getRowCount() {
return data.size();
}
@Override
public int getColumnCount() {
try {
return meta.getColumnCount();
} catch (SQLException e) {
e.printStackTrace(System.err);
}
return 0;
}
@Override
public Object getValueAt(int rowIndex, int colIndex) {
Row row = data.get(rowIndex);
switch (colIndex) {
case 0:
return row.ID;
case 1:
return row.name;
}
return null;
}
@Override
public String getColumnName(int colIndex) {
try {
return meta.getColumnName(colIndex + 1);
} catch (SQLException e) {
e.printStackTrace(System.err);
}
return null;
}
private class JDBCWorker extends SwingWorker<List<Row>, Row> {
@Override
protected List<Row> doInBackground() {
try {
while (rs.next()) {
Row r = new Row();
r.ID = rs.getInt(1);
r.name = rs.getString(2);
publish(r);
}
} catch (SQLException e) {
e.printStackTrace(System.err);
}
return data;
}
@Override
protected void process(List<Row> chunks) {
int n = getRowCount();
for (Row row : chunks) {
data.add(row);
}
fireTableRowsInserted(n, n + chunks.size());
}
}
}
private static void createTestDatabase(int n) {
Connection conn = getConnection();
try {
Statement st = conn.createStatement();
st.execute("create table city(id integer, name varchar2)");
PreparedStatement ps = conn.prepareStatement(
"insert into city values (?, ?)");
for (int i = 0; i < n; i++) {
ps.setInt(1, i);
ps.setString(2, (char) ('A' + r.nextInt(26))
+ String.valueOf(r.nextInt(1_000_000)));
ps.execute();
}
} catch (SQLException ex) {
ex.printStackTrace(System.err);
}
}
private static Connection getConnection() {
try {
return DriverManager.getConnection(URL, "", "");
} catch (SQLException e) {
e.printStackTrace(System.err);
}
return null;
}
public static void main(String[] args) {
EventQueue.invokeLater(new WorkerTest()::display);
}
}
GUI Not working with custom AbstractTableModel. Errors Unknown Source
The LinkedList
is never initalised...
LinkedList<Student> data = null;
// ... ///
StudentTable.setModel(new StudentTableModel(data));
Meaning that when getRowCount
is called, the data
object is null
, hence the NullPointerException
Updated
You have any number of options that you can use to pass a reference to main LinkedList
into the UI, but it will come down to how your work flow works.
You could...
Pass the reference to the UI via it's constructor....
public GUI(Database DB, LinkedList<Student> students) {...}
You could...
Pass the LinkedList
in via a method in you GUI
class...
public class GUI extends JFrame {
//...//
public GUI(Database DB) {...}
public void setStudent(LinkedList<Student> students) {
StudentTable.setModel(new StudentTableModel(students));
}
}
Updated with example
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.LinkedList;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.AbstractTableModel;
public class PassTable {
public static void main(String[] args) {
new PassTable();
}
public PassTable() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private ExampleTable example;
public TestPane() {
setLayout(new BorderLayout());
example = new ExampleTable();
add(example);
JButton add = new JButton("Add");
add(add, BorderLayout.SOUTH);
add.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
LinkedList<Person> people = new LinkedList<Person>();
people.add(new Person("A", "A", 1));
people.add(new Person("B", "B", 2));
people.add(new Person("C", "C", 3));
people.add(new Person("D", "D", 4));
example.setPeople(people);
}
});
}
}
public class ExampleTable extends JPanel {
private JTable table;
public ExampleTable() {
this(new LinkedList<Person>());
}
public ExampleTable(LinkedList<Person> people) {
setLayout(new BorderLayout());
table = new JTable(new SampleTableModel(people));
add(new JScrollPane(table));
}
public void setPeople(LinkedList<Person> people) {
table.setModel(new SampleTableModel(people));
}
}
public class SampleTableModel extends AbstractTableModel {
private LinkedList<Person> data;
public SampleTableModel(LinkedList<Person> data) {
this.data = data;
}
@Override
public int getRowCount() {
return data.size();
}
@Override
public String getColumnName(int column) {
String name = "";
switch (column) {
case 0:
name = "First name";
break;
case 1:
name = "Last name";
break;
case 2:
name = "Age";
break;
}
return name;
}
@Override
public int getColumnCount() {
return 3;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
Object value = null;
Person person = data.get(rowIndex);
switch (columnIndex) {
case 0:
value = person.getFirstName();
break;
case 1:
value = person.getLastName();
break;
case 2:
value = person.getAge();
break;
}
return value;
}
}
public class Person {
private String firstName;
private String lastName;
private int age;
public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public int getAge() {
return age;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
}
TableModel - Doesn`t show anything in the gui?
This method may be the problem,
public void setPlaylist(Playlist playlists){
playlist.add(playlists);
fireTableRowsInserted(playlist.size()-1, playlist.size()-1);
}
Here for example if we got the playlists with size 20. Then after assigning to the table model playlist you are calling,
fireTableRowsInserted(playlist.size()-1, playlist.size()-1);
which will reduce to,
fireTableRowsInserted(19, 19);
But actually what has happened is we are not just inserting one row but 20 rows. So as suggested by @Ivan.Latysh in the answer section, you need to call insert from the start of the row count to the end of the row count. This will repaint
the inserted rows.
P.S: You can simply call fireTableDataChanged();
method also. But this method will repaint
entire table. Prefer this method only if the entire table list is changed. Else you have respective fireXX
methods for Insertion, Update, Delete.
Problem with JCheckbox in my table that extends AbstractTableModel
I don't see where you store the data in the TableModel. All you have is an Array of filenames. You don't store the Boolean value for each row to know if it is selected or not.
File f = new File(dir, filenames[row]);
In your getValueAte(...)
method you continually create a new File object. This is not very efficient and the getValueAt(...) method is constantly invoked by the table. For example every time you highlight a row you need to repaint the previous row as unselected and then the current row as selected.
So the getValueAt(...)
method should be as efficient as possible.
So what I would do:
create a custom TableModel that contains all the values you want to display in your table, plus the Boolean value for the check box.
forget about the custom renderer/editor for now. First get the logic working using the default renderer/editor for the Boolean column. Prove that the basic logic works. Then if you think you need to make the rendering fancier you create a custom renderer. Then if you have problems you know where the problems are.
You can check out Row Table Model for a step by step example of creating a TableModel based on a custom object.
AbstractTableModel updating from different threads
expect using
SwingWorker
.
I too would expect to use a SwingWorker
, but you may have meant except SwingWorker
.
The best practice is to update the TableModel
on the event dispatch thread as shown here in the process()
implementation of JDBCWorker
, a subclass of SwingWorker
.
Alternatively, you can update the table's model using EventQueue.invokeLater()
, as shown here, but the approach is tedious and error prone. See the comments for details.
Update Table GUI that extends custom AbstractTableModel
Your solution seems overly complicated. If I understand the basics, the user chooses a value from a combo box, then based on the selection some data is loaded into the table.
There is no need to create a custom table model to do this.
A TableModel contains data. If you want to change the data, then one way to do this is to simply create a new TableModel. So you add an ActionListener to your combo box. When an item is selected you retrive your data and load the data into an Vector or an Array. Using this data you can create a new TableModel and update the JTable in two lines of code:
DefaultTableModel model = new DefaultTableModel(...);
table.setModel( model );
If you need to customize the model to override the getColumnClass() or isCellEditable() methods, then you should extend the DefaultTableModel. I don't see any need to implement the whole model.
JTable extended from AbtractTableModel is not updating GUI when i add row
I see that you add the row to a new table that you've just created inside the actionPerformed
method. Usually, we use actions to change/alter already existing GUI components. This might be a reason why you don't see any change on the GUI.
I guess, the table that is displayed in the scroll pane is created with
MyJtable tv = new MyJtable(a);
table = new JTable(tv);
Try adding the row to table
(via tv
which has to be made an instance variable first) instead of the newly created table.
Related Topics
Redirect to an External Url from Controller Action in Spring MVC
Java Executors: How to Be Notified, Without Blocking, When a Task Completes
Convert Timestamp in Milliseconds to String Formatted Time in Java
Abstract Class VS Interface in Java
What Is the Meaning of the Cascadetype.All for a @Manytoone JPA Association
How to Use Raw SQL Within a Spring Repository
Convert Float to String and String to Float in Java
Interview Question: Check If One String Is a Rotation of Other String
Android 6.0 Open Failed: Eacces (Permission Denied)
Java - Convert Integer to String
Jackson - Deserialize Using Generic Class
When Should One Use Final for Method Parameters and Local Variables
What Is the Java's Internal Represention for String? Modified Utf-8? Utf-16
Differencebetween Join and Join Fetch When Using JPA and Hibernate