How do I know if an item of an auto-complete decor

2019-02-09 22:25发布

问题:

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);
      }
    });
  }

}

回答1:

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);