[Java] SwingWorker en JDialog met Modal generiek opzetten

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • Matis
  • Registratie: Januari 2007
  • Laatst online: 11:15

Matis

Rubber Rocket

Topicstarter
Beste PRGers,

momenteel ben ik met een GUI-applicatie in Java bezig.
Het is een "domme" applicatie, dat wil zeggen, middels xml-rpc haalt de GUI alle informatie op van de server.
Omdat sommige responses langer dan "instant" duren, kan de gebruiker de indruk krijgen dat de applicatie niet reageert.

Om dit gevoel weg te nemen, leek het me de beste oplossing om een JDialog met een JLabel, Icon ofzo weer te geven, met daarin de melding dat de applicatie bezig is met het verwerken van het verzoek.
Dit heb ik momenteel voor elkaar op de volgende manier:
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
JDialog dialog;
/* Initialiseer dialog en zorg dat Modal true is en geen actionhandlers */
final SwingWorker<Object, Object> worker = new SwingWorker<Object, Object>()
{
    @Override
    protected Object doInBackground() throws Exception
    {
    try
    {
    /* Simmuleer een lange reques
    *  Normaal staat hier telkens andere xmlrpc verzoeken met andere parameters en return types.
    */
    Thread.sleep(5000);
    }
    catch (InterruptedException e)
    {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    dialog.dispose();
    return null;
    }
};
worker.execute();
dialog.setVisible(true);


Omdat ik op een tiental plekken een dergelijke "Please wait" dialog wil weergeven en er telkens een andere functie aanroepen met return types nodig zijn, wil ik de doInBackground() graag lokaal definiëren.
Echter staat nu de JDialog inline, terwijl ik die graag asynchroon zou willen openen vanuit een aparte klasse.
Daarnaast is de JDialog door de Modal eigenschap nu ook nog blocking, terwijl hij dat ook niet moet zijn.

Voorbeelden op het internet (deze bijvoorbeeld) geeft de volgende "oplossing".
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class SwingWorkerCompletionWaiter extends PropertyChangeListener {
     private JDialog dialog;
 
     public SwingWorkerCompletionWaiter(JDialog dialog) {
         this.dialog = dialog;
     }
 
     public void propertyChange(PropertyChangeEvent event) {
         if ("state".equals(event.getPropertyName())
                 && SwingWorker.StateValue.DONE == event.getNewValue()) {
             dialog.setVisible(false);
             dialog.dispose();
         }
     }
 }
 JDialog dialog = new JDialog(owner, true);
 swingWorker.addPropertyChangeListener(
     new SwingWorkerCompletionWaiter(dialog));
 swingWorker.execute();
 //the dialog will be visible until the SwingWorker is done
 dialog.setVisible(true); 

Dit stukje voorbeeldcode doet al bijna wat ik wil, alleen zou ik graag een lokale functie als doInBackground() willen aanroepen, maar ik zou niet weten hoe ik een functiepointer (zoals dat in c eenvoudig kan) mee kan geven als hook van de SwingWorker.

Ik hoop dat het een beetje duidelijk is :P

Matis

If money talks then I'm a mime
If time is money then I'm out of time


Acties:
  • 0 Henk 'm!

  • Styxxy
  • Registratie: Augustus 2009
  • Laatst online: 10:03
Zo ver ik weet kan dat niet in Java. Na wat opzoekwerk, is de meest gebruikte manier of Anonymous classes (aan de hand van Interfaces). Een iets betere oplossing zou zijn om het Strategy Pattern of Command Pattern te gebruiken.

Interessant leesvoer (dat over die problematiek gaat):
http://stackoverflow.com/...-function-pointer-in-java

Acties:
  • 0 Henk 'm!

  • Matis
  • Registratie: Januari 2007
  • Laatst online: 11:15

Matis

Rubber Rocket

Topicstarter
Thnx voor je reactie, ik was zelf al iets verder met het "generiek" opzetten van de helper classes:
Java:
1
2
3
4
5
AapSwingWorker swingWorker = new AapSwingWorker(); /*public class AapSwingWorker extends SwingWorker<Void, Void> */
JDialog waitDialog = new AapWaitDialog(_mainWindow); /* public class AapWaitDialog extends JDialog */
swingWorker.addPropertyChangeListener(new AapCompletionWaiter(waitDialog)); /* class AapCompletionWaiter implements PropertyChangeListener */
swingWorker.execute(); /* Thread.sleep(2000); */
waitDialog.setVisible(true);


Op deze manier blijft alles lekker asynchroon lopen, het enige wat ik nu nog voor elkaar moet krijgen, is (zoals je al stelt) een soort van functie pointer door te geven aan de SwingWorker, waarmee ik de doInBackground een functie uit de aanroepende class kan laten uitvoeren :)

