Why does a JTable view update block the entire GUI?

我是研究僧i 提交于 2019-12-12 02:49:47

问题


I have a simple JTable, displaying many data (~1000 rows) with a complex custom cell renderer. It takes a little time to render this cells. During this time the entire GUI seems to be frozen. This can be seen, because a progress indicator is shown to the user. During data generation, it works normally. But if the JTable is triggered trough fireTableDataChanged to update its view, the progress indicator gets frozen.

In the following little example, you should press on the button and see the effect after generating the data. The static waiting blocks (through Thread.sleep()) represent the complex real world code for data generation and representation generation.

My question is now, where my fault is. Because I don't belive, that there is not a possibility to solve this problem.

Clarification:

  • In my application, every cell displays data in two lines with different colors and icons. To show a simple example I only added the sleep to indicate that it takes long for all this cells to be created and rendered. Please note, that my application shows about 1000 or maybe 2000 of this two line cells with different colors and icons. This takes a lot time to render. And because of the fact, that this simple example can't show this complexity, I added the sleep (which should represent the complex work).

  • You asked "why does it take so long?". As I explained before, there are thousands of cells and they invoke action on every single object in every cell and display setup. It might not be so long in real, but to show the problem I set this time to the specific 42 ms.

  • I know that the GUI freezes, when I block the EDT. But I don't know, that I block the EDT on any place in my program. The sleep statements are used to represent a lot of code lines doing either data generation or setting up every cell.

  • "... there no reason to create something, nor JComponents as JLabel is ...". I need a multi line cell with the ability to color every line and add an icon to every line. The best way I found was this way with two JLabels on a JPanel as cell. If there is an simpler way, I would be pleased to hear. I already tried a JList with multiline HTML. The problem is, that it tends to render wrong if there are a lot of different rows.

  • "... Whats your goal ..." - it should simple work. Could this example be changed to display an "running" indeterminate progress bar until the table is fully updated without removing the Thread.sleep() statements?


package example;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;

public class Example extends DefaultTableModel {

    static List<String> data = new ArrayList<>();

    @Override
    public int getRowCount() {
        return data.size();
    }

    @Override
    public Object getValueAt(int row, int column) {
        return data.get(row);
    }

    @Override
    public String getColumnName(int column) {
        return "Column 0";
    }

    @Override
    public int getColumnCount() {
        return 1;
    }

    @Override
    public boolean isCellEditable(int row, int column) {
        return false;
    }

    public void updateView() {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                fireTableDataChanged();
            }
        });
    }

    static class CustomCellRenderer extends JPanel implements TableCellRenderer {

        private JLabel line1, line2;

        public CustomCellRenderer() {
            super();
            setOpaque(true);
            setLayout(new BorderLayout());

            this.line1 = new JLabel();
            this.line1.setOpaque(false);
            add(this.line1, BorderLayout.CENTER);
            this.line2 = new JLabel();
            this.line2.setOpaque(false);
            add(this.line2, BorderLayout.SOUTH);
        }

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, 
                boolean isSelected, boolean hasFocus, int row, int column) {
            if (isSelected) {
                setForeground(table.getSelectionForeground());
                this.line1.setForeground(table.getSelectionForeground());
                this.line2.setForeground(table.getSelectionForeground());
                setBackground(table.getSelectionBackground());
            } else {
                setForeground(table.getForeground());
                this.line1.setForeground(table.getForeground());
                this.line2.setForeground(table.getForeground());
                setBackground(table.getBackground());
            }

            // This wait represents other complex layout preparations
            // for this cell
            try {
                Thread.sleep(42);
            } catch (Exception e) {
            }

            line1.setText("Value: " + value);
            line2.setText("isSelected? " + isSelected + ", hasFocus?" + hasFocus);
            return this;
        }

    }

    static JPanel overlay;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame frame = new JFrame("Example");
                frame.setLayout(new BorderLayout(4, 4));

                // Add JTable
                final Example model = new Example();
                JTable table = new JTable(model) {

                    @Override
                    public void doLayout() {
                        TableColumn col = getColumnModel().getColumn(0);
                        for (int row = 0; row < getRowCount(); row++) {
                            Component c = prepareRenderer(col.getCellRenderer(), row, 0);
                            setRowHeight(row, c.getPreferredSize().height);
                        }
                        super.doLayout();
                    }
                };
                TableColumn col = table.getColumnModel().getColumn(0);
                col.setCellRenderer(new CustomCellRenderer());
                frame.add(new JScrollPane(table), BorderLayout.CENTER);

                // Add button
                Box hBox = Box.createHorizontalBox();
                hBox.add(new JButton(new AbstractAction("Load data") {

                    @Override
                    public void actionPerformed(ActionEvent e) {
                        new Thread(new Runnable() {

                            @Override
                            public void run() {
                                overlay.setVisible(true);
                                data.clear();

                                System.out.println("Generating data ...");

                                for (int i = 0; i < 42; i++) {
                                    data.add("String no. " + (i + 1));
                                    try {
                                        Thread.sleep(42);
                                    } catch (Exception e) {
                                    }
                                }

                                System.out.println("Updating view ...");

                                model.updateView();
                                overlay.setVisible(false);

                                System.out.println("Finished.");
                            }
                        }).start();
                    }
                }));
                hBox.add(Box.createHorizontalGlue());
                frame.add(hBox, BorderLayout.NORTH);

                // Create loading overlay
                overlay = new JPanel(new FlowLayout(FlowLayout.CENTER)) {

                    @Override
                    protected void paintComponent(Graphics g) {
                        g.setColor(new Color(0, 0, 0, 125));
                        g.fillRect(0, 0, getWidth(), getHeight());
                        super.paintComponent(g);
                    }
                };
                overlay.setOpaque(false);
                overlay.setBackground(new Color(0, 0, 0, 125));
                JProgressBar bar = new JProgressBar();
                bar.setIndeterminate(true);
                overlay.add(bar);

                frame.setGlassPane(overlay);
                frame.getGlassPane().setVisible(false);

                // Create frame
                frame.setSize(600, 400);
                frame.setVisible(true);
            }
        });
    }

}

来源:https://stackoverflow.com/questions/24115907/why-does-a-jtable-view-update-block-the-entire-gui

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!