2010-05-25 15 views
5

Mi "problema" puede describirse de la siguiente manera. Supongamos que tenemos un proceso intensivo que queremos que se ejecute en segundo plano y le pedimos que actualice una barra Swing JProgress. La solución es fácil:Cómo delegar la publicación de SwingWorker a otros métodos

import java.util.List; 

import javax.swing.JOptionPane; 
import javax.swing.JProgressBar; 
import javax.swing.SwingWorker; 


/** 
* @author Savvas Dalkitsis 
*/ 
public class Test { 

    public static void main(String[] args) { 
     final JProgressBar progressBar = new JProgressBar(0,99); 
     SwingWorker<Void, Integer> w = new SwingWorker<Void, Integer>(){ 

      @Override 
      protected void process(List<Integer> chunks) { 
       progressBar.setValue(chunks.get(chunks.size()-1)); 
      } 

      @Override 
      protected Void doInBackground() throws Exception { 

       for (int i=0;i<100;i++) { 
        publish(i); 
        Thread.sleep(300); 
       } 

       return null; 
      } 

     }; 
     w.execute(); 
     JOptionPane.showOptionDialog(null, 
       new Object[] { "Process", progressBar }, "Process", 
       JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, 
       null, null, null); 
    } 

} 

Ahora supongo que tengo varios métodos que llevan mucho tiempo. Por ejemplo, tenemos un método que descarga un archivo de un servidor. U otro que sube a un servidor. O cualquier cosa realmente. ¿Cuál es la forma correcta de delegar el método de publicación a esos métodos para que puedan actualizar la GUI de manera adecuada?

lo que he encontrado hasta ahora es esto (a suponer que el método "aMethod" reside en algún otro paquete, por ejemplo):

import java.awt.event.ActionEvent; 
import java.util.List; 

import javax.swing.AbstractAction; 
import javax.swing.Action; 
import javax.swing.JOptionPane; 
import javax.swing.JProgressBar; 
import javax.swing.SwingWorker; 


/** 
* @author Savvas Dalkitsis 
*/ 
public class Test { 

    public static void main(String[] args) { 
     final JProgressBar progressBar = new JProgressBar(0,99); 
     SwingWorker<Void, Integer> w = new SwingWorker<Void, Integer>(){ 

      @Override 
      protected void process(List<Integer> chunks) { 
       progressBar.setValue(chunks.get(chunks.size()-1)); 
      } 

      @SuppressWarnings("serial") 
      @Override 
      protected Void doInBackground() throws Exception { 

       aMethod(new AbstractAction() { 

        @Override 
        public void actionPerformed(ActionEvent e) { 
         publish((Integer)getValue("progress")); 
        } 
       }); 

       return null; 
      } 

     }; 
     w.execute(); 
     JOptionPane.showOptionDialog(null, 
       new Object[] { "Process", progressBar }, "Process", 
       JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, 
       null, null, null); 
    } 

    public static void aMethod (Action action) { 
     for (int i=0;i<100;i++) { 
      action.putValue("progress", i); 
      action.actionPerformed(null); 
      try { 
       Thread.sleep(300); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
     } 
    } 

} 

Funciona pero sé que le falta algo. ¿Alguna idea?

Respuesta

0

Quizás haga un SwingWorker para cada método largo. Cada SwingWorker tiene su propio nivel de progreso.

Cada SwingWorker actualiza su propio nivel de progreso durante el método doInBackground, luego llama a publish. Dentro del método de proceso, dentro del EDT, cada SwingWorker lee su nivel de progreso y actualiza el modelo y la barra de progreso general de la vista.

+0

¿y si tenemos solo un método terrible largo? No estoy bajoneando, pero esta no es una solución – Xorty

+0

Puedes revocar si mi respuesta es mala. Pero leí en la pregunta "Por ejemplo, tenemos un método que descarga un archivo de un servidor u otro que se carga a un servidor. O algo realmente." ... así que supongo que hay muchos métodos, no solo uno terrible largo ? – Istao

+0

El problema que tengo es que estos métodos se pueden llamar desde diferentes partes del código. Y el elemento GUI que necesitarán para actualizar no será siempre el mismo. Con mi solución puedo crear un método genérico y cada vez que creo un nuevo swingworker puedo evaluar el método de proceso en mi GUI. –

0

Me enfrenté a un problema similar. Aquí es lo que encontré, tal vez la respuesta correcta en realidad carece, pero vamos a darle una oportunidad:

  • si tenemos gran cantidad de iteraciones, podemos actualizar barra de progreso en doInBackGround() método. JProgressBar es el parámetro constructor de nuestro SwingWorker que extiende SwingWorker (así que sí, usamos personalizado)
  • si no tenemos iteraciones y no podemos interrumpir el método que lleva mucho tiempo completarlo, podemos atornillarlo todo y hacer es como la mayoría de las personas (por lo que nuestra barra de progreso no tiene un proceso lineal, sino que simplemente lo actualiza después de que se realiza un trabajo parcial). Las malas noticias son que si nuestro método es el único que hace el trabajador (por ejemplo, enviando un correo electrónico en segundo plano) la barra de progreso se convertirá en repentinamente llena. Aunque no es muy agradable, echemos un vistazo a la tercera opción
  • esto puede ser bastante loco y el rendimiento se ralentiza, todo se debe a que nuestra aplicación debe ser . Así que vayamos al código fuente del método, que tarda mucho tiempo en completarse. Lo reemplazamos y pegamos exactamente el mismo código, pero agregamos un parámetro más: adivina qué, sí JProgressBar. Dentro del método, creamos Thread que se ejecutará hasta que algún parámetro booleano (indicador que indica que el método se haya completado finalmente) esté configurado en verdadero. El hilo mantendrá la actualización de JProgressBar en algunos intervalos razonables. El mayor problema es suponer, cuál es el intervalo razonable. Deberíamos realizar algunas pruebas y estimar el valor de los intervalos.

En tercer punto, describí cómo ejecutar el método Thread from que completa una tarea que no es iterativa (al menos no en nuestro código Java) y no se puede interrumpir. El subproceso actualiza JProgressBar que se proporcionó como un parámetro de método. Esto es, sin embargo, sín más lenta tan puro método de llamada

10

(estoy actualizando mi respuesta para que sea más clara y generalizada)

A pesar de haber desacoplado con éxito su lógica y presentación, no es hecho en una forma que se presta para la reutilización de código.El PropertyChangeSupport de Java hace que sea fácil desacoplar la lógica de la presentación implementando bound properties, y obtener una reutilización sustancial. La idea es usar controladores de eventos en lugar de objetos de acción.

Primero, conceptualice la abstracción. El trabajo de fondo debe "gritar" (publicar) a la GUI de forma intermitente, y la GUI necesita escucharlo. Dos clases genéricas codificarán esta idea:

/** 
* Wrapper for the background logic. 
* 
* <T> return type 
* <S> intermediary type (the "shout out") 
*/ 
public static abstract class LoudCall<T, S> implements Callable<T> { 

    private PropertyChangeSupport pcs; 
    private S shout; 

    public LoudCall() { 
     pcs = new PropertyChangeSupport(this); 
    } 

    public void shoutOut(S s) { 
     pcs.firePropertyChange("shoutOut", this.shout, 
       this.shout = s); 
    } 

    public void addListener(PropertyChangeListener listener) { 
     pcs.addPropertyChangeListener(listener); 
    } 

    public void removeListener(PropertyChangeListener listener) { 
     pcs.removePropertyChangeListener(listener); 
    } 

    @Override 
    public abstract T call() throws Exception; 
} 

/** 
* Wrapper for the GUI listener. 
* 
* <T> return type 
* <S> intermediary type (the "shout out" to listen for) 
*/ 
public static abstract class ListenerTask<T, S> extends SwingWorker<T, S> 
     implements PropertyChangeListener { 

    private LoudCall<T, S> aMethod; 

    public ListenerTask(LoudCall<T, S> aMethod) { 
     this.aMethod = aMethod; 
    } 

    @Override 
    protected T doInBackground() throws Exception { 
     aMethod.addListener(this); 
     return aMethod.call(); 
    } 

    @Override 
    public void propertyChange(PropertyChangeEvent evt) { 
     if ("shoutOut".equals(evt.getPropertyName())) { 
      publish((S)evt.getNewValue()); 
     } 
    } 

    @Override 
    protected abstract void process(List<S> chunks); 
} 

Estas clases se pueden utilizar para todos sus widgets Swing. Para un ProgressBar, el "grito hacia fuera" será un entero, y el tipo de retorno es nulo:

public class ProgressExample { 
    public static void main(String[] args) { 
     SwingUtilities.invokeLater(new Runnable() { 
    @Override 
    public void run() { 

     // 1. setup the progress bar 
     final JProgressBar progressBar = new JProgressBar(0, 99); 

     // 2. Wrap the logic in a "Loud Call" 
     LoudCall<Void, Integer> aMethod = new LoudCall<Void, Integer>() { 
      @Override 
      public Void call() throws Exception { 
       for (int i = 0; i < 100; i++) { 
        // "i have an update for the GUI!" 
        shoutOut(i); 
        Thread.sleep(100); 
       } 
       return null; 
      } 
     }; 

     // 3. Run it with a "Listener Task" 
     (new ListenerTask<Void, Integer>(aMethod) { 
      @Override 
      protected void process(List<Integer> chunks) { 
       progressBar.setValue(chunks.get(chunks.size() - 1)); 
      } 
     }).execute(); 

     // 4. show it off! 
     JOptionPane.showOptionDialog(null, 
      new Object[] { "Process", progressBar }, "Process", 
      JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, 
      null, null, null 
     ); 
    } 
     }); 
    } 
} 

Sólo el oyente tiene que saber nada acerca de los detalles de interfaz gráfica de usuario, y la lógica de fondo todavía tiene control sobre la publicación (indirectamente, por "gritar"). Este código es más escueto, legible y reutilizable.

Me doy cuenta de que esta pregunta ya es bastante antigua, ¡pero espero que ayude a alguien!

+1

Es difícil subestimar lo útil que es esta respuesta. ¡Recompensa la relectura! – Arvanem

Cuestiones relacionadas