java - Actualización de jProgressBar desde SwingWorker
progress-bar (4)
Utilizo para monitorear una tarea larga ejecutando una ProgressBar. La tarea de larga ejecución se realiza en un hilo de Swingworker.
bien, puede usar SwingWorker
en todos los casos para redirigir cualquier tarea pesada y larga al Background
import java.awt.*;
import java.util.*;
import javax.swing.*;
import javax.swing.table.*;
public class TableCellProgressBar {
private String[] columnNames = {"String", "ProgressBar"};
private Object[][] data = {{"dummy", 100}};
private DefaultTableModel model = new DefaultTableModel(data, columnNames) {
private static final long serialVersionUID = 1L;
@Override
public Class<?> getColumnClass(int column) {
return getValueAt(0, column).getClass();
}
@Override
public boolean isCellEditable(int row, int col) {
return false;
}
};
private JTable table = new JTable(model);
public JComponent makeUI() {
TableColumn column = table.getColumnModel().getColumn(1);
column.setCellRenderer(new ProgressRenderer());
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
startTask("test");
startTask("error test");
startTask("test");
}
});
JPanel p = new JPanel(new BorderLayout());
p.add(new JScrollPane(table));
return p;
}
//http://java-swing-tips.blogspot.com/2008/03/jprogressbar-in-jtable-cell.html
private void startTask(String str) {
final int key = model.getRowCount();
SwingWorker<Integer, Integer> worker = new SwingWorker<Integer, Integer>() {
private int sleepDummy = new Random().nextInt(100) + 1;
private int lengthOfTask = 120;
@Override
protected Integer doInBackground() {
int current = 0;
while (current < lengthOfTask && !isCancelled()) {
if (!table.isDisplayable()) {
break;
}
if (key == 2 && current > 60) { //Error Test
cancel(true);
publish(-1);
return -1;
}
current++;
try {
Thread.sleep(sleepDummy);
} catch (InterruptedException ie) {
break;
}
publish(100 * current / lengthOfTask);
}
return sleepDummy * lengthOfTask;
}
@Override
protected void process(java.util.List<Integer> c) {
model.setValueAt(c.get(c.size() - 1), key, 1);
}
@Override
protected void done() {
String text;
int i = -1;
if (isCancelled()) {
text = "Cancelled";
} else {
try {
i = get();
text = (i >= 0) ? "Done" : "Disposed";
} catch (Exception ignore) {
ignore.printStackTrace();
text = ignore.getMessage();
}
}
System.out.println(key + ":" + text + "(" + i + "ms)");
}
};
model.addRow(new Object[]{str, 0});
worker.execute();
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.getContentPane().add(new TableCellProgressBar().makeUI());
frame.setSize(320, 240);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
class ProgressRenderer extends DefaultTableCellRenderer {
private final JProgressBar b = new JProgressBar(0, 100);
public ProgressRenderer() {
super();
setOpaque(true);
b.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Integer i = (Integer) value;
String text = "Completed";
if (i < 0) {
text = "Error";
} else if (i < 100) {
b.setValue(i);
return b;
}
super.getTableCellRendererComponent(table, text, isSelected, hasFocus, row, column);
return this;
}
}
pero ¿por qué complicar la GUI de Wwing mediante el uso de SwingWorker
(también se requería el conocimiento más profundo sobre Java Essential Classes
and Generics
),
Las implementaciones básicas para Runnable#Thread
solo requieren invokeLater
para la salida a la GUI de Swing, y en el caso que se inició desde EDT (de Swing / AWT Listener), y sin ninguna línea de código contiene Thread.sleep(int)
entonces invokeLater
solo se invokeLater
/ requerido para el código de producción
import java.awt.Component;
import java.util.Random;
import javax.swing.JFrame;
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;
public class TableWithProgressBars {
public static class ProgressRenderer extends JProgressBar implements TableCellRenderer {
private static final long serialVersionUID = 1L;
public ProgressRenderer(int min, int max) {
super(min, max);
this.setStringPainted(true);
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
this.setValue((Integer) value);
return this;
}
}
private static final int maximum = 100;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new TableWithProgressBars().createGUI();
}
});
}
public void createGUI() {
final JFrame frame = new JFrame("Progressing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Integer[] oneRow = {0, 0, 0, 0};
String[] headers = {"One", "Two", "Three", "Four"};
Integer[][] data = {oneRow, oneRow, oneRow, oneRow, oneRow,};
final DefaultTableModel model = new DefaultTableModel(data, headers);
final JTable table = new JTable(model);
table.setDefaultRenderer(Object.class, new ProgressRenderer(0, maximum));
table.setPreferredScrollableViewportSize(table.getPreferredSize());
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
new Thread(new Runnable() {
@Override
public void run() {
Object waiter = new Object();
synchronized (waiter) {
int rows = model.getRowCount();
int columns = model.getColumnCount();
Random random = new Random(System.currentTimeMillis());
boolean done = false;
while (!done) {
int row = random.nextInt(rows);
int column = random.nextInt(columns);
Integer value = (Integer) model.getValueAt(row, column);
value++;
if (value <= maximum) {
model.setValueAt(value, row, column);
try {
waiter.wait(15);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
done = true;
for (row = 0; row < rows; row++) {
for (column = 0; column < columns; column++) {
if (!model.getValueAt(row, column).equals(maximum)) {
done = false;
break;
}
}
if (!done) {
break;
}
}
}
frame.setTitle("All work done");
}
}
}).start();
}
}
mi conclusión para tareas realmente pesadas y de larga ejecución es que has visto Runnable#Thread
(simple, fácil, no burocrático y claro), solo si tu conocimiento sobre Java
& Swing
muy bueno, entonces puedes pensar en SwingWorker
Utilizo para monitorear una tarea larga ejecutando una ProgressBar. La tarea de larga ejecución se realiza en un hilo de Swingworker.
Solía programar cosas como esa:
public class MySwingWorkerClass extends SwingWorker<Void, Void> {
private JProgressBar progressBar;
public MySwingWorker(JProgressBar aProgressBar) {
this.progressBar = aProgressBar;
progressBar.setVisible(true);
progressBar.setStringPainted(true);
progressBar.setValue(0);
}
@Override
public Void doInBackground() {
//long running task
loop {
calculation();
progressBar.setValue(value);
}
return null;
}
@Override
public void done() {
progressBar.setValue(100);
progressBar.setStringPainted(false);
progressBar.setVisible(false);
}
}
pero recientemente descubrí que podía hacerlo usando el "setProgress" y definiendo el cambio de propiedad y haciendo cosas como esa
public class MySwingWorkerClass extends SwingWorker<Void, Void> {
private JProgressBar progressBar;
public MySwingWorker(JProgressBar aProgressBar) {
addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
progressBar.setValue((Integer) evt.getNewValue());
}
}
});
progressBar.setVisible(true);
progressBar.setStringPainted(true);
progressBar.setValue(0);
setProgress(0);
}
@Override
public Void doInBackground() {
//long running task
loop {
calculation();
setProgress(value);
}
return null;
}
@Override
public void done() {
setProgress(100);
progressBar.setValue(100);
progressBar.setStringPainted(false);
progressBar.setVisible(false);
}
}
Mi pregunta es: ¿es aceptable mi primer código o debo usar el setProgress para cualquier razón? Encuentro el segundo código más complicado y en mi caso, y no sé si hay alguna ventaja o razón para usar el segundo.
¿Algún consejo?
EDITAR Gracias por la respuesta. Como un resumen. La primera solución es "incorrecta" debido a que la actualización de la barra de progreso se realiza fuera del EDT. La segunda solución es "correcta" porque la actualización de la barra de progreso se realiza dentro del EDT
Ahora, de acuerdo con la respuesta "interesante" de @mKorbel, en mi caso mi cálculo da resultados en el texto HTML que "inserto" (ver este enlace ). Mi código actual es el siguiente.
Publico (cadena) y mi código de proceso se ve así
@Override
protected void process(List<String> strings) {
for (String s : strings) {
try {
htmlDoc.insertBeforeEnd(htmlDoc.getElement(htmlDoc.getDefaultRootElement(), StyleConstants.NameAttribute, HTML.Tag.TABLE), s);
} catch (BadLocationException ex) {
} catch (IOException ex) {
}
}
}
¿Cómo puedo volver a usar @mKobel para hacer lo mismo en mi caso? Quiero decir, él utiliza para anular la representación de la tabla en mi caso, ¿qué procesador debo anular (jTextPane?) Y cómo?
En el primer código, está llamando a la siguiente línea en un subproceso no EDT (subproceso del distribuidor de eventos). Por lo tanto, no es seguro para subprocesos
progressBar.setValue(value);
Esto puede provocar un comportamiento inesperado ya que Swing no está diseñado como una biblioteca segura para subprocesos.
Hay diferentes métodos para realizar esto en el modo Swing. Una forma correcta de esto es lo que has hecho en la segunda publicación. Otra sería utilizar los métodos publish()/process()
, y un tercer método sería escribir su propio hilo en lugar de SwingWorker
y usar SwingUtilities.invokeLater()
.
Como se muestra en este ejemplo , su uso del setProgress()
del trabajador en su segundo ejemplo es correcto: cualquier PropertyChangeListener
se notificará de manera asincrónica en la cadena de distribución del evento.
Su segundo enfoque es correcto e incluso está documentado en la clase javadoc de la clase SwingWorker
. El evento ''progreso'' se dispara en el EDT, por lo que su oyente actualiza la barra de progreso en el EDT. Este no es el caso en su primer acercamiento.
Un ejemplo de otro enfoque (utilizando publish/process
según lo indicado por el visir) se puede encontrar en mi respuesta en una pregunta SO anterior.