How do I know if an item of an auto-complete decorated JComboBox is mouse clicked?
Asked Answered
O

1

6

I'm using the SwingX AutoCompleteDecorator for a JComboBox. The autocomplete feature works beautifully...

But I have trouble to identify the moment of the final user selection; to persist my data seldom.

Let me try to explain: The combobox fires an "comboBoxChanged"-ActionEvent for every selection. I have to ignore these events while the user is typing characters and the combobox is auto-matching and selecting items. If the user hits the return-key an "comboBoxEdited"-ActionEvent is generated and I can save the selected value. Great ;-)

If the mouse is used to open the JComboBox-PopUp and to select an item, the only fired event is a "comboBoxChanged"-ActionEvent (like when auto-matching or selecting an item with the cursor-keys). The mouse-clicked-Event is consumed somehow!? That's why I can't identify the final mouse selection.

How can I figure this out? My failed attempts to listen for the mouseClicked-Event are documented in this SSCCE:

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import org.jdesktop.swingx.autocomplete.AutoCompleteDecorator;


public class SearchForThePopUpMouseClick extends JPanel
{
  private JComboBox<String> comboBox;

  public SearchForThePopUpMouseClick()
  {
    comboBox = new JComboBox<String>(new String[] { "Anna", "Marc", "Maria", "Marten", "Peter" });
    add(comboBox);
    add(new JTextField("textfield to click"));

    AutoCompleteDecorator.decorate(comboBox);


    comboBox.addActionListener(new ActionListener()
    {
      @Override
      public void actionPerformed(ActionEvent e)
      {
        System.out.println("Action Event with '" + e.getActionCommand() + " " + e.getID() + "'");
      };
    });


    ((Component) comboBox.getUI().getAccessibleChild(comboBox, 0)).addMouseListener(new MouseListener()
    {
      @Override
      public void mouseReleased(MouseEvent e)
      {
        System.out.println(e);
      }
      @Override
      public void mousePressed(MouseEvent e)
      {
        System.out.println(e);
      } 
      @Override
      public void mouseExited(MouseEvent e)
      {
        System.out.println(e);
      }
      @Override
      public void mouseEntered(MouseEvent e)
      {
        System.out.println(e);
      }
      @Override
      public void mouseClicked(MouseEvent e)
      {
        System.out.println(e);
      }
    });
  }


  public static void main(String[] args) throws Exception
  {
    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    SwingUtilities.invokeLater(new Runnable()
    {
      @Override
      public void run()
      {
        SearchForThePopUpMouseClick autoCompletePanel = new SearchForThePopUpMouseClick();
        JFrame frame = new JFrame("SwingX Autocomplete Example");
        frame.add(autoCompletePanel);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
      }
    });
  }

}
Ostap answered 30/10, 2012 at 11:43 Comment(5)
What gets fired when you addItemListener(ItemListener) ?Anastomose
@Anastomose - the same DESELECTED- and SELECTED-ItemEvents for keyboard-cursor-navigation and mouse-clicks :-(Ostap
It's been a long while since I've done some Swing and I've never used SwingX. I'm guessing that listening for a focus lost event isn't acceptable? Alternatively, you might be lucky with a PropertyChangeListener or a VetoableChangeListener but I'm stabbing in the dark! Maybe download the source for SwingX and have a look at what they're doing... :PAnastomose
there are three ways listselectionlistener, swingutilities, AWTListener, I this case is Actionlistener better listener, for example for all (mouse & keyboard from derived jlist) event from low_level AWT listenersMarxmarxian
good catch :-) But not really different compared to a plain undecorated combo when selecting the items by keyboard vs. mouse: the action is always fired when the selection changes, no support to distinguish between "final" and "in-between" change.Anisometropia
A
9

A comboBox has no notion of final selection: all selections have equal semantic weight independent on their trigger (mouse, keyboard navigation, programmatically, selection by first letter in core) and fire an actionEvent. Same behaviour for plain and decorated comboBox.

That's exactly what you need in most contexts: react to a selection always as if it were final (whatever that might mean)

If in your case you really want to regard a selection triggered by a mouseEvent as more final than those triggered by anything else (again: that's typically not recommended for a good user experience, so be very, very careful in your evaluation) you can check the modifiers returned by the actionEvent:

if ((e.getModifiers() & InputEvent.BUTTON1_MASK) != 0) {
    // triggered by mouse
}

Edit

Seeing the use cases (thanks for providing them!) in the comments, realized that my beware partly barked at the wrong tree :-)

In this context, the mouse- vs. keyboard gesture have indeed different semantics

  • keyboard: typing in the editor as well as navigating in the popup denote the process to build the final selection, with a special key (enter) denoting a commit
  • mouse: clicking in the popup is both selecting and commit

JComboBox doesn't support that use-case optimally, firing too much. That's problem even swing-internally, f.i. when using a comboBox as CellEditor. That's partly fixed by a magic clientProperty:

public DefaultCellEditor(final JComboBox comboBox) {
    editorComponent = comboBox;
    comboBox.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);

Detecting that property, the BasicComboBoxUI (actually the BasicComboPopup) keyStrokes navigation selects in the list of the popup only, defering the synch of the listSelection to the comboSelection until committed via enter. It's partial because the look-ahead (aka: typing and selecting by first letter) still selects (and thereby committing) immediately in the combo.

Short summary: there already is a swing-internal use-case, which leads to an already available swingx-internal solution for autoComplete editing in tables - a class named ComboBoxCellEditor. Can be used stand-alone also:

AutoCompleteDecorator.decorate( withEditor );
ComboBoxCellEditor editor = new ComboBoxCellEditor(withEditor);
CellEditorListener listener = new CellEditorListener() {

    @Override
    public void editingStopped(ChangeEvent e) {
        // do commit stuff
    }

    @Override
    public void editingCanceled(ChangeEvent e) {
    }
};
editor.addCellEditorListener(listener);
contentPane.add(withEditor, BorderLayout.SOUTH);
Anisometropia answered 30/10, 2012 at 12:56 Comment(9)
A good use-case where you do want a different reaction on the final selection is where you connect to a remote server. If you start typing "htt p://ww w..." (added space to avoid converting it to a real link) you will most likely trigger the autocomplete functionality a few times with different URLs, and you only want to make the connection on the final selection.Gather
@Gather good use-case - but not specific to autoComplete: the normal keySelectionHandler behaves eactly the same, as does navigating with the keyboard. Might be worth supporting in SwingX, hmm ... something along the lines of behaviour of a combo as cellEditor might do.Anisometropia
I need to check my application where I use that autocomplete functionality with the use case of URLs as it has some usability issues (both for the end user as for the developer). Perhaps I can log a good summary + SSCCE in the SwingX bug trackerGather
@Anisometropia - hooray! That is the solution for my problem. Thank you! I searched all the events and properties for this BUTTON1_MASK information; but I thought the getModifiers()-method just deals with modifier keys.Ostap
@Gather & kleopatra - my use-case is "save data using a (perhaps) slow service-call". So the selection triggered by a mouseClick should not be more final than anything else, it should be as final as an enter-key selection. A side note: there is also a different fireEvent-behaviour between mouse-hovering over items of an open combobox-popup (items get visually selected, but no itemChanged- or actionEvent) and 'walking' through the items with the cursor keys (itemChanged & actionEvent are fired). This behaviour is not caused by the AutoCompleteDecorator, but appears with it. :-)Ostap
triggered by a mouseClick should not be more final than anything else then use ListSelectionListener instead, this code line could be for JTableHeaderMarxmarxian
@Marxmarxian - What JTableHeader? I surrender, forfeit, capitulate, give up and give in. You won! ;-)Ostap
JTableHeaderMarxmarxian
See java.net/jira/browse/SWINGX-1532 for the SwingX issue. Probably part of it is simply a JComboBox issue, but hey, logged it anywayGather

© 2022 - 2024 — McMap. All rights reserved.