问题
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