2010-05-25 10 views
5

Mój "problem" można opisać następująco. Załóżmy, że mamy intensywny proces, który chcemy uruchomić w tle i zaktualizować pasek Swing JProgress. Rozwiązanie jest proste:Jak delegować publikację SwingWorker do innych metod

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

} 

Teraz przyjmijmy, że mam różne metody, które zajmują dużo czasu. Na przykład mamy metodę, która pobiera plik z serwera. Lub inny, który przesyła na serwer. Albo cokolwiek naprawdę. Jaki jest właściwy sposób delegowania metody publikowania do tych metod, aby mogły one odpowiednio zaktualizować GUI?

co znalazłem do tej pory jest to (zakładając, że metoda „aMethod” mieszka w innym opakowaniu, na przykład):

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

} 

To działa, ale wiem, że czegoś brakuje. jakieś pomysły?

Odpowiedz

0

Być może wykonaj SwingWorker dla każdej długiej metody. Każdy SwingWorker ma swój własny poziom zaawansowania.

Każdy SwingWorker aktualizuje swój własny poziom postępu podczas metody doInBackground, a następnie wywołuje funkcję publikowania. Wewnątrz metody procesu, czyli wewnątrz EDT, każdy SwingWorker odczytuje jego poziom zaawansowania i aktualizuje ogólny pasek postępu modelu i wizji.

+0

co, jeśli mamy tylko jedną straszną długą metodę? Nie spadam, ale to nie jest rozwiązanie. – Xorty

+0

Możesz zaniechać, jeśli moja odpowiedź jest zła. Ale przeczytałem w pytaniu "Na przykład mamy metodę, która pobiera plik z serwera lub inną, która ładuje się na serwer, lub coś naprawdę". ... więc przypuszczam, że jest wiele metod, nie tylko jeden straszny długi ? – Istao

+0

Problem w tym, że te metody można wywołać z różnych części kodu. Element gui, którego będą potrzebować do odświeżenia, nie zawsze będzie taki sam. Dzięki mojemu rozwiązaniu mogę stworzyć ogólną metodę i za każdym razem, gdy tworzę nowy swingworker mogę przypisać metodę procesu na moim gui. –

0

Miałem podobny problem. Oto co znalazłem, może naprawdę poprawna odpowiedź brakuje, ale dajmy mu szansę:

  • jeśli mamy dużo powtórzeń, możemy zaktualizować pasek postępu w doInBackGround() metody. JProgressBar jest parametrem konstruktora naszego SwingWorker, który rozszerza SwingWorker (więc tak, używamy niestandardowego)
  • jeśli nie mamy iteracji i nie możemy przerwać metody, która zajmuje dużo czasu, możemy albo wszystko sprowadzić i zrobić to jak większość ludzi (więc nasz pasek postępu nie ma liniowego procesu, ale po prostu odświeża wartości po wykonaniu częściowej pracy). Zła wiadomość jest taka, że ​​jeśli nasza metoda jest jedyną czynnością wykonywaną przez pracownika (na przykład wysyłanie wiadomości e-mail w tle) pasek postępu stanie się nagle pełny. Niezbyt przyjemne, ale spójrzmy na trzecią opcję:
  • To może być dość szalone, a wydajność spowolnienia, to wszystko, ponieważ nasza aplikacja musi być fantazyjna. Przejdźmy więc do kodu źródłowego metody, która zajmuje dużo czasu. Nadpisujemy go i wkleja dokładnie ten sam kod, ale dodajemy jeszcze jeden parametr - zgadnij co, tak JProgressBar. Wewnątrz metody tworzymy wątek, który będzie działał, dopóki jakiś parametr boolowski (metoda wskazująca flagę nie zostanie zakończona) zostanie ustawiony na true. Wątek będzie aktualizował JProgressBar w pewnych interwałach rozsądnych. Największym problemem jest założenie, jaka jest rozsądna przerwa. Powinniśmy wziąć kilka testów i oszacować wartość dla interwałów.

W trzecim punkcie opisałem, jak wykonać wątek z metody, która wykonuje pewne zadanie, które nie jest iteracyjne (przynajmniej nie w naszym kodzie Java) i nie może być przerwane. Aktualizacje wątków JProgressBar, które podano jako parametr metody. Jest to jednak definitelly wolniej jako czysta metoda wywoływania

10

(jestem aktualizowania moją odpowiedź, aby to bardziej jasne i uogólnione)

chociaż trzeba skutecznie oddzielone logiki i prezentacji, to nie odbywa się w sposób, który nadaje się do ponownego użycia kodu.Java PropertyChangeSupport ułatwia odseparowanie logiki od prezentacji poprzez implementację bound properties i uzyskanie znacznego ponownego użycia. Chodzi o to, aby używać obiektów obsługi zdarzeń zamiast obiektów akcji.

Po pierwsze, konceptualizuj abstrakcję. Praca w tle musi sporadycznie "krzyczeć" (publikować) do interfejsu GUI, a GUI musi tego słuchać. Dwie ogólne klasy skodyfikują ten pomysł:

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

Te klasy mogą być używane do wszystkich widżetów Swing. Dla ProgressBar, w „wykrzyczeć” będzie liczbą całkowitą, a typ zwracany jest nieważna:

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

Tylko słuchacz musi wiedzieć nic o szczegółach GUI, a logika tło wciąż ma kontrolę nad Publishing (pośrednio, poprzez "krzyczenie"). Ten kod jest bardziej zwięzły, czytelny i możliwy do ponownego użycia.

Zdaję sobie sprawę, że to pytanie jest dość stare, ale mam nadzieję, że pomoże komuś!

+1

Trudno jest zaniżać użyteczność tej odpowiedzi. Nagradza ponowne czytanie! – Arvanem

Powiązane problemy