Abstracttablemodel Gui Display Issue

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 extends AbstractTableModel. For simplicity, the model's data is stored in a List<Row>, and ResultSetMetaData is used for the column names. As a more abstract alternative, see Apache Commons DbUtils, which uses Class Literals as Runtime-Type Tokens and ResultSetMetaData to safely create instances of row data.

  • JDBCModel delegates row retrieval to a private JDBCWorker; it invokes publish() on each row retrieved from the ResultSet; because process() runs on the EDT, the worker can optimize the number of table model events that it fires on behalf of the parent model using fireTableRowsInserted().

  • Similarly, your implementation of delete() should reside in JDBCModel, not the GUI; it should fireTableRowsDeleted() after the row is successfully deleted from the database and removed from data.

  • Add Thread.sleep() to the worker's background loop to see the effect of artificially increasing latency.

  • Use setProgress() and a PropertyChangeListener, shown here, to display progress; a JOptionPane when done() may be superfluous.

  • Override getPreferredScrollableViewportSize() to customize the size of the table's enclosing JScrollPane.

  • Avoid class names, e.g. TableModel, that collide with common API names.

  • A variation that implements live filtering in the view is examined here.

image

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:

  1. create a custom TableModel that contains all the values you want to display in your table, plus the Boolean value for the check box.

  2. 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



Leave a reply



Submit