Dec 29, 2010

terminateEditOnFocusLost making problems for table in JDesktopPane–workaround for Java 6

 

There is a minor bug affecting JTable behaviour when used in connection with JInternalFrame and setting property “terminateEditOnFocusLost”. This bug is resolved in Java 7. For those who need the solution now, I have found a workaround.

The original bug description: (see http://bugs.sun.com/view_bug.do?bug_id=6505027 for details)

In 6.0, first I focused the TextField, and then I tried to focus on the second columns of the table which contain JComboBox. I was not able to perform this action on the second column.

In order to focus it first I have to focus the first columns and only the the table is focused then I can go to the second columns. This implies that the JComboBox can't get focus in the JTable when we using the command row:
Jtable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);.

It is a bug in JInternalFrame, fixed in Java 7. But for those who need a workaround and can't wait for Java 7, I have something. It turns out you can override JInternalFrame so that it uses fixed variant of the code (the same that's gonna be in Java 7). If you derive your internal frame from this class, the JTable problem will disappear. Here we go:

import java.awt.KeyboardFocusManager;
import java.awt.Window;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JInternalFrame;
import javax.swing.SwingUtilities;
/*
 * Copies the fix for bug 6505027 from Java 7
 * http://hg.openjdk.java.net/jdk7/jdk7/jdk/rev/f727cac13697
 * Some support code (private) needed to be copied as well
 */
public class OKInternalFrame extends JInternalFrame {
    public OKInternalFrame() {
        addPropertyChangeListenerIfNecessary();
    }
    private java.awt.Component lastFocusOwner;
    private void setLastFocusOwner(java.awt.Component lastFocusOwner) {
        this.lastFocusOwner = lastFocusOwner;
    }
    private static boolean initializedFocusPropertyChangeListener = false;
    private static void addPropertyChangeListenerIfNecessary() {
        if (!initializedFocusPropertyChangeListener) {
            PropertyChangeListener focusListener =
                    new FocusPropertyChangeListener();
            initializedFocusPropertyChangeListener = true;
            KeyboardFocusManager.getCurrentKeyboardFocusManager().
                    addPropertyChangeListener(focusListener);
        }
    }
    private static class FocusPropertyChangeListener implements
            PropertyChangeListener {
        @Override
        public void propertyChange(PropertyChangeEvent e) {
            if (e.getPropertyName().equals("permanentFocusOwner")) {
                updateLastFocusOwner((java.awt.Component) e.getNewValue());
            }
        }
    }
    private static void updateLastFocusOwner(java.awt.Component component) {
        if (component != null) {
            java.awt.Component parent = component;
            while (parent != null && !(parent instanceof Window)) {
                if (parent instanceof OKInternalFrame) {
                    // Update lastFocusOwner for parent.
                    ((OKInternalFrame) parent).setLastFocusOwner(component);
                }
                parent = parent.getParent();
            }
        }
    }
    @Override
    public void restoreSubcomponentFocus() {
        if (isIcon()) {
            super.restoreSubcomponentFocus(); //delegated to super implementation, because it's correct there
        } else {
            java.awt.Component component = KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner();
            if ((component == null) || !SwingUtilities.isDescendingFrom(component, this)) {
                // FocusPropertyChangeListener will eventually update
                // lastFocusOwner. As focus requests are asynchronous
                // lastFocusOwner may be accessed before it has been correctly
                // updated. To avoid any problems, lastFocusOwner is immediately
                // set, assuming the request will succeed.
                setLastFocusOwner(getMostRecentFocusOwner());
                if (lastFocusOwner == null) {
                    // Make sure focus is restored somewhere, so that
                    // we don't leave a focused component in another frame while
                    // this frame is selected.
                    setLastFocusOwner(getContentPane());
                }
                if (!lastFocusOwner.hasFocus()) {
                    lastFocusOwner.requestFocus();
                }
            }
        }
    }
}

5 comments:

  1. This workaround solved problems for us, thanks a lot. But we have one more.

    If one of the cell is checkbox then you can not click on it. You have to click on a textfield that is on the other column of the table. After you do that, you would be able to click the checkbox.

    Do you have any idea how to solve the problem?

    ReplyDelete
  2. This seems to be related to a problem I am working on - I have trouble with ComboBoxes in the table. However, I have no solution now. If you find anything, please let me now.

    ReplyDelete
  3. As I debugged the method restoreSubcomponentFocus(), i saw that, if you stop that in that method for a while, it works fine. And I have enclosed the else statement in a SwingUtilities.invokeLater, it works fine now for the checkboxes.

    ReplyDelete
  4. I've been having this issue for a while on a project I'm working on. It took me a while to figure out that JInternalFrame was causing this bug. This workaround fixed it for me no problems, I should note that I did not have to do anything more to have checkboxes work properly inside cells with boolean values using the default renderers and editors provided by JTable.

    Many thanks :).

    Robert V.

    ReplyDelete
  5. Thank you very much for your solution!

    I have been looking for one for a long time since I never thought, the problem could be in JInternalFrame. Now my application works perfectly.

    Good done and many thank :)

    Pedro RA.
    Spain.

    ReplyDelete