Как переключить значение флажка в JTable при расширении от AbstractTableModel

Я надеюсь, что вы можете помочь мне с проблемой, которая мучает меня уже несколько дней!

Я прочитал много ответов 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? Если это невозможно или невероятно нетривиально, может ли кто-нибудь указать мне альтернативный метод отображения данных, который может помочь?

Второе редактирование: игнорировать меня. На самом деле я могу относительно легко добавить новый элемент данных в класс, с которым я работаю. Я назвал ответ Гилберта Леблана правильным. Я предполагаю, что это был дублирующий вопрос все это время. Извини за это! Спасибо за помощь, я очень ценю это.

Спасибо.

Я не хочу закрывать это как дубликат, если я не уверен на 100%, но я думаю, что это то, что вам нужно. Если это я или кто-то другой закрою его как таковой.

David Kroukamp 11.12.2020 21:17

Также вы расширяете класс JFrame, чего не следует делать, затем создаете новый экземпляр класса JFrameframe и добавляете свою расширенную корневую панель JFrame в новый JFrame, просто читая, что это запутанно. Не расширяйте класс JFrame, создайте экземпляр JPanel с BorderLayout и просто добавьте свои компоненты в JPanel, а затем добавьте JPanel в экземпляр frame. Также все компоненты свинга должны быть созданы на EDT

David Kroukamp 11.12.2020 21:29

Возможно, я не вижу чего-то очевидного из-за непонимания, поэтому, если это действительно дубликат, примите мои извинения. Однако я просмотрел эту ссылку и не вижу, где они переопределяют setValueAt(). Они просто вызывают реализацию суперкласса, не так ли? Опять же - я мог что-то упустить, так что извиняюсь, если это так. Я принял ваши предложения относительно JPane и т. д., Так что спасибо за это. Что касается создания в EDT - я думал, что-нибудь в рамках класса ExampleForm выполняется в EDT? Так или иначе IntelliJ настраивает вещи по умолчанию?

DoTheDonkeyKonga 11.12.2020 22:17
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
3
778
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Предоставленный вами код не работает. Я изменил имя основного класса с 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 12.12.2020 01:16

@DoTheDonkeyKonga: Вы должны где-то сохранить логические значения. Даже если вы не можете изменить таблицы базы данных, почему вы не можете изменить класс Java?

Gilbert Le Blanc 12.12.2020 09:38

Вы правы. Первоначально я сказал, что, поскольку я не сопровождаю класс, с которым я работаю... buuuuuut, я, конечно, могу просто расширить класс и добавить необходимое логическое значение. Я думаю, что я был просто ослеплен тем, как я пытался реализовать то, что хотел, и поэтому не мог видеть очевидного. Виноват! Извините, что отнимаю у всех время. Однако из вашего ответа я многому научился, работая с Java Swing, и, возможно, этот вопрос поможет другим в будущем, так что не все потеряно. Спасибо за ваш вклад :)

DoTheDonkeyKonga 12.12.2020 12:22

@DoTheDonkeyKonga: Нет проблем. Вам даже не нужно расширять класс. Вы можете создать свой собственный класс с логическим значением и экземпляром исходного класса. Кажется, это называется декоратор. Я рад, что мой пример помог вам. Удачи с вашими будущими приложениями Swing.

Gilbert Le Blanc 12.12.2020 13:28

Другие вопросы по теме