简体   繁体   中英

How do I add an ActionListener to a JCheckBox?

Problem

So my program takes in a.csv file and loads the data and displays it. When data is loaded in, it creates a new JCheckBox for every column header there is in the data. How do I add an ActionListener such that when the user ticks/unticks any of the boxes, it should do a certain function?

When data is loaded in, it updates the JPanel by the code:

    public void updateChecklistPanel(){

        checklistPanel.removeAll();
        checklistPanel.setLayout(new GridLayout(currentData.getColumnNames().length, 1, 10, 0));
        for (String columnName : currentData.getColumnNames()){
            JCheckBox checkBox = new JCheckBox();
            checkBox.setText(columnName);
            checklistPanel.add(checkBox);
        }
        checklistPanel.revalidate();
        checklistPanel.repaint();
    }

I also have the following at the bottom:

    @Override
    public void actionPerformed(ActionEvent e) {

        if (e.getSource() == newDataFrameItem){
            newFile();
            System.out.println("New DataFrame Loaded in");
        }
        if (e.getSource() == loadDataFrameItem){
            loadFile();
            System.out.println(".csv Data loaded into DataFrame.");
        }
        if (e.getSource() == saveDataFrameItem){
            System.out.println("Saved the data to a .csv file");
        }

    }

What I'm trying to do is that when a checkbox is unticked, it should hide a column in the JTable and when ticked, it should redisplay the column.

Current Solution

The solution that I have come up with is to make a variable allColumnHeaders that is an ArrayList of Strings. I then also have a variable shownColumnHeaders that is an ArrayList of Booleans. When the user wants to show/hide a column, the showColumn(String columnName) and hideColumn(String columnName) function finds the index of the column Name in allColumnHeaders and sets the Boolean value of the index in shownColumnHeaders to either true/false.

It the proceeds to create a new table model where the columns are only added if the Boolean value for that column is true. It will then set the model for the table to the new table model.

The code for the following is show below:

import java.awt.*;
import java.util.ArrayList;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;

public class MRE extends JPanel {

    private static JTable table;
    private static ArrayList<String> allColumnHeaders = new ArrayList<>();
    private static ArrayList<Boolean> shownColumnHeaders = new ArrayList<>();

    private static void createAndShowGUI()
    {
        table = new JTable(5, 7);
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        JScrollPane scrollPane = new JScrollPane( table );

        JPanel buttons = new JPanel( new GridLayout(0, 1) );

        for (int i = 0; i < table.getColumnCount(); i++) {
            String column = table.getColumnName(i);
            allColumnHeaders.add(column);
            JCheckBox checkBox = new JCheckBox(column);

            checkBox.addActionListener(event -> {
                JCheckBox cb = (JCheckBox) event.getSource();
                if (cb.isSelected()) {
                    System.out.println(checkBox.getText() + " is now being displayed");
                    showColumn(checkBox.getText());
                } else {
                    System.out.println(checkBox.getText() + " is now being hidden");
                    hideColumn(checkBox.getText());
                }

                table.setModel(createTableModel());
            });

            checkBox.setSelected( true );
            buttons.add( checkBox );

            shownColumnHeaders.add(true);

        }

        JPanel wrapper = new JPanel();
        wrapper.add( buttons );

        JFrame frame = new JFrame("MRE");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(scrollPane, BorderLayout.CENTER);
        frame.add(wrapper, BorderLayout.LINE_END);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationByPlatform( true );
        frame.setVisible( true );
    }

    public static DefaultTableModel createTableModel(){

        DefaultTableModel tableModel = new DefaultTableModel(0, 0);
        String[] columnValues = new String[1];
        for (int i = 0; i < shownColumnHeaders.size(); i++){
            if (shownColumnHeaders.get(i)){
                tableModel.addColumn(allColumnHeaders.get(i), columnValues);
            }
        }

        return tableModel;
    }

    public static void showColumn(String columnName){
        for (int i = 0; i < allColumnHeaders.size(); i++) {
            if (allColumnHeaders.get(i).equals(columnName)){
                shownColumnHeaders.set(i, true);
            }
        }
    }

    public static void hideColumn(String columnName){
        for (int i = 0; i < allColumnHeaders.size(); i++) {
            if (allColumnHeaders.get(i).equals(columnName)){
                shownColumnHeaders.set(i, false);
            }
        }
    }

