United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
JDK-4372119 : Disappearing of busy cursor on JDK 1.3

Details
Type:
Bug
Submit Date:
2000-09-19
Status:
Closed
Updated Date:
2003-12-19
Project Name:
JDK
Resolved Date:
2003-09-15
Component:
client-libs
OS:
windows_nt,windows_xp,windows_2000
Sub-Component:
java.awt
CPU:
x86
Priority:
P2
Resolution:
Fixed
Affected Versions:
1.3.0,1.3.1,1.4.0,1.4.2
Fixed Versions:
1.3.1_10 (10)

Related Reports
Backport:
Backport:
Backport:
Duplicate:
Duplicate:
Relates:

Sub Tasks

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
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
                                     
2000-10-12
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
                                     
2003-07-24
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


                                     
2004-10-02



Hardware and Software, Engineered to Work Together