JDK-4144505 : Compound components don't behave properly
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version:
    1.0b,3.0,1.1.5,1.1.6,1.1.8,1.2.0,1.2.1,1.2.2,1.3.0,1.4.0 1.0b,3.0,1.1.5,1.1.6,1.1.8,1.2.0,1.2.1,1.2.2,1.3.0,1.4.0
  • Priority: P4
  • Status: Closed
  • Resolution: Won't Fix
  • OS:
    generic,solaris_2.5.1,solaris_2.6,windows_95,windows_98,windows_nt,windows_2000 generic,solaris_2.5.1,solaris_2.6,windows_95,windows_98,windows_nt,windows_2000
  • CPU: generic,x86,sparc
  • Submitted: 1998-06-01
  • Updated: 2000-03-10
  • Resolved: 2000-03-10
Related Reports
Duplicate :  
Duplicate :  
Duplicate :  
Duplicate :  
Duplicate :  
Duplicate :  
Duplicate :  
Duplicate :  
Duplicate :  
Duplicate :  
Duplicate :  
Duplicate :  
Duplicate :  
Duplicate :  
Duplicate :  
Relates :  
Description
Swing components that are composed of other components behave inconsistently compared to simple Swing components.  An example of a compound component is JComboBox.  JComboBox has an edit field and an arrow button contained within itself.  The edit field and the button are children of the JComboBox.

Compound components have the following problems:
1) They confuse event handling by having multiple sources.
2) They stop tool tips from working properly.
3) Setting cursors on them will cause strange behavior.
4) They are very difficult to initialize.

Problem #1: They confuse event handling by having multiple sources.
People have been registering mouse and keyboard listeners with JComboBoxes.  When the user clicks on the arrow button the events come from the arrow button, not the JComboBox.  This has been confusing developers.  They expect event handling to be the same for all Swing components regardless of how they were implemented.  In fact, whether or not a component is a compound component is totally dependent on the UI delegate.  If one L&F doesn't use children to create the component then a developer can't rely on the fact that it's compound or not when they decide to attach listeners to it.

Problem #2: They stop tool tips from working properly.
Tool tips suffer from a similar problem to event handling.  If a developer puts a tool tip on an editable JComboBox, the tool tip will only appear if the cursor is over the combo box but not over the arrow button or the edit field.

Problem #3: Setting cursors on them will cause strange behavior.
Trying to set a cursor on an editable combo box will result in behavior similar to tool tips.  The cursor will only appear if the cursor is over the combo box but not over the arrow button or the edit field.

Problem #4: They are very difficult to initialize
In order for a compound component to be properly initialized during a Look & Feel switch, an invokeLater() call must be used to initialize the child components.  The way that component-trees are updated during a L&F switch (using SwingUtilities.updateComponetTreeUI) causes this to be a problem.  I'll use JComboBox as an example:

1.  updateComponetTreeUI() tells the JComboBox to update its UI
2.  A ComboBoxUI subclass gets created
3.  The ComboBoxUI adds some child components to the JComboBox (like the editor)
4.  The ComboBoxUI initializes the child components with fonts and colors
5.  updateComponentTreeUI() asks the JComboBox's children to update their UIs
6.  JComboBox's children now make new UI delegates
7.  The new UI delegates re-initialize the child components with values from the defaults table.

The ComboBoxUI set values in the children but they were later wiped out because the children were asked to create new delegates who re-initialized the children.

tom.santos@eng 1998-06-01

Name: krT82822			Date: 06/28/99


setEnabled is inconsistant between JComponents.
For most components(All the ones I checked), Components will still generate events when setEnabled(false) is called
This does not occur for JComboBox though.

eg:
If you run this program as is, clicking will not generate a mouse event.
Change variable0 to any other component(eg: a JButton), and it will