    public static void main(String[] args) throws Exception
    {
        SwingUtilities.invokeLater( () -> createAndShowGUI() );
    }

}

Introduction

It took me a while, but I came up with the following JTable GUI. Here's the starting display.

JCheckBox 表 GUI 1

Here's the GUI after I remove the item description.

JCheckBox 表 GUI 2

Here's the GUI after I remove the item price.

JCheckBox 表 GUI 3

Here's the GUI after I add the columns back.

JCheckBox 表 GUI 4

Explanation

I created an Item class to hold one item.

I created an Inventory class to hold a List of Item instances and a String array of the column headers.

I created the JFrame and two JPanels . One JPanel holds the JTable and the other holds the JCheckBoxes . I used Swing layout managers to create the JPanels .

So far, so basic. Creating the JTable took a bit of effort. I wanted to display the item price as currency, but that wasn't important for this example GUI.

I created an ActionListener to add and remove columns from the JTable . I had to experiment a bit. The TableColumnModel addColumn method appends the column to the table.

I had to create a DisplayTableColumn class to hold a TableColumn and a boolean that tells me whether or not to display the TableColumn . I wound up removing all the columns from the JTable and adding all the columns back to the JTable so that I could maintain the column sequence. I probably ran 100 tests before I could get this code to work.

Code

Here's the complete runnable code.

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

