У меня есть простой JTable, отображающий много данных (~ 1000 строк) со сложным настраиваемым средством визуализации ячеек. Для рендеринга этих ячеек требуется немного времени. В это время весь графический интерфейс кажется замороженным. Это видно, потому что пользователю показывается индикатор прогресса. Во время генерации данных работает нормально. Но если JTable запускается через fireTableDataChanged
для обновления своего представления, индикатор выполнения замораживается.
В следующем небольшом примере вы должны нажать на кнопку и увидеть эффект после генерации данных. Статические блоки ожидания (до Thread.sleep()
) представляют собой сложный код реального мира для генерации данных и генерации представления.
Теперь мой вопрос, где моя вина. Потому что я не верю, что нет возможности решить эту проблему.
Пояснение:
В моем приложении каждая ячейка отображает данные в две строки с разными цветами и значками. Чтобы показать простой пример, я добавил сон только для того, чтобы показать, что для создания и отображения всех этих ячеек требуется много времени. Обратите внимание, что мое приложение показывает около 1000 или, может быть, 2000 из этих двух строковых ячеек с разными цветами и значками. Это требует много времени для рендеринга. И из-за того, что этот простой пример не может показать эту сложность, я добавил сон (который должен отражать сложную работу).
Вы спросили: «Почему так долго?». Как я объяснял ранее, существуют тысячи ячеек, и они вызывают действие для каждого отдельного объекта в каждой ячейке и настройках отображения. На самом деле это может быть не так долго, но чтобы показать проблему, я установил на этот раз конкретные 42 мс.
Я знаю, что графический интерфейс зависает, когда я блокирую EDT. Но я не знаю, что я блокирую EDT в любом месте в моей программе. Операторы сна используются для представления большого количества строк кода, которые либо генерируют данные, либо настраивают каждую ячейку.
"... нет причин что-то создавать, ни JComponents, как JLabel...". Мне нужна многострочная ячейка с возможностью окрашивания каждой строки и добавления значка к каждой строке. Лучший способ, который я нашел, - это два JLabels на JPanel в качестве ячейки. Если есть более простой способ, буду рад услышать. Я уже пробовал JList с многострочным HTML. Проблема в том, что он имеет тенденцию отображаться неправильно, если есть много разных строк.
"... Какова ваша цель..." - это должно работать просто. Можно ли изменить этот пример, чтобы отображать «работающий» неопределенный индикатор выполнения до тех пор, пока таблица не будет полностью обновлена без удаления операторов
Thread.sleep()
?
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);
}
});
}
}
// This wait represents other complex layout preparations.. for this cell
42 мс на ячейку? Почему это занимает так много времени? Тем не менее, поскольку это только видимые ячейки, их не должно быть много. Но в целом: не блокируйте EDT (поток отправки событий) — когда это произойдет, графический интерфейс «зависнет». Вместо вызоваThread.sleep(n)
реализуйте SwingTimer
для повторяющихся задач или aSwingWorker
для длительных задач. См. Параллелизм в Swing для получения дополнительной информации. - person Andrew Thompson   schedule 09.06.2014getTableCellRendererComponent
? - person MadProgrammer   schedule 09.06.2014