JDK-4372119 : Disappearing of busy cursor on JDK 1.3
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.awt
  • Affected Version: 1.3.0,1.3.1,1.4.0,1.4.2
  • Priority: P2
  • Status: Closed
  • Resolution: Fixed
  • OS: windows_nt,windows_2000,windows_xp
  • CPU: x86
  • Submitted: 2000-09-19
  • Updated: 2003-12-19
  • Resolved: 2003-09-15
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other Other Other
1.3.1_10 10Fixed 1.4.1_07Fixed 1.4.2_04Fixed
Related Reports
Duplicate :  
Duplicate :  
Relates :  
Description
System Information
==================
Product and Version : Interim JDK v1.3RC1 for UOB Bank, Singapore
Hardware Platform : Intel 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();
    }
}

Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: 1.3.1_10 1.4.1_07 1.4.2_04 generic tiger-beta FIXED IN: 1.3.1_10 1.4.1_07 1.4.2_04 tiger-beta INTEGRATED IN: 1.3.1_10 1.4.1_07 1.4.2_04 tiger-b26 tiger-beta VERIFIED IN: 1.3.1_10 1.4.1_07 1.4.2_04
02-10-2004

EVALUATION This is an escalation. rray@eng 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. eric.hawkes@eng 2000-09-29 Win32 only richard.ray@eng 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. richard.ray@eng 2000-10-03 See all 4533002. Same bug. ###@###.### 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. ###@###.### 2003-07-24
24-07-2003

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(); } } } richard.ray@eng 2000-10-12
12-10-2000