public class JCheckBoxTableGUI implements Runnable {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new JCheckBoxTableGUI());
    }
    
    private final Inventory inventory;
    
    private final InventoryTableModel tableModel;
    
    private JFrame frame;
    
    private JTable table;
    
    public JCheckBoxTableGUI() {
        this.tableModel = new InventoryTableModel();
        this.inventory = new Inventory();
        
        String[] columns = inventory.getTableHeader();
        for (String column : columns) {
            tableModel.addColumn(column);
        }
        
        List<Item> items = inventory.getInventory();
        for (Item item : items) {
            Object[] object = new Object[5];
            object[0] = item.getItemNumber();
            object[1] = item.getItemName();
            object[2] = item.getItemDescription();
            object[3] = item.getItemQuantity();
            object[4] = item.getItemPrice();
            tableModel.addRow(object);
        }
    }

    @Override
    public void run() {
        frame = new JFrame("JCheckBox Table GUI");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        frame.add(createTablePanel(), BorderLayout.CENTER);
        frame.add(createSelectionPanel(), BorderLayout.AFTER_LINE_ENDS);
        
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }
    
    private JPanel createTablePanel() {
        JPanel panel = new JPanel(new BorderLayout());
        panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        
        table = new JTable(tableModel);
        table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        table.getColumnModel().getColumn(0).setPreferredWidth(100);
        table.getColumnModel().getColumn(1).setPreferredWidth(150);
        table.getColumnModel().getColumn(2).setPreferredWidth(150);
        table.getColumnModel().getColumn(3).setPreferredWidth(100);
        table.getColumnModel().getColumn(4).setPreferredWidth(100);
        JScrollPane scrollPane = new JScrollPane(table);
        scrollPane.setPreferredSize(new Dimension(620, 300));
        panel.add(scrollPane, BorderLayout.CENTER);
        
        return panel;
    }
    
    private JPanel createSelectionPanel() {
        JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
        panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        
        JPanel innerPanel = new JPanel(new GridLayout(0, 1, 5, 5));
        
        ColumnListener listener = new ColumnListener(this);
        String[] columns = inventory.getTableHeader();
        for (String column : columns) {
            JCheckBox checkBox = new JCheckBox("Display " +  column);
            checkBox.addActionListener(listener);
            checkBox.setActionCommand(column);
            checkBox.setSelected(true);
            innerPanel.add(checkBox);
        }
        
        panel.add(innerPanel);
        
        return panel;
    }
    
    public JTable getTable() {
        return table;
    }
    
    public JFrame getFrame() {
        return frame;
    }
    
    public class ColumnListener implements ActionListener {
        
        private final JCheckBoxTableGUI frame;
        
        private final List<DisplayTableColumn> displayColumns;

        public ColumnListener(JCheckBoxTableGUI frame) {
            this.frame = frame;
            this.displayColumns = new ArrayList<>();
            
            TableColumnModel tcm = frame.getTable().getColumnModel();
            for (int index = 0; index < tcm.getColumnCount(); index++) {
                TableColumn tc = tcm.getColumn(index);
                displayColumns.add(new DisplayTableColumn(tc, true));
            }
        }

        @Override
        public void actionPerformed(ActionEvent event) {
            JCheckBox checkBox =  (JCheckBox) event.getSource();
            String column = event.getActionCommand();
            TableColumnModel tcm = frame.getTable().getColumnModel();
            
            for (int index = 0; index < displayColumns.size(); index++) {
                DisplayTableColumn dtc = displayColumns.get(index);
                if (dtc.isShowTableColumn()) {
                    tcm.removeColumn(dtc.getTableColumn());
                }
            }
            
            int columnIndex = getColumnIndex(column);
            displayColumns.get(columnIndex).setShowTableColumn(
                    checkBox.isSelected());
            
            for (int index = 0; index < displayColumns.size(); index++) {
                DisplayTableColumn dtc = displayColumns.get(index);
                if (dtc.isShowTableColumn()) {
                    tcm.addColumn(dtc.getTableColumn());
                }
            }
            
            frame.getFrame().pack();
        }
        
        private int getColumnIndex(String column) {
            for (int index = 0; index < displayColumns.size(); index++) {
                DisplayTableColumn dtc = displayColumns.get(index);
                if (column.equals(dtc.getTableColumn().getHeaderValue())) {
                    return index;
                }
            }
            
            return -1;
        }
        
    }
    
    public class DisplayTableColumn {
        
        private boolean showTableColumn;
        
        private final TableColumn tableColumn;

        public DisplayTableColumn(TableColumn tableColumn, boolean showTableColumn) {
            this.tableColumn = tableColumn;
            this.showTableColumn = showTableColumn;
        }

        public boolean isShowTableColumn() {
            return showTableColumn;
        }

        public void setShowTableColumn(boolean showTableColumn) {
            this.showTableColumn = showTableColumn;
        }

        public TableColumn getTableColumn() {
            return tableColumn;
        }
        
    }
    
    public class InventoryTableModel extends DefaultTableModel {

        private static final long serialVersionUID = 1L;

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            if (columnIndex <= 2) {
                return String.class;
            } else if (columnIndex == 3) {
                return Integer.class;
            } else {
                return Integer.class;
            }
        }

    }
    
    public class Inventory {
        
        private final List<Item> inventory;
        
        private final String[] tableHeader;
        
        public Inventory() {
            this.tableHeader = new String[] { "Item Number", "Item Name", 
                    "Item Description", "Item Quantity",
                    "Item Price" };
            
            this.inventory = new ArrayList<>();
            
            inventory.add(new Item("X101111", "Samsung Camera", " ", 20, 69.99));
            inventory.add(new Item("X101112", "Samsung Monitor", " ", 10, 279.99));
            inventory.add(new Item("X101113", "Samsung Smartphone", " ", 110, 599.99));
            inventory.add(new Item("X101114", "Apple Watch", " ", 20, 1259.99));
            inventory.add(new Item("X101115", "Sony Playstation 5", " ", 0, 399.99));
        }

        public String[] getTableHeader() {
            return tableHeader;
        }

        public List<Item> getInventory() {
            return inventory;
        }
        
    }
    
    public class Item {
        
        private int itemPrice;
        private int itemQuantity;
        
        private final String itemNumber;
        private final String itemName;
        private final String itemDescription;
        
        public Item(String itemNumber, String itemName, 
                String itemDescription, int itemQuantity, double itemPrice) {
            this.itemNumber = itemNumber;
            this.itemName = itemName;
            this.itemDescription = itemDescription;
            this.itemQuantity = itemQuantity;
            setItemPrice(itemPrice);
        }

        public int getItemPrice() {
            return itemPrice;
        }

        public void setItemPrice(double itemPrice) {
            this.itemPrice = (int) Math.round(itemPrice * 100.0);
        }

        public int getItemQuantity() {
            return itemQuantity;
        }

        public void setItemQuantity(int itemQuantity) {
            this.itemQuantity = itemQuantity;
        }

        public String getItemNumber() {
            return itemNumber;
        }

        public String getItemName() {
            return itemName;
        }

        public String getItemDescription() {
            return itemDescription;
        }
        
    }

}

