Я надеюсь, что вы можете помочь мне с проблемой, которая мучает меня уже несколько дней!
Я прочитал много ответов SO и различных примеров со всего Интернета, и я изо всех сил старался прочитать исходный код DefaultTableModel, чтобы попытаться понять, что там происходит за кулисами, но, к сожалению, мне совершенно не хватает более широкой картины . Кажется, я не могу найти какие-либо примеры, статьи или видеоролики, объясняющие, как или почему TableModel делает то, что она делает, и, следовательно, как эффективно изменить ее в соответствии с моими потребностями.
Я работаю над проектом, и мне нужно отображать данные, связанные с каждым объектом в JTable. У меня есть класс ObjectCollection, который содержит все объекты в папке внутри ArrayList. Именно эта коллекция передается моей пользовательской TableModel для заполнения JTable. Однако проблема в том, что мне нужно, чтобы в JTable был столбец (в данном случае столбец 0), содержащий флажки, с которыми пользователь может взаимодействовать. Однако состояние флажка не отражается в Object или ObjectCollection. Существует отдельный метод, который вызывается после того, как пользователь сделал какой-либо выбор, который затем ищет в JTable проверенные строки и работает с соответствующим объектом внутри ObjectCollection - таким образом, ни сами объекты, ни коллекция не должны иметь представления о состоянии флажок. По сути, это похоже на то, как если бы TableModel хранит данные из двух разных источников — столбцы с 1 по 4 из объекта в этой строке и проверенное состояние ячейки в столбце 0 этой строки. Я надеюсь, что это имеет смысл, но если нет, надеюсь, что мой пример кода ниже продемонстрирует это лучше, чем мои слова.
Проблема у меня двоякая. Во-первых, хотя я могу отображать флажки и регистрировать щелчки по ним, я понятия не имею, как на самом деле переключать отмеченное состояние флажка (ничего не происходит, когда я нажимаю на флажки). Во-вторых, предполагая, что я могу выяснить, как переключать состояние флажка, я не знаю, как использовать метод getValue() для его возврата, учитывая, что он не является членом объекта (опять же, см. мой пример кода ниже, если тут я не разбираюсь).
Я не могу опубликовать код (пока) из моего реального проекта по разным причинам, поэтому я создал поддельный пример проекта для целей этого вопроса. Принцип точно такой же, и проблемы, с которыми я сталкиваюсь, такие же, поэтому, если я могу решить это здесь, в этом примере, я могу решить это таким же образом в моей реальной кодовой базе. Во всяком случае, вот пример кода, который у меня есть...
Простой пример объекта
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;
}
}
Коллекция объектов
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 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;
}
}
Метод main() и код создания GUI и т. д.
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();
}
}
Вышеприведенное работает отлично, за исключением того, что я не могу установить или получить состояние флажка в столбце 0. Я могу подтвердить, что метод setValueAt() вызывается, когда я нажимаю на флажки с оператором System.out.println, но у меня нет идея, как на самом деле переключить проверенное состояние, и, к сожалению, документация не помогла, и ни один из ответов/учебников, которые я нашел, не охватывает эту конкретную проблему.
Надеюсь, я предоставил достаточно информации, но если нет, дайте мне знать, и я соответствующим образом отредактирую вопрос.
Изменить для ясности: в примере кода тривиально добавить логическое значение в класс ExampleMovie и отразить это значение в столбце 0 таблицы, но, к сожалению, это не вариант в фактической кодовой базе, с которой я работаю, поэтому я думаю, мой конкретный вопрос: есть ли способ получить и установить/переключить значение флажка в столбце 0 без соответствующего члена данных в классе ExampleMovie? Если это невозможно или невероятно нетривиально, может ли кто-нибудь указать мне альтернативный метод отображения данных, который может помочь?
Второе редактирование: игнорировать меня. На самом деле я могу относительно легко добавить новый элемент данных в класс, с которым я работаю. Я назвал ответ Гилберта Леблана правильным. Я предполагаю, что это был дублирующий вопрос все это время. Извини за это! Спасибо за помощь, я очень ценю это.
Спасибо.
Также вы расширяете класс JFrame
, чего не следует делать, затем создаете новый экземпляр класса JFrame
— frame
и добавляете свою расширенную корневую панель JFrame
в новый JFrame
, просто читая, что это запутанно. Не расширяйте класс JFrame
, создайте экземпляр JPanel
с BorderLayout
и просто добавьте свои компоненты в JPanel
, а затем добавьте JPanel
в экземпляр frame
. Также все компоненты свинга должны быть созданы на EDT
Возможно, я не вижу чего-то очевидного из-за непонимания, поэтому, если это действительно дубликат, примите мои извинения. Однако я просмотрел эту ссылку и не вижу, где они переопределяют setValueAt(). Они просто вызывают реализацию суперкласса, не так ли? Опять же - я мог что-то упустить, так что извиняюсь, если это так. Я принял ваши предложения относительно JPane и т. д., Так что спасибо за это. Что касается создания в EDT - я думал, что-нибудь в рамках класса ExampleForm выполняется в EDT? Так или иначе IntelliJ настраивает вещи по умолчанию?
Предоставленный вами код не работает. Я изменил имя основного класса с ExampleForm на 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)
После исправления всех ошибок я добавил логическое значение в класс ExampleMovie. Вот как выглядит графический интерфейс.
Я сделал все классы внутренними классами, чтобы я мог опубликовать полный исполняемый код в виде одного блока.
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;
}
}
}
Спасибо за ваш ответ. Извините - я должен был быть более точным в своем вопросе. В примере, который я привел, достаточно просто добавить логическое значение в класс ExampleMovie, но в реальной кодовой базе, с которой я работаю, это, к сожалению, невозможно. Это основная проблема, которая у меня есть. Есть ли способ получить и установить/переключить логическое значение в столбце 0, не будучи членом ExampleMovie? Извините, что не ясно изложил мой вопрос. Я попытаюсь отредактировать это сейчас, чтобы исправить это. Кроме того, что вы подразумеваете под кодом «abends»?
@DoTheDonkeyKonga: Вы должны где-то сохранить логические значения. Даже если вы не можете изменить таблицы базы данных, почему вы не можете изменить класс Java?
Вы правы. Первоначально я сказал, что, поскольку я не сопровождаю класс, с которым я работаю... buuuuuut, я, конечно, могу просто расширить класс и добавить необходимое логическое значение. Я думаю, что я был просто ослеплен тем, как я пытался реализовать то, что хотел, и поэтому не мог видеть очевидного. Виноват! Извините, что отнимаю у всех время. Однако из вашего ответа я многому научился, работая с Java Swing, и, возможно, этот вопрос поможет другим в будущем, так что не все потеряно. Спасибо за ваш вклад :)
@DoTheDonkeyKonga: Нет проблем. Вам даже не нужно расширять класс. Вы можете создать свой собственный класс с логическим значением и экземпляром исходного класса. Кажется, это называется декоратор. Я рад, что мой пример помог вам. Удачи с вашими будущими приложениями Swing.
Я не хочу закрывать это как дубликат, если я не уверен на 100%, но я думаю, что это то, что вам нужно. Если это я или кто-то другой закрою его как таковой.