Hi take a look on this fragment of code. My aim is to make my app reacting when i will type in a textarea one of words listed in slowa[]. As u can see i created inputMethodListner however when i type whatever word it is working at all. i tryied to put a debug prints to see what is going on and i see that neither method inputMethodTextChanged() nor inputMethodTextChanged() is called even once:( what im doing wrong?
import java.awt.*;
import java.awt.event.InputMethodEvent;
import java.awt.event.InputMethodListener;
import javax.swing.*;
public class BrzydkieSlowa extends JFrame {
static String[] slowa = {"shit", "fuck"};
private BrzydkieSlowa(){
//Create and set up the window.
JFrame frame = new JFrame("Brzydkie slowa");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JTextArea textArea1 = new JTextArea(10,10);
textArea1.addInputMethodListener(new InputMethodListener() {
#Override
public void caretPositionChanged(InputMethodEvent arg0) {
int brzydkie = 0;
int i = 0;
while(brzydkie == 1 || i > 1){
if(textArea1.getText().compareTo(slowa[i])== 0)
brzydkie = 0;
i++;
}
if(brzydkie == 1)
JOptionPane.showMessageDialog(null, "brzydkie slowo");
}
#Override
public void inputMethodTextChanged(InputMethodEvent event) {
// TODO Auto-generated method stub
}
});
frame.getContentPane().add(textArea1, BorderLayout.CENTER);
//Display the window.
frame.setLocationRelativeTo(null);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
new BrzydkieSlowa();
}
}
You should use DocumentListener instead.
Try
textArea1.getDocument().addDocumentListener(new DocumentListener() {
#Override
public void insertUpdate(DocumentEvent e) {
String text = textArea1.getText();
int firstOccurence = StringUtils.indexOfAny(text, slowa);
if (firstOccurence > -1) {
/* do something */
}
}
#Override
public void removeUpdate(DocumentEvent e) {/* do nothing */ }
#Override
public void changedUpdate(DocumentEvent e) { /* do nothing */ }
});
Please notify that How to "Write a Document Listener" warns against modifying text:
Document listeners should not modify the contents of the document; The change is already complete by the time the listener is notified of the change. Instead, write a custom document that overrides the insertString() or remove() methods, or both.
There are some examples for a Document model which might help you. They included approaches for filter and undo changes.
Related
I want two textfields (from now A and B) sharing the same content as the user inputs on any of them. I can make one mirror the other (B mirrors A) or the opposite (A mirrors B). But when I keep both DocumentListeners the execution starts to throw Exceptions.
According to the Oracle's Docs I can't use a DocumentListener to mutate the content of a document from within the Listener itself. Which I find weird since I already did it in the first case (B mirrors A) or the opposite case. Anyway the code still "works" but with an Exception thrown every two events triggered.
KeyListeners are not reliable for this particular case and I refuse to use buttons because I like the real-time look DocumentListener gives.
Any suggestions?
Here's my code:
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
public class Mirror {
private JTextField oriText;
private JTextField mirrorText;
private static int debugCounter; //Counts the times an Event is Triggered.
public static void main(String[] args) {
Mirror gui = new Mirror();
gui.build();
}
public void build(){
JFrame frame = new JFrame();
JPanel panel = new JPanel();
panel.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
frame.getContentPane().add(panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
c.gridx = 0;
c.gridy = 0;
JLabel original = new JLabel("Original");
panel.add(original, c);
c.gridy = 1;
oriText = new JTextField(10);
panel.add(oriText,c);
c.gridy = 2;
JLabel mirror = new JLabel("Mirror");
panel.add(mirror, c);
c.gridy = 3;
mirrorText = new JTextField(10);
panel.add(mirrorText, c);
mirrorText.getDocument().addDocumentListener(new MyDocumentListenerII()); // Comment this line to see only the 1st Case (B mirrors A)
oriText.getDocument().addDocumentListener(new MyDocumentListener()); // Comment this line to see only the 2nd Case (A mirrors B)
frame.pack();
frame.setVisible(true);
}
class MyDocumentListener implements DocumentListener{
#Override
public void changedUpdate(DocumentEvent e) {
//Does nothing.
}
#Override
public void insertUpdate(DocumentEvent e) {
mirror();
}
#Override
public void removeUpdate(DocumentEvent e) {
mirror();
}
}
class MyDocumentListenerII implements DocumentListener{
#Override
public void changedUpdate(DocumentEvent e) {
// Does nothing.
}
#Override
public void insertUpdate(DocumentEvent e) {
mirror1();
}
#Override
public void removeUpdate(DocumentEvent e) {
mirror1();
}
}
public void mirror(){
if (!oriText.getText().equals(mirrorText.getText())){ //Without this each Event trigger the other in some sort of Paradoxical cycle.
mirrorText.setText(oriText.getText());
debugCounter++;
System.out.println(debugCounter+" events triggered");
}
}
public void mirror1(){
if (!mirrorText.getText().equals(oriText.getText())){
oriText.setText(mirrorText.getText());
debugCounter++;
System.out.println(debugCounter+" events triggered");
}
}
}
The problem you're having is that since both JTextFields need to be sync, each field's DocumentListener needs to update the other field. However, that update causes the other DocumentListener to attempt to update the first field, causing the thrown IllegalStateException, since something is attempting to modify the field while a DocumentListener for it is executing.
The solution is to block calls to setText(String) when a DocumentListener is being executed for that field. This can be done with boolean variables. Below is the code for one of the DocumentListeners:
textFieldA.getDocument().addDocumentListener(new DocumentListener() {
#Override
public void removeUpdate (DocumentEvent e) {
blockA = true;
if (!blockB) textFieldB.setText(textFieldA.getText());
blockA = false;
}
#Override
public void insertUpdate (DocumentEvent e) {
blockA = true;
if (!blockB) textFieldB.setText(textFieldA.getText());
blockA = false;
}
#Override
public void changedUpdate (DocumentEvent e) {
blockA = true;
if (!blockB) textFieldB.setText(textFieldA.getText());
blockA = false;
}
});
where blockA and blockB are boolean instance fields (or possibly final variables in the method). Now, when a method fires in textFieldA's DocumentListener, a flag is set to indicate not to use textFieldA.setText(String). We, also see how textFieldB.setText(String) is blocked when blockB is set. The DocumentListener for textFieldB looks about the same.
Now, textFieldA won't be set during a call inside its DocumentListener, same for the other field. Such a call would be redundant anyway: the text of each field would be same at that point.
I'm not entirely sure this is the 'correct' way to do it, but all I did was to attach the same Document to each JTextField.
Here's the relevant part from my initializer:
textFieldA = new JTextField();
textFieldA.setBounds(10, 11, 414, 20);
textFieldA.setColumns(10);
textFieldB = new JTextField();
textFieldB.setBounds(10, 42, 414, 20);
textFieldB.setColumns(10);
textFieldB.setDocument(textFieldA.getDocument());
Now the JTextFields both have the same document, so they can't be different.
I want to write a live search using Swing components. I am using a keyListener to keep track of the input. Basically i dont want the keyListener to take action every time a button is pressed but instead wait (for some period of time) for more incoming input. This period of time is refreshed every time a button is pressed and the input gets evaluated when it eventually times out (e.g. no button is being pressed within the period meaning that the input is complete). How do I implement that into my keyListener?
Code snippet of main method:
static JTextField nameTextField = new JTextField();
public static void main(String args[]) throws Exception {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(nameTextField, BorderLayout.NORTH);
nameTextField.addKeyListener(new KeyListener() {
public void keyTyped(KeyEvent keyEvent) {
//
}
#Override
public void keyPressed(KeyEvent e) {
//
}
#Override
public void keyReleased(KeyEvent e) {
if(waitForMoreInput(50)) {
doSomething(nameTextField.getText());
}
}
}
}
}
);
frame.setSize(250, 100);
frame.setVisible(true);
}
Thanks in advance
Much better is for you to use a DocumentListener or DocumentFilter, depending on if you want to listen before or after text has been fully registered with the text component.
The DocumentListener will register any time the text has changed, be it via a key press, via a copy and paste, via a deletion of text. The Timer will then wait however long you wish to do whatever action is required on the text. For example:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
public class DocListenerFoo extends JPanel {
private JTextField nameTextField = new JTextField(20);
public DocListenerFoo() {
add(new JLabel("Add Text:"));
add(nameTextField);
int timerDelay = 1000; // one second
nameTextField.getDocument().addDocumentListener(new MyDocListener(timerDelay));
}
private class MyDocListener implements DocumentListener {
private Timer docTimer;
private int timerDelay;
public MyDocListener(int timerDelay) {
this.timerDelay = timerDelay;
}
#Override
public void changedUpdate(DocumentEvent e) {
textChangedAction(e);
}
#Override
public void insertUpdate(DocumentEvent e) {
textChangedAction(e);
}
#Override
public void removeUpdate(DocumentEvent e) {
textChangedAction(e);
}
private void textChangedAction(DocumentEvent e) {
Document doc = e.getDocument();
try {
String text = doc.getText(0, doc.getLength());
if (docTimer != null && docTimer.isRunning()) {
docTimer.stop();
}
docTimer = new Timer(timerDelay, new TimerListener(text));
docTimer.setRepeats(false);
docTimer.start();
} catch (BadLocationException e1) {
e1.printStackTrace();
}
}
}
private class TimerListener implements ActionListener {
private String text;
public TimerListener(String text) {
this.text = text;
}
#Override
public void actionPerformed(ActionEvent e) {
// TODO do check on text here
System.out.println("Checking text here: " + text);
}
}
private static void createAndShowGui() {
DocListenerFoo mainPanel = new DocListenerFoo();
JFrame frame = new JFrame("DocListenerFoo");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Don't wait inside the key or document event, it just blocks the program from being processed further. Instead save the current time or (re)start a timer in the event and execute your action later somewhere else.
I'm guessing that you're trying to use a KeyListener with a Swing text component such as a JTextField (I have to guess since you don't tell or show us). If so, then the best solution is don't. Using a KeyListener with these components can mess up the functionality of the components. Much better is for you to use a DocumentListener or DocumentFilter, depending on if you want to listen before or after text has been fully registered with the text component.
For a better more complete answer, post a better more complete question, including your minimal code example and details about your problem.
I added one MouseMotionListener to the JTextField. But when I use jf.getMouseMotionListeners().length to know about how many mouse listeners are registered then I get 3!!. That's causing me some problem because I'm trying to add a listener depending on that length. If its zero I add or else I don't want to add a listener.
Below is the code and I have written code to know the length in mouse moved event.
public static void main(String args[]) {
JFrame fr = new JFrame();
final JTextPane jf = new JTextPane ();
jf.addMouseMotionListener(new MouseMotionListener() {
#Override
public void mouseMoved(MouseEvent arg0) {
System.out.println(jf.getMouseListeners().length);
}
#Override
public void mouseDragged(MouseEvent arg0) {
// TODO Auto-generated method stub
}
});
jf.setBounds(30,30,100,50);
fr.setSize(new Dimension(500, 500));
fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
fr.setLayout(null);
fr.add(jf);
fr.setVisible(true);
}
Why is that length 3 when I added just one listener?
is there a better way to check whether a listener on a component is registered or not?
EDIT
Here's what I'm trying to do.
I should be able to drag the JTextPane any where inside window and I should be able to edit it by double clicking on the JTextPane .
If I have a drag listener while editing and If wan't to select a text to style it the JTextPane gets dragged instead of selecting a text from JTextPane.
Now I want to remove the motionlistener when I am in editing mode and add it when I'm not editing.
So thats why I'm trying to remove it or in simple I need to disable the motionlistener.
output is correct, JTextField has another notifiers implemented API, they are notified from added MouseListener
see whats debuger returns
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class MouseAndJTextField {
private JFrame fr = new JFrame();
private JTextField jf = new JTextField(20);
public MouseAndJTextField() {
jf.addMouseListener(new MouseListener() {
#Override
public void mouseReleased(MouseEvent arg0) {
}
#Override
public void mousePressed(MouseEvent arg0) {
}
#Override
public void mouseExited(MouseEvent arg0) {
}
#Override
public void mouseEntered(MouseEvent arg0) {
}
#Override
public void mouseClicked(MouseEvent arg0) {
System.out.println(jf.getMouseListeners().length);
}
});
fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
fr.add(jf);
fr.pack();
fr.setVisible(true);
}
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
MouseAndJTextField fs = new MouseAndJTextField();
}
});
}
}
You can define your class (inner class) instead of the Anonymous inner class for the listener and go through the listeners calling instanceof to find whether your instance is added.
Alternatively you can define a flag indicating whether the listener should process event or not. Set the flag by default to true. Set to false when you have to skip event (in your edit mode) and reset after.
I made a button and did a .setText() on it because I have to compare the value of the .setText() with something else.
I applied the .setText() to a JButton, but I don't want the text to be visible in my button.
If I do setVisible(false) then it hides the whole button, but I only want it to hide the text.
Is there an option for this? I've considered making a custom font and apply it on the text in the .setText() but I'm wondering if there's a more efficient option to my problem.
Thanks in advance guys.
EDIT: I can't use .setText(" ") because I have to compare the value within it.
You state:
EDIT: I can't use .setText(" ") because I have to compare the value within it.
Nonsense. As I've mentioned in a comment, set the JButton's text to " ", and don't use the JButton's text for comparison. Instead use its actionCommand easily obtained via getActionCommand(). Or use a HashMap<JButton, SomethingElse>.
You may consider changing the JButton's Action when you need to change its behavior and state which is easily done by calling setAction(...)
For example,
import java.awt.event.ActionEvent;
import javax.swing.*;
public class ButtonActions {
private static void createAndShowGui() {
JPanel mainPanel = new JPanel();
JButton myButton = new JButton();
StartAction startAction = new StartAction();
PauseAction pauseAction = new PauseAction();
BlankAction blankAction = new BlankAction();
startAction.setNextAction(pauseAction);
pauseAction.setNextAction(blankAction);
blankAction.setNextAction(startAction);
myButton.setAction(startAction);
mainPanel.add(myButton);
JFrame frame = new JFrame("ButtonActions");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class SwappingAction extends AbstractAction {
private Action nextAction;
public SwappingAction(String text) {
super(text);
}
public void setNextAction(Action nextAction) {
this.nextAction = nextAction;
}
public Action getNextAction() {
return nextAction;
}
#Override
/**
* super method needs to be called in child for swap to work
*/
public void actionPerformed(ActionEvent e) {
System.out.println("ActionCommand: " + e.getActionCommand());
((AbstractButton)e.getSource()).setAction(nextAction);
}
}
class StartAction extends SwappingAction {
public static final String START = "Start";
public StartAction() {
super(START);
}
#Override
public void actionPerformed(ActionEvent e) {
super.actionPerformed(e);
// start-specific code goes here
}
}
class PauseAction extends SwappingAction {
public static final String PAUSE = "Pause";
public PauseAction() {
super(PAUSE);
}
#Override
public void actionPerformed(ActionEvent e) {
super.actionPerformed(e);
// pause-specific code goes here
}
}
class BlankAction extends SwappingAction {
public static final String BLANK = " ";
public BlankAction() {
super(BLANK);
}
#Override
public void actionPerformed(ActionEvent e) {
super.actionPerformed(e);
}
}
Write buttonName.setText(" ") this will not display any name to the button. And whenever you feel like displaying the name (on any event) then set it again buttonName.setText("some text")
If you insist not to use setText(""), try setting same colour as a background colour and text colour. Check the below links
setBackground(java.awt.Color)
setForeground(java.awt.Color)
Why don't you name the first button " " (1 space).
the second: " " (2 spaces)
the third: " "(3 spaces) and so on ..
Now, compare:
if((event.getActionCommand()).equals(" "))
{ //1st button }
if((event.getActionCommand()).equals(" "))
{ //2nd button }
..and so on
where event is an object of ActionEvent
This way the buttons will have a unique names and be invisible.
Horrible coding, I know. But it does the trick ;)
Instead of .setText(), use .setTag() and .getTag() to attach some value to a View - including a Button - for later retrieval.
These methods are there directly for that kind of purpose.
I need to stop user making multiple clicks on a JButton while the first click still execute.
I was able to came with a solution for this issue but I do not completelly understand why it's working.
Bellow I posted the code (trimmed to a minimum) that works and the one that does not work.
In first example (good) if you run it and click the button multiple times only one action is considered as for the second example (bad) if you click the mouse multiple times you get action executed at least twice.
The second (bad) example simply does not use invokeLater() method.
Where the difference in behaviour cames from?
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
public class TestButtonTask {
public static void main(String[] args) {
final JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
final JButton task = new JButton("Test");
task.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
long t = System.currentTimeMillis();
System.out.println("Action received");
task.setText("Working...");
task.setEnabled(false);
SwingUtilities.invokeLater(new Thread() {
#Override
public void run() {
try {
sleep(2 * 1000);
} catch (InterruptedException ex) {
Logger.getLogger(TestButtonTask.class.getName()).log(Level.SEVERE, null, ex);
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
task.setEnabled(true);
task.setText("Test");
}
});
}
});
}
});
frame.add(task);
frame.pack();
frame.setVisible(true);
} //end main
} //end class
And now the "wrong" code
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
public class TestButtonTask {
public static void main(String[] args) {
final JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
final JButton task = new JButton("Test");
task.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
long t = System.currentTimeMillis();
System.out.println("Action received");
task.setText("Working...");
task.setEnabled(false);
SwingUtilities.invokeLater(new Thread() {
#Override
public void run() {
try {
sleep(2 * 1000);
} catch (InterruptedException ex) {
Logger.getLogger(TestButtonTask.class.getName()).log(Level.SEVERE, null, ex);
}
//SwingUtilities.invokeLater(new Runnable() {
//public void run() {
task.setEnabled(true);
task.setText("Test");
//}
//});
}
});
}
});
frame.add(task);
frame.pack();
frame.setVisible(true);
} //end main
} //end class
After info provided by #kleopatra and #Boris Pavlović here is the code I created that seems to work pretty decent.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
public class TestButtonTask {
public static void main(String[] args) {
final JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
final JButton task = new JButton("Test");
task.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
task.setText("Working...");
task.setEnabled(false);
SwingWorker worker = new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() throws Exception {
try {
Thread.sleep(3 * 1000);
} catch (InterruptedException ex) {
Logger.getLogger(TestButtonTask.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
};
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
System.out.println("Event " + evt + " name" + evt.getPropertyName() + " value " + evt.getNewValue());
if ("DONE".equals(evt.getNewValue().toString())) {
task.setEnabled(true);
task.setText("Test");
}
}
});
worker.execute();
}
});
frame.add(task);
frame.pack();
frame.setVisible(true);
} //end main
} //end class
you have two choises
1) JButton#setMultiClickThreshhold
2) you have to split this idea to the two separated actions inside actionListener or Action
1st. step, JButton#setEnabeld(false);
2nd. step, then call rest of code wrapped to the javax.swing.Action (from and dealyed by javax.swing.Timer), SwingWorker or Runnable#Thread
Okay, here's a code snippet using an Action
it disable's itself on performed
it spawns a task, at the end of which is enables itself again. Note: for simplicity here the task is simulated by a Timer, real-world would spawn a SwingWorker to do the background work, listening to its property changes and enable itself on receiving a done
set as the button's action
The code:
Action taskAction = new AbstractAction("Test") {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Action received ");
setEnabled(false);
putValue(NAME, "Working...");
startTask();
}
// simulate starting a task - here we simply use a Timer
// real-world code would spawn a SwingWorker
private void startTask() {
ActionListener l = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
putValue(NAME, "Test");
setEnabled(true);
}
};
Timer timer = new Timer(2000, l);
timer.setRepeats(false);
timer.start();
}};
JButton task = new JButton(taskAction);
There are two more ways.
You can define a flag. Set it when action start and reset back after the end. Check the flags in the actionPerformed. If inProgress==true just do nothing.
Another way is to remove the listener and assign it back after the action ends.
The right way is using a SwingWorker. When user click the button before submmiting a job to the SwingWorker the state of the button should be changed to disabled JButton#setEnabled(false). After the SwingWorker finished the job state of the button should be reset to enabled. Here's Oracle's tutorial on SwingWorker
After years of dealing with the frustration of this problem, I've implemented a solution that I think is the best.
First, why nothing else works:
JButton::setMutliclickThreshold() is not really an optimal solution, because (as you said) there is no way to know how long to set the threshold. This is only good to guard against double-click happy end-users because you have to set an arbitrary threshold.
JButton::setEnabled() is an obviously fragile solution that will only make life much more difficult.
So, I've created the SingletonSwingWorker. Now, Singletons are called anti-patterns, but if implemented properly, they can be a very powerful. Here is the code:
public abstract class SingletonSwingWorker extends SwingWorker {
abstract void initAndGo();
private static HashMap<Class, SingletonSwingWorker> workers;
public static void runWorker(SingletonSwingWorker newInstance) {
if(workers == null) {
workers = new HashMap<>();
}
if(!workers.containsKey(newInstance.getClass()) || workers.get(newInstance.getClass()).isDone()) {
workers.put(newInstance.getClass(), newInstance);
newInstance.initAndGo();
}
}
}
This will enable you to create classes which extend SingletonSwingWorker and guarantee only one instance of that class will be executable at one time. Here is an example implementation:
public static void main(String[] args) {
final JFrame frame = new JFrame();
JButton button = new JButton("Click");
button.setMultiClickThreshhold(5);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
DisplayText_Task.runWorker(new DisplayText_Task(frame));
}
});
JPanel panel = new JPanel();
panel.add(button);
frame.add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
static class DisplayText_Task extends SingletonSwingWorker {
JFrame dialogOwner;
public DisplayText_Task(JFrame dialogOwner) {
this.dialogOwner = dialogOwner;
}
JDialog loadingDialog;
#Override
void initAndGo() {
loadingDialog = new JDialog(dialogOwner);
JProgressBar jpb = new JProgressBar();
jpb.setIndeterminate(true);
loadingDialog.add(jpb);
loadingDialog.pack();
loadingDialog.setVisible(true);
execute(); // This must be put in the initAndGo() method or no-workie
}
#Override
protected Object doInBackground() throws Exception {
for(int i = 0; i < 100; i++) {
System.out.println(i);
Thread.sleep(200);
}
return null;
}
#Override
protected void done() {
if(!isCancelled()) {
try {
get();
} catch (ExecutionException | InterruptedException e) {
loadingDialog.dispose();
e.printStackTrace();
return;
}
loadingDialog.dispose();
} else
loadingDialog.dispose();
}
}
In my SwingWorker implementations, I like to load a JProgressBar, so I always do that before running doInBackground(). With this implementation, I load the JProgressBar inside the initAndGo() method and I also call execute(), which must be placed in the initAndGo() method or the class will not work.
Anyways, I think this is a good solution and it shouldn't be that hard to refactor code to refit your applications with it.
Very interested in feedback on this solution.
Note that when you are modifying anything in GUI your code must run on Event Dispatch thread using invokeLater or invokeAndWait if you are in another thread. So second example is incorrect as you are trying to modify enabled state from another thread and it can cause unpredictable bugs.