5

I'd like to be able to react when currently highlighted item in a JComboBox drop down list changes. Note that I'm not looking for a way to get the currently selected item, but the highlighted one. When mouse hovers over this popup it highlights the item at mouse position, but this does not affect currently selected item, so I cannot simply listen via an ItemListener or ActionListener to achieve what I want.

I'm trying to create a component which consists of a JComboBox and a coupled tooltip which displays additional information (documentation) for the currently highlighted item.

As my first attempt I'm adding some code to my constructor (extended JComboBox):

import java.awt.BorderLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.accessibility.AccessibleContext;
import javax.accessibility.AccessibleState;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.SwingUtilities;
import javax.swing.plaf.basic.ComboPopup;

public class SomeFrame extends JFrame {

    private MyComboBox combo;

    public SomeFrame() {
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        setSize(100,20);
        setLocationRelativeTo(null);
        setLayout(new BorderLayout());
        combo = new MyComboBox();        
        combo.setModel(new DefaultComboBoxModel(new String[]{"one", "two", "three", "four"}));
        add(combo);
        pack();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                SomeFrame frame = new SomeFrame();
                frame.setVisible(true);
            }
        });
    }    

    // this is the important part
    private static class MyComboBox extends JComboBox {

        public MyComboBox() {
            getAccessibleContext().addPropertyChangeListener(new PropertyChangeListener() {
                public void propertyChange(PropertyChangeEvent evt) {
                    if (AccessibleContext.ACCESSIBLE_STATE_PROPERTY.equals(evt.getPropertyName())
                            && AccessibleState.FOCUSED.equals(evt.getNewValue())
                            && getAccessibleContext().getAccessibleChild(0) instanceof ComboPopup) {
                        ComboPopup popup = (ComboPopup) getAccessibleContext().getAccessibleChild(0);
                        JList list = popup.getList();
                        System.out.println("--> " + String.valueOf(list.getSelectedValue()));
                    }
                }
            });
        }
    }

}

It seems to work, but I got to this code through some shady channels and trial/error, so I'm thinking there has to be a better way of doing it. Any ideas? Is above code even production safe?

predi
  • 5,528
  • 32
  • 60
  • 1
    I ended up going with what kelopatra suggested. My question might have been somewhat misleading because I mentioned tooltips and forgot to mention that I might switch to a different more flexible component in the future. – predi Apr 11 '13 at 08:09
  • Did you solve this? I have a similar problem at a combo that I am creating. I just want to get the item that is marked in the popup list: https://github.com/kl0ck/searchcombobox/commit/e196c98b58e9ec1b82bf20f9e00b251b326edcce – ceklock Sep 30 '21 at 22:06
  • 1
    @ceklock see what kleopatra suggested in the accepted answer below. – predi Oct 01 '21 at 07:11

2 Answers2

4

Good question and good solution - except that there seems to be bug in accessibleCombo which doesn't update its internal wiring on updateUI, that is, when toggling the LAF:

  • the accessible selection changes of the list are fired by an internal ListSelectionListener registered to the comboPopup list
  • the comboPopup is controlled by the ui-delegate and re/created in installUI
  • accessibleCombo doesn't update its internal list to the newly created and installed

Not much you can do about it. So I would listen directly to the list selection, then you have full control about re-wiring on LAF changes:

public static class XComboBox extends JComboBox {

    private ListSelectionListener listener;

    public XComboBox() {
        uninstall();
        install();
    }

    @Override
    public void updateUI() {
        uninstall();
        super.updateUI();
        install();
    }

    private void uninstall() {
        if (listener == null) return;
        getPopupList().removeListSelectionListener(listener);
        listener = null;
    }

    protected void install() {
        listener = new ListSelectionListener() {
            @Override
            public void valueChanged(ListSelectionEvent e) {
                if (e.getValueIsAdjusting()) return;

                JList list = getPopupList();
                System.out.println("--> " + String.valueOf(list.getSelectedValue()));
            }
        };
        getPopupList().addListSelectionListener(listener);
    }

    private JList getPopupList() {
        ComboPopup popup = (ComboPopup) getUI().getAccessibleChild(this, 0);
        return popup.getList();

    }
}
kleopatra
  • 51,061
  • 28
  • 99
  • 211
  • I was not aware of this bug at all. I tend to stay away from anything LAF related most of the time. I'll try this too if I'm not able to put the custom renderer solution into submission. Thanks. – predi Apr 10 '13 at 15:55
3

I'm trying to create a component which consists of a JComboBox and a coupled tooltip

Create a custom renderer for the combo box. Then in the renderer use the setToolTipText(...) method.

The section Specifying Tool Tips For Cells from the JTable tutorial shows how to do this for tables. The concept should be the same for a comboBox renderer.

camickr
  • 321,443
  • 19
  • 166
  • 288
  • If I'm not mistaken, this would not allow me to position the tooltip the way I please. But this would be way better then what I'm doing now. I'll give it a go. Thanks for the custom renderer suggestion. – predi Apr 10 '13 at 15:43