If money talks then I'm a mime
If time is money then I'm out of time


Acties:
  • 0 Henk 'm!

  • Matis
  • Registratie: Januari 2007
  • Laatst online: 11:15

Matis

Rubber Rocket

Topicstarter
Ik heb de oplossing gevonden, en het is nog mooier dan gehoopt O+ Ik heb overigens gebruik gemaakt van het Wikipedia: Strategy pattern

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* Aanroepende class */
public class AapCalculateMenu extends JPanel implements ActionListener, AapBackgroundWorker
{
/* heel veel zut */
  @Override
  public void actionPerformed(final ActionEvent actionEvent)
  {
        new AapSwingWorker(_mainWindow, this);
  }

  @Override
  public int doInBackground()
  {
  /* heel veel berekeningen, :z */
    return 0;
  }
}


Java:
1
2
3
4
5
6
7
public class AapWaitDialog extends JDialog
{
  public AapWaitDialog(AapMainWindow mainWindow)
  {
    /* Init enzo */
  }
}


Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class AapCompletionWaiter implements PropertyChangeListener
{
  private transient final JDialog _dialog;

  public AapCompletionWaiter(JDialog dialog)
  {
    _dialog = dialog;
  }

  @Override
  public void propertyChange(PropertyChangeEvent event)
  {
    if ("state".equals(event.getPropertyName()) && SwingWorker.StateValue.DONE == event.getNewValue())
    {
      _dialog.setVisible(false);
      _dialog.dispose();
    }
  }
}


En de class die alles aan elkaar knoopt
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
interface AapBackgroundWorker
{
  int doInBackground();
}

public class AapSwingWorker extends SwingWorker<Void, Void>
{
  private final transient AapBackgroundWorker _backgroundWorker;

  public AapSwingWorker(AapMainWindow mainWindow, AapBackgroundWorker backgroundWorker)
  {
    _backgroundWorker = backgroundWorker;
    JDialog waitDialog = new AapWaitDialog(mainWindow);
    addPropertyChangeListener(new AapCompletionWaiter(waitDialog));
    execute();
    waitDialog.setVisible(true);
  }

  @Override
  protected Void doInBackground() throws Exception
  {
    _backgroundWorker.doInBackground();
    return null;
  }
}


Zoals je ziet, staat er in de aanroepende class maar één regeltje code. En er is een override bijgekomen, maar dat maakt het eigenlijk alleen maar leesbaarder _/-\o_

[ Voor 12% gewijzigd door Matis op 08-08-2011 22:50 ]

If money talks then I'm a mime
If time is money then I'm out of time


Acties:
  • 0 Henk 'm!

  • Styxxy
  • Registratie: Augustus 2009
  • Laatst online: 10:03
Matis schreef op maandag 08 augustus 2011 @ 22:49:
Ik heb de oplossing gevonden, en het is nog mooier dan gehoopt O+ Ik heb overigens gebruik gemaakt van het Wikipedia: Strategy pattern
Was ook mijn advies om te doen ;). En het ziet er inderdaad een goede oplossing uit.
Styxxy schreef op maandag 08 augustus 2011 @ 19:38:
[...] Een iets betere oplossing zou zijn om het Strategy Pattern of Command Pattern te gebruiken.

Acties:
  • 0 Henk 'm!

  • Matis
  • Registratie: Januari 2007
  • Laatst online: 11:15

Matis

Rubber Rocket

Topicstarter
Ik heb jouw advies ook opgevolgd ;) Alleen niet het Command, maar het Strategy Pattern :)

Vond het zelf ook een nette en generieke oplossing.

If money talks then I'm a mime
If time is money then I'm out of time


Acties:
  • 0 Henk 'm!

  • Matis
  • Registratie: Januari 2007
  • Laatst online: 11:15

Matis

Rubber Rocket

Topicstarter
Toch nog een klein schopje, want het zit me nog niet helemaal lekker.

De code zoals bovenstaande gaf vaak een exception. Dat kwam doordat de background thread ook Swing-objecten wijzigden (voornamelijk JTables).

Om dat uit te sluiten, heb ik het volgende geschreven:
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
  public AapSwingWorker(AapMainWindow mainWindow, final AapBackgroundWorker backgroundWorker)
  {
    final JLabel waitLabel = new JLabel(new ImageIcon("pleaseWait.gif"), JLabel.CENTER);
    final JDialog dialog = new JDialog(mainWindow, "Please wait", true);
    dialog.add(waitLabel);
    dialog.setSize(300, 160);
    dialog.setResizable(false);
    dialog.setLocationRelativeTo(mainWindow);
    SwingWorker<Void, Void> sw = new SwingWorker<Void, Void>()
    {
      @Override
      protected Void doInBackground() throws Exception
      {
        Runnable runnable = new Runnable()
        {
          @Override
          public void run()
          {
            System.out.println("Invoked before");
            try
            {
              /* Simmulate looooooooong action */
              Thread.sleep(5000);
            }
            catch (InterruptedException e)
            {
              e.printStackTrace();
            }
            System.out.println("Invoked after");
          }
        };
        System.out.println("Sleep before");
        Thread.sleep(1000);
        System.out.println("Invoke");
        SwingUtilities.invokeLater(runnable);
        System.out.println("Sleep after");
        Thread.sleep(1000);
        System.out.println("Sleep done");
        return null;
      }

      @Override
      protected void done()
      {
        dialog.dispose();
      };
    };
    sw.addPropertyChangeListener(this);
    sw.execute();
    dialog.setVisible(true);
  }

Het pleaseWait.gif is dit gifje:
Afbeeldingslocatie: http://tweakers.net/ext/f/F3GoCIGxKqFba7h35mHkOX23/full.gif

Wat er gebeurt (wat ik waarneem is het volgende):
* JDialog wordt getoond en het gifje "draait" 
Sleep before
Invoke
* het gifje stopt met draaien, maar JDialog is nog gewoon sleepbaar en achterliggende GUI blijft actief  
Sleep after
Invoked before
Sleep done
Invoked after
* JDialog verdwijnt


Nu wil ik dat het gifje blijft draaien tijdens het uitvoeren van de (nu gesimuleerde) actie.

Ik kan er geen vinger opleggen, waarom het gifje stopt met draaien.

Wanneer ik in plaatst van invokeAfter invokeAndWait gebruik, krijg ik de volgende uitvoer in de console:
Sleep before
Invoke
Invoked before
Invoked after
Sleep after
Sleep done

Dit is volkomen verklaarbaar, want de SwingUtilities blijft blokkeren totdat de runnable returned.
De uitkomst is hetzelfde, de waitGif bevriest na 1 seconde draaien.

If money talks then I'm a mime
If time is money then I'm out of time


Acties:
  • 0 Henk 'm!

  • CyBoB
  • Registratie: Januari 2001
  • Laatst online: 15-05 11:05

CyBoB

.::BURB::.

Heb al een tijd niet in java gewerkt, maar als ik de documentatie van SwingUtilities.invokeLater bekijk dan zegt deze:

"Causes doRun.run() to be executed asynchronously on the AWT event dispatching thread."

Dit houd dus in de invokeLater wel asynchroon is, maar de code uitvoert op de UI thread, wat er dus voor zorgt de de UI niet meer update totdat het process klaar is. De UI moet wel geupdate kunnen worden om je animated gif te tonen. (even in het midden gelaten of dat uberhaupt werkt, want heb zelf geen ervaring met animated gifjes in java)

Ik weet niet wat de best practice is hiervoor, maar waarschijnlijk kan je het langdurige proces het beste in een background worker/thread uitvoeren en eventueel de UI calls vanuit een andere thread via invokeLater doen.

Acties:
  • 0 Henk 'm!

  • Remus
  • Registratie: Juli 2000
  • Laatst online: 15-08-2021
Matis schreef op maandag 26 september 2011 @ 10:32:
Nu wil ik dat het gifje blijft draaien tijdens het uitvoeren van de (nu gesimuleerde) actie.

Ik kan er geen vinger opleggen, waarom het gifje stopt met draaien.

Wanneer ik in plaatst van invokeAfter invokeAndWait gebruik, krijg ik de volgende uitvoer in de console:
Sleep before
Invoke
Invoked before
Invoked after
Sleep after
Sleep done

Dit is volkomen verklaarbaar, want de SwingUtilities blijft blokkeren totdat de runnable returned.
De uitkomst is hetzelfde, de waitGif bevriest na 1 seconde draaien.
SwingUtilities.invokeLater() wordt uitgevoerd op de GUI eventthread. Je blokkeert nu gewoon alle voortgang op de eventthread, dus geen wonder dat je gif niet meer draait. Waarom roep je die invokeLater hier überhaupt aan, wat is je doel?
CyBoB schreef op maandag 26 september 2011 @ 12:42:
"Causes doRun.run() to be executed asynchronously on the AWT event dispatching thread."

Dit houd dus in de invokeLater wel asynchroon is, maar de code uitvoert op de UI thread, wat er dus voor zorgt de de UI niet meer update totdat het process klaar is.
Ter verduidelijking: invokeLater() is asynchroon voor de aanroeper (in tegenstelling tot SwingUtilitiesl#invokeAndWait(java.lang.Runnable).

[ Voor 22% gewijzigd door Remus op 26-09-2011 21:21 ]


Acties:
  • 0 Henk 'm!

  • Matis
  • Registratie: Januari 2007
  • Laatst online: 11:15

Matis

Rubber Rocket

Topicstarter
Remus schreef op maandag 26 september 2011 @ 21:17:
SwingUtilities.invokeLater() wordt uitgevoerd op de GUI eventthread. Je blokkeert nu gewoon alle voortgang op de eventthread, dus geen wonder dat je gif niet meer draait.
Dat is duidelijke taal :)
Waarom roep je die invokeLater hier überhaupt aan, wat is je doel?
Wat ik wil bereiken is het volgende:

Ik heb diverse XML-RPC calls welke uitgevoerd adhv acties gegenereerd door de GUI (clicks op buttons, JList index changed etc.) In de actionPerformed van iedere GUI-class wordt op de volgende manier AapSwingWorker aangeroepen:
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 public class AapCalculateMenu extends JPanel implements ActionListener, AapBackgroundWorker
{
  public AapCalculateMenu(final AapCalculateWindow parent, final AapMainWindow mainWindow)
  {
    super();
    _calculateWindow = parent;
    _mainWindow = mainWindow;
    _btnSubmit = new JButton("Submit");
    _btnSubmit.addActionListener(this);
    add(_btnSubmit);
  }

  @Override
  public void actionPerformed(final ActionEvent actionEvent)
  {
    final Object source = actionEvent.getSource();
    if (source.equals(_btnSubmit))
    {
        new AapSwingWorker(_mainWindow, this);
    }
  }

  @Override
  public int doInBackground()
  {
    final DatePicker datePicker = _calculateWindow.getSelectedDate();
    new AapXmlRpcWrapper().submitCalculations(datePicker.getStartDate(), datePicker.getStopDate());
    _calculateWindow.fillTable(datePicker.getStartDate(), datePicker.getStopDate());
    return 0;
  }
}

Omdat AapCalculateMenu AapBackgroundWorker implements moet die klasse dus een public int doInBackground() hebben.
Die functie wordt dan weer in de AapSwingWorker middels backgroundWorker.doInBackground(); aangeroepen (maar daarvoor heb ik in bovenstaande post dus de simulatie gebruik).

Wat ik wil bereiken is het volgende:
Gebruiker klikt op (bijvoorbeeld) een button, een JDialog pop upt zolang op de achtergrond de XML-RPC call wordt uitgevoerd en de data welke terugkomt in de GUI wordt geupdated. Als dat allemaal gebeurt is, wordt vanuit de code de JDialog weer gedisposed en kan de gebruiker de volgende actie doen.
Ik had in eerste instantie een SwingWorker geimplementeerd, maar dat gaf Threading-problemen (Null pointer exceptions op JTables enzo).
Daarom heb ik nu dus een SwingUtilities gebruikt om Thread-synchronisatie af te dwingen. Dat is eigenlijk waar ik tegenaan liep.

De JDialog blijft de hele tijd netjes zichtbaar, de Threading-problemen zijn weg, maar mijn gifje draait niet meer.
Volgens mij blockt dan alleen nog maar de AapSwingWorker.JDialog, waardoor het gifje niet geupdated wordt, terwijl vroeger de hele GUI bevroor.

Wat ik dus wil bereiken:
  • Bij elke XML-RPC actie een JDialog popup met please wait (geimplementeerd en werkt)
  • Een JDialog welke modal is en dus niet toestaat dat op de GUI geklikt kan worden (geimplementeerd en werkt)
  • Een gifje welke de gebruiker het gevoel geeft dat de applicatie nog steeds bezig is en niet is vastgelopen (werkt gedeeltelijk, het gifje wordt getoond, maar draait niet).
  • Na het uitvoeren van de XML-RPC actie wordt de data geupdated in de GUI en hertekent, daarna JDialog vanuit de code afsluiten (geimplementeerd en werkt)
Ter verduidelijking: invokeLater() is asynchroon voor de aanroeper (in tegenstelling tot SwingUtilitiesl#invokeAndWait(java.lang.Runnable).
Daar was ik al achter, derhalve heb ik ook voor invokeLater gekozen.

If money talks then I'm a mime
If time is money then I'm out of time


Acties:
  • 0 Henk 'm!

  • Neverwinterx
  • Registratie: December 2005
  • Laatst online: 16-05 16:50
En als je in plaats van SwingUtilities.invokeLater(runnable); dit gebruikt: new Thread(runnable).start(); ?

Acties:
  • 0 Henk 'm!

  • Matis
  • Registratie: Januari 2007
  • Laatst online: 11:15

Matis

Rubber Rocket

Topicstarter
Neverwinterx schreef op dinsdag 27 september 2011 @ 09:17:
En als je in plaats van SwingUtilities.invokeLater(runnable); dit gebruikt: new Thread(runnable).start(); ?
Dan krijg ik onherroepelijk Threading-problemen (meteen getest, na 3 kliks was het al raak)
Exception occurred during event dispatching:
java.lang.ArrayIndexOutOfBoundsException: 3 >= 3
	at java.util.Vector.elementAt(Vector.java:427)
usw...

If money talks then I'm a mime
If time is money then I'm out of time


Acties:
  • 0 Henk 'm!

  • NetForce1
  • Registratie: November 2001
  • Laatst online: 16-05 09:18

NetForce1

(inspiratie == 0) -> true

Matis schreef op dinsdag 27 september 2011 @ 08:48:
Ik had in eerste instantie een SwingWorker geimplementeerd, maar dat gaf Threading-problemen (Null pointer exceptions op JTables enzo).
Je background task doe je in #doInBackground, als je dan wat met je GUI wilt doen moet dat weer op de AWT event thread, dat kan dmv SwingUtilities#invokeLater, maar ook bijv. met SwingWorker#publish / SwingWorker#process bijv.

Ik zou in dit geval denk ik niet zo snel zelf met Thread gaan werken, gebruik gewoon SwingWorker, dat is veel makkelijker.

De wereld ligt aan je voeten. Je moet alleen diep genoeg willen bukken...
"Wie geen fouten maakt maakt meestal niets!"


Acties:
  • 0 Henk 'm!

  • Remus
  • Registratie: Juli 2000
  • Laatst online: 15-08-2021
Neverwinterx schreef op dinsdag 27 september 2011 @ 09:17:
En als je in plaats van SwingUtilities.invokeLater(runnable); dit gebruikt: new Thread(runnable).start(); ?
Dat is al helemaal fout. Die Runnable wordt aan de queue van de event thread geleverd en uitgevoerd (aanroepen van run()) door de event thread. Door new Thread(runnable).start() te doen breek je gewoon helemaal met de reden om invokeLater te gebruiken: dan had je dat namelijk net zo goed direct kunnen doen.

Aan de TS: ik kan je bedoelingen niet helemaal rijmen met wat je doet in in AapSwingWorker en AapCalculateMenu. Ik begrijp nog steeds niet waarom je de event thread zo lang laat slapen. Misschien moet je je echte code tonen. Verder, zoals NetForce1 al aangeeft: het terugkoppelen van resultaten op de eventthread moet of met process() of done() gebeuren (in jouw geval is denk ik done() het juiste punt).

[ Voor 27% gewijzigd door Remus op 27-09-2011 13:02 ]


  • Matis
  • Registratie: Januari 2007
  • Laatst online: 11:15

Matis

Rubber Rocket

Topicstarter
Remus schreef op dinsdag 27 september 2011 @ 12:57:
Aan de TS: ik kan je bedoelingen niet helemaal rijmen met wat je doet in in AapSwingWorker en AapCalculateMenu. Ik begrijp nog steeds niet waarom je de event thread zo lang laat slapen. Misschien moet je je echte code tonen. Verder, zoals NetForce1 al aangeeft: het terugkoppelen van resultaten op de eventthread moet of met process() of done() gebeuren (in jouw geval is denk ik done() het juiste punt).
Omdat je in Java geen functie-pointers kunt gebruiken en ik toch een soort van call-back functionaliteit wilde realiseren, heb ik het volgende bedacht.

Ik heb een 16-tal Swing-classes die ik allemaal via hetzelfde WaitDialog wil laten lopen. Alle 16 classes implementeren AapBackgroundWorker welke afdwingt dat iedere class de functie doInBackground implementeert.

In AapCalculateMenu zit de volgende functie:
Java:
1
2
3
4
5
6
7
8
  @Override
  public int doInBackground()
  {
    final DatePicker datePicker = _calculateWindow.getSelectedDate();
    new AapXmlRpcWrapper().submitCalculations(datePicker.getStartDate(), datePicker.getStopDate());
    _calculateWindow.fillTable(datePicker.getStartDate(), datePicker.getStopDate());
    return 0;
  }

AapXmlRpcWrapper.submitCalculations() doet niets anders dan een XML-RPC call afvoeren naar de XML-RPC-server en blokkeert tijdens het wachten op response.
In sommige gevallen kan dat 5 secondes duren, vandaar de gesimuleerde Thread.sleep(5000). Omdat die ook blockend is.

Het nadeel is dat ik de XML-RPC niet asynchroon kan laten lopen.

Wat ik misschien kan doen is het XML-RPC request asynchroon laten starten en de data die dan terugkomt weer in de done kunnen koppelen aan het grafische element wat er achter zit.
Is dat wat jij bedoelt?

If money talks then I'm a mime
If time is money then I'm out of time


  • NetForce1
  • Registratie: November 2001
  • Laatst online: 16-05 09:18

NetForce1

(inspiratie == 0) -> true

Matis schreef op woensdag 28 september 2011 @ 15:47:
[...]
Het nadeel is dat ik de XML-RPC niet asynchroon kan laten lopen.
Dat doe je al door het in doInBackground te doen.
Wat ik misschien kan doen is het XML-RPC request asynchroon laten starten en de data die dan terugkomt weer in de done kunnen koppelen aan het grafische element wat er achter zit.
Is dat wat jij bedoelt?
Wat je moet doen is de data die terugkomt retourneren uit doInBackground, en dan in done() die data oppakken met get() en in je tabel stoppen.

De wereld ligt aan je voeten. Je moet alleen diep genoeg willen bukken...
"Wie geen fouten maakt maakt meestal niets!"


  • Remus
  • Registratie: Juli 2000
  • Laatst online: 15-08-2021
Matis schreef op woensdag 28 september 2011 @ 15:47:
[...]

Omdat je in Java geen functie-pointers kunt gebruiken en ik toch een soort van call-back functionaliteit wilde realiseren, heb ik het volgende bedacht.

Ik heb een 16-tal Swing-classes die ik allemaal via hetzelfde WaitDialog wil laten lopen. Alle 16 classes implementeren AapBackgroundWorker welke afdwingt dat iedere class de functie doInBackground implementeert.

In AapCalculateMenu zit de volgende functie:
Java:
1
2
3
4
5
6
7
8
  @Override
  public int doInBackground()
  {
    final DatePicker datePicker = _calculateWindow.getSelectedDate();
    new AapXmlRpcWrapper().submitCalculations(datePicker.getStartDate(), datePicker.getStopDate());
    _calculateWindow.fillTable(datePicker.getStartDate(), datePicker.getStopDate());
    return 0;
  }

AapXmlRpcWrapper.submitCalculations() doet niets anders dan een XML-RPC call afvoeren naar de XML-RPC-server en blokkeert tijdens het wachten op response.
In sommige gevallen kan dat 5 secondes duren, vandaar de gesimuleerde Thread.sleep(5000). Omdat die ook blockend is.

Het nadeel is dat ik de XML-RPC niet asynchroon kan laten lopen.
Die doInBackGround() van Swing worker wordt al op een aparte thread uitgevoerd. Wat jij in je voorbeeld code deed was doInBackGround() weer code op de event thread (die lange sleep) te laten uitvoeren. Dat moet je dus niet doen.

Als je het dus in doInBackGround() van Swing worker uitvoert, dan wordt het al asynchroon uitgevoerd!
Wat ik misschien kan doen is het XML-RPC request asynchroon laten starten en de data die dan terugkomt weer in de done kunnen koppelen aan het grafische element wat er achter zit.
Is dat wat jij bedoelt?
Dat is precies waarvoor SwingWorker bedoelt is. Die done() wordt automatisch aangeroepen vanaf de eventthread, zodra doInBackGround() voltooid is. Het in de GUI tonen van je resultaten moet je dus vanuit done() doen.

Daarnaast kan je ook status updates doorgeven door in doInBackGround() publish() aan te roepen, dan zal SwingWorker automagisch process() aanroepen vanaf de event thread, of dit nuttig is hangt af van de toepassing.

  • Matis
  • Registratie: Januari 2007
  • Laatst online: 11:15

Matis

Rubber Rocket

Topicstarter
Ik denk dat ik de oplossing heb :)

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
interface AapBackgroundWorker
{
  Object doInBackground();

  int done(Object returnValue);
}

public class AapSwingWorker
{
  private transient final JProgressBar progressBar;

  public AapSwingWorker(final AapMainWindow mainWindow, final AapBackgroundWorker backgroundWorker)
  {
    progressBar = new JProgressBar(JProgressBar.HORIZONTAL, 0, 100);
    final JLabel waitLabel = new JLabel(new ImageIcon("pleaseWait.gif"), JLabel.CENTER);
    final JDialog dialog = new JDialog(mainWindow, "Please wait", true);
    progressBar.setValue(0);
    progressBar.setStringPainted(true);
    dialog.add(waitLabel);
    dialog.setSize(300, 160);
    dialog.setResizable(false);
    dialog.setModal(true);
    dialog.setLocationRelativeTo(mainWindow);
    SwingWorker<Void, Void> sw = new SwingWorker<Void, Void>()
    {
      private Object returnValue;

      @Override
      protected Void doInBackground() throws Exception
      {
        returnValue = backgroundWorker.doInBackground();
        return null;
      }

      @Override
      protected void done()
      {
        backgroundWorker.done(returnValue);
        dialog.dispose();
      };
    };
    sw.execute();
    dialog.setVisible(true);
  }
}


De AapBackgroundWorker heeft nu twee functies, ik heb de naamgeving hetzelfde gehouden, misschien is het verwarrend, maar nu kon ik iig even geen betere verzinnen.

doInBackground haalt de data asynchroon op en stopt het in een tijdelijke variabele. Daarna wordt done() afgevuurd en die haalt de data weer op (waarschijnlijk kan dat netter, want ik moet de argumenten voor SwingWorker goed zetten) en zet ze in de GUI :)

Ik heb het nu getest; Het wait-gifje blijft draaien en er treden geen thread-issues meer op *O*

Edit; Zo dus
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    SwingWorker<Object, Void> sw = new SwingWorker<Object, Void>()
    {
      @Override
      protected Object doInBackground() throws Exception
      {
        return backgroundWorker.doInBackground();
      }

      @Override
      protected void done()
      {
        try
        {
          backgroundWorker.done(get());
        }
        catch (InterruptedException e)
        {
          e.printStackTrace();
        }
        catch (ExecutionException e)
        {
          e.printStackTrace();
        }
        dialog.dispose();
      };
    };

[ Voor 14% gewijzigd door Matis op 28-09-2011 18:05 ]

If money talks then I'm a mime
If time is money then I'm out of time

Pagina: 1