(test case follows at end, but the next section summarizes additional comments from the user received 6/28/99 [6/29 in user's timezone]):

> Subject: Re: [Re: (Review ID: 53321) setEnabled does not work correctly with JComboBox]
> Date:  29 Jun 99 09:27:21 WST
> From:  Daniel Fletcher <###@###.###>


G'day
...
Basically I believe that when the JComboBox is disabled, it should begreyed
out, and clicking shouldnt change it in any way, but the mouseEvents should
still be generated and sent to any listeners.

My reason for wanting this?
I was making a GUI builder called
Korfe(http://www.javalobby.org/jfa/projects/korfe.html)
which allowed you to move and drag components around.

For all components except JCombobox, I was able to disable the Component, and
then let the user drag it around when they clicked in it etc.

For JCombobox once the component was disabled, clicking in it would not
generate mouse events, so I couldnt catch them, and thus it couldnt be
dragged.

I think the correct behaviour would be the same as the other components.
Consistency is defintely the way to go, so if you do decide to continue not
generating mouse events, you should do it for all other components as
well(Even though it will totally break my application :)

Daniel

----------------------



------------------------
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

class Test extends JPanel implements MouseListener
{

	public Test()
	{
		super();
		setupGUI();
		addMouseListener(this);
		variable0.addMouseListener(this);
		variable0.setEnabled(false);		
	}

	//Call this method to setup the GUI
	public void setupGUI()
	{
		BorderLayout layout1 = new BorderLayout();
		setLayout(layout1);
		variable0 = new JComboBox();
		add(variable0 , "Center");

	}

	public static void main(String args[])
	{
		JFrame f=new JFrame();
		Test t=new Test();
		f.getContentPane().setLayout(new BorderLayout());
		f.getContentPane().add(t,BorderLayout.CENTER);
		f.setBounds(0,0,300,200);
		f.setVisible(true);
	}

	public void mouseClicked(MouseEvent e)
	{
	}

	public void mouseEntered(MouseEvent e)
	{
	}

	public void mouseExited(MouseEvent e)
	{
	}

	public void mousePressed(MouseEvent e)
	{
		System.out.println("Mouse pressed");
	}

	public void mouseReleased(MouseEvent e)
	{
	}

	//Variables
	private JComponent variable0;
}
(Review ID: 53321)
======================================================================

Name: rlT66838			Date: 09/28/99


Since the JDK 1.1.8 is impossible to intercept the keyboard event in the JComboBox component.

see the example :
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class MyComboBox extends JFrame implements KeyListener
{
    public MyComboBox()
    {
        super("MyComboBox");
        getContentPane().setLayout(new GridLayout(1, 2));
        addWindowListener(new WindowAdapter()
        {
           public void windowClosing(WindowEvent e)
           {
               System.exit(0);
           }
        });

        JComboBox myComboBox = new JComboBox();
        myComboBox.addKeyListener(this);
        myComboBox.setEditable(true);
        getContentPane().add(myComboBox);
        
	    pack();
	    setVisible(true);
    }

    public static void main(String arg[]) {new MyComboBox();}
    public void keyPressed(KeyEvent e) {System.out.println("keyPressed");}
    public void keyReleased(KeyEvent e) {System.out.println("keyReleased");}
    public void keyTyped(KeyEvent e) {System.out.println("keyTyped");}
}
(Review ID: 95842)
======================================================================

Comments
EVALUATION hans.muller@Eng 1998-06-02 This is a basic design flaw (perhaps "choice") in Swing. Providing a general solution that enabled users to treat composites as simple mouse or keyboard listeners could significantly complicate the Swing implementation. Alternatively we specify that this sort of support was only available for a small number of Simple Swing components, e.g. JLabel. I think the latter is the only reasonable choice until after JDK1.2 has shipped. Here's the final evaluation. It's long, it's HTML; you have my apologies. Please send comments to ###@###.###. hans.muller@Eng 2000-03-09 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> <title>Bug 4144505: Problems With Compound Components, Notably JComboBox </title> </head> <body bgcolor="white"> <h1>Bug 4144505: Problems With Compound Components, Notably JComboBox </h1> <p> This article discusses many of the common problems developers have had with compound components, notably JComboBox. The focus of the articles is the list of bugs that were collapsed (as duplicates) into bug report 4144505. <p> <a href="http://developer.java.sun.com/developer/bugParade/bugs/4144505.html"> Bug 4144505 </a> was originally submitted as <a href="http://developer.java.sun.com/developer/bugParade/bugs/4110721.html"> bug 4110721 </a> against an early version of Swing 1.1 in February 1998. Six months later bug 4144505 was created by a Swing developer as a catch-all for bugs that were effectively caused by the Swing "compound component" implementation of several components, notably JComboBox. This had the good effect of centralizing all of the complaints related to this Swing implementation feature however it had the very bad effect of creating a bug report that wasn't likely to ever be closed. Although some of the problems that fell under the bug 4144505 umbrella were corrected, the bug report persists to this day because some of the 12 bugs that fell into this sinkhole are still open. <p> This document is an attempt to address the issues raised by bug 4144505. We plan to close the bug and open a small set of RFEs (two) that cover missing features that were identified here. The RFEs will be committed for the 1.4 release of 'Java2 Standard Edition' (J2SE), also known as "Merlin". <p> The remaining sections review each of the bug 4144505 issues and our proposals for resolving them. <h2>Adding Listeners to a JComboBox</h2> <p> Adding AWT event listeners (like mouse and keyboard listeners) to a JComboBox doesn't give one access to all of the events associated with the combo box. Adding a listener a JComboBox and all of its descendants gives one access to events associated with the (non dynamically generated) parts of the combo box however it's difficult to intepret the events because they come from look and feel specific subcomponents. This approach carries an additional penalty after a dynamic L&F switch because the listeners added to descendants of the JComboBox aren't restored after the new UI delegate has been created. <p> We do not believe that, in general, one can add a mouse or keyboard event listener to a complex or compound Swing component without creating an undesirable dependency on the components look and feel (L&F) and on its implementation. On the other hand many of the developers who were struggling with this had legitimate reasons for doing so. In most cases they were attempting to compensate for the lack of higher level listeners in JComboBox, and in a few cases for JComboBox bugs. Here's a list of the problems we observed and how we plan to address them. <h3>Input Verification</h3> <p> The motivation for adding focus, mouse, or key event listeners was often to enable input verification or to track the value of the editor part of an editable combo box. The idea was that if one could detect when the user was effectively moving the focus to another component, the current value of the combo box could be checked and forced back to the combo box if invalid. <p> Explicit support for input verification was added for the 1.3 (Kestrel) release of J2SE. There's a <a href="http://www.theswingconnection.com"> Swing Connection article </a> that describes the new input verification API . <p> One can use the input verififcation facility with an editable combo box by setting the <code>inputVerifier</code> property of the combo boxes <code>editor</code>. In the example below the verifier only allows a focus change if the one of the first three items was selected: <pre> InputVerifier checkInput = new InputVerifier() { public boolean verify(JComponent c) { return comboBox.getSelectedIndex() < 2; } public boolean shouldYieldFocus(JComponent c) { boolean ok = verify(c); if (!ok) { c.getToolkit().beep(); } return ok; } }; Component editor = comboBox.getEditor().getEditorComponent(); if (editor instanceof JTextField) { ((JTextField)editor).setInputVerifier(checkInput); } </pre> <p> This approach only verifies the input when the user attempts to tab out of the editors textfield and it only works if the current L&F uses a JTextField to implement the editor. Although all of the L&F implementations we know of use a JTextField, one could safeguard against the unexpected and deal with dynamic L&F switching by creating a custom ComboBoxEditor. Here's an example: <pre> class MyComboBoxEditor implements ComboBoxEditor { private final JTextField editor; MyComboBoxEditor() { editor = new JTextField("", 9); // Configure the editors inputVerifier or whatever else // you want here. } public Component getEditorComponent() { return editor; } public void setItem(Object item) { editor.setText((item != null) ? item.toString() : ""); } public Object getItem() { String s = editor.getText().trim(); return (s.length() == 0) ? null : s; } public void selectAll() { editor.selectAll(); editor.requestFocus(); } public void addActionListener(ActionListener l) { editor.addActionListener(l); } public void removeActionListener(ActionListener l) { editor.removeActionListener(l); } } </pre> <h3>Customizing ComboBox Editor Behavior</h3> <p> A common reason for attempting to install a KeyListener on a JComboBox is to customize or override the default key bindings. The <a href="http://java.sun.com/products/jfc/tsc/special_report/kestrel/keybindings.html"> new key binding architecture </a>, introduced in J2SE 1.3, makes this easy to do. The bindings used by JComboBox are defined in a input map that's consulted when the JComboBox is the ancestor of the focused component. To add a new key binding, we add an entry to the combo boxes <code>InputMap</code> for the <code>KeyStroke</code> which maps from the key to the name of an action, and we add the action to the combo boxes <code>ActionMap</code>. In the example below we've added a binding for VK_F1, that's the "F1" function key, that selects the first item in the list: <pre> Action selectFirstAction = new AbstractAction("Select First Item") { public void actionPerformed (ActionEvent e) { comboBox.setSelectedIndex(0); } }; InputMap inputMap = comboBox.getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); inputMap.put(KeyStroke.getKeyStroke("F1"), "selectFirst"); ActionMap actionMap = comboBox.getActionMap(); actionMap.put("selectFirst", selectFirstAction); </pre> <p> It's easy to override predefined actions with the same mechanism. For example JComboBox defines an action for "selectNext", which is usually mapped to the down arrow key, that pops up the drop down menu or selects the next item in the list. The up arrow ("selectPrevious") mapping is similar. To customize the menu so that the arrow keys just move the selection up and down, without showing the drop down menu, we can just override the bindings for "selectNext" and "selectPrevous". <pre> Action upAction = new AbstractAction("Select Previous Item") { public void actionPerformed (ActionEvent e) { int i = comboBox.getSelectedIndex(); if (i == -1) { comboBox.setSelectedIndex(0); } else if (i == 0) { comboBox.setSelectedIndex(comboBox.getItemCount() - 1); } else { comboBox.setSelectedIndex((i - 1) % comboBox.getItemCount()); } } }; Action downAction = new AbstractAction("Select Next Item") { public void actionPerformed (ActionEvent e) { int i = comboBox.getSelectedIndex(); if (i == -1) { comboBox.setSelectedIndex(0); } else { comboBox.setSelectedIndex((i + 1) % comboBox.getItemCount()); } } }; ActionMap actionMap = comboBox.getActionMap(); actionMap.put("selectPrevious", upAction); actionMap.put("selectNext", downAction); </pre> <h3>Dynamic Creation of the Menu</h3> <p> Developers often try adding mouse listeners to JComboBox or a mouse/action listener to the combo boxes arrow button descendant because they want to be notified just before the drop down menu pops up. Usually the notification triggers some code that dynamically populates the ComboBoxModel. <p> In some cases this isn't strictly neccessary, because the ComboBoxModel itself can be a view on dynamic data. <p> In other cases it's either more convenient or just simpler to have the combo box notify the application before the menu drops down. We've created an RFE to address this problem, it's: <a href="http://developer.java.sun.com/developer/bugParade/bugs/4287690.html"> 4287690 - JComboBox should send a drop down event. </a> We plan to add a PopupMenuListener to JComboBox for the Merlin release which should address this problem nicely. <h2>JComboBox and ToolTips</h2> <p> Setting a tooltip on an editable combo box didn't work correctly - the tooltip doesn't appear when the mouse is over the arrow button. <p> This was fixed in January of 1999 (SCCS version 1.95 of BasicComboBoxUI.java). BasicComboBoxUI agressively sets the tooltip text on the descendants of JComboBox whenever the JComboBox toolTipText property changes. <h2>JComboBox and Cursors</h2> <p> Setting the cursor on JComboBox fails to change the cursor for the editable text field (if the combo box is editable) or for the the drop down menu. The cursor does change over the arrow button and the (not editable) selected item field. <p> This bug has not been fixed. It's particularly difficult to address this problem because the java.awt.Component cursor property is not bound and the property has "inherited from component ancestor" semantics. In other words the value of the cursor property for a component is the value of the first ancestors cursor that's not null, starting with the component itself. This means that one can trivially change the cursor for all descendants of a container - <i>that haven't explicitly set their own cursor</i>. Unfortunately this doesn't really match the one idiom that developers would often like to implement with a simple setCursor call - temporarily displaying a wait cursor for <b>all</b> descendants of a container. <p> Here's a sketch of the idiom that gives many developers trouble. <pre> JComboBox myComboBox = new JComboBox(new String[]{"doWork"}); Runnable doDisableMyComboBox = new Runnable() { public void run() { myComboBox.setEnabled(true); } }; Runnable doWork = new Runnable() { public void run() { // ... do something that takes a while EventQueue.invokeLater(doEnableMyComboBox); } }; ActionListener doWorkAction = new ActionListener() { actionPerformed(ActionEvent e) { myComboBox.setEnabled(false); new Thread(doWork).start(); }; myComboBox.addActionListener(doWorkAction); </pre> <p> In this example we'd like to temporarily disable the JComboBox while the potentially long-running "doIt" action executes on a separate thread. It's important that the action runs on a separate thread; if it didn't the "doIt" action would block event dispatching and the application would appear to hang. More to the point, the repainting that we'd like to see when the the combo box's enabled property changes wouldn't happen. <p> Unfortunately there are some problems with the code in this sketch: <ul> <li> Disabling components while the application is busy can be visually disruptive, particularly if a large number of components are temporarily disabled. Additionally the enabled property does not have the inherited semantics we'd like in this situation, a component can be enabled even if it's parent is disabled. We are considering a change to the Swing or the AWT that would provide a counterpart property for enabled that had inherited semantics, in roughly the same way the read-only java.awt.Component "showing" property provides inherited semantics for the Component visible property. Here's the RFE: <a href="http://developer.java.sun.com/developer/bugParade/bugs/4177727.html"> 4177727 - propagating disEnabled "event" to all children </a> <li> And just disabling the combo box may not be what you want anyway. Typically one wants to take all or part of the application modal in a situation like this. For example you might want to block all components that can be used to launch more work, but not the button that cancels work in progress. <li> Although we could try and make the busy cursor appear, e.g. by temporarily setting the cursor property of myComboBox and all of its descendants, temporarily defeating event processing is messy. One could temporarily remove all of the listeners on myComboBox and its descendants, or temporarily replace the event queue with one that didn't dispatch events to myComboBox and its descendants. These solutions are just too painful to be practical. </ul> <h3>A Solution</h3> <p> Here's an example of a utility class that addresses this problem. The <code>BusyPanel</code> is simple container for one
11-06-2004

EVALUATION component that has a glass pane, like the one provided by JRootPane. The glass pane is a simple transparent JPanel that absorbs mouse and keyboard events, and displays the busy cursor. The BusyPanel stacks the glass pane on top of its other child. Setting the BusyPanel "busy" property to true makes the glass pane visible which means that it's busy cursor appears and begins getting all mouse and keyboard events. The BusyPanel also temporarily assigns the focus to the glass pane. <pre> class BusyPanel extends JPanel { private Component oldFocusOwner = null; private final JComponent child; private final JPanel glass; private final BlockEvents blockEventsListener; private void beep() { getToolkit().beep(); } private class BlockEvents extends MouseAdapter implements KeyListener { public void mousePressed(MouseEvent e) { beep(); } public void keyPressed(KeyEvent e) { beep(); } public void keyReleased(KeyEvent e) { } public void keyTyped(KeyEvent e) { } } public BusyPanel(JComponent c) { super(new BorderLayout()); child = c; blockEventsListener = new BlockEvents(); glass = new JPanel(null); glass.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); glass.addMouseListener(blockEventsListener); glass.addKeyListener(blockEventsListener); glass.setOpaque(false); glass.setVisible(false); add(glass, 0); // glass is on top, but not visible add(child, 1); // child is below the glass } public void setBusy(boolean busy) { if (isBusy() != busy) { if (busy) { oldFocusOwner = SwingUtilities.findFocusOwner(this); glass.requestFocus(); } else if (oldFocusOwner != null) { oldFocusOwner.requestFocus(); } glass.setVisible(busy); } } public boolean isBusy() { return glass.isVisible(); } public boolean isOptimizedDrawingEnabled() { return false; } public Dimension getPreferredSize() { return child.getPreferredSize(); } public Dimension getMinimumSize() { return child.getMinimumSize(); } public Dimension getMaximumSize() { return child.getMaximumSize(); } public void doLayout() { glass.setBounds(0, 0, getWidth(), getHeight()); child.setBounds(0, 0, getWidth(), getHeight()); } } </pre> <p> Here are few notes about the BusyPanel implementation: <ul> <li> Layout is very simple so we don't bother we a layout manager. The preferred, minimum, and maximum size computations are delegated to the child, and the doLayout method just ensures that the glass pane and the child have the same size as the BusyPanel itself. <li> The glass panes opaque property is set to false so that it's background will not be cleared. This is all that's needed to ensure that the glass pane JPanel is transparent. <li> The BusyPanel class does not "tile" it's children as most containers do so we override the <code>isOptimizedDrawingEnabled</code> to return false. This isn't absolutely neccessary since the glass pane is transparent. </ul> <h2>JComboBox and SwingUtilities.updateComponentTreeUI</h2> <p> The SwingUtilities.updateComponentTreeUI() method is commonly used to switch the look and feel of a tree of components. Unfortunately it just blindly walks from the root of the tree and sets the UI property of all descendants - even if they're just parts of a compound component. For example when the UI of a JComboBox is set the L&F implementation adds subcomponents like the arrow button and the item text field. When the updateComponentTreeUI redundantly sets the UI property of the subcomponents, the color/font/etc configuration created by the BasicComboBoxUI is lost. <p> This problem was "fixed" in Swing by using EventQueue.invokeLater to initialize combo box subcomponents however this can cause secondary problems and it's inefficient. What's really needed is a way to identify compound components so that utility methods like updateUI can avoid descending into them. This distinction is available in the BeanInfo classes for Swing components however one can't assume that the BeanInfo classes (e.g. JComboBoxBeanInfo) will be available in a normal application environment. <p> In the next Java release we plan to remove the invokeLater workaround and either provide a way to programatically identify compound Swing components or move the special case to SwingUtilities.updateComponentTreeUI(). <h2>Should Listeners in a Disabled Component Receive Events?</h2> <p> The AWT lightweight component support doesn't prevent disabled components from receiving any of events defined by the listeners on java.awt.Component and java.awt.Container (and java.awt.Window). Obviously Swing components will not fire higher level listeners when disabled, e.g. a JButtons ActionListener will not run when the button has been disabled. This effect is produced by the mouse/keyboard listeners installed by the components L&F. When the component is disabled, input that would trigger a higher level listener is ignored. <p> Adding an AWT event listener to a compound component like JComboBox, may be ineffective because some of the L&F implementations pack the JComboBox with subcomponents. The subcomponents usually absorb mouse and keyboard events, whether or not the JComboBox is disabled. <p> Ofcourse we don't believe that adding event listeners to a compound component is an effective way to customize a compound component, see the "Adding Listeners to a JComboBox" above. <hr> <address><a href="mailto:###@###.###">Hans Muller</a></address> <!-- Created: Tue Feb 29 10:49:30 PST 2000 --> <!-- hhmts start --> Last modified: Thu Mar 9 17:17:28 PST 2000 <!-- hhmts end --> </body> </html>
11-06-2004