0

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);
            }
        });
    }

}
mythbu
  • 786
  • 1
  • 7
  • 20
  • 1
    you should execute long running queries in the separate context. objects that not synchronized blocking the the threads that was unable to process the events coming to a window having messy gui that has unrecoverable errors that you was not able to reproduce. – Roman C Jun 09 '14 at 07:52
  • 1
    `// This wait represents other complex layout preparations.. for this cell` 42 mseconds per cell? Why does it take so long? Still, since it is only the viewable cells, it shouldn't be many. But generally: Don't block the EDT (Event Dispatch Thread) - the GUI will 'freeze' when that happens. Instead of calling `Thread.sleep(n)` implement a Swing `Timer` for repeating tasks or ***a `SwingWorker` for long running tasks.*** See [Concurrency in Swing](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/) for more details. – Andrew Thompson Jun 09 '14 at 07:54
  • 1
    fireTableDataChanged(); is implemented in DefaultTableModel and correctly, – mKorbel Jun 09 '14 at 08:27
  • 2
    Thread.sleep(42); must be deleted, nor disposed inside Renderer, whole Renderer looks like as doing wrong job – mKorbel Jun 09 '14 at 08:28
  • cast JLabel from public Component getTableCellRendererComponent (returns Component, JComponent, JLabel by default), there no reason to create something, nor JComponents as JLabel is – mKorbel Jun 09 '14 at 08:30
  • everything from main(String[] args) { should be moved to the constructor, super.doLayout(); should be inside renderer, put all pieces together, important question is Whats your goal, – mKorbel Jun 09 '14 at 08:32
  • I added some details to explain. – mythbu Jun 09 '14 at 08:49
  • use JLabel (by cast from public Component getTableCellRendererComponent), add LayoutManager (GridLayout), still don't see to use doLayout, use row hieght to JTable in the case that this value is similair for all rows, this two point to safe a important amount of time (renderer is initialize from all mouse and key events and from model to view events implemented in APIs) – mKorbel Jun 09 '14 at 08:54
  • Are you loading you icons in the cell renderer, `getTableCellRendererComponent` method? – MadProgrammer Jun 09 '14 at 08:54
  • Possible [duplicate](http://stackoverflow.com/q/13753562/230513). See also this [answer](http://stackoverflow.com/q/13753562/230513). – trashgod Jun 09 '14 at 10:34

0 Answers0