This only demonstrates how to create a basic MRE:

  1. the csv file is irrelevant.
  2. data in the model is irrelevant.
  3. your loadFile, newFile and saveFile buttons are irrelevant.
  4. the TableModel is irrelevant

Your question is about adding an ActionListener to dynamically created checkboxes based on the columns of the table.

So all you need is a table with some columns and the resulting checkboxes.

import java.awt.*;
import javax.swing.*;

public class MRE extends JPanel
{
    private static void createAndShowGUI()
    {
        JTable table = new JTable(5, 7);
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        JScrollPane scrollPane = new JScrollPane( table );

        JPanel buttons = new JPanel( new GridLayout(0, 1) );

        for (int i = 0; i < table.getColumnCount(); i++)
        {
            String column = table.getColumnName(i);
            JCheckBox checkBox = new JCheckBox("Display " + column);
            checkBox.setSelected( true );
            buttons.add( checkBox );
        }

        JPanel wrapper = new JPanel();
        wrapper.add( buttons );

        JFrame frame = new JFrame("MRE");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(scrollPane, BorderLayout.CENTER);
        frame.add(wrapper, BorderLayout.LINE_END);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationByPlatform( true );
        frame.setVisible( true );
    }

    public static void main(String[] args) throws Exception
    {
        SwingUtilities.invokeLater( () -> createAndShowGUI() );
    }
}

If you can modify this to demonstrate how to use your "reusable class" to manage column visibility, then I will update the above code to use my reusable class with 4 additional lines of code.

Edit:

Suggested improvements to your code:

  1. Make the code reusable and self contained
  2. Don't use static methods
  3. Don't change the data.

Your original question was:

How do I add an ActionListener to a JCheckBox?

So to do that you need a class that:

  1. implements an ActionListener
  2. is self contained to implement the functionality you require.

So the basic structure could be like the following:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class SomeFunction implements ActionListener
{
    private JTable table;

    public SomeFunction(JTable table)
    {
        this.table = table;
    }

    @Override
    public void actionPerformed(ActionEvent e)
    {
        if (e.getSource() instanceof AbstractButton)
        {
            String command = e.getActionCommand();
            AbstractButton button = (AbstractButton)e.getSource();

            if (button.isSelected())
                doSelected( command );
            else
                doUnselected( command );
        }
    }

    private void doSelected(String command)
    {
        System.out.println(command + " selected");
    }

    private void doUnselected(String command)
    {
        System.out.println(command + " unselected");
    }

    private static void createAndShowGUI()
    {
        JTable table = new JTable(5, 7);
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        JScrollPane scrollPane = new JScrollPane( table );

        JPanel buttons = new JPanel( new GridLayout(0, 1) );
        SomeFunction listener = new SomeFunction( table ); // added
//      TableColumnManager listener = new TableColumnManager(table, false);
//      ColumnListener listener = new ColumnListener();

        for (int i = 0; i < table.getColumnCount(); i++)
        {
            String column = table.getColumnName(i);
            JCheckBox checkBox = new JCheckBox("Display " + column);
            checkBox.setSelected( true );
            checkBox.setActionCommand( column ); // added
            checkBox.addActionListener( listener ); // added
            buttons.add( checkBox );
        }

        JPanel wrapper = new JPanel();
        wrapper.add( buttons );

        JFrame frame = new JFrame("MRE");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(scrollPane, BorderLayout.CENTER);
        frame.add(wrapper, BorderLayout.LINE_END);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationByPlatform( true );
        frame.setVisible( true );
    }

    public static void main(String[] args) throws Exception
    {
        SwingUtilities.invokeLater( () -> createAndShowGUI() );
    }
}

Whatever your function does it only needs to know about the table it should act upon.

So try to restructure your code into a reusable class.

If you want to use Gilberts code, then you need to restructure the ColumnListener class into a separate reusable self contained class.

Finally you can also try my reusable class. Check out Table Column Manager for the class to download.

Whatever reusable class you decide to use, you will only need to change a single line of code from the above example (once your functionality is contained in a reusable class).

Note the static methods would not be part of the final class. They are only there so simplify testing and posting of an MRE in a single class file.

Hope this helps with future design considerations.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM