SwingWorker: shoot just once, please.

Many GUI users don't understand double-clicks. My father, for instance, usually clicks twice on buttons. That's a consequence, I imagine, of experience with bad user interfaces. Windows and Linux users, for instance, have to double-click on icons on the desktop, but click just once on buttons. Weird. Experienced users, like me, are just used to this weirdness and just "behave" as the GUI programmers expect (;-)). People with an innovative, creative mind, like the recently deceased genius Jef Raskin (see movies), just build new user interfaces.

So it's important to consider novice/inexpert users when building GUIs. When using SwingWorkers (or any other threading framework of your liking) it's important to fire Threads just once, although the user double-clicks on buttons.

Otherwise you can fire SwingWorkers twice. And that's probably not what you want. You can see this behaviour in my previous JDBC example. If you click the search button twice quickly then the SwingWorker is fired twice, and the model (the table) contains twice the entries it should.

Single shots: handling multiple clicks

The first thing to deal with is avoiding the user to press the button multiple times, quickly. Well, that's not a big deal with Swing. After JDK1.4 we have a AbstractButton.setMultiClickThreshhold(long) that may be of help. By setting that to, say, 200L (200 milliseconds) we avoid the JButton to generate ActionEvents less than 0.2 seconds apart. So if the user clicks the JButton three times within 150 milliseconds only one ActionEvent is generated.

Now, if you don't use an AbstractButton or derivative you can easily solve the thing yourself. Using timestamps. For instance, for an Action you may use something like this:

public class MyAction
  extends Action
{
  ...
  private long lastTimestamp = 0L;
  public void actionPerformed( ActionEvent anActionEvent )
  {
    long currentTimestamp = System.currentTimeMillis();
    // Threshhold of 200 milliseconds
    if ( (currentTimestamp-lastTimestamp) > 200L )
    {
       lastTimestamp = currentTimestamp;
       ... proceed
    }
  }
}

Single shots: disabling input until done

The second thing to deal with is avoiding the user to press the button until our operation is either done or cancelled.

Meanwhile we may block all user input by using a GlassPane that blocks all user input (but a Cancel option, probably) until the operation is done.

Or we may just disable the action that generated the event, by invoking setEnabled(false) on that JButton (or Action).

Note that, in both cases, it's desirable to include a "Cancel" option that actually cancels the operation and allows the user to try again. If the operation fails, or if it generates a runtime exception not handled by the application, the user may continue working instead of being kept locked. This is critical for the solution with GlassPanes, since GlassPanes will block all user input (you should consider using "finally" clauses that remove the GlassPane whether the operation suceeds or not).

It's also a best practice to inform the user that the operation is in progress meanwhile. Otherwise the user may think the application has hung. Doing that with a GlassPane is a piece of cake (the example above uses Java2D to visually show progress), but if we use the second option (disabling just an action) we should include either a status bar or even a progress bar informing of the progress of the operation. Even better we can update the UI with intermediate results (by publish()-ing and process()-ing them with the SwingWorker) so that the user does not get nervous while waiting.

Example

The Mandelbrot example contains these features. All buttons have a MultiClickThreshold() setting. The appropriate buttons and entries are disabled and enabled (see MandelbrotViewController.java#setWorkingState(boolean) ) depending on the working state, and a blocking ProgressPane is shown to the user while the operation is in progress (allowing as well to cancel the operation).

So here it comes my third suggestion for SwingWorkers (that can be applied to other threading frameworks around).

SwingWorker Suggestion III: Handle multi-clicks, show progress, allow cancel

Be aware of multi-clicks by using timestamps or setting MultiClickThreshold in buttons and then disabling user input where appropriate. While the operations are working visually show progress to the user. Allow the user to cancel the operation if he whishes.

Enough for today. Next time I'll talk about automagically timing-out tasks that have been running for a long time.

Keep swinging,
Antonio

Comentarios:

Well, I do it this way:
public void actionPerformed(final ActionEvent e) {
  ((JButton)e.getSource()).setEnabled(false);
  WorkerThread,invokeLater(new Runnable() {
    public void run() {
      doMyFancyStuff();
      EventQueue.invokeLater(new Runnable() {
        public void run() {
          ((JButton)e.getSource()).setEnabled(true);
        }
      });
    }
  });
}
Works flawlessly and without stange timing stuff. I've learned that those ones actually never work reliably. Best regards, Dirk

Enviado por Dirk Hillbrecht en agosto 16, 2005 a las 05:11 PM CEST #

Good one....

Enviado por guest en diciembre 20, 2005 a las 11:48 PM CET #

Enviar un comentario:
Los comentarios han sido deshabilitados.
About

swinger

Search

Archives
« abril 2014
lunmarmiéjueviesábdom
 
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
    
       
Hoy