United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
JDK-6607170 : Focus not set by requestFocus

Details
Type:
Bug
Submit Date:
2007-09-20
Status:
Closed
Updated Date:
2011-03-07
Project Name:
JDK
Resolved Date:
2011-03-07
Component:
client-libs
OS:
windows_vista
Sub-Component:
java.awt
CPU:
x86
Priority:
P3
Resolution:
Fixed
Affected Versions:
7
Fixed Versions:

Related Reports
Relates:
Relates:

Sub Tasks

Description
FULL PRODUCT VERSION :
1.7.0-ea
JRE build 1.7.0-ea-b20
HotSpot build 11.0-b06


ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.0.6000]

A DESCRIPTION OF THE PROBLEM :
In 1.7 build 15, the requestFocus method resulted in the cursor visible in a TextField.  As of build 20, this fails in some circumstances, illustrated here.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1.  Run the working applet at http://www.segal.org/java/focus_panel/ or use the source code provided there or included here.  The applet switches between two panels that have TextFields for which requestFocus is called.
2.  Click the "Change panel" button.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The cursor should be visible in the TextField, as a result of calling requestFocus.  This works in 1.7 build 15.
ACTUAL -
The cursor is not visible in the TextField in 1.7 build 20.  However, minimizing the Frame and restoring it results in the cursor appearing.

I have not tested builds 16, 17, 18 or 19, but since many focus changes were made  for build 17 it seems likely that the problem began with that build.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.applet.*;
import java.awt.*;
import java.awt.event.*;

public class focus_panel extends Applet implements ActionListener, WindowListener {

Frame frame;
BoxPanel boxPanelA, boxPanelB;
boolean panelAShowing;

public void init()
{
	frame = new Frame("A frame");
	frame.setLayout(new GridBagLayout());
	frame.setBounds(100, 100, 600, 300);
	boxPanelA = new BoxPanel("A");
	boxPanelB = new BoxPanel("B");
	boxPanelA.button.addActionListener(this);
	boxPanelB.button.addActionListener(this);
	frame.validate();
	frame.show();
	frame.addWindowListener(this);
	displayProperPanel();
}

void displayProperPanel()
{
	if (panelAShowing)
	{
		frame.remove(boxPanelA);
		constrain(frame, boxPanelB, 0, 0, 1, 1, GridBagConstraints.WEST, 0, 0, 0, 0,
			GridBagConstraints.NONE, 0, 0);
		panelAShowing = false;
	}
	else
	{
		frame.remove(boxPanelB);
		constrain(frame, boxPanelA, 0, 0, 1, 1, GridBagConstraints.WEST, 0, 0, 0, 0,
			GridBagConstraints.NONE, 0, 0);
		panelAShowing = true;
	}
	frame.invalidate();
	frame.validate();
	if (panelAShowing) boxPanelA.panelTextField.requestFocus();
	else boxPanelB.panelTextField.requestFocus();
}

static final void constrain(Container container, Component component, int grid_x, int grid_y,
		int grid_width, int grid_height, int anchor, int topPad, int leftPad, int bottomPad,
		int rightPad, int fill, double weightx, double weighty)
{
	GridBagConstraints c = new GridBagConstraints();
	c.gridx = grid_x;
	c.gridy = grid_y;
	c.gridwidth = grid_width;
	c.gridheight = grid_height;
	c.anchor = anchor;
	c.insets.top   = topPad;
	c.insets.left  = leftPad;
	c.insets.bottom= bottomPad;
	c.insets.right = rightPad;
	c.fill = fill;
	c.weightx = weightx;
	c.weighty = weighty;
	((GridBagLayout)container.getLayout()).setConstraints(component, c);
	container.add(component);
}

public void windowOpened(WindowEvent we){}

public void windowClosed(WindowEvent we){}

public void windowIconified(WindowEvent we){}

public void windowDeiconified(WindowEvent we){}

public void windowActivated(WindowEvent we){}

public void windowDeactivated(WindowEvent we){}

public void windowClosing(WindowEvent we)
{
	if (we.getSource() == frame)
	{
		frame.setVisible(false);
		frame.dispose();
	}
}

public void paint(Graphics g)
{
	g.drawString("A frame should pop up", 10 , 20);
}

public void actionPerformed(ActionEvent ae)
{
	if (ae.getSource() instanceof Button)
	{
		displayProperPanel();
	}
}
}  // END OF Class focus_panel



class BoxPanel extends Panel  {  // panel with a box around it
int width = 0;
int height = 0;
TextField panelTextField;
Button button;

BoxPanel(String s)
{
	setLayout(new GridBagLayout());
	Label panelLabel = new Label(s);
	focus_panel.constrain(this, panelLabel, 0, 0, 1, 1, GridBagConstraints.WEST, 20, 20, 20, 20,
		GridBagConstraints.VERTICAL, 0, 1);
	panelTextField = new TextField(5);
	focus_panel.constrain(this, panelTextField, 0, 1, 1, 1, GridBagConstraints.WEST, 20, 20, 20, 20,
		GridBagConstraints.VERTICAL, 0, 1);
	button = new Button("Change panel");
	focus_panel.constrain(this, button, 0, 2, 1, 1, GridBagConstraints.WEST, 0, 20, 0, 0,
		GridBagConstraints.NONE, 0, 0);
}

public void addNotify()
{
	super.addNotify();
	repaint();
}

public final void paint(Graphics g)
{
	width = getSize().width;
	height = getSize().height;
	g.setColor(Color.black);
	g.drawRect(0, 0, width -1, height -1); // out
	g.drawRect(1, 1, width -3, height -3); // in
}
}  // END OF Class BoxPanel

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
The user has to select the TextField manually, which represents a loss of previous functionality of programs and is considered by users to be inconsiderate.

Release Regression From : 7
The above release value was the last known release where this 
bug was not reproducible. Since then there has been a regression.

                                    

Comments
SUGGESTED FIX

The webrev: http://sa.sfbay.sun.com/projects/awt_data/7/6607170.0
                                     
2008-04-08
EVALUATION

It appeared to be more complicated. The above observations are not complete.
So, from the beginning. We have PA (the panel A), TFA (the PA's text field),
BA (the PA's button) and the same with "B" postfix. The frame is shown,
the PA is shown. Tabbing to BA. Pressing the space key. Now we have:

1. PA is in removeNotify() method. It initiates removing of all the childs.

2. BA is in removeNotify(). As it's the focus owner, focus is auto transfered
   (requested) to the next component - TFA.

  2.1. The request is queued. TFA gets WM_SETFOCUS, FOCUS_GAINED is posted.

3. TFA is in removeNotify(). The hide() method is called for the TFA's peer.

  3.1. ::ShowWindow(m_hwnd, SW_HIDE) is called for the TFA.
       It triggers native focus transfer from TFA to its parent - PA.
  3.2. The request is not queued (as it's the native one),
       PA gets WM_SETFOCUS, FOCUS_GAINED is posted.

4. PA itself is being removed.

  4.1. Also, native focus is transfered to its parent - the frame.
  4.2. The frame gets WM_SETFOCUS, FOCUS_GAINED is posted.

5. The testcase adds another panel PB and requests focus to TFB.

  5.1. The request is queued. TFB gets WM_SETFOCUS, FOCUS_GAINED is posted.

6. FOCUS_GAINED (see 2.1) is being dispatched for TFA (the appropriate request is
   removed from the queue).
   As it's no longer visible, the focus-restore mechanism is about to start
   (introduced with the fix for 4726458). However, KFM.isAutoFocusTransferEnabled()
   returns false as there're current pending requests (see 5.1) and the
   focus-restore is not started.

7. FOCUS_GAINED (see 3.2) is being dispatched for PA.
   This event doesn't match to any requests queued (see 3.2).
   So, KFM.retargetUnexpectedFocusEvent() is called and it removes the
   first request from the queue (for TFB). Then, DKFM dispatches the event
   and now (as PA is also not visible) it starts focus-restore, because
   KFM.isAutoFocusTransferEnabled() returns true (no more requests queued).
   The restoreFocus() method initiates CLEAR_MOST_RECENT_FOCUS_OWNER
   (see the previous post for the reason).

  7.1. TFB gets WM_KILLFOCUS.
-----------------------------

Thus, there're several causes of the bug:

1. Imperfect machanism of auto-focus-transfer when removing a parent
   containing the focus owner.
2. Fix for 4726458.
3. Unexpected native focus messages (see 3.1)
                                     
2007-10-26
EVALUATION

The problem is as follows. The panel A is being removed from the frame. At first,
all its child components are removed. When its child button is being removed, the auto-focus-transfer procedure is initiated (as the button is the current focus owner).
The text field, another child of the panel A, is taken as the focus transfer candidate
(it's not yet removed). Focus is requested to the text field. Right after that, in the testcase, the panel B is added to the frame and focus is requested to its text field.
Thus, we have two focus requests pending. When the first request, for the removed
text field, is being dispatched bu the DKFM it detects that the component is not
showing and initiates focus-restore process. This has been introduced with the fix
for 4726458 in JDK 7.0 b17. What happens next is as follows. The DKFM passes
newFocusedWindow object to the restoreFocus(..) method. The newFocusedWindow is
calculated in this way:

Window newFocusedWindow = Component.getContainingWindow(newFocusOwner);

where the newFocusOwner is the _removed_ text field. So the newFocusedWindow is
set to null. Then the following method is executed:

    private void restoreFocus(FocusEvent fe, Window newFocusedWindow) {
        Component realOppositeComponent = this.realOppositeComponentWR.get();
        Component vetoedComponent = fe.getComponent();

        if (newFocusedWindow != null && restoreFocus(newFocusedWindow, 
                                                     vetoedComponent, false))
        {
        } else if (realOppositeComponent != null &&
                   doRestoreFocus(realOppositeComponent, vetoedComponent, false)) {
        } else if (fe.getOppositeComponent() != null &&
                   doRestoreFocus(fe.getOppositeComponent(), vetoedComponent, false))          {
        } else {
            clearGlobalFocusOwner();
        }
    }

and ends up by the last clearGlobalFocusOwner() call.
This overrides the result of the focus request to the text field in the panel B.
                                     
2007-10-25
EVALUATION

The problem can be easily reproduced using appletviewer, since 7.0-b17.
                                     
2007-09-27
WORK AROUND

Wrap 'requestFocus' calls with EventQueue/SwingUtilities.invokeLater().
                                     
2007-09-27



Hardware and Software, Engineered to Work Together