Reputation: 527
I have a system where I need to be able to ask a simple "continue?" style question. There is a text field, and a button. Either modify the text field in isolation, or pressing the button in isolation and I will need to be able to ask a user to confirm. So, that gives me a focus listener on the text field and an action listener on the button.
However, if I edit the field, and immediately click the button, two confirmation dialogs will pop up - the one from the focus listener and then the one from the action listener immediately after. Now, I obviously only need confirmation once, so I tried synchronizing the function that throws up the JOptionPane so it doesn't get entered twice, but that seems to have no effect.
Some code demonstrating this:
public class Main extends JFrame {
static public void main(String [] args) {
SwingUtilities.invokeLater(() -> {
Main m = new Main();
m.setVisible(true);
});
}
private JTextField field;
private JButton button;
private boolean isValid;
public Main() {
isValid = false;
setLayout(new BorderLayout());
field = new JTextField(15);
field.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent arg0) {}
@Override
public void focusLost(FocusEvent e) {
doValidate();
}
});
add(field, BorderLayout.CENTER);
button = new JButton("Push Me");
button.addActionListener((ActionEvent e) -> {
doValidate();
doAction();
});
add(button, BorderLayout.SOUTH);
pack();
}
private synchronized void doValidate() {
System.out.println("Validating");
if(!isValid) {
int answer = JOptionPane.showConfirmDialog(this, "Really do it?");
if(answer == JOptionPane.YES_OPTION)
isValid = true;
}
}
private void doAction() {
System.out.println("Action done!");
}
}
First off, this is somewhat surprising behavior. I'd love if anyone has an explanation as to how Swing/JOptionPane is able to circumnavigate the synchronized keyword.
As for how to deal with, my thinking is that I need to implement a thread that waits for the answer to the confirm dialog. Then, I need to check if the thread is active or not (if the dialog is already up, that is), and if it is up I need to add a listener for when the answer is given. I'd already have gone ahead and done this, but it feels like way over-engineering a solution for the problem, especially since I have such limited understanding of why this is happening in the first place. Further, I can already imagine at least a few different cases where I'd end up in trouble, making it probably more error-prone.
===EDIT===
To be clear, these message boxes pop up at the same time, not in sequence. For example:
Upvotes: 1
Views: 101
Reputation: 573
I recommend adding the following so that clicking the red x will exit the program.
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); //new
Please consider that you can get your desired behavior simply by removing the button listener.
Here is the code that I believe gets you the behavior that you want:
package swingdemo1;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class SwingDemo1 extends JFrame {
static public void main(String [] args) {
SwingUtilities.invokeLater(() -> {
SwingDemo1 m = new SwingDemo1();
m.setVisible(true);
});
}
private JTextField field;
private JButton button;
private boolean isValid;
private boolean validationDone;
String state;
public SwingDemo1() {
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); //new
isValid = false;
setLayout(new BorderLayout());
field = new JTextField(15);
field.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent arg0) {
}
@Override
public void focusLost(FocusEvent e) {
doValidate();
doAction();
}
});
add(field, BorderLayout.CENTER);
button = new JButton("Push Me");
/* //new
button.addActionListener((ActionEvent e) -> {
System.out.println("ActionEvent: " + e.getActionCommand());
doValidate();
doAction();
});
*/ //new
add(button, BorderLayout.SOUTH);
pack();
}
private synchronized void doValidate() {
System.out.println("Validating");
if(!isValid) {
int answer = JOptionPane.showConfirmDialog(this, "Really do it?");
if(answer == JOptionPane.YES_OPTION)
isValid = true;
}
}
private void doAction() {
System.out.println("Action done!");
}
}
Upvotes: 0
Reputation: 7267
Synchronised means that 2 threads cannot enter the block at the same time, not ever. They will simply wait for other threads to release their lock on that method and then call it. Hence your 2 dialogs.
This probably isn't relevant anyway, since I think the UI render is the same thread so you're just calling the same method twice from 2 separate UI events (I may be wrong on this).
As for getting it working, don't worry about threads, instead look to the DocumentListener
.
Upvotes: 1