Java Solaris Communities Sun Store Join SDN My Profile Why Join?
 
Bug Database
Bug Detail
Quick Lists
Top 25 Bugs
Top 25 RFE's
Recently Closed Bugs
Printable Page Printable Page


Bug Database
Bug ID: 4372119
Votes 1
Synopsis Disappearing of busy cursor on JDK 1.3
Category java:classes_awt
Reported Against 1.3 , 1.3.1 , mantis-rc , merlin-beta , merlin-beta2
Release Fixed 1.3.1_10, 1.4.1_07(Bug ID:2120660) , 1.4.2_04(Bug ID:2120661) , 1.5(tiger-b26) (Bug ID:2120662)
State 10-Fix Delivered, Verified, bug
Priority: 2-High
Related Bugs 4475859 , 4320939 , 4533002
Submit Date 19-SEP-2000
Description
System Information
==================
Product and Version : Interim JDK v1.3RC1 for UOB Bank, Singapore
Hardware Platform :  customer  Pentium II PC
OS Version : Window NT4.0 SP 5

Problem description 
===================
In our application, we turn on the busy cursor when the task takes some
time to complete. In the current version of JDK1.3 that we are using (
Note : a special version with 2 patches given by Sun Support), the busy
cursor will disappear (i.e. return to normal) when user starts to move
the mouse around, especially when the mouse is outside of the
application frame. We observed that this seems to happen more frequently
when the application is doing CPU intensive tasks.

If this frame is covered by another window and bgought to front again, the busy cursor always disappears after the frame is restored. Only the cursor part is not restored correctly. 

Some people suggested to put the cursor setting thread out of the AWT thread. I have tested it but it does not solve the problem either.

Source Code
===========

import java.awt.*;
import java.awt.event.*;
import java.util.Date;

public class TestCursor
{
    static public void main(String args[]) 
    {
       TestCursor app = new TestCursor();
       GUI gui = new GUI(app);
    }
}

class RunHandler implements ActionListener 
{
    TestCursor app;
    GUI gui;

    public RunHandler(TestCursor app, GUI gui) 
    {
        this.app = app;
        this.gui = gui;
    }

    public void actionPerformed(ActionEvent ae) 
    {
        gui.setCursor( Cursor.getPredefinedCursor( Cursor.WAIT_CURSOR ) );
        gui.setResult("Running...");
        int i=0;
        while (i<1000000)
        {
          Date d= new Date();
          System.out.println(i + ": " + d.toString());
          i++;
        }
        gui.setResult("Finished.");
        gui.setCursor( Cursor.getPredefinedCursor( Cursor.HAND_CURSOR ) );
    }
}


class AboutBox extends Frame
{

    class ShutdownAdapter extends WindowAdapter
    {
        public void windowClosing(WindowEvent e)
        { dispose(); }
    }

    public AboutBox()
    {
        super("About TestCursor");
        setLayout(new BorderLayout());
        setLocation(325,300);
        setSize(240,80);
        setResizable(false);
        TextArea text = new TextArea ("Testing Cursor problem.\n09 Sep 2000");
        text.setEditable(false);
        add("North",text);
        setVisible(true);
        addWindowListener(new ShutdownAdapter());
    }

}

class MenuHandler implements ActionListener 
{
    TestCursor app;
    GUI gui;

    public MenuHandler(TestCursor app, GUI gui) 
    {
        this.app = app;
        this.gui = gui;
    }

    public void actionPerformed(ActionEvent ae) 
    {
        String sCommand = ae.getActionCommand();
        if ("Exit".equals(sCommand))
        {
            gui.dispose();
            System.exit(0);
        } 
        else if ("About TestCursor".equals(sCommand))
        {
            AboutBox about = new AboutBox();
            about.toFront();
        }
    }
}

class GUI extends Frame
{

    TestCursor app;
    Panel statusPanel;
    TextField statusText;

    public void setResult(String value) 
    { 
        this.statusText = new TextField(value,20); 
        this.statusText.setEditable(false);
        this.statusPanel.removeAll();
        this.statusPanel.add(statusText);
        pack();
        show();
    }

    class ShutdownAdapter extends WindowAdapter
    {
        public void windowClosing(WindowEvent e)
        { System.exit(0); }
    }


    public GUI(TestCursor app) 
    {

        super ("Test Cursor");
        this.app = app;

        addWindowListener(new ShutdownAdapter());

        MenuHandler mh = new MenuHandler(app , this);
        MenuBar mBar = new MenuBar();
        setMenuBar(mBar);
        Menu menu = new Menu("File");
        mBar.add(menu);
        MenuItem menuItem = new MenuItem("Exit");
        menu.add(menuItem);
        menuItem.addActionListener(mh);

        menu = new Menu("Help");
        mBar.add(menu);
        menuItem = new MenuItem("About TestCursor");
        menu.add(menuItem);
        menuItem.addActionListener(mh);

        setLayout(new BorderLayout());
        Panel testPanel = new Panel();
        Button b;
        testPanel.setLayout(new GridLayout(1,1));
        
        statusPanel = new Panel();
        statusPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
        statusText=new TextField("",20);
        statusText.setEditable(false);
        statusPanel.add(statusText);
        add("North",statusPanel);

        RunHandler rh = new RunHandler(app, this);
        testPanel.add(b = new Button("Press to run"));  b.addActionListener(rh);
        add("Center",testPanel);

        pack();
        setLocation(300,300);
        show();
    }
}
Work Around
Move long processing to a seperate thread...

