Problems updating an AbstractTableModel
I'm building a database search for a CCG. I have the GUI built with ComboBoxes, TextFields and Buttons that queries a MySQL database when the user presses the search button. All of that is working and spitting the correct information back to the console. Now I'm trying to add a table to display the results to the GUI.
I used an AbstractTableModel because two of my columns (one for now) are going to display ImageIcons. The table displays correctly with all the information when I start the program and the GUI initially appears. However, I can't get the table to update when I press the search button, which I currently have doing a different search than the initial search. I have the query spitting back the information to the console and it is grabbing the correct and different data from the query, but I can't get the table to update.
I tried invoking the fireTableDataChanged() method as mentioned in the JavaDocs. I tried repainting, not creating the table initially, etc. None of it is working and I feel like there is a simple mistake or concept I am getting wrong.
Code :
public class SSCCE extends JFrame {
String imageString;
ImageIcon setIcon;
static String dbUsername = "...";
static String dbPassword = "...";
static String dbName = "...";
static ResultSet rs;
JButton buttonSearch = new JButton("Search");
JPanel panel = new JPanel();
Connection conn = null;
List<SpiritData> sqlSearchResultsTable = new ArrayList<>();
//---------------------------------------------------------------------------------------------------------
public SSCCE() {
panel.setLayout(new MigLayout(
"debug 0, insets 15, gapy 5",
"[80, right] 5 [105] 16 [80, right] 5 [105] 16" +
"[80, right] 5 [60] 5 [60] 16" +
"[80, right] 5 [60] 5 [60] 16" +
"[80, right] 5 [105] 16 [80, right] 5 [105]",
"[align center]"));
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/" + dbName, dbUsername, dbPassword);
Statement sqlState = conn.createStatement();
String selectStuff = "SELECT * FROM swdbtest.spiritdatabase2 WHERE ActivationCost = 8";
rs = sqlState.executeQuery(selectStuff);
while(rs.next()){
String sqlType = rs.getString("Type");
String sqlHomeland = rs.getString("Homeland");
String sqlName = rs.getString("Name");
String sqlReleaseSet = rs.getString("ReleaseSet");
System.out.println(sqlName);
setIcon = getImage(sqlReleaseSet + ".png");
sqlSearchResultsTable.add(new SpiritData(sqlType, sqlHomeland, sqlName, setIcon));
}
}
catch (SQLException ex) {
System.out.println("SQLException: " + ex.getMessage());
System.out.println("VendorError: " + ex.getErrorCode()); }
catch (ClassNotFoundException e) {
e.printStackTrace();
}
SpiritTableModel model = new SpiritTableModel(sqlSearchResultsTable);
JTable table = new JTable(model);
panel.add(buttonSearch, "wrap");
buttonSearch.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
buttonSearchActionPerformed(evt);
//table.fireTableDataChanged(); //<---- doesn't work
}
});
panel.add(new JScrollPane(table), "growx, spanx");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(panel);
pack();
setLocationRelativeTo(null);
setVisible(true);
}
// --------------------------------------------------------------------------------------------------------------------
private void buttonSearchActionPerformed(ActionEvent evt) {
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/" + dbName, dbUsername, dbPassword);
Statement sqlState = conn.createStatement();
String selectStuff = "SELECT * FROM swdbtest.spiritdatabase2 WHERE ActivationCost BETWEEN 9 AND 10";
rs = sqlState.executeQuery(selectStuff);
while(rs.next()){
String sqlType = rs.getString("Type");
String sqlHomeland = rs.getString("Homeland");
String sqlName = rs.getString("Name");
String sqlReleaseSet = rs.getString("ReleaseSet");
System.out.println(sqlName);
setIcon = getImage(sqlReleaseSet + ".png");
sqlSearchResultsTable.add(new SpiritData(sqlType, sqlHomeland, sqlName, setIcon));
//UPDATE TABLE ?????????????????
//table.fireTableDataChanged(); //<---- doesn't work
} }
catch (SQLException ex) {
System.out.println("SQLException: " + ex.getMessage());
System.out.println("VendorError: " + ex.getErrorCode()); }
catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
// --------------------------------------------------------------------------------------------------------------------
private ImageIcon getImage(String path)
{
java.net.URL url = getClass().getResource(path);
if (url != null)
return (new ImageIcon(url));
else
{
return null;
}
}
// --------------------------------------------------------------------------------------------------------------------
public class SpiritData {
private String spiritType;
private String spiritHomeland;
private String spiritName;
private ImageIcon spiritReleaseSet;
public SpiritData(String spiritType, String spiritHomeland, String spiritName, ImageIcon spiritReleaseSet) {
this.spiritType = spiritType;
this.spiritHomeland = spiritHomeland;
this.spiritName = spiritName;
this.spiritReleaseSet = spiritReleaseSet;
}
public String getSpiritType() {
return spiritType;
}
public String getSpiritHomeland() {
return spiritHomeland;
}
public String getSpiritName() {
return spiritName;
}
public ImageIcon getSpiritReleaseSet() {
return spiritReleaseSet;
}
}
// --------------------------------------------------------------------------------------------------------------------
public class SpiritTableModel extends AbstractTableModel {
private List<SpiritData> spiritTable;
public SpiritTableModel(List<SpiritData> spTable) {
this.spiritTable = new ArrayList<>(spTable);
}
@Override
public int getRowCount() {
return spiritTable.size();
}
@Override
public int getColumnCount() {
return 4;
}
@Override
public String getColumnName(int column) {
String name = "??";
switch (column) {
case 0:
name = "Type";
break;
case 1:
name = "Homeland";
break;
case 2:
name = "Name";
break;
case 3:
name = "Set";
break;
}
return name;
}
@Override
public Class<?> getColumnClass(int columnIndex) {
Class type = String.class;
if(columnIndex == 3) {
type = ImageIcon.class;
}
return type;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
SpiritData cellValue = spiritTable.get(rowIndex);
Object value = null;
switch (columnIndex) {
case 0:
value = cellValue.getSpiritType();
break;
case 1:
value = cellValue.getSpiritHomeland();
break;
case 2:
value = cellValue.getSpiritName();
break;
case 3:
value = cellValue.getSpiritReleaseSet();
break;
}
return value;
}
}
// --------------------------------------------------------------------------------------------------------------------
public static void main(String[] args) {
new SSCCE();
}
}
Re: Problems updating an AbstractTableModel
Code :
SpiritTableModel model = new SpiritTableModel(sqlSearchResultsTable);//creates a new TableModel
...
sqlSearchResultsTable.add(new SpiritData(sqlType, sqlHomeland, sqlName, setIcon));//adds data to the sqlSearchResultsTable List
...
private List<SpiritData> spiritTable;
public SpiritTableModel(List<SpiritData> spTable) {
this.spiritTable = new ArrayList<>(spTable);//creates a copy of sqlSearchResultsTable
}
You should make sure the List that backs the TableModel actually contains what you want (via a debugger or println). Looks to me like your TableModel creates a new instance of a List for it's data, initially containing the data from the first query. Any later changes to sqlSearchResultsTable will not be reflected in spiritTable (and hence, the TableModel which is backed by that List).
Re: Problems updating an AbstractTableModel
Quote:
Originally Posted by
Psyclone625
I used an AbstractTableModel because two of my columns (one for now) are going to display ImageIcons. The table displays correctly with all the information when I start the program and the GUI initially appears. However, I can't get the table to update when I press the search button, which I currently have doing a different search than the initial search. I have the query spitting back the information to the console and it is grabbing the correct and different data from the query, but I can't get the table to update.
Your SpiritTableModel seems correct, at first sight. But the problem is that you are updating the original list sqlSearchResultsTable which is not the list contained in the table model.
The SpiritTableModel constructor, creates a new ArrayList, "cloning" (in the simple sense of duplicating) the list from the received list. Thus a modification to the original list has no effect.
If you want to add one SpiritData to your table model, define a method like:
public void addSpiritData(SpiritData sd)
in the SpiritTableModel. In this method: 1) add to the list and 2) invoke the fireTableRowsInserted(int firstRow, int lastRow) (you have to determine the index of the new row, but it's easy).
If you want to replace totally the list in your table model, define a method like:
public void setSpiritDataList(List<SpiritData> spTable)
in the SpiritTableModel. In this method: 1) assign the new list (it's a good idea to "clone" like you have done in the constructor) and 2) invoke fireTableDataChanged()
Re: Problems updating an AbstractTableModel
First of all, thank you for trying to help me and I apologize for not fully understanding everything. I think I understand what you're saying, but I am trying to track down where I am going wrong.
I took the entire MySQL query try-catch out of the constructor. I only put it in there to test whether I was getting anything into the table and then to see if I could refresh it. I could see that I was initially building the objects, but wasn't able to update them.
I took your advice and am using println to debug try to follow what is happening. I usually try to use println as much as I can to debug, but I forgot that I can use it to see the Objects address. I was only using it to look at variables. After removing the initial try-catch which was populating the table in the constructor, here's what I'm seeing when I walk through it.
In the constructor...
1) Creating new SpiritTableModel model (an AbstractTableModel) with data from the sqlSearchResultsTable (which is currently null/blank/empty).
2) Create new JTable with model as the TableModel.
3) Add Search button and table to the GUI (JFrame)
Initially, I just have a button and an empty table. When the SEARCH button is pressed...
4) sqlSearchResultsTable.clear(); to clear the ArrayList (I noticed that I wasn't doing this in my initial code and was just appending the old search results).
5) Query MySQL and use the while(rs.next()) loop to grab each line of the result set and append an object of class.SpiritData to the sqlSearchResults ArrayList.
6) ??? update model ???
If all that is correct, I guess here's where I'm stuck...
I don't understand the TableModels enough obviously. Here's what I think I'm supposed to do...
After the resultset while-loop completes building the ArrayList, and prior to exiting the try-block, update model (a SpiritTableModel) without creating a new model object? If so, is there a built-in method for that?
Is that correct? Or have I screwed up long before that? LOL
By the way, here's the updated version of my code. Everything is pretty much the same, but I removed MySQL query try-catch out of the constructor and added the arraylist.clear() method to the beginning of my try-catch in the search button's actionevent.
Code :
public class SSCCE1 extends JFrame {
String imageString;
ImageIcon setIcon;
static String dbUsername = "root";
static String dbPassword = "...";
static String dbName = "swdbtest";
static ResultSet rs;
JButton buttonSearch = new JButton("Search");
JPanel panel = new JPanel();
Connection conn = null;
List<SpiritData> sqlSearchResultsTable = new ArrayList<>();
//---------------------------------------------------------------------------------------------------------
public SSCCE1() {
panel.setLayout(new MigLayout(
"debug 0, insets 15, gapy 5",
"[80, right] 5 [105] 16 [80, right] 5 [105] 16" +
"[80, right] 5 [60] 5 [60] 16" +
"[80, right] 5 [60] 5 [60] 16" +
"[80, right] 5 [105] 16 [80, right] 5 [105]",
"[align center]"));
System.out.println("sqlSearchResultsTable #1 = " + sqlSearchResultsTable);
SpiritTableModel model = new SpiritTableModel(sqlSearchResultsTable);
System.out.println("Model #1: " + model);
JTable table = new JTable(model);
panel.add(buttonSearch, "wrap");
buttonSearch.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
buttonSearchActionPerformed(evt);
}
});
panel.add(new JScrollPane(table), "growx, spanx");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(panel);
pack();
setLocationRelativeTo(null);
setVisible(true);
}
// --------------------------------------------------------------------------------------------------------------------
private void buttonSearchActionPerformed(ActionEvent evt) {
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/" + dbName, dbUsername, dbPassword);
Statement sqlState = conn.createStatement();
String selectStuff = "SELECT * FROM swdbtest.spiritdatabase2 WHERE ActivationCost BETWEEN 9 AND 10";
rs = sqlState.executeQuery(selectStuff);
sqlSearchResultsTable.clear();
while(rs.next()){
String sqlType = rs.getString("Type");
String sqlHomeland = rs.getString("Homeland");
String sqlName = rs.getString("Name");
String sqlReleaseSet = rs.getString("ReleaseSet");
setIcon = getImage(sqlReleaseSet + ".png");
System.out.println("sqlSearchResultsTable #2 = " + sqlSearchResultsTable);
sqlSearchResultsTable.add(new SpiritData(sqlType, sqlHomeland, sqlName, setIcon));
System.out.println("sqlSearchResultsTable #3 = " + sqlSearchResultsTable);
}
// UPDATE MODEL
}
catch (SQLException ex) {
System.out.println("SQLException: " + ex.getMessage());
System.out.println("VendorError: " + ex.getErrorCode()); }
catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
// --------------------------------------------------------------------------------------------------------------------
private ImageIcon getImage(String path)
{
java.net.URL url = getClass().getResource(path);
if (url != null)
return (new ImageIcon(url));
else
{
return null;
}
}
// --------------------------------------------------------------------------------------------------------------------
public class SpiritData {
private String spiritType;
private String spiritHomeland;
private String spiritName;
private ImageIcon spiritReleaseSet;
public SpiritData(String spiritType, String spiritHomeland, String spiritName, ImageIcon spiritReleaseSet) {
this.spiritType = spiritType;
this.spiritHomeland = spiritHomeland;
this.spiritName = spiritName;
this.spiritReleaseSet = spiritReleaseSet;
}
public String getSpiritType() {
return spiritType;
}
public String getSpiritHomeland() {
return spiritHomeland;
}
public String getSpiritName() {
return spiritName;
}
public ImageIcon getSpiritReleaseSet() {
return spiritReleaseSet;
}
}
// --------------------------------------------------------------------------------------------------------------------
public class SpiritTableModel extends AbstractTableModel {
private List<SpiritData> spiritTable;
public SpiritTableModel(List<SpiritData> spTable) {
this.spiritTable = new ArrayList<>(spTable);
}
@Override
public int getRowCount() {
return spiritTable.size();
}
@Override
public int getColumnCount() {
return 4;
}
@Override
public String getColumnName(int column) {
String name = "??";
switch (column) {
case 0:
name = "Type";
break;
case 1:
name = "Homeland";
break;
case 2:
name = "Name";
break;
case 3:
name = "Set";
break;
}
return name;
}
@Override
public Class<?> getColumnClass(int columnIndex) {
Class type = String.class;
if(columnIndex == 3) {
type = ImageIcon.class;
}
return type;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
SpiritData cellValue = spiritTable.get(rowIndex);
Object value = null;
switch (columnIndex) {
case 0:
value = cellValue.getSpiritType();
break;
case 1:
value = cellValue.getSpiritHomeland();
break;
case 2:
value = cellValue.getSpiritName();
break;
case 3:
value = cellValue.getSpiritReleaseSet();
break;
}
return value;
}
}
// --------------------------------------------------------------------------------------------------------------------
public static void main(String[] args) {
new SSCCE1();
}
}
--- Update ---
Hey andbin, thanks for the reply. I just saw it. Every time a user presses the search button, I want to replace totally the list in my table model. I will never be appending the current table.
Re: Problems updating an AbstractTableModel
Quote:
Originally Posted by
Psyclone625
If all that is correct, I guess here's where I'm stuck...
I don't understand the TableModels enough obviously. Here's what I think I'm supposed to do...
After the resultset while-loop completes building the ArrayList, and prior to exiting the try-block, update model (a SpiritTableModel) without creating a new model object? If so, is there a built-in method for that?
First, you should not create a new table model instance every time, it would be a waste of time and resources. Just manipulate the datas in your current table model instance.
And when you say "is there a built-in method for that?", well, these methods are up to you. It's you that can create an expanded API for your table model.
When you extend AbstractTableModel you have to redefine some methods (getRowCount(), getColumnCount(), etc...) but these methods are primarily for JTable. You are free (and you should, in your case) to add any other method to manipulate the datas in the table model.
You can add methods like addSpiritData(SpiritData), removeSpiritDataAt(int index), removeAllSpiritData(), setSpiritDataList(List<SpiritData>), etc...
These "extra" methods are not for JTable, nor for the contract of TableModel. They are for you to use in the rest of your application! The important thing in these extra methods is that you have to correctly update the internal data structure and then invoke the minimal/right fireXXX method to notify at least the JTable (or any other registered listener) about these modifications.
Re: Problems updating an AbstractTableModel
Thanks andbin. I'm about to go to bed right now, but I'll try that tomorrow.
This is just about the end of my project. Once I get the table working, I'm going to add in a render of the row colors based on the data in column 1. I've already got that to work in another program, so hopefully it won't be too much of a problem. After that, it's ready to go up on the web, which should be another fun obstacle to tackle. :)
Thanks again for your help, it is much appreciated. Buona notte ;)
Re: Problems updating an AbstractTableModel
Thanks for all your help guys. It seems to be working correctly finally. It took me a couple days of headaches and a lot of reading and debugging, but I finally got it working.
I'm not sure if it's 100% correct though as I'm creating a new AbstractTableModel every time. Is this ok? or is it going to create problems down the road?
My table is not allowed to be edited and the only possible change allowed is to perform a new database search which re-displays the entire table. Rows and columns will never be added or deleted from the current table.
After someone presses the search button, the program queries MySQL and
Code :
model = new SpiritTableModel(sqlSearchResultsTable);
table.setModel(model);
model.fireTableDataChanged();
setTableProperties();
table.updateUI();
I'm not sure if that is enough information, but that's the only real changes I made I think. Here's that entire section of code where the query takes place.
Code :
public void searchSpiritDatabase(String search) {
try {
tableSize = 0;
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/" + dbName, dbUsername, dbPassword);
statement = connection.createStatement();
sqlSearchResultsTable.clear();
resultset = statement.executeQuery(search);
while(resultset.next())
{
tableSize++;
String sqlType = resultset.getString("Type");
String sqlHomeland = resultset.getString("Homeland");
String sqlName = resultset.getString("Name");
String sqlReleaseSet = resultset.getString("ReleaseSet");
setIcon = getImage(sqlReleaseSet + ".png");
sqlSearchResultsTable.add(new SpiritData(sqlType, sqlHomeland, sqlName, setIcon));
}
if(tableSize == 0) {
errorDisplay.setText("The search didn't find any results matching your parameters. Please try again."); }
else if(tableSize == 1){
errorDisplay.setText("The search found 1 matching your parameters."); }
else {
errorDisplay.setText("The search found " + tableSize + " results matching your parameters.");
}
model = new SpiritTableModel(sqlSearchResultsTable);
table.setModel(model);
model.fireTableDataChanged();
setTableProperties();
table.updateUI();
}
catch (SQLException ex) {
System.out.println("SQLException: " + ex.getMessage());
System.out.println("VendorError: " + ex.getErrorCode()); }
catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
Re: Problems updating an AbstractTableModel
Quote:
Originally Posted by
Psyclone625
Code :
model = new SpiritTableModel(sqlSearchResultsTable);
table.setModel(model);
This is not bad but creates a new table model on every query. I repeat, it's not wrong by itself.
Quote:
Originally Posted by
Psyclone625
Code :
model.fireTableDataChanged();
This is not necessary, it's useless.
Quote:
Originally Posted by
Psyclone625
I don't known what it do .... it's yours. But for me it's ok.
Quote:
Originally Posted by
Psyclone625
In general, updateUI() in Swing components has to do with Look&Feels. A normal/typical programmer should never use updateUI() .
Basing on my preceding answers, have you understood that you can add any method you want in your table model to manipulate the datas?
Re: Problems updating an AbstractTableModel
You were correct, I didn't need the model.fireTableDataChanged() method which I realized as soon as you said it, it was just old code from my attempt to change the data in the model. Also, my code doesn't require "table.updateUI()" which I didn't realize until I removed it.
Quote:
Originally Posted by
andbin
I don't known what it do .... it's yours. But for me it's ok.
This calls a method of my own where I set the font, row height, individual column widths, column selection allowed, row sorter, column alignment, and eventually other rendering (such as merged cells in certain rows and varying background colors depending on which group a record belongs to.
Quote:
Originally Posted by
andbin
Basing on my preceding answers, have you understood that you can add any method you want in your table model to manipulate the datas?
Yes, actually I did understand what you were saying, but was stuck. I knew I wasn't supposed to create a new model.
After spending about 16 hours of reading, researching and coding without success, I traced everything back to editing the model and realized the main part I was stuck on was editing and updating the model. That's how I ended up with the following in my code...
Code :
model = new SpiritTableModel(sqlSearchResultsTable);
table.setModel(model);
Prior to that, I wasn't creating a NEW model and never used updateUI. I know this isn't the best way, but I was mainly doing this as a way of debugging and to verify that updating the model was actually my problem. This, of course, confirmed it for me.
After I got something working, I went back and parsed out a lot of the code that I realized wasn't needed and about 50 lines of prinln's. Part of the code I parsed out was the method setValueAt() because when I was creating a new model, it wasn't needed anymore. I can't remember exactly what errors I
This is why I re-posted again asking how much of a problem that was going to be because I knew that I was going to be creating a new model object every time and resources would be wasted. But I'm not sure how much resources. This is close to being the end of my project except for some rendering and getting the program on a web page. If it ends up being that each model object takes up something small like 1 KB or 1 MB of memory, then my thinking was that someone would have to do hundreds or thousands of searches before it became a resource issue. I'm not even sure if my understanding of resources and creating multiple objects for the same variable/model is correct.
I'd actually prefer to do this the correct way, but am just completely lost on how to edit the model. Almost every example and document I found online deals with DefaultTableModel or the solution offered to people using AbstractTableModels is to just use DefaultTableModel, which I can't really do since I'm using my own class with Strings and ImageIcons.
I'm clearly not understanding how to use the AbstratModel API to repopulate the model. I never need to edit a row or cell at a time, but I just can't figure out how to edit the model. Every time I try, I get errors.