I hope you can help me with a problem that has been bugging me for days now!
I've read a lot of SO answers and various examples from around the web and I've tried my best to read the DefaultTableModel source code to try and understand what's happening behind the scenes there but unfortunately I'm completely lacking the bigger picture still. I can't seem to find any examples, articles or videos that explain how or why a TableModel does what it does and thus how to effectively alter it to my needs.
I'm working on a project and I have need of displaying the data associated with each Object in a JTable. I have an ObjectCollection class which holds all the Objects in a folder inside an ArrayList. It is this collection that is passed to my custom TableModel to populate the JTable. The problem is however that I need the JTable to have a column (in this case column 0) containing checkboxes which the user can interact with. The state of the checkbox is not reflected in the Object or the ObjectCollection however. There is a separate method which is called once the user has made any selections which then searches the JTable for the checked rows and operates on the corresponding Object inside the ObjectCollection - thus neither the Objects themselves nor the collection need have any idea about the state of the checkbox. It's essentially as if the TableModel is holding data from two different sources - columns 1 to 4 from the Object in that row and the checked state of the cell in column 0 of that row. I hope that makes sense but if not hopefully my example code below will demonstarte it better than my words can.
The problem I'm having is twofold. Firstly, though I can get the checkboxes to display and I can register clicks on them I have no idea how to actually toggle the checked state of the checkbox (nothing happens when I click on the checkboxes). Secondly, assuming I can figure out how to toggle the checkbox state, I don't know how to use the getValue() method to return it, considering it isn't a member of the Object (again, see my example code below if I'm not being clear here).
I can't post the code (yet) from my real project for various reasons so I've created a fake example project for the purposes of this question. The principle is exactly the same and the problems I'm facing are too so if I can solve it here in this example I can solve it the same way in my real codebase. Anyway, here's the example code I've got so far...
A simple example Object
public class ExampleMovie {
private final String title;
private final String year;
private final String director;
private final double score;
public ExampleMovie(String title, String year, String director, double score) {
this.title = title;
this.year = year;
this.director = director;
this.score = score;
}
public String getTitle() {
return title;
}
public String getYear() {
return year;
}
public String getDirector() {
return director;
}
public double getScore() {
return score;
}
}
The ObjectCollection
public class ExampleMovieCollection {
private List<ExampleMovie> allMovies;
public ExampleMovieCollection() {
allMovies = new ArrayList<>();
}
public void addMovie(ExampleMovie movie) {
allMovies.add(movie);
}
public void removeMovie(ExampleMovie movie) {
if (allMovies.contains(movie)) {
allMovies.remove(movie);
}
}
public List<ExampleMovie> getMovies() {
return allMovies;
}
}
The custom TableModel
public class ExampleMovieCollectionTableModel extends AbstractTableModel {
private List<ExampleMovie> items;
private String[] columnNames = {"Checked?", "Title", "Year", "IMDB Score", "Director"};
public ExampleMovieCollectionTableModel(ExampleMovieCollection movieCollection) {
this.items = new ArrayList<>(movieCollection.getMovies());
}
@Override
public int getRowCount() {
return items.size();
}
@Override
public int getColumnCount() {
return columnNames.length;
}
@Override
public String getColumnName(int columnIndex) {
return columnNames[columnIndex];
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return columnIndex == 0; // Only the first column in the table should be editable
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
ExampleMovie item = items.get(rowIndex);
switch (columnIndex) {
case 0: // How do I return the checked state of the cell as it isn't a member of item?
case 1: return item.getTitle();
case 2: return item.getYear();
case 3: return item.getScore();
case 4: return item.getDirector();
default: return null;
}
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
// How do I toggle the checked state of the cell when columnIndex == 0?
System.out.println("(" + rowIndex + ", " + columnIndex + ") clicked. Value = " + aValue);
}
@Override
public Class<?> getColumnClass(int columnIndex) {
if (columnIndex == 0) {
return Boolean.class;
}
return String.class;
}
}
The main() method and GUI creation code etc.
public class ExampleForm extends JFrame {
private JPanel rootPanel;
private JTable exampleTable;
private JScrollPane tableScrollPane;
private ExampleMovieCollection movieCollection;
private ExampleMovieCollectionTableModel tableModel;
public ExampleForm() {
movieCollection = new ExampleMovieCollection();
movieCollection.addMovie(new ExampleMovie("The Shawshank Redemption", "1994", "Frank Darabont", 9.3));
movieCollection.addMovie(new ExampleMovie("The Godfather", "1972", "Francis Ford Coppola", 9.2));
movieCollection.addMovie(new ExampleMovie("The Godfather: Part II", "1974","Francis Ford Coppola", 9.1));
movieCollection.addMovie(new ExampleMovie("The Dark Knight", "2008", "Christopher Nolan", 9.0));
movieCollection.addMovie(new ExampleMovie("Schindler's List", "1993", "Steven Spielberg", 8.9));
createExampleTable();
}
private static void createAndShowGui() {
JFrame frame = new JFrame("TableModel Example");
frame.setContentPane(new ExampleForm().rootPanel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null); // Center on primary monitor
frame.setVisible(true);
}
private void createExampleTable() {
tableModel = new ExampleMovieCollectionTableModel(movieCollection);
exampleTable = new JTable(tableModel);
exampleTable.setPreferredScrollableViewportSize(exampleTable.getPreferredSize());
exampleTable.changeSelection(0, 0, false, false);
exampleTable.setAutoCreateRowSorter(true);
tableScrollPane.setViewportView(exampleTable);
}
public static void main(String[] args) {
createAndShowGui();
}
}
The above works perfectly, except for not being able to set or retrieve the checkbox state in column 0. I can confirm the setValueAt() method is being called when I click on the checkboxes with the System.out.println statement but I have no idea how to actually toggle the checked state and unfortunately the documentation hasn't been any help and none of the answers/tutorials I've found cover this specific issue.
Hopefully I've provided enough information but if not please let me know and I'll edit the question accoringly.
Edit for clarity: In the example code it is trivial to add a boolean to the ExampleMovie class and have this value reflected in column 0 of the table, but unfortunately this isn't an option in the actual codebase I'm working with so I guess my specific question is: is there a way to get and set/toggle the checkbox value in column 0 without there being a corresponding data member in the ExampleMovie class? If that's not possible, or is incredibly non-trivial to achieve, can anyone point me toward an alternative method of displaying the data that might do the trick?
Second edit: Ignore me. I actually can relatively easily add a new data member to the class I'm working with. I've market Gilbert Le Blanc's answer as correct. I guess this was a duplicate question all along then. Sorry about that! Thanks for the help, I really appreciate it.
Thanks.
The code you provided abends. I changed the name of the main class from ExampleForm to ExampleMovieGUI.
Exception in thread "main" java.lang.NullPointerException
at com.ggl.testing.ExampleMovieGUI.createExampleTable(ExampleMovieGUI.java:45)
at com.ggl.testing.ExampleMovieGUI.<init>(ExampleMovieGUI.java:27)
at com.ggl.testing.ExampleMovieGUI.createAndShowGui(ExampleMovieGUI.java:32)
at com.ggl.testing.ExampleMovieGUI.main(ExampleMovieGUI.java:49)
After fixing all the abends, I added a boolean to the ExampleMovie class. Here's what the GUI looks like.
I made all the classes inner classes so I could post the complete runnable code as one block.
import java.awt.BorderLayout;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
public class ExampleMovieGUI {
public static void main(String[] args) {
ExampleMovieGUI gui = new ExampleMovieGUI();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame("TableModel Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(gui.createExampleTable(), BorderLayout.CENTER);
frame.pack();
frame.setLocationRelativeTo(null); // Center on primary monitor
frame.setVisible(true);
}
});
}
private JTable exampleTable;
private JScrollPane tableScrollPane;
private ExampleMovieCollection movieCollection;
private ExampleMovieCollectionTableModel tableModel;
public ExampleMovieGUI() {
movieCollection = new ExampleMovieCollection();
movieCollection.addMovie(new ExampleMovie(
"The Shawshank Redemption", "1994", "Frank Darabont", 9.3));
movieCollection.addMovie(new ExampleMovie(
"The Godfather", "1972", "Francis Ford Coppola", 9.2));
movieCollection.addMovie(new ExampleMovie(
"The Godfather: Part II", "1974","Francis Ford Coppola", 9.1));
movieCollection.addMovie(new ExampleMovie(
"The Dark Knight", "2008", "Christopher Nolan", 9.0));
movieCollection.addMovie(new ExampleMovie(
"Schindler's List", "1993", "Steven Spielberg", 8.9));
}
private JPanel createExampleTable() {
JPanel panel = new JPanel(new BorderLayout());
tableModel = new ExampleMovieCollectionTableModel(movieCollection);
exampleTable = new JTable(tableModel);
exampleTable.setPreferredScrollableViewportSize(
exampleTable.getPreferredSize());
exampleTable.changeSelection(0, 0, false, false);
exampleTable.setAutoCreateRowSorter(true);
tableScrollPane = new JScrollPane(exampleTable);
panel.add(tableScrollPane, BorderLayout.CENTER);
return panel;
}
public class ExampleMovieCollectionTableModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
private List<ExampleMovie> items;
private String[] columnNames = {"Checked?", "Title", "Year",
"IMDB Score", "Director"};
public ExampleMovieCollectionTableModel(
ExampleMovieCollection movieCollection) {
this.items = new ArrayList<>(movieCollection.getMovies());
}
@Override
public int getRowCount() {
return items.size();
}
@Override
public int getColumnCount() {
return columnNames.length;
}
@Override
public String getColumnName(int columnIndex) {
return columnNames[columnIndex];
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
// Only the first column in the table should be editable
return columnIndex == 0;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
ExampleMovie item = items.get(rowIndex);
switch (columnIndex) {
case 0: return item.isSelected();
case 1: return item.getTitle();
case 2: return item.getYear();
case 3: return item.getScore();
case 4: return item.getDirector();
default: return null;
}
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
// How do I toggle the checked state of the cell when columnIndex == 0?
System.out.println("(" + rowIndex + ", " + columnIndex +
") clicked. Value = " + aValue);
if (columnIndex == 0) {
ExampleMovie item = items.get(rowIndex);
item.setSelected(Boolean.valueOf(aValue.toString()));
}
}
@Override
public Class<?> getColumnClass(int columnIndex) {
if (columnIndex == 0) {
return Boolean.class;
} else if (columnIndex == 3) {
return Double.class;
}
return String.class;
}
}
public class ExampleMovieCollection {
private List<ExampleMovie> allMovies;
public ExampleMovieCollection() {
allMovies = new ArrayList<>();
}
public void addMovie(ExampleMovie movie) {
allMovies.add(movie);
}
public void removeMovie(ExampleMovie movie) {
if (allMovies.contains(movie)) {
allMovies.remove(movie);
}
}
public List<ExampleMovie> getMovies() {
return allMovies;
}
}
public class ExampleMovie {
private boolean selected;
private final String title;
private final String year;
private final String director;
private final double score;
public ExampleMovie(String title, String year,
String director, double score) {
this.title = title;
this.year = year;
this.director = director;
this.score = score;
this.selected = false;
}
public String getTitle() {
return title;
}
public String getYear() {
return year;
}
public String getDirector() {
return director;
}
public double getScore() {
return score;
}
public boolean isSelected() {
return selected;
}
public void setSelected(boolean selected) {
this.selected = selected;
}
}
}