class LongProcess extends Window implements Runnable
{
    Label   statusText;

    LongProcess(Frame frame)
    {
        super(frame);
        setLayout(new FlowLayout());
        setBounds(frame.getBounds());
        statusText = new Label("Idle");
        add(statusText);
        setVisible(true);
    }

    public void run()
    {
        setCursor( Cursor.getPredefinedCursor( Cursor.WAIT_CURSOR ) );
        statusText.setText("Busy");
        int i = 0;
        while (i < 20000)
        {
          Date d= new Date();
          System.out.println(i + ": " + d.toString());
          i++;
        }
        setCursor( Cursor.getPredefinedCursor( Cursor.HAND_CURSOR ) );
        statusText.setText("Idle");
    }
}


class RunHandler implements ActionListener 
{
    TestCursor app;
    GUI gui;
    LongProcess process;

    public RunHandler(TestCursor app, GUI gui) 
    {
        this.app = app;
        this.gui = gui;
        process = null;
    }

    public void actionPerformed(ActionEvent ae) 
    {
        if (process == null)
        {
            process = new LongProcess(gui);
            new Thread(process).start();
        }
    }
}

  xxxxx@xxxxx   2000-10-12
Evaluation
This is an escalation.    xxxxx@xxxxx   has agreed to work with the escalation 
engineer on this.  

My first thought is that the problem is that the test case has a bunch of 
time-consuming work done on the event dispatch thread, and the 
GlobalCursorManager uses polling.  I assume that the cursor change cannot 
be processed until the work done on the event dispatch thread is finished.  
The first thing I would try would be to move this work out of the 
actionPerformed() method - start another thread to do it.  That way the event dispatch thread won't be blocked, and hopefully, the cursor change can be 
processed.   

I'll commit this to Merlin just in case there is some work that we want 
to put into Merlin wrt this issue.  

  xxxxx@xxxxx   2000-09-29


Win32 only
  xxxxx@xxxxx   2000-10-02

This problem is occurring because the cursor code runs in the same thread as the event handler.  In otherwords the OS queries Win32 for what cursor it needs to use for a given window, but since the application is busy in the lengthy processing the native peer code is unable to execute letting Win32 know the correct cursor.

Per the Java tutorial...

Important:  The code in event handlers should execute very quickly! Otherwise, your program's perceived performance will be poor. If you need to perform some lengthy operation as the result of an event, do it by starting up another thread (or somehow sending a request to another thread) to perform the operation. 

So the real solution to this problem is to put the lengthy processing code in a seperate thread.

  xxxxx@xxxxx   2000-10-03

See all 4533002.  Same bug.
  xxxxx@xxxxx   2002-01-15


This bug got reopenend for an escalation. The problem is same as described
by rray as above. The testcase does a lengthy time consuming process in the
event dispatch thread. Hence, further events to change the cursor gets blocked
till the event dispatch thread is ready to process the mouse enter events in the event queue. 
The problem is corrected by handling mouse enter events at the native
level and updating the cursor at the native level instead of posting an event to
the already busy event dispatch thread. With the fix, now the cursor change happens in the ToolKit thread.

  xxxxx@xxxxx   2003-07-24
Comments
  
  Include a link with my name & email   

Submitted On 27-SEP-2000
gwanner
This problem also occurs with a non patched version 1.3.0 
of the Java 2 JDK, Standard Edition under Windows NT SP4!


Submitted On 23-OCT-2000
Dan Estepp
Is this a fundamental change from 1.2.x to 1.3?  This works 
fine in 1.2.2.

We were hoping for an easy upgrade path, but this keeps us 
at 1.2.2 and all its problems.


Submitted On 10-JAN-2001
koning
I agree on Dan Estepp,
This problem was introduced by upgrading to 1.3
It indeed happens by doing heavy processing in the awt 
thread (but that's an easy way to block user input).
Is there an easy way to simulate the 1.2.2 behaviour?


Submitted On 11-MAY-2001
subc
Why is this closed? Aren't you supposed to be backward
compatible with jdk1.2? Are you telling all GUI developers
to do their callback in a different thread? Are you trying
to jam in a new paradigm down our throat? Fix it so that
it behaves like all other Windowing systems!!! 
Unbelievable!!!



Submitted On 08-JUL-2002
La Balafre
Did you ever read the developer comments? It is so 
complicated to set a simple busy cursor in Java.


Submitted On 28-JAN-2003
jmorehouse
So if code in the event handlers must execute "very quickly," 
what exactly is the point of the wait cursor? So it can be set 
while a worker thread runs and all input is blocked on the UI? 
C'mon... file opens are generally handled in UI events and 
sometimes occur over slow network connections. Should it 
really be necessary to move this type of task to a separate 
thread just so the wait cursor will stick? This should be fixed.


Submitted On 13-MAR-2003
jumu
This definitly has to be fixed. The 'workaround' to use another thread is a bad joke. Nothing more.


Submitted On 21-JUL-2003
parubok
It is really very disappointing that such simple functionality as 
displaying busy cursor requires such a non-trivial solution as 
thread programming. Please fix this bug ASAP!



PLEASE NOTE: JDK6 is formerly known as Project Mustang