I have a JFileChooser in a JFrame. I've added an ActionListener to the JFileChooser so that the "Cancel" button works when clicked. I can also tab to the "Cancel" button, but when I then hit the "Enter" key, nothing happens (i.e., the ActionListener isn't called with the event command JFileChooser.CANCEL_SELECTION). What must I do with the JFileChooser so that hitting the "Enter" key when on the "Cancel" button is equivalent to clicking on the "Cancel" button?
Here's a simple example of the (mis)behavior I'm seeing:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
public final class TestApp {
public static void main(final String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
final JFileChooser chooser = new JFileChooser();
chooser.addActionListener(new ActionListener() {
public void actionPerformed(final ActionEvent e) {
System.exit(0);
}
});
final JFrame frame = new JFrame();
frame.add(chooser);
frame.pack();
frame.setVisible(true);
}
catch (final Throwable t) {
t.printStackTrace();
}
}
});
}
}
To see the (mis)behavior, execute the program, tab to "Cancel", and then hit the "Enter" key. The program doesn't terminate on my platform -- although it does when I click on the "Cancel" button.
Extending JFileChooser and overriding cancelSelection() also doesn't work (apparently, that function isn't called when the "Enter" key is hit while on the "Cancel" button).
The (mis)behavior occurs on my Fedora 10 x86_64 system with Java 5, 6, and 7.
ADDENDUM: The following adds a KeyEventPostProcessor to the current KeyboardFocusManager and appears to do what I want:
import java.awt.Component;
import java.awt.KeyEventPostProcessor;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
public final class TestApp {
public static void main(final String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
final JFileChooser chooser = new JFileChooser();
chooser.addActionListener(new ActionListener() {
public void actionPerformed(final ActionEvent e) {
System.out.println(e.paramString());
System.exit(0);
}
});
final KeyboardFocusManager kfm = KeyboardFocusManager
.getCurrentKeyboardFocusManager();
kfm.addKeyEventPostProcessor(new KeyEventPostProcessor() {
#Override
public boolean postProcessKeyEvent(final KeyEvent e) {
if (e.getID() == KeyEvent.KEY_RELEASED
&& e.getKeyCode() == KeyEvent.VK_ENTER) {
final Component comp = e.getComponent();
if (chooser.isAncestorOf(comp)) {
if (!(comp instanceof JButton)) {
chooser.approveSelection();
}
else {
final JButton button = (JButton) comp;
if ("Cancel".equals(button.getText())) {
chooser.cancelSelection();
}
else {
chooser.approveSelection();
}
}
}
}
return false;
}
});
final JFrame frame = new JFrame();
frame.add(chooser);
frame.pack();
frame.setVisible(true);
}
catch (final Throwable t) {
t.printStackTrace();
}
}
});
}
}
It seems like a lot of work, however, just to be able to distinguish between hitting the enter key on the "Cancel" button versus anywhere else.
Do you see any problems with it?
DISCOVERED SOLUTION: Setting the GUI Look and Feel to the native one for my system (Linux) does what I want without the need for anything else. This is what I was ignorant of and what I was looking for. The solution is to have the following
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
as the first executable statement of the main() method. One can then dispense with all focus listeners, key event processors, etc.
I've awarded the 100 points to the most helpful respondent.
The program doesn't terminate on my platform.
I see normal operation on Mac OS X 10.5, Ubuntu 10 and Windows 7 using (variously) Java 5 and 6. I replaced your exit() with println() to see the event:
System.out.println(rootDirChooser.getSelectedFile().getName() + e.paramString());
It may help to specify your platform and version; if possible, verify correct installation as well.
I'm not sure I understand your goal; but, as an alternative, consider overriding approveSelection():
private static class MyChooser extends JFileChooser {
#Override
public void approveSelection() {
super.approveSelection();
System.out.println(this.getSelectedFile().getName());
}
}
Addendum:
The goal is to have the action of hitting the "Enter" key while on the "Cancel" button be identical to clicking on the "Cancel" button.
As discussed in Key Bindings, you can change the action associated with VK_ENTER.
KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
InputMap map = chooser.getInputMap(JFileChooser.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
map.put(enter, "cancelSelection");
If you want the change to occur only while the "Cancel" button has focus, you'll need to do it in a Focus Listener.
Addendum:
I found a solution that uses KeyboadFocusManager, instead. What do you think?
I can see pros & cons each way, so I've outlined both below. Using KeyboadFocusManager finds all buttons, but offers no locale independent way to distinguish among them; the Focus Listener approach can only see the approve button, and it's UI specific. Still, you might combine the approaches for better results. A second opinion wouldn't be out of order.
Addendum:
I've updated the code below to eliminate the need to know the localized name of the "Cancel" button and use key bindings.
import java.awt.EventQueue;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.KeyStroke;
import javax.swing.plaf.metal.MetalFileChooserUI;
public final class FileChooserKeys
implements ActionListener, FocusListener, PropertyChangeListener {
private final JFileChooser chooser = new JFileChooser();
private final MyChooserUI myUI = new MyChooserUI(chooser);
private final KeyStroke enterKey =
KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
private void create() {
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
chooser.addActionListener(this);
myUI.installUI(chooser);
myUI.getApproveButton(chooser).addFocusListener(this);
KeyboardFocusManager focusManager =
KeyboardFocusManager.getCurrentKeyboardFocusManager();
focusManager.addPropertyChangeListener(this);
frame.add(chooser);
frame.pack();
frame.setVisible(true);
}
#Override
public void actionPerformed(ActionEvent e) {
System.out.println(e.paramString());
}
#Override
public void focusGained(FocusEvent e) {
System.out.println("ApproveButton gained focus.");
}
#Override
public void focusLost(FocusEvent e) {
System.out.println("ApproveButton lost focus.");
}
#Override
public void propertyChange(PropertyChangeEvent e) {
Object o = e.getNewValue();
InputMap map = chooser.getInputMap(
JFileChooser.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
if (o instanceof JButton) {
if ("focusOwner".equals(e.getPropertyName())) {
JButton b = (JButton) o;
String s = b.getText();
boolean inApproved = b == myUI.getApproveButton(chooser);
if (!(s == null || "".equals(s) || inApproved)) {
map.put(enterKey, "cancelSelection");
} else {
map.put(enterKey, "approveSelection");
}
}
}
}
private static class MyChooserUI extends MetalFileChooserUI {
public MyChooserUI(JFileChooser b) {
super(b);
}
#Override
protected JButton getApproveButton(JFileChooser fc) {
return super.getApproveButton(fc);
}
}
public static void main(final String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new FileChooserKeys().create();
}
});
}
}
Related
The following is my program. the goal is to convert from a roman numeral to an Arabic number after a user types in the numeral and presses the enter key.
This is a homework assignment and we are forced to user JTextAreas in lieu of JTextFields.
My error exists on the line: enterRomanNumber.addActionListener(handler);
The error reads:
"The method addActionListener(ArabicToRomanGUI.HandlerForTextArea) is
undefined for the type JTextArea"
I can't seem to figure out why I am getting this error or how to correct it, can someone please advise.
import java.awt.FlowLayout;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.ActionEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextArea;
public class ArabicToRomanGUI extends JFrame
{
private static final long serialVersionUID = 1L;
private JTextArea enterRomanNumber = new JTextArea();
JLabel label = new JLabel();
JPanel panel = new JPanel();
JFrame frame = new JFrame();
//TestArea contructor adds jtextArea to jframe
public ArabicToRomanGUI()
{
super("Convert a Roman Numeral");
setLayout(new FlowLayout());
//Text field to enter a roman numeral
enterRomanNumber = new JTextArea(1,25);
enterRomanNumber.setText("Delete this text and Enter a Roman Numerial Here!");
//enterRomanNumber.setAlignmentX(0);
//enterRomanNumber.setAlignmentY(0);
add(enterRomanNumber);
HandlerForTextArea handler = new HandlerForTextArea();
enterRomanNumber.addActionListener(handler);
}
private class HandlerForTextArea implements ActionListener
{
//used to process textArea events
#Override
public void actionPerformed(ActionEvent e)
{
String userInput = "";
userInput = enterRomanNumber.getText();
userInput = userInput.toUpperCase();
ConversionLogic.ConvertFromRomanToArabic(userInput); //converts user string of Roman numerals to an int in arabic
String arabicNumberAsString = ConversionLogic.getConvertedRomanNumeral();
enterRomanNumber.setText(arabicNumberAsString);
//user pressed enter in JTextField enterNumberField
if(e.getSource() == enterRomanNumber)
{
//enterRomanNumber.setText(arabicNumberAsString);
if (ConversionLogic.getCheckFail() == true)
{
JOptionPane.showMessageDialog(frame, "The Roman Numeral entered is Invalid", "Error", JOptionPane.ERROR_MESSAGE);
}
else
{
JOptionPane.showMessageDialog(frame, "The arabic equivalent is " + arabicNumberAsString + "." , "Conversion Successful", JOptionPane.PLAIN_MESSAGE);
}
}
}
}
}//end inner class TextAreaHandler
For a better answer, see #MadProgrammer 's answer.
My solution:
There is no ActionListener for JTextArea.
So just use KeyListener instead
HandlerForTextArea handler = new HandlerForTextArea();
enterRomanNumber.addKeyListener(handler);
Implements KeyListener
private class HandlerForTextArea implements KeyListener
{
#Override
public void keyPressed(KeyEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void keyReleased(KeyEvent arg0) {
// TODO Auto-generated method stub
if (arg0.getKeyCode() == VK_ENTER){
// TODO Your bussiness
}
}
#Override
public void keyTyped(KeyEvent arg0) {
// TODO Auto-generated method stub
}
}
KeyListeners are NEVER appropriate solutions to use with text components, if you want to be notified when a text component changes, you use a DocumentListener, if you want to change/filter what can be entered into a text component, you use a DocumentFilter, if you need to change a special key, like Enter, you should use a key binding
See How to Use Key Bindings for more details.
One of the problems you could have with KeyListener is not knowing when the key stroke is processed by the text component, in your case, it may not be a major issue, but it could change the way the program works on different platforms.
Instead, you can override the JTextArea's key binding for the Enter key (named insert-break). This provides you with the ability to actually change the behavior of the key stroke, or, in your case, manage how you process the event. For example, this replaces the Action for the text area's Enter key, but retains the previous/default behavior...
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class KeyBindingsTest {
public static void main(String[] args) {
new KeyBindingsTest();
}
public KeyBindingsTest() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JTextArea ta;
public TestPane() {
ta = new JTextArea(10, 20);
ActionMap am = ta.getActionMap();
Action proxy = am.get("insert-break");
am.put("insert-break", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
// You can process the event through the original action BEFORE
// you do your own processing
//proxy.actionPerformed(e);
System.out.println("You pressed the enter key, you can have candy");
// Or you can process the event through the original action AFTER
// you do your own processing, you have now gained control
proxy.actionPerformed(e);
}
});
setLayout(new BorderLayout());
add(ta);
}
}
}
Now, you could even go to the extent for creating your own JTextArea which supported ActionPerformed...
public class ActionableTextArea extends JTextArea {
private String actionCommand;
public ActionableTextArea() {
installKeyBindings();
}
public ActionableTextArea(String text) {
super(text);
installKeyBindings();
}
public ActionableTextArea(int rows, int columns) {
super(rows, columns);
installKeyBindings();
}
public ActionableTextArea(String text, int rows, int columns) {
super(text, rows, columns);
installKeyBindings();
}
public ActionableTextArea(Document doc) {
super(doc);
installKeyBindings();
}
public ActionableTextArea(Document doc, String text, int rows, int columns) {
super(doc, text, rows, columns);
installKeyBindings();
}
protected void installKeyBindings() {
ActionMap am = getActionMap();
Action proxy = am.get("insert-break");
am.put("insert-break", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
// You can process the event through the original action BEFORE
// you do your own processing
//proxy.actionPerformed(e);
fireActionPerformed();
// Or you can process the event through the original action AFTER
// you do your own processing, you have now gained control
proxy.actionPerformed(e);
}
});
}
public void addActionListener(ActionListener listener) {
listenerList.add(ActionListener.class, listener);
}
public void removeActionListener(ActionListener listener) {
listenerList.remove(ActionListener.class, listener);
}
public void setActionCommand(String actionCommand) {
this.actionCommand = actionCommand;
}
public String getActionCommand() {
return actionCommand;
}
protected void fireActionPerformed() {
ActionListener[] listeners = listenerList.getListeners(ActionListener.class);
if (listeners.length > 0) {
ActionEvent evt = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, getActionCommand());
for (ActionListener listener : listeners) {
listener.actionPerformed(evt);
}
}
}
}
Wrting a chat application, I want the user to be able to send images out of his/her clipboard. For this, I would like to catch any CTRL+Vkeyboard input. Since pasting text should be possible as by default, the original ctrl+v-function (pasting text) must not be overridden.
I see can two approaches, of which none works for me:
1st: Taken from the official Java documentation: KEY LISTENER
editorPane.addKeyListener(new KeyListener() {
#Override
public void keyPressed(KeyEvent e) {
e.getKeyChar()
// when I press ctrl+v, ^ this is falsely a white square character, looks like (U+25A1). Plain v without ctrl does work.
e.getKeyCode()
// ^ this is falsely 0
// (e.getModifiersEx() correctly returns InputEvent.CTRL_DOWN_MASK)
}
2nd: KEY BINDING
InputMap iMap = editorPane.getInputMap(condition);
ActionMap aMap = editorPane.getActionMap();
iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_DOWN_MASK), "ctrlV");
aMap.put("ctrlV", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
// works, but overrides natural ctrl+v function!
}
});
Any ideas?
Note: I am using a "foreign" keyboard layout (German). But I can't see why this should make any difference - I would pretty much like to have my application work internationally.
Cheers
edit. Alt+SomeKey however is correctly recognized by the KeyListener
edit2. after changing keyboard layout to US, problem persists.
Stick to Keybindings: KeyListener is a low-level API, while Keybindings will provide you consistent, predictable and robust behaviour.
The solution here is quite easy. You can simply combine the actions yourself by adding a CombinedAction class that will execute the "original" action bound to CTRL+V and the "custom" action you want to execute.
See a small example below combining both actions (here my custom action is a Sysout):
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
public class TestEditorPane {
private JEditorPane editorPane;
public static class CombinedAction implements ActionListener {
private final ActionListener action1;
private final ActionListener action2;
public CombinedAction(ActionListener action1, ActionListener action2) {
super();
this.action1 = action1;
this.action2 = action2;
}
#Override
public void actionPerformed(ActionEvent e) {
if (action1 != null) {
action1.actionPerformed(e);
}
if (action2 != null) {
action2.actionPerformed(e);
}
}
}
public TestEditorPane() {
}
private void initUI() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// JTEXTBOX
editorPane = new JEditorPane();
KeyStroke ctrlV = KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.CTRL_DOWN_MASK);
final ActionListener ctrlVAction = editorPane.getActionForKeyStroke(ctrlV);
editorPane.registerKeyboardAction(new CombinedAction(ctrlVAction, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("This is my action on CTRL+V");
}
}), ctrlV, JComponent.WHEN_FOCUSED);
// JSCROLLPANE
JScrollPane scroll1 = new JScrollPane(editorPane);
scroll1.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scroll1.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
frame.add(scroll1);
frame.setSize(400, 400);
frame.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
TestEditorPane test = new TestEditorPane();
test.initUI();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
So I have a swing application where a button opens up a window. It is pretty simple, to open it I use:
private static logPicker logWindow;
static boolean logViewerOpen = false;
if (!logViewerOpen) {
logWindow = new logPicker();
logWindow.frmOpenLog.setVisible(true);
logViewerOpen = true;
}
else {
logWindow.frmOpenLog.requestFocus();
}
I also have a window listener to know when the viewer is closed:
frmOpenLog.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent arg0) {
indexPage.logViewerOpen = false;
frmOpenLog.dispose();
}
});
I do this because I want to keep track on whether or not the window is already open, because if it is then I have to update information. The window I open has a list of logs that a user can double click on to view the information about that log. The problem right now is, when a user double clicks on the list it gets called however many times I have opened and closed that window. example: I open the log picker window, and then close it. I open it again and double click on the log I want to view, and it will open 2 of those. I have the double click simple do a .doClick() on the Open Log button. The weird thing is, when I use the button to open the log, it does not do this. It will only open the log once. Here is the code for the double click event and the Open Log button.
#Override
public void mouseClicked(MouseEvent arg0) {
if (arg0.getClickCount() == 2) {
btnOpenLog.doClick();
}
}
btnOpenLog.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
logViewer window = new logViewer(log.getSelectedValue());
window.frmLogViewer.setVisible(true);
}
});
#LiverpoolFTW: Please provide a SSCCE demonstrating the problem. Absent sufficient code, I speculate you're (re-)adding the MouseListener/MouseAdapter each time your window is opened. The following example works as desired as-is, incrementing the clickCount once per button press or label double-click. But if you uncomment the indicated section, you'll see that the doClick() is executed twice when you double-click the label. If you have, for example, some component to which you're adding a listener each time the window opens, each of those listeners will be executed.
package example.stackoverflow;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
public class ClickCheck extends JFrame
{
private static final long serialVersionUID = -6446528001976145548L;
private static final JButton btnOpenLog = new JButton("Open Log");
public ClickCheck()
{
JLabel label = new JLabel("Double-Click Me");
label.addMouseListener(new MouseAdapter()
{
#Override
public void mouseClicked(MouseEvent arg0) {
if (arg0.getClickCount() == 2) {
btnOpenLog.doClick();
}
}
});
// Uncomment to demonstrate the effect of multiple listeners
// label.addMouseListener(new MouseAdapter()
// {
// #Override
// public void mouseClicked(MouseEvent arg0) {
// if (arg0.getClickCount() == 2) {
// btnOpenLog.doClick();
// }
// }
// });
btnOpenLog.addActionListener(new ActionListener() {
private int clickCount = 0;
public void actionPerformed(ActionEvent e) {
System.out.println(++clickCount + ": Button clicked");
}
});
setSize(200, 200);
setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
add(btnOpenLog);
add(label);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
ClickCheck c = new ClickCheck();
c.setVisible(true);
}
});
}
}
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);
}
});
}
}
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);
I have a Popup that is shown when a user clicks on a button. I would like to hide the popup when any of the following events occur:
The user clicks somewhere else in the application. (The background panel for example)
The user minimizes the application.
The JPopupMenu has this behavior, but I need more than just JMenuItems. The following code block is a simplified illustration to demonstrate the current usage.
import java.awt.*;
import java.awt.event.ActionEvent;
import javax.swing.*;
public class PopupTester extends JFrame {
public static void main(String[] args) {
final PopupTester popupTester = new PopupTester();
popupTester.setLayout(new FlowLayout());
popupTester.setSize(300, 100);
popupTester.add(new JButton("Click Me") {
#Override
protected void fireActionPerformed(ActionEvent event) {
Point location = getLocationOnScreen();
int y = (int) (location.getY() + getHeight());
int x = (int) location.getX();
JLabel myComponent = new JLabel("Howdy");
Popup popup = PopupFactory.getSharedInstance().getPopup(popupTester, myComponent, x, y);
popup.show();
}
});
popupTester.add(new JButton("No Click Me"));
popupTester.setVisible(true);
popupTester.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Use a JPopupMenu. You can add any component to it, not just menu items.
As pajton noted in a previous comment, Popup is not a JComponent to which listeners can be readily bound. But, as its documentation states, "implementations of Popup are responsible for creating and maintaining their own Components to render [its subject] to the user."
So in using it as your presentation mechanism, your Popup is going to have to present itself as an actual Swing component anyway. Have it register itself to that component. Have it hide itself when the component loses focus.
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.Popup;
public class PopupTester extends JFrame {
private static class MessagePopup extends Popup
implements WindowFocusListener
{
private final JDialog dialog;
public MessagePopup(Frame base, String message) {
super();
dialog = new JOptionPane().createDialog( base, "Message" );
dialog.setModal( false );
dialog.setContentPane( new JLabel( message ) );
}
#Override public void show() {
dialog.addWindowFocusListener( this );
dialog.setVisible( true );
}
#Override public void hide() {
dialog.setVisible( false );
dialog.removeWindowFocusListener( this );
}
public void windowGainedFocus( WindowEvent e ) {
// NO-OP
}
public void windowLostFocus( WindowEvent e ) {
hide();
}
}
public static void main(String[] args) {
final PopupTester popupTester = new PopupTester();
popupTester.setLayout(new FlowLayout());
popupTester.setSize(300, 100);
popupTester.add(new JButton("Click Me") {
#Override
protected void fireActionPerformed(ActionEvent event) {
Point location = getLocationOnScreen();
MessagePopup popup = new MessagePopup( popupTester, "Howdy" );
popup.show();
}
});
popupTester.add(new JButton("No Click Me"));
popupTester.setVisible(true);
popupTester.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
You can add MouseListener to your background panel and hide the popup when somebody clicks on the panel.
To react on application minimization, use WindowListener attached to a JFrame.
Etc, etc. May seem tedious, but surely will work.
Thanks pajton and Noel Ang for getting me pointed in the right direction! Here is the solution that I ended up with. I'm just including it here so that others may benefit from it.
I ended up going with a JWindow since it doesn't get the window decorations but does get focus events.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class PopupTester extends JFrame {
private static class MessagePopup extends Popup implements WindowFocusListener {
private final JWindow dialog;
public MessagePopup(Frame base, JLabel component, int x, int y) {
super();
dialog = new JWindow(base);
dialog.setFocusable(true);
dialog.setLocation(x, y);
dialog.setContentPane(component);
component.setBorder(new JPopupMenu().getBorder());
dialog.setSize(component.getPreferredSize());
dialog.addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
dialog.setVisible(false);
}
}
});
}
#Override
public void show() {
dialog.addWindowFocusListener(this);
dialog.setVisible(true);
}
#Override
public void hide() {
dialog.setVisible(false);
dialog.removeWindowFocusListener(this);
}
public void windowGainedFocus(WindowEvent e) {
// NO-OP
}
public void windowLostFocus(WindowEvent e) {
hide();
}
}
public static void main(String[] args) {
final PopupTester popupTester = new PopupTester();
popupTester.setLayout(new FlowLayout());
popupTester.setSize(300, 100);
popupTester.add(new JButton("Click Me") {
#Override
protected void fireActionPerformed(ActionEvent event) {
Point location = getLocationOnScreen();
int x = (int) location.getX();
int y = (int) (location.getY() + getHeight());
JLabel myComponent = new JLabel("Howdy");
MessagePopup popup = new MessagePopup(popupTester, myComponent, x, y);
popup.show();
}
});
popupTester.add(new JButton("No Click Me"));
popupTester.setVisible(true);
popupTester.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
You could add a FocusListener to your popup-window, and dispose it when it loses focus. However, that will cause you some troubles when the focus loss is due to some other application (new windows comes to the foreground, you switch virtual desktops, etc.)
But perhaps you (a) know that that cannot happen in your case or (b) would want to close the popup in such cases anyway, a focus-based approach may still be interesting to you.
I know this is an old question but I really needed the Popup to work in my case. So I tried a few things and the following is my solution.
Add a FocusListener to the component you add to the popup and program the focusLost event on that component to hide the popup when focus is lost. Call the requestFocus method on your component just after showing the popup.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import javax.swing.*;
public class PopupTester extends JFrame {
JButton myButton = new JButton("Click Me");
JLabel myComponent = new JLabel("Howdy");
Popup popup = null;
public PopupTester() {
setLayout(new FlowLayout());
setSize(300, 100);
add(myButton);
add(new JButton("No Click Me"));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myComponent.addFocusListener(new FocusAdapter() {
public void focusLost(FocusEvent e) {
if (popup != null) {
popup.hide();
}
}
});
myButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
if (popup != null) {
popup.hide();
popup = null;
}
Point location = myButton.getLocationOnScreen();
int y = (int) (location.getY() + myButton.getHeight());
int x = (int) location.getX();
popup = PopupFactory.getSharedInstance().getPopup(PopupTester.this, myComponent, x, y);
popup.show();
myComponent.requestFocus();
}
});
}
public static void main(String[] args) {
PopupTester popupTester = new PopupTester();
popupTester.setVisible(true);
}
}