jcombobox as cell editor java.awt.IllegalComponent

2020-04-08 01:29发布

问题:

I am using a custom JComboBox as a cell editor in a JTable. When the users gets to the cell using keyboard controls it tries to open the popup. This causes the following error:

java.awt.IllegalComponentStateException: component must be showing on the screen to determine its location
    at java.awt.Component.getLocationOnScreen_NoTreeLock(Component.java:1964)
    at java.awt.Component.getLocationOnScreen(Component.java:1938)
    at javax.swing.JPopupMenu.show(JPopupMenu.java:887)
    at javax.swing.plaf.basic.BasicComboPopup.show(BasicComboPopup.java:191)
    at javax.swing.plaf.basic.BasicComboBoxUI.setPopupVisible(BasicComboBoxUI.java:859)
    at javax.swing.JComboBox.setPopupVisible(JComboBox.java:796)

I have seen some articles stating that this is a known problem and the solution is to set:

    comboBox.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);

This however does not help. What is this supposed to do anyways?

All the threads and articles I have read about this are very vague about the nature of the problem.

Does anyone have any insight into the nature of why this problem occurs? My combobox is very custom so it would help to understand the basis of the problem so I can fix the code.

This is triggered on a focus gained event on the combo box which is captured and call setPopupVisible(true);

 public void focusGained(java.awt.event.FocusEvent e)
 {
        //if focus is gained then make sure we show the popup if it is suppose to be visible
            setPopupVisible(true);
        //and highlight the selected text if any
        comboTextEditor.setCaretPosition(comboTextEditor.getText().length());
        comboTextEditor.moveCaretPosition(0);
 }

By the way I get the same results in Java 1.7_40 as Java 1.6_45

Full Stack Trace:

Exception in thread "AWT-EventQueue-1" java.awt.IllegalComponentStateException: component must be showing on the screen to determine its location
    at java.awt.Component.getLocationOnScreen_NoTreeLock(Component.java:1964)
    at java.awt.Component.getLocationOnScreen(Component.java:1938)
    at javax.swing.JPopupMenu.show(JPopupMenu.java:887)
    at javax.swing.plaf.basic.BasicComboPopup.show(BasicComboPopup.java:191)
    at javax.swing.plaf.basic.BasicComboBoxUI.setPopupVisible(BasicComboBoxUI.java:859)
    at javax.swing.JComboBox.setPopupVisible(JComboBox.java:796)
    at com.mbs.generic.view.swing.combobox.AutoCompleteComboBox$1.focusGained(AutoCompleteComboBox.java:185)
    at java.awt.AWTEventMulticaster.focusGained(AWTEventMulticaster.java:203)
    at java.awt.Component.processFocusEvent(Component.java:6179)
    at java.awt.Component.processEvent(Component.java:6046)
    at java.awt.Container.processEvent(Container.java:2039)
    at java.awt.Component.dispatchEventImpl(Component.java:4653)
    at java.awt.Container.dispatchEventImpl(Container.java:2097)
    at java.awt.Component.dispatchEvent(Component.java:4481)
    at java.awt.KeyboardFocusManager.redispatchEvent(KeyboardFocusManager.java:1848)
    at java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(DefaultKeyboardFocusManager.java:901)
    at java.awt.DefaultKeyboardFocusManager.dispatchEvent(DefaultKeyboardFocusManager.java:513)
    at java.awt.Component.dispatchEventImpl(Component.java:4525)
    at java.awt.Container.dispatchEventImpl(Container.java:2097)
    at java.awt.Component.dispatchEvent(Component.java:4481)
    at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:648)
    at java.awt.EventQueue.access$000(EventQueue.java:84)
    at java.awt.EventQueue$1.run(EventQueue.java:607)
    at java.awt.EventQueue$1.run(EventQueue.java:605)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:87)
    at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:98)
    at java.awt.EventQueue$2.run(EventQueue.java:621)
    at java.awt.EventQueue$2.run(EventQueue.java:619)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:87)
    at java.awt.EventQueue.dispatchEvent(EventQueue.java:618)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
    at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
    at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)

thanks

回答1:

First, let me explain what comboBox.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE); does. Normally, hoovering the mouse over an item or pressing the arrow keys on the keyboard will cause the selection of items on the JComboBox immediately. Since selection events from the JComboBox will cause the cell edit process to stop, this behavior is not suitable for table cells. So when setting this special client property items will be shown selected inside the popup list but not set on the JComboBox yet. Only committed items (via click or Enter key) will change the selected item on the JComboBox causing the end of the edit then. At least, this holds for BasicLookAndFeel and its derivatives.

The problem you have is completely different. As the exception message and the stack trace clearly say, the look and feel tries to open the JPopupMenu associated with the JComboBox (as you requested) but it can’t determine the on-screen location for the popup menu because your JComboBox is not shot showing on the screen. The reason why it wants the location of the JComboBox is that it opens the new window relative to the JComboBox.

The remaining question is why you received a focusGained from a JComboBox that is not showing on the screen (or why you thought you did).



回答2:

Popups like the pulldown in a JComboBox tend to have edge cases for event handling order because they are not geometrically nested inside their ancestors in the component hierarchy. In your case, you are causing the box's focus handler to show the pulldown. To do this it needs the box to be already located on the screen, but it's not.

The solution is almost certainly to defer showing the pulldown until all the events that will make the box visible have been processed. I had a similar (though not exactly the same) problem and was able to resolve it in this manner. Happily there is a Swing utility function that does the trick. Try wrapping the body of the focus gained handler in invokeLater and a Runnable:

void focusGained() {
  SwingUtilities.invokeLater(new Runnable() { 
    ... focus gained body including show of pulldown menu here ... 
  });
}

The invokeLater puts a new message containing the Runnableat the end of the queue, i.e. after all the existing ones. That Runnable is executed only when the message makes it to the head, after all those other messages have been processed. This is exactly what you want.



回答3:

I second (third? fourth?) everyone asking for a pared-down example of a table using your custom combobox any maybe a bit of the code from the combobox itself, but just to take a stab at it anyway...have you tried making a customized version of EditorDelegate to go with your other custom code and moving the code for showing the popup from focusGained() into your delegate's startCellEditing() method?



回答4:

If you embed your instruction inside a try .. catch instruction , your program will run without problems:

SwingUtilities.invokeLater(new Runnable(){

                        public void run()
                        {
                        try {
                        tInput.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
                        tInput.showPopup();
                        }
                        catch   (IllegalComponentStateException e) {
                                return;
                                }

                          }
                 });