I want to create a JDialog where the text in the textfields is selected but only if the focus is gained from keyboard (TAB, CTRL+TAB). I have found several topics on this matter but had problems with implementing it.
Here is one which I was trying.
And my code:
public class Dialogg extends JDialog implements FocusListener, MouseListener {
private boolean focusFromMouse = false;
public Dialogg() {
JTextField tf1 = new JTextField("text1");
JTextField tf2 = new JTextField("text2");
tf1.addMouseListener(this);
tf2.addMouseListener(this);
tf1.addFocusListener(this);
tf2.addFocusListener(this);
}
#Override
public void focusGained(FocusEvent e) {
if (!focusFromMouse) {
JTextField tf = (JTextField) e.getComponent();
tf.selectAll();
focusFromMouse = true;
}
}
#Override
public void focusLost(FocusEvent e) {
focusFromMouse = false;
}
#Override
public void mouseClicked(MouseEvent e) {
focusFromMouse = true;
}
}
It does not work as intended, it does not matter what is focus source the text always highlights. When I run the code and follow it step by step it turns out that focusGained code happens before mouseClicked code so the flag is not reset when it should. Any hints?
EDIT:
As suggested by M. Prokhorov I have deleted less relevant (for the question) lines from the code.Thank you.
EDIT 2:
I am trying to wrap focus listener as suggested by camickr. It looks like this now:
tf1.addFocusListener(new FocusAdapter() {
public void focusGained(FocusEvent evt){
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
if (!focusFromMouse){
tf1.selectAll();
focusFromMouse=true;
}
}
});
}
public void focusLost(FocusEvent evt){
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
focusFromMouse=false;
}
});
}
});
public void mouseClicked(MouseEvent e) {
focusFromMouse=true;
I am printing line after each event to see the action order and still mouseClicked happens last. What am I doing wrong?
EDIT 3:
OK, I have found a solution which fulfils requirements of my simple Dialog.
I could not find a way of doing this with use of invokeLater or EventQueue. Vladislav's method works but as I understand it restricts the user to only use the keyboard.
I have used the initial approach but I have added an auxiliary variable and few conditions which allow to pass the flag "unharmed" trough Events that should not change the flag at given moment. It may not be subtle or universal but works for my app. Here is the code:
public void focusGained(FocusEvent e) {
if(!focusFromMouse){
if (higlight){
JTextField tf = (JTextField) e.getComponent();
tf.selectAll();
focusFromMouse=false;
}
}
}
public void focusLost(FocusEvent e) {
if (focusFromMouse){
higlight=false;
focusFromMouse=false;
}else{
higlight=true;
}
}
public void mousePressed(MouseEvent e) {
focusFromMouse=true;
}
At the first, by default, focus on JTextField is requested by mouse-press event, not by mouse-click.
So, this method:
public void mouseClicked(MouseEvent e) {
focusFromMouse = true;
}
is useless because the mouse-click event is triggered after the mouse-press event.
One way to solve your problem is to remove all native MouseListeners from JTextField:
...
for( MouseListener ml : tf1.getMouseListeners() ){
tf1.removeMouseListener(ml);
}
for( MouseMotionListener mml : tf1.getMouseMotionListeners() ){
tf1.removeMouseMotionListener(mml);
}
...
Another way is to handle all mouse events and consume those of them, which are triggered by JTextField:
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
#Override
public void eventDispatched(AWTEvent event) {
if( event.getSource() == tf1 ){
((MouseEvent)event).consume();
}
}
}, AWTEvent.MOUSE_EVENT_MASK);
When I run the code and follow it step by step it turns out that focusGained code happens before mouseClicked
Wrap the code in the FocusListener in a SwingUtilities.invokeLater(). The will place the code on the end of the Event Dispatch Thread (EDT), so the code will run after the variable in the MouseListener has been set.
See Concurrency in Swing for more information about the EDT.
Edit:
Just noticed the other answer. You might be able to do something simpler. Istead of listener for mouseClicked, listen for mousePressed. A mouseClicked event is only generated AFTER the mouseReleased event, so by that time the FocusListener logic has already been executed, even when added to the end of the EDT.
Edit 2:
If the above doesn't work then you might be able to use the EventQueue.peek() method to see if a MouseEvent is on the queue. This might even be easier than worrying about using the invokeLater.
Related
The cells in my JTable become editable only on the second click. When I debugged I noticed that on the second click the mouse released event is not fired. I saw a lot of answers for this problem with create a setSingleClick(1)... but it doesn't to work. I think that if i can get that second mouseReleased event to get fire i might be able to make it work. Does anybody has any sugestions?
table.addMouseListener(new TableMouseListener()) ;
class TableMouseListener extends MouseAdapter{
public void mousePressed(MouseEvent e) {
System.out.println("mousePressed");
}
public void mouseClicked(MouseEvent e) {
System.out.println("mouseClicked");
}
public void mouseReleased(MouseEvent e) {
System.out.println("mouseReleased");
}
}
Try something like this:
container_table.addMouseListener(new MouseAdapter() {
public void mouseClicked (MouseEvent me) {
if (me.getClickCount() == 2) {
//Double clicked
}
}
});
This way, you know that the 'container_table' has been clicked twice, and then you can get the selected row, and make things with it.
Hope it helps.
I am trying to extend JXDatePicker so that it opens up when it gains focus.
Have searched for suggest that I understand without success.
Is there an elegant way of doing this?
Astonishingly, it's not really possible :-(
For once, the JXDatePicker itself has no api to show/hide the popup (only BasicDatePickerUI has). Plus the ui delegate has some internal magic (read: hacks ... cough) that makes a FocusListener even worse to handle than usually in compound components.
A snippet to play with:
final JXDatePicker picker = new JXDatePicker();
FocusListener l = new FocusListener() {
#Override
public void focusGained(FocusEvent e) {
// no api on the picker, need to use the ui delegate
BasicDatePickerUI pickerUI = (BasicDatePickerUI) picker.getUI();
if (!pickerUI.isPopupVisible()) {
pickerUI.toggleShowPopup();
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// opening the popup moves the focus to ... ?
// need to grab it back onto the editor
picker.getEditor().requestFocusInWindow();
}
});
}
#Override
public void focusLost(FocusEvent e) {
}
};
// need to register the listener on the editor
picker.getEditor().addFocusListener(l);
JComponent content = new JPanel();
content.add(new JButton("dummy"));
content.add(picker);
Not really satisfying, as automatic closing of the popup on transfering the focus out again doesn't work reliably, needs two tabs (don't know why)
I had the same problem.
This worked for me:
jXDatePicker.getEditor().addFocusListener(new FocusListener() {
#Override
public void focusGained(FocusEvent e) {
BasicDatePickerUI pickerUI = (BasicDatePickerUI) jXDatePicker.getUI();
if (!pickerUI.isPopupVisible() && e.getOppositeComponent() != getRootPane() && e.getOppositeComponent() != jXDatePicker.getMonthView()) {
pickerUI.toggleShowPopup();
}
}
#Override
public void focusLost(FocusEvent e) {}
});
This piece of code is used to avoid focus issues:
e.getOppositeComponent() != getRootPane()
I'm making a level editor for my game. I have a property panel where I can modify the selected object its properties. I also have a Save button to write the level xml.
A field-edit is submitted(*) when the editor component lost the focus or Enter is pressed. This is working great, but the only problem is that when I have this sequence of actions:
Edit a field
Press the save button
Because, what happens is this:
I edit the field
I press the save button
The level is saved
The field lost the focus
The edit is submitted
As you can see, this is the wrong order. Of course I want the field to lose its focus, which causes the submit and then save the level.
Is there a trick, hack or workaround to make the field first lose the focus and then perform the action listener of the save button?
Thanks in advance.
(* submit = the edit to the field is also made in the object property)
EDIT: For the field I'm using a FocusAdapter with focusLost:
FocusAdapter focusAdapter = new FocusAdapter()
{
#Override
public void focusLost(FocusEvent e)
{
compProperties.setProperty(i, getColor());
record(); // For undo-redo mechanism
}
};
And for the button a simple ActionListener with actionPerformed`.
btnSave.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
// Save the level
}
});
Hmm ... can't reproduce: in the snippet below the lost is always notified before the actionPerfomed, independent on whether I click the button or use the mnemonic:
final JTextField field = new JTextField("some text to change");
FocusAdapter focus = new FocusAdapter() {
#Override
public void focusLost(FocusEvent e) {
LOG.info("lost: " + field.getText());
}
};
field.addFocusListener(focus);
Action save = new AbstractAction("save") {
#Override
public void actionPerformed(ActionEvent e) {
LOG.info("save: " + field.getText());
}
};
save.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_S);
JButton button = new JButton(save);
JComponent box = Box.createHorizontalBox();
box.add(field);
box.add(button);
On the other hand, focus is a tricky property to rely on, the ordering might be system-dependent (mine is win vista). Check how the snippet behave on yours.
If you see the same sequence as I do, the problem is somewhere else
if you get the save before the lost, try to wrap the the save action into invokeLater (which puts it at the end of the EventQueue, so it's executed after all pending events)
Action save = new AbstractAction("save") {
#Override
public void actionPerformed(ActionEvent e) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
LOG.info("save: " + field.getText());
}
});
}
};
Normally, wrapping your save code into an SwingUtilities.invokeLater() should do the trick. As you already mentioned, this doesn't work? Try this:
private boolean editFocus = false;
FocusAdapter focusAdapter = new FocusAdapter()
{
#Override
public void focusGained(FocusEvent e){
editFocus = true;
}
#Override
public void focusLost(FocusEvent e){
compProperties.setProperty(i, getColor());
record(); // For undo-redo mechanism
editFocus = false;
if (saveRequested){
save();
}
}
};
and for your button:
private boolean saveRequested = false;
btnSave.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
if (editFocus){
saveRequested = true;
return;
} else {
save();
}
}
});
and then your save method:
private void save(){
// do your saving work
saveRequested = false;
}
This only works when your focusLost gets called after your button's action. If suddenly the order is correct, this code will get save() called twice.
But again, wrapping your save() code in your original approach should work, because the save code will execute after processing all events. That is after processing your button click and your focusLost events. Because your focusLost code executes immediately (it's not wrapped in an invokeLater()), the focusLost code should be executed always before your save code. This does not mean that the event order will be correct! But the code associated to the events will executed in the right order.
Is there a way to close a JDialog through code such that the Window event listeners will still be notified? I've tried just setting visible to false and disposing, but neither seem to do it.
Closing a window (with dispose()) and hiding it (with setVisible(false)) are different operations, and produce different events -- and closing it from the operating system is yet another different operation that produces yet a different event.
All three will produce windowDeactivated to tell you the window's lost focus, but dispose() will then produce windowClosed, while closing from the OS will first produce windowClosing. If you want to handle both of these the same way, you can set the window to be disposed when closed:
window.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
In general, setVisible(false) implies that you might want to use the window again, so it doesn't post any window events (apart from windowDeactivated). If you want to detect the hiding of a window, you need to use a ComponentListener;
window.addComponentListener(new ComponentAdapter() {
#Override
public void componentHidden(ComponentEvent e) {
System.out.println("componentHidden()");
}
})
Note though that this will pretty much only work for explicit setVisible() calls. If you need to detect hiding more generally, you can use a HierarchyListener, but it's probably more trouble than it's worth.
window.addHierarchyListener(new HierarchyListener() {
#Override
public void hierarchyChanged(HierarchyEvent e) {
System.out.println("valid: " + window.isValid());
System.out.println("showing: " + window.isShowing());
}
});
Note that when you dispose a window you'll get a couple of HierarchyEvents, first for hiding and then for invalidation, but when you hide it with setVisible() it's still valid, so you won't get the invalidation.
I don't seem to have your problem. When I use the code below windowDeactivated() is called for either setVisible( false ) or dispose() and windowClosed() is also called for dispose().
ClosingDialog.java:
public class ClosingDialog extends JDialog {
public ClosingDialog(Frame owner, String title, boolean modal) {
super(owner, title, modal);
JPanel contentPanel = (JPanel) this.getContentPane();
JButton setVisButton = new JButton("setVisible( false )");
setVisButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
ClosingDialog.this.setVisible(false);
}
});
JButton disposeButton = new JButton("dispose()");
disposeButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
ClosingDialog.this.dispose();
}
});
contentPanel.setLayout(new FlowLayout());
contentPanel.add(setVisButton);
contentPanel.add(disposeButton);
this.addWindowListener(new WindowListener() {
public void windowActivated(WindowEvent e) {
System.out.println("windowActivated");
}
public void windowClosed(WindowEvent e) {
System.out.println("windowClosed");
}
public void windowClosing(WindowEvent e) {
System.out.println("windowClosing");
}
public void windowDeactivated(WindowEvent e) {
System.out.println("windowDeactivated");
}
public void windowDeiconified(WindowEvent e) {
System.out.println("windowDeiconified");
}
public void windowIconified(WindowEvent e) {
System.out.println("windowIconified");
}
public void windowOpened(WindowEvent e) {
System.out.println("windowOpened");
}
});
this.setSize(300, 300);
}
}
Dispatch a windowClosing event to the Window. Check out the ExitAction example from the Closing an Application entry.
Untested suggestion:
Have you tried getWindowListeners() and then iterating around to fire windowClosed() to each of the WindowListeners?
EDIT: the above suggestion is wrong. Keeping it for posterity.
I'm afraid calling dialog.dispose() works fine for me in my simple example.
I wanted to fire a windowClosing event from the code (just as if the user clicked the X), because I have an extra close button in the JDialog and want the same WindowListener (that I implemented using a WindowAdapter) to be run when the X is clicked and when the button is clicked. Running dispose() only fires windowClosed, not windowClosing, and I want a message to appear before the window is closed, for confirmation. I also didn't manage to fire windowClosing via JDialog's method processWindowEvent since it is protected.
Here is how I got it working though:
WindowAdapter adapter = (WindowAdapter)jdialog.getWindowListeners()[0];
adapter.windowClosing(new WindowEvent((Window)jdialog, WindowEvent.WINDOW_CLOSING));
Hope that helps someone.
I needed a JButton with an attached dropdown style menu. So I took a JPopupMenu and attached it to the JButton in the way you can see in the code below. What it needs to do is this:
show the popup when clicked
hide it if clicked a second time
hide it if an item is selected in the popup
hide it if the user clicks somewhere else in the screen
These 4 things work, but because of the boolean flag I'm using, if the user clicks somewhere else or selects an item, I have to click twice on the button before it shows up again. That's why I tried to add a FocusListener (which is absolutely not responding) to fix that and set the flag false in these cases.
EDIT: Last attempt in an answer post...
Here are the listeners: (It's in a class extending JButton, so the second listener is on the JButton.)
// Show popup on left click.
menu.addFocusListener(new FocusListener() {
#Override
public void focusLost(FocusEvent e) {
System.out.println("LOST FOCUS");
isShowingPopup = false;
}
#Override
public void focusGained(FocusEvent e) {
System.out.println("GAINED FOCUS");
}
});
addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("isShowingPopup: " + isShowingPopup);
if (isShowingPopup) {
isShowingPopup = false;
} else {
Component c = (Component) e.getSource();
menu.show(c, -1, c.getHeight());
isShowingPopup = true;
}
}
});
I've been fighting with this for way too long now. If someone can give me a clue about what's wrong with this, it would be great!
Thanks!
Code:
public class Button extends JButton {
// Icon.
private static final ImageIcon ARROW_SOUTH = new ImageIcon("ArrowSouth.png");
// Unit popup menu.
private final JPopupMenu menu;
// Is the popup showing or not?
private boolean isShowingPopup = false;
public Button(int height) {
super(ARROW_SOUTH);
menu = new JPopupMenu(); // menu is populated somewhere else
// FocusListener on the JPopupMenu
menu.addFocusListener(new FocusListener() {
#Override
public void focusLost(FocusEvent e) {
System.out.println("LOST FOCUS");
isShowingPopup = false;
}
#Override
public void focusGained(FocusEvent e) {
System.out.println("GAINED FOCUS");
}
});
// ComponentListener on the JPopupMenu
menu.addComponentListener(new ComponentListener() {
#Override
public void componentShown(ComponentEvent e) {
System.out.println("SHOWN");
}
#Override
public void componentResized(ComponentEvent e) {
System.out.println("RESIZED");
}
#Override
public void componentMoved(ComponentEvent e) {
System.out.println("MOVED");
}
#Override
public void componentHidden(ComponentEvent e) {
System.out.println("HIDDEN");
}
});
// ActionListener on the JButton
addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("isShowingPopup: " + isShowingPopup);
if (isShowingPopup) {
menu.requestFocus();
isShowingPopup = false;
} else {
Component c = (Component) e.getSource();
menu.show(c, -1, c.getHeight());
isShowingPopup = true;
}
}
});
// Skip when navigating with TAB.
setFocusable(true); // Was false first and should be false in the end.
menu.setFocusable(true);
}
}
Here's a variant of Amber Shah's "big hack" suggestion I just made. Without the isShowingPopup flag...
It's not bulletproof, but it works quite well until someone comes in with an incredibly slow click to close the popup (or a very fast second click to reopen it...).
public class Button extends JButton {
// Icon.
private static final ImageIcon ARROW_SOUTH = new ImageIcon("ArrowSouth.png");
// Popup menu.
private final JPopupMenu menu;
// Last time the popup closed.
private long timeLastShown = 0;
public Button(int height) {
super(ARROW_SOUTH);
menu = new JPopupMenu(); // Populated somewhere else.
// Show and hide popup on left click.
menu.addPopupMenuListener(new PopupMenuListener() {
#Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent arg0) {
timeLastShown = System.currentTimeMillis();
}
#Override public void popupMenuWillBecomeVisible(PopupMenuEvent arg0) {}
#Override public void popupMenuCanceled(PopupMenuEvent arg0) {}
});
addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if ((System.currentTimeMillis() - timeLastShown) > 300) {
Component c = (Component) e.getSource();
menu.show(c, -1, c.getHeight());
}
}
});
// Skip when navigating with TAB.
setFocusable(false);
}
}
As I said in comments, that's not the most elegant solution, but it's horribly simple and it works in 98% of the cases.
Open to suggestions!
Here is another approach which is not too bad of a hack, if not elegant, and which, as far as I could tell, works. First, at the very top, I added a second boolean called showPopup.
The FocusListener has to be as follows:
menu.addFocusListener(new FocusListener() {
#Override
public void focusLost(FocusEvent e) {
System.out.println("LOST FOCUS");
isShowingPopup = false;
}
#Override
public void focusGained(FocusEvent e) {
System.out.println("GAINED FOCUS");
isShowingPopup = true;
}
});
The isShowingPopup boolean does not get changed anywhere else--if it gains focus, it assumes it's shown and if it loses focus, it assumes it isn't.
Next, the ActionListener on the button is different:
addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("isShowingPopup: " + isShowingPopup);
if (showPopup) {
Component c = (Component) e.getSource();
menu.show(c, -1, c.getHeight());
menu.requestFocus();
} else {
showPopup = true;
}
}
});
Now comes the really new bit. It's a MouseListener on the button:
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
System.out.println("ispopup?: " + isShowingPopup);
if (isShowingPopup) {
showPopup = false;
}
}
#Override
public void mouseReleased(MouseEvent e) {
showPopup = true;
}
});
Basically, mousePressed gets called before the menu loses focus, so isShowingPopup reflects whether the popup was shown before the button is pressed. Then, if the menu was there, we just set showPopup to false, so that the actionPerformed method does not show the menu once it gets called (after the mouse is let go).
This behaved as expected in every case but one: if the menu was showing and the user pressed the mouse on the button but released it outside of it, actionPerformed was never called. This meant that showPopup remained false and the menu was not shown the next time the button was pressed. To fix this, the mouseReleased method resets showPopup. The mouseReleased method gets called after actionPerformed, as far as I can tell.
I played around with the resulting button for a bit, doing all the things I could think of to the button, and it worked as expected. However, I am not 100% sure that the events will always happen in the same order.
Ultimately, I think this is, at least, worth trying.
You could use the JPopupMenu.isVisible() instead of your Boolean variable to check the current state of the popup menu.
Have you tried adding a ComponentListener to the JPopupMenu, so that you know when it's been shown and hidden (and update your isShowingPopup flag accordingly)? I'm not sure listening for focus changes is necessarily the right approach.
What you need is a PopupMenuListener:
menu.addPopupMenuListener(new PopupMenuListener() {
#Override
public void popupMenuWillBecomeVisible(PopupMenuEvent arg0) {
}
#Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent arg0) {
System.out.println("MENU INVIS");
isShowingPopup = false;
}
#Override
public void popupMenuCanceled(PopupMenuEvent arg0) {
System.out.println("MENU CANCELLED");
isShowingPopup = false;
}
});
I inserted this into your code and verified that it works.
Well, I can't be sure without seeing all of your code, but is it possible that the popup never actually gets focus at all? I've had problems with things' not getting focus properly in Swing before, so it could be the culprit. Try calling setFocusable(true) on the menu and then calling requestFocus() when you make the menu appear.
I tried the Answer of Tikhon Jelvis (introducing a smart combination of focusListener and mouseListener). It does not work for me on Linux (Java7/gtk). :-(
Reading http://docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html#requestFocus%28%29 there is written "Note that the use of this method is discouraged because its behavior is platform dependent."
It may be that the order of listener calls changed with Java7 or it changes with GTK vs Windows. I would not recommend this solution if you want to be platform independent.
BTW: I created a new account on stackoverflow to give this hint. It seems I am not allowed to comment to his answer (because of reputation). But it seems I have a button to edit it. This stackoverflow is a very funny thing. :-)