|
Replies:
14
-
Last Post:
Aug 11, 2006 7:29 AM
by: coxcu
|
|
|
|
|
|
|
SwingUtilities.waitCursor(Runnable run)?
Posted:
Aug 10, 2006 9:53 AM
|
|
|
I was looking at the steps a user has to go through to get a wait cursor active around an operation that is expensive. 1. Set the cursor to the wait cursor 2. Invoke the task on the event queue 3. When done, invoke a Runnable that restores the wait cursor.
Some sample code that shows this off: import javax.swing.*; import java.awt.*; import javax.swing.event.*; import java.awt.event.*;
public class ExpensiveOperation implements Runnable, ActionListener{
JFrame jf; public void run(){ JButton jb = new JButton("Expensive Operation"); jb.addActionListener(this); jf = new JFrame(); jf.add(jb); jf.pack(); jf.setLocationRelativeTo(null); jf.setVisible(true); }
public void actionPerformed(ActionEvent e){ Component c = jf.getGlassPane(); c.setVisible(true); Cursor cursor = new Cursor(Cursor.WAIT_CURSOR); c.setCursor(cursor); SwingUtilities.invokeLater(new ExpensiveRunnable()); }
class ExpensiveRunnable implements Runnable{ public void run(){ try{ Thread.currentThread().sleep(10000); System.out.println("Done..."); } catch(Exception x){} SwingUtilities.invokeLater(new Restorer()); } }
class Restorer implements Runnable{ public void run(){ Component c= jf.getGlassPane(); c.setVisible(false); c.setCursor(null); } }
public static void main(String ... args){ SwingUtilities.invokeLater(new ExpensiveOperation()); }
} ///////////// Now I guess my question is: is this a common operation? Do users frequently need to consume the EDT so that they can block the user from doing something else? If yes, maybe there should be a SwingUtilities operation that gets rid of the complexity of having to manage the cursor and the operation. Maybe a method called waitCursor(Runnable run)(better name?)? Who knows, maybe even a waitCursor(MethodReference aMethodReference)? Or maybe the code Ive written for setting the wait cursor is not as simple as it could be and there isn't a need for it? 
thoughts? leouser
|
|
|
|
|
|
|
Re: SwingUtilities.waitCursor(Runnable run)?
Posted:
Aug 10, 2006 11:10 AM
in response to: leouser
|
|
|
> Now I guess my question is: is this a common > operation? Do users frequently need to consume the > EDT so that they can block the user from doing > something else?
I'd say it's a very common thing to do. I don't like user's events even getting to the EDT when a long running task has been initiated. It's just goofy and causes all sorts of confusion.
As we all move into the rich client world, this problem will be more and more prevalent and common in desktop java. With the delay involved in running network / web service aware applications, it's in my mind critical to have a simple mechanism which blocks all the user's input while some long running task occurs.
What I like to do is have a wait cursor show up after a short amount of time on any task, maybe 1/4 second. Then, after 2 seconds (for example) a dialog appears with some sort of status/progress and a cancel button in case the task is wedged somehow. This is pie-in-the-sky functionality for rich client apps if you ask me.
> If yes, maybe there should be a > SwingUtilities operation that gets rid of the > complexity of having to manage the cursor and the > operation.
+100
> Maybe a method called waitCursor(Runnable > run)(better name?)? Who knows, maybe even a > waitCursor(MethodReference aMethodReference)? Or > maybe the code Ive written for setting the wait > cursor is not as simple as it could be and there > isn't a need for it? 
I like the idea of having to pass a Runnable. This is similar to what I do in my projects. I'd even suggest a chain of Runnables so the wait cursor can be displayed across multiple linked tasks (though putting these all in a single runnable is usually doable). Oh, and the option to use a Callable so you can return values and throw execeptions.
I think it would be really nice to have some event and listener support too, so that you can use the observer pattern when needed. When the task starts, when it ends, if there's any progress, etc.
The critical thing with the wait cursor is getting it up and blocking all the other events. Using a glasspane seems to be the best solution. However, in my mind, it would be nice to not even have to mess with a glasspane and just tell the application to show a wait cursor across all windows, etc. I guess some sort of modality type of system similar to the modality enhancements to jdialog in mustang.
To sum up, in my mind there is a huge need to simplify the life of the developer with this concept. Some real support in Java would just be fantastic. SwingWorker and Spin help move tasks to the background, but you still have to block EDT and display the wait cursor manually. Seems like a logical thing to have core support for.
Cheers,
Adam
|
|
|
|
|
|
|
|
Re: SwingUtilities.waitCursor(Runnable run)?
Posted:
Aug 10, 2006 11:31 AM
in response to: bobsledbob
|
|
|
hmmm...
waitCursor(Runnable ... runnables); waitCursor(Callable ... callables);
why not? 
Another win for this kind of thing is that it will help the new user out. There is alot of stuff he has to figure out to get it to work and for someone just starting out in Swing it could be quite maddening.
leouser
|
|
|
|
|
|
|
|
Re: SwingUtilities.waitCursor(Runnable run)?
Posted:
Aug 10, 2006 1:01 PM
in response to: bobsledbob
|
|
|
'I think it would be really nice to have some event and listener support too, so that you can use the observer pattern when needed. When the task starts, when it ends, if there's any progress, etc.'
I wonder if this need could be met by returning an Observable that would allow you to hook into: public Observable waitCursor(Runnable ... runnables);
I suppose the mechanism could call: notifyObservers(Object arg)
with each Runnable that is completed.
leouser
|
|
|
|
|
|
|
|
JSR 296?
Posted:
Aug 10, 2006 1:11 PM
in response to: leouser
|
|
|
We're talking about a standard mechanism to execute potentially long-running tasks, while giving the user feedback and without blocking the EDT. That sounds like it falls within the scope of an application framework (JSR 296) to me. I'm not on the EG, however.
I guess it's time to head over to their public page and discuss it on the lists there.
- Curt
|
|
|
|
|
|
|
|
Re: JSR 296?
Posted:
Aug 10, 2006 3:22 PM
in response to: coxcu
|
|
|
swing@javadesktop.org wrote: > We're talking about a standard mechanism to execute potentially long-running tasks, while giving the user feedback and without blocking the EDT. That sounds like it falls within the scope of an application framework (JSR 296) to me. I'm not on the EG, however. > > I guess it's time to head over to their public page and discuss it on the lists there. > > - Curt > [Message sent by forum member 'coxcu' (coxcu)] > > http://forums.java.net/jive/thread.jspa?messageID=141860 > My JavaOne presentation about a prototype for JSR-296 covers the basics of the approach I've taken for asynchronous actions:
http://weblogs.java.net/blog/hansmuller/archive/ts-3399-final.pdf
See slides #36-50. Just to summarize, @Action methods can return a SwingWorker which the dispatching machinery monitors with a PropertyChange listener. The SwingWorker's progress is reported to the Application's ActionDisplay object; it's just an interface that applications use to control how progress reports are displayed.
- Hans
|
|
|
|
|
|
|
|
Re: JSR 296?
Posted:
Aug 10, 2006 4:31 PM
in response to: Hans Muller
|
|
|
Hans,
From your presentation (slide 50)...
@Actions That Block â—� Block keyword specifies scope: â—� Three scopes: NONE, WINDOW, APPLICATION â—� Application.getBlockingDialog() creates dialog â—� Blocking dialog can provide an ActionDisplay @Action(block=Action.Block.APPLICATION)
Cheers. Good stuff.
As mentioned in my previous message, pie-in-the-sky for me is something like:
o User invokes some long running action o EDT is immediately blocked / rerouted / locked o 0.25 seconds later, wait cursor is displayed o 3.00 seconds later, a dialog is displayed (unblocks EDT so user can "cancel" the task) o Task finishes and EDT is unblocked
(the times above are obviously arbitrary)
Please keep this in mind when working through your @Action functionality. Also, ideally one could setup some defaults for your Application, such that you don't have to specify @Action(block=...) each time, simply just @Action will do.
Any code for the above annotation yet? I for one could use it today. I'm sure others could too.
Again, great direction. Good stuff.
Adam
|
|
|
|
|
|
|
|
Re: JSR 296?
Posted:
Aug 10, 2006 4:50 PM
in response to: bobsledbob
|
|
|
That does look interesting, Im hoping that the developer isn't 'stuck' having to use the whole framework to get benefits out of it. If I write a 300 line miniprogram Id probably like to skip subclassing Application and just use some of the annotations(Of course I haven't seen how many annotations there are or what they do, but the idea seems attractive).
Im going to guess that the blocking dialog is an option to use, being able to use the wait cursor would be a good alternative.
I guess we will have to wait for the code to hit the streets, leouser
|
|
|
|
|
|
|
|
Re: JSR 296?
Posted:
Aug 10, 2006 5:25 PM
in response to: leouser
|
|
|
swing@javadesktop.org wrote: > That does look interesting, Im hoping that the developer isn't 'stuck' having to use the whole framework to get benefits out of it. If I write a 300 line miniprogram Id probably like to skip subclassing Application and just use some of the annotations(Of course I haven't seen how many annotations there are or what they do, but the idea seems attractive). > Being able to use aspects of the framework, without going "whole-hog", is becoming important for tools too. That said, I'd be happier if you were comfy using the framework for a 300 line mini-program too. Requiring developers to subclass Application seems like an iffy choice for a variety of reasons, we're considering alternatives that feel a little lighter.
> Im going to guess that the blocking dialog is an option to use, being able to use the wait cursor would be a good alternative. > Yes. > I guess we will have to wait for the code to hit the streets, > leouser > [Message sent by forum member 'leouser' (leouser)] > > http://forums.java.net/jive/thread.jspa?messageID=141925 >
|
|
|
|
|
|
|
|
300 line? How about 13 line?
Posted:
Aug 11, 2006 7:29 AM
in response to: Hans Muller
|
|
|
"That said, I'd be happier if you were comfy using the framework for a 300 line mini-program too."
A resonable design goal is that practically every Swing program would want to use the framework. Consider the following:
HelloWorldSwing http://java.sun.com/docs/books/tutorial/uiswing/learn/examples/HelloWorldSwing.java
The example programs on the Swing trail are a great resource and learning tool for Swing programmers. Most of them use the the same micro-framework shown in HelloWorldSwing. Let's call it the "Create and show GUI later in a frame" framework. The cut-and-paste form of code reuse is used to implement it.
- Curt
|
|
|
|
|
|
|
|
What about progress reporting?
Posted:
Aug 10, 2006 12:22 PM
in response to: leouser
|
|
|
As is often the case, "The Java Specialists' Newsletter" has some interesting discussion on the topic:
"Wait, Cursor, Wait!" http://www.javaspecialists.co.za/archive/newsletter.do?issue=065
Is this a common operation? No, but it should be. Developers are fantastically bad about anticipating what might be slow. The ideal user interface for a time-consuming event that blocks the user interface is both a busy cursor and a progress indicator. The typical user interface is an application that simply appears unresponsive.
Let me give some real examples. I used to used JEdit, quite a bit. I noticed it was joyously quick on my slow machine at home, and dog slow on my fast machine at work. I suspect the cause was the high-latency file system at work. You should never do any sort of IO on the EDT for this reason. Thus not only would I favor @EDT=only for most Swing methods, but @EDT=never for things that could take a long time. Most people realize that URL.openStream() might take a long time. Fewer people realize File.exists() could be slow.
I've often encountered cases where small test cases used by the developer were essentially instantaneous. Adding some sort of blocking indicator never occurred to them, because they didn't realize how much size could impact performance. When this happens, the usual solution is to optimize the problem away. The best solution is to add a busy cursor and progress indicator, too.
This is how I found out that the execution speed of Collection.retainAll() is O(x * y) and not O(x + y) as you would expect. Annotation could be useful here, too. "Collection.retainAll(Collection) implementations are not optimized" http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5028425
- Curt
|
|
|
|
|
|
|
|
Re: SwingUtilities.waitCursor(Runnable run)?
Posted:
Aug 10, 2006 1:44 PM
in response to: leouser
|
|
|
The wait cursor event queue is one of my pet peeves (http://www.javaspecialists.co.za/archive/newsletter.do?issue=065). It's such a horrible fix to a common problem. The approach you've suggested is pretty much the same, since you're using one thread, when the real heart of the problem is that this is a multi-threading issue.
I've only seen a few decent solutions to long-running GUI tasks.
One solution is to use a SwingWorkerVariant to do the multithreading. This is like the Java SwingWorker, but it also uses a blocking glass pane so the user cannot interact until the task completes. There are some bugs in the published implementation, such as it does not set the cursor on the glass pane, etc (http://www.javaworld.com/javaworld/jw-06-2003/jw-0606-swingworker.html).
Another solution is to use a third-party multithreading framework (http://foxtrot.sourceforge.net), (http://spin.sourceforge.net), etc. Again, a learning curve is required and you will have to fix the bugs yourself.
I would simply suggest anyone who reads this to learn more about the multi-threading solutions above.
|
|
|
|
|
|
|
|
Re: SwingUtilities.waitCursor(Runnable run)?
Posted:
Aug 10, 2006 2:14 PM
in response to: jvaudry
|
|
|
arguably, doing expensive stuff should be done on the EDT at all. But there are going to be situations where it may be unadvoidable. Take this thread: http://forums.java.net/jive/thread.jspa?threadID=17064&tstart=15
the developer wanted wait cursors because the computations that were taking place because of fireContentsAdded were taking so long. I don't believe you should be executing that method off the EDT, so here it seems inescapable. Arguably you could try and find the cause of the expense of this and cut it down, but what if that's not an option?
leouser
|
|
|
|
|
|
|
|
Re: SwingUtilities.waitCursor(Runnable run)?
Posted:
Aug 10, 2006 2:19 PM
in response to: jvaudry
|
|
|
> I would simply suggest anyone who reads this to learn > more about the multi-threading solutions above.
And then what? Do their own one-off implementations everytime?
Learning about the problem is different than having standard support (ideally in the core) which addresses the problem.
SwingWorker, Spin and Foxtrot don't address anything about blocking current events, displaying wait cursors or even jdialogs. They just simply, happily, move tasks off the EDT. It's then the responsibility of the developer to do something nice within their app, like show the wait cursor, block input events, etc. Rerouting the EDT and using the glasspane seems to be a complicated solution for something which should be common and easy to do.
Annotations might be a decent answer. A core library might be a better solution. Who knows. Having some sort of standard way to do this, whatever the mechanism, would sure be ideal (especially for your run-of-the-mill java desktop developer).
Adam
|
|
|
|
|
|
|
|
Re: SwingUtilities.waitCursor(Runnable run)?
Posted:
Aug 10, 2006 4:12 PM
in response to: bobsledbob
|
|
|
This is roughly what we do:
import java.awt.Cursor;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
/**
* An implementation of actionPerformed() is supplied, where it is broken into
* three phases: a non-busy preActionPerformed() phase, a busy
* busyActionPerformed() phase, and a non-busy postActionPerformed() phase.
*
* When busyActionPerformed() is invoked, it is wrapped by the setting the
* glass pane, the wait cursor, the status text on status bar, and the
* indeterminate progress bar on the status bar. Long running code in
* busyActionPerformed() should be wrapped in a foxtrot-like thread to allow
* AWT-Event thread processing. busyActionPerformed() is declared abstract,
* so it must be provided by any concrete subclasses.
* Both preActionPerformed() and postActionPerformed() may be overridden to do
* any pre-busy-action work or post-busy-action work.
*
* In preActionPerformed(), if you want to skip busyActionPerformed(), return
* PERFORM_POST_ACTION to jump right to postActionPerformed() or return
* PERFORM_NO_ACTION to just exit actionPerformed(). In busyActionPerformed(),
* if you want to skip postActionPerformed(), return PERFORM_NO_ACTION.
*
* If you don't want or need to use any of this fancy action stuff, you can
* override actionPerformed() like normal, although you must still supply a
* busyActionPerformed() (it doesn't have to do anything).
*/
abstract public class MyAbstractAction extends AbstractAction
{
/**
* used to tell actionPerformed() that busyActionPerformed() should be
* invoked after preActionPerformed()
*/
protected static final int PERFORM_BUSY_ACTION = 0;
/**
* used to tell actionPerformed() that postActionPerformed() should be
* invoked, either right after preActionPerformed() or busyActionPerformed()
*/
protected static final int PERFORM_POST_ACTION = 1;
/**
* used to tell actionPerformed() to not invoke either preActionPerformed()
* or busyActionPerformed()
*/
protected static final int PERFORM_NO_ACTION = 2;
/**
* must be implemented by concrete subclasses
* perform any busy action that requires the busy cursor, indeterminate
* progress bar, and action process status message to be displayed.
* If normal processing occurred, should return PERFORM_POST_ACTION, but
* if we don't want to have postActionPerformed() called, then it should
* return PERFORM_NO_ACTION.
* @param e is the action event to perform
* @return status about whether to call post action function
*/
abstract public int busyActionPerformed(ActionEvent e);
/**
* override to perform anything that needs to occur prior to the busy action.
* Should return PERFORM_BUSY_ACTION if busyActionPerformed() is needed.
*
* @param e is the action event to perform
* @return status about whether to call busyActionPerformed(),
* postActionPerformed(), or neither
*/
public int preActionPerformed(ActionEvent e)
{
// override to do something interesting
return PERFORM_BUSY_ACTION;
} // preActionPerformed()
/**
* override to perform anything that needs to occur after the busy action
* @param e is the action event to perform
*/
public void postActionPerformed(ActionEvent e)
{
// override to do something interesting
} // postActionPerformed()
/**
* handle the action event. First, perform any pre-action. Then set the
* UI up for doing a busy thing. Then perform the busy action. Then
* restore UI to non-busy state. Then perform any post-action.
* @param e is the action event to be processed
*/
public void actionPerformed(ActionEvent e)
{
UserInterface ui = MyApplication.getInstance().getUserInterface();
// prevent action if user events are already disabled
if (!ui.areUserEventsEnabled())
{
return;
}
// perform any pre-action
int status = preActionPerformed(e);
// setup the busy action
if (status == PERFORM_BUSY_ACTION)
{
StatusBar statusBar = ui.getStatusBar();
// set glass pane
ui.disableUserEvents();
// start indeterminate progress bar on status bar
statusBar.startProgressBar();
// set the action short description text on the status bar
statusBar.setActionText((String)getValue(Action.SHORT_DESCRIPTION));
// set wait cursor
Cursor oldCursor = ui.getCursor();
ui.setCursor(ui.getWaitCursor());
// perform main busy action
status = busyActionPerformed(e);
// return ui, et al, to default state
ui.setCursor(oldCursor);
statusBar.setActionText("Ready");
statusBar.stopProgressBar();
ui.enableUserEvents();
}
// perform any post-action
if (status == PERFORM_POST_ACTION)
{
postActionPerformed(e);
}
} // actionPerformed()
} // class MyAbstractAction
Classes MyApplication, UserInterface, StatusBar are left as an exercise to the reader.
Yes, I know, there is no try/catch/finally stuff there.
|
|
|
|
|