Java 当 AbstractTableModel 调用 fireTableStructureChanged 时,JTable 列丢失 TableCellRenderer

Java JTable column loses TableCellRenderer when AbstractTableModel calls fireTableStructureChanged

我有一个 JTable,我想在其中使用 JProgressBar 在其中一列中呈现数据。我可以通过调用 setCellRenderer() 来做到这一点。

但是,我还有一个 SwingWorker,它可以修改我的 AbstractTableModel 以向 table 添加一列,在这种情况下,我调用 fireTableStructureChanged()。不幸的是,这会导致我的专栏丢失自定义 TableCellRenderer。

我已经尝试过 setAutoCreateColumnsFromModel(false),但这会阻止我的 AbstractTableModel 添加新列。这是我的代码:

package components;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingWorker;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;

public class LostRenderer extends JPanel {

    private static final long serialVersionUID = 1L;

    // Make the JTable a field, so inner class TableCellTask can access it
    private final JTable table;

    public class ProgressBarCellRenderer extends JProgressBar implements TableCellRenderer {

        private static final long serialVersionUID = 1L;

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            setValue((int) value);
            return this;
        }
    }
    
    public LostRenderer() {
        super(new GridLayout(1,0));

        MyTableModel myTableModel = new MyTableModel();
        table = new JTable(myTableModel);
        table.setPreferredScrollableViewportSize(new Dimension(500, 70));
        table.setFillsViewportHeight(true);

        table.getColumn("Progress").setCellRenderer(new ProgressBarCellRenderer());
        /*
         * NOTE: table.setAutoCreateColumnsFromModel(true); (or commenting out the call)
         * results in the column renderer getting lost when table structure is changes.
         * Setting it to false maintains column renderer, but no new columns get added.
         */
        table.setAutoCreateColumnsFromModel(true);
        
        //Create the scroll pane and add the table to it.
        JScrollPane scrollPane = new JScrollPane(table);

        //Add the scroll pane to this panel.
        add(scrollPane);

        new TableCellTask(myTableModel).execute();
    }

    private class TableCellTask extends SwingWorker<Void, Integer> {
        
        private final MyTableModel myTableModel;
        
        public TableCellTask(MyTableModel myTableModel) {
            this.myTableModel = myTableModel;
        }
        
        @Override
        protected Void doInBackground() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
            publish(new Integer( 0));
            return null;
        }

        @Override
        protected void process(List<Integer> tableCells) {
            for (int tableCell : tableCells) {
                myTableModel.process(tableCell);
            }
        }
    }

    class MyTableModel extends AbstractTableModel {

        private static final long serialVersionUID = 1L;
        
        private List<Integer> data = new ArrayList<Integer>();

        public int getColumnCount() {
            return data.size() + 1;
        }

        public int getRowCount() {
            return 1;
        }

        public String getColumnName(int col) {
            if (col == 0) {
                return "Progress";
            } else {
                return "Data" + col;
            }
        }

        public Object getValueAt(int row, int col) {
            if (col == 0) {
                return 50;
            } else {
                return data.get(col - 1);
            }
        }

        public Class<?> getColumnClass(int c) {
            return getValueAt(0, c).getClass();
        }

        public void process(int value) {
            data.add(value);
            fireTableStructureChanged();
        }

    }

    /**
     * Create the GUI and show it.  For thread safety,
     * this method should be invoked from the
     * event-dispatching thread.
     * 
     */
    private static void createAndShowGUI() {
        //Create and set up the window.
        JFrame frame = new JFrame("LostRenderer");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //Create and set up the content pane.
        LostRenderer newContentPane = new LostRenderer();
        newContentPane.setOpaque(true); //content panes must be opaque
        frame.setContentPane(newContentPane);

        //Display the window.
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {

        //Schedule a job for the event-dispatching thread:
        //creating and showing this application's GUI.
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });

    }
}

这是 table 开始时的样子(使用正确的 JProgressBar 渲染):

这是我添加一列后的样子(列已经丢失了 JProgressBar 渲染):

如有任何建议,我们将不胜感激。

我不得不重新安排您的代码,但我想出了以下 GUI。

2 秒后。

我所做的主要更改是在您的 TableModel 中为第一列提供唯一的 class 值。然后,我可以将 JProgressBar 渲染器设置为该列作为默认渲染器。

它使阅读您的代码的人更容易将您的 class 分开并组织代码,以便重要的部分在前面,更详细的部分在后面。

不需要扩展 JPanelJProgressBar。唯一应该扩展 Swing 组件或任何 Java class 的时候是当您想要重写一个或多个 class 方法时。

这是完整的可运行代码。

import java.awt.BorderLayout;
import java.awt.Component;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;

public class JTableRendererGUI implements Runnable {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new JTableRendererGUI());
    }
    
    private JTable table;
    
    private MyTableModel myTableModel;
    
    public JTableRendererGUI() {
        this.myTableModel = new MyTableModel();
    }
    
    @Override
    public void run() {
        JFrame frame = new JFrame("JTable Renderer");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        frame.add(createMainPanel(), BorderLayout.CENTER);

        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
        
        new TableCellTask(myTableModel).execute();
    }
    
    private JPanel createMainPanel() {
        JPanel panel = new JPanel(new BorderLayout());
        
        table = new JTable(myTableModel);
        table.setDefaultRenderer(MyTableModel.class, 
                new ProgressBarCellRenderer());
        table.setAutoCreateColumnsFromModel(true);
        table.setFillsViewportHeight(true);
        JScrollPane scrollPane = new JScrollPane(table);
        
        panel.add(scrollPane, BorderLayout.CENTER);
        
        return panel;
    }
    
    public class TableCellTask extends SwingWorker<Void, Integer> {

        private final MyTableModel myTableModel;

        public TableCellTask(MyTableModel myTableModel) {
            this.myTableModel = myTableModel;
        }

        @Override
        protected Void doInBackground() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            publish(0);
            return null;
        }

        @Override
        protected void process(List<Integer> tableCells) {
            for (int tableCell : tableCells) {
                myTableModel.process(tableCell);
            }
        }
    }
    
    public class MyTableModel extends AbstractTableModel {

        private static final long serialVersionUID = 1L;

        private List<Integer> data = new ArrayList<Integer>();

        public int getColumnCount() {
            return data.size() + 1;
        }

        public int getRowCount() {
            return 1;
        }

        public String getColumnName(int col) {
            if (col == 0) {
                return "Progress";
            } else {
                return "Data" + col;
            }
        }

        public Object getValueAt(int row, int col) {
            if (col == 0) {
                return 50;
            } else {
                return data.get(col - 1);
            }
        }

        public Class<?> getColumnClass(int c) {
            if (c == 0) {
                return MyTableModel.class;
            } else {
                return getValueAt(0, c).getClass();
            }
        }

        public void process(int value) {
            data.add(value);
            fireTableStructureChanged();
        }

    }
    
    public class ProgressBarCellRenderer implements TableCellRenderer {
        
        private JProgressBar progressBar;
        
        public ProgressBarCellRenderer() {
            this.progressBar = new JProgressBar();
        }

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, 
                boolean isSelected, boolean hasFocus, int row, int column) {
           progressBar.setValue((int) value);
           return progressBar;
        }

    }

}