Moving code from ActionListener to main() - java

PROBLEM:
I have following code for the java.awt.Button :
Button btn = new Button("abcde");
btn.addActionListener((ActionEvent e) ->
{
String s = btn.getLabel().toUpperCase();
btn.setLabel(s);
});
I need to move the code inside btn.addActionListener to the public static void main(String[] args), something like below (in pseudo code):
Button btn = new Button("abcde");
btn.addActionListener((ActionEvent e) ->
{
notify_main()_that_button_had_been_clicked();
});
public static void main(String[] args)
{
block_until_button_clicked();
String s = UI.getButton().getLabel();
s = s.toUpperCase();
UI.getButton().setLabel(s);
}
RELEVANT INFORMATION :
I am aware that there are better solutions for GUI development, but I am restricted to using AWT for UI.
I have no power to change the above, nor can I provide details about the real code due to legal restrictions.
To remedy above, I am submitting the below MVCE. Please base your answers on it:
import java.awt.Frame;
import java.awt.Button;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class ResponsiveUI extends Frame
{
public final Button btn = new Button("abcde");
public ResponsiveUI()
{
add(btn);
btn.addActionListener((ActionEvent e) ->
{
String s = btn.getLabel().toUpperCase();
btn.setLabel(s);
});
}
public static void main(String[] args)
{
ResponsiveUI rui = new ResponsiveUI();
rui.addWindowListener(new WindowAdapter()
{
#Override
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
rui.setSize(250, 150);
rui.setResizable(false);
rui.setVisible(true);
}
}
MY EFFORTS TO SOLVE THIS:
I have used Google extensively, and was able to find some useful links.
UI will run in the separate thread, which will make it responsive (I do not know how to join() it properly, though).
For signaling mechanism, wait() and notify() seem like the right way to go.
To set Button's text, I can use EventQueue.InvokeAndWait.
To get Button's text, I do not know what to do, but I have an ugly workaround.
Below is the modified MVCE :
import java.awt.Frame;
import java.awt.Button;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.InvocationTargetException;
public class ResponsiveUI extends Frame
{
public final Object lock = new Object(); // POINT #2 : signaling mechanism
public final Button btn = new Button("abcde");
public ResponsiveUI()
{
add(btn);
btn.addActionListener((ActionEvent e) ->
{
// POINT #2 : signal the main() thread that button is clicked
synchronized (lock)
{
lock.notify();
}
});
}
public static void main(String[] args)
{
ResponsiveUI rui = new ResponsiveUI();
// POINT #1: put UI into separate thread, so we can keep it responsive
// POINT #1: I still do not know how to properly join() (it works OK though)
Runnable r = () ->
{
rui.addWindowListener(new WindowAdapter()
{
#Override
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
rui.setSize(250, 150);
rui.setResizable(false);
rui.setVisible(true);
};
EventQueue.invokeLater(r);
try
{
synchronized (rui.lock) // POINT #2
{ // POINT #2
rui.lock.wait(); // POINT #2 : wait for button press
final Button b = new Button(); // POINT #4 : EventQueue uses final local variables
// store text into temp button (ugly but works)
EventQueue.invokeAndWait(() -> // POINT #4
{
b.setLabel(rui.btn.getLabel());
});
// we could do all kind of things, but for illustrative purpose just transform text into upper case
EventQueue.invokeAndWait(() -> // POINT #3 :
{
rui.btn.setLabel(b.getLabel().toUpperCase());
});
}
}
catch (InterruptedException | InvocationTargetException ex)
{
System.out.println("Error : " + ex);
}
}
}

As I understand your question, you want the main thread to be notified when the [AWT] button is clicked and upon receipt of that notification, you want to change the text of that button's label.
I started with the code from your modified minimal-reproducible-example that you posted in your question.
Here is my code with explanatory notes after it.
import java.awt.Button;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class ResponsiveUI extends Frame {
private static String btnTxt;
public final Object lock = new Object();
public final Button btn = new Button("abcde");
public ResponsiveUI() {
add(btn);
btn.addActionListener((ActionEvent e) -> {
synchronized (lock) {
btnTxt = e.getActionCommand();
lock.notifyAll();
}
});
}
public static void main(String[] args) {
ResponsiveUI rui = new ResponsiveUI();
Runnable r = () -> {
rui.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent we) {
System.exit(0);
}
});
rui.setSize(250, 150);
rui.setResizable(false);
rui.setVisible(true);
};
EventQueue.invokeLater(r);
synchronized (rui.lock) {
try {
rui.lock.wait();
String newBtnTxt = btnTxt.toUpperCase();
EventQueue.invokeLater(() -> rui.btn.setLabel(newBtnTxt));
}
catch (InterruptedException x) {
x.printStackTrace();
}
}
}
}
In order for the GUI thread to notify the main thread, I agree with you that wait() and notifyAll() is the mechanism to use. I use notifyAll() rather than notify() to make sure that all the waiting threads are notified.
I use a shared variable btnTxt to transfer the text of the button's label between the GUI thread and the main thread.
By default, the action command of the ActionEvent is the text of the [AWT] button's label. Hence the use of method getActionCommand() in my ActionListener implementation.
All changes to the GUI must be executed on the GUI thread, so after creating the new text for the button's label in the main thread, I use method invokeLater() of class java.awt.EventQueue to actually change the button's label text.
You should be able to copy the above code as is, compile it and run it.

Change:
public final Button btn = new Button("abcde");
to:
public static final Button btn = new Button("abcde");
and move:
btn.addActionListener((ActionEvent evt) -> {
String s = btn.getLabel().toUpperCase();
btn.setLabel(s);
});
inside the main method.

The general approach is that you need to share some lock or semaphore with both the main method and your ActionListener implementation. Once you have a shared semaphore, the main method can block (wait) upon the semaphore and the ActionListener can notify the semaphore.
In pseudocode:
Lock lock = new Lock();
Button btn = new Button("abcde");
btn.addActionListener((ActionEvent e) -> {
lock.notify();
});
public static void main(String[] args) {
lock.waitUntilNotified();
String s = UI.getButton().getLabel();
s = s.toUpperCase();
UI.getButton().setLabel(s);
}
I have left the exact semaphore implementation to you since the nature of your application will dictate which mechanism will work best. For example, maybe you want to use a simple object and call the wait and notify methods on it, or you can use a blocking queue if you need to perform processing on elements that are loaded into the queue (i.e., items are queued up and the button press signals that the queued up items should now be processed).
You can find information on how to implement the wait and notify approach here:
wait and notify() Methods in Java
Guarded Blocks
Sharing the lock may be difficult since passing in the lock as a method argument is not an option in the main method. A simple approach (that would not be the first choice outside of this context) is creating the lock as a static variable:
public class Application {
public static final Lock LOCK = new Lock();
public static void main(String[] args) {
LOCK.waitUntilNotified();
String s = UI.getButton().getLabel();
s = s.toUpperCase();
UI.getButton().setLabel(s);
}
}
In some other class:
public class SomeOtherClass {
public void doSomething() {
Button btn = new Button("abcde");
btn.addActionListener((ActionEvent e) -> {
Application.LOCK.notify();
});
}
}
Bare in mind Lock is just pseudocode and should be replaced with whatever implementation of the lock that you decide to use.

Why do you need to mess with threads in first place? Threading in an AWT/Swing application has some "conventions" and you seem to be far away from them.
Why don't you use a getter to access the button and the action listener in main's layer?
First solution. Clean, and everyone is able to understand it (no public static final modifiers in an instance of a component)":
public class ResponsiveUI extends Frame {
private Button button;
public ResponsiveUI() {
super("UI");
addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent e) {
System.exit(1);
}
});
button = new Button("something");
add(button);
}
public Button getButton() {
return button;
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
ResponsiveUI ui = new ResponsiveUI();
Button button = ui.getButton();
button.addActionListener(e -> {
button.setLabel(button.getLabel().toUpperCase());
});
ui.setSize(250, 150);
ui.setResizable(false);
ui.setVisible(true);
});
}
}
Second solution, using some sort of dependency injection and avoid having a getter for the button:
public class ResponsiveUI extends Frame {
private Button button;
public ResponsiveUI(ActionListener buttonListener) {
super("UI");
addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent e) {
System.exit(1);
}
});
button = new Button("something");
button.addActionListener(buttonListener);
add(button);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
ActionListener changeLabelToUppercaseListener = e -> {
Object source = e.getSource();
if (source instanceof Button) {
Button b = (Button) source;
b.setLabel(b.getLabel().toUpperCase());
}
};
ResponsiveUI ui = new ResponsiveUI(changeLabelToUppercaseListener);
ui.setSize(250, 150);
ui.setResizable(false);
ui.setVisible(true);
});
}
}

I've used a CountDownLatch to simplify all the locking system.
import java.awt.Frame;
import java.awt.Button;
import java.awt.EventQueue;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.CountDownLatch;
public class ResponsiveUI extends Frame
{
private static final CountDownLatch LOCK = new CountDownLatch(1);
public final Button btn = new Button("abcde");
public ResponsiveUI()
{
add(btn);
btn.addActionListener(ae -> LOCK.countDown());
}
public static void main(String[] args)
{
ResponsiveUI rui = new ResponsiveUI();
rui.addWindowListener(new WindowAdapter()
{
#Override
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
rui.setSize(250, 150);
rui.setResizable(false);
rui.setVisible(true);
try {
LOCK.await();
String s = rui.btn.getLabel().toUpperCase();
EventQueue.invokeAndWait(() ->
{
rui.btn.setLabel(s);
});
}
catch (InvocationTargetException | InterruptedException ex)
{
System.out.println("Error : " + ex);
}
}
}

Related

How do I make a loop start and end with a key press and release?

Here is my code... How can I make it work so that it runs the loop while the user is holding a button and stops when the user releases the button?
public void nextPrimeNum()
{
x = false;
int b = 2;
ArrayList<Integer> next = new ArrayList<Integer>();
while(x)
{
next = factors(b);
if(next.size()==2)
{
System.out.println(b);
}
b++;
}
System.out.println("End");
}
public void keyPressed(KeyEvent e)
{
if(e.getKeyCode() == 401)
{
x = true;
}
}
public void keyRealesed(KeyEvent e)
{
if(e.getKeyCode() == 402)
{
x = false;
}
}
GUI and multi-thread programming is inherently difficult.
So, this is as simple as it could be, without violating best practices too much.
You need several things:
A separate Thread for printing primes:
Its run method loops for ever, but pauses when the Space key is not pressed.
(see Defining and Starting a Thread for more info)
A KeyListener which will be called from AWT's event dispatch thread:
The event handling methods are designed to finish fast, so that other events
(like moving, resizing and closing the frame) still are handled fast.
(see How to Write a Key Listener
and The Event Dispatch Thread for more info)
A visible GUI component (JFrame) for adding the KeyListener
Some synchronization between the 2 threads (via synchronized, notify and wait)
so that the prime-printing starts/continues on keyPressed
and suspends on keyReleased
(see Guarded Blocks for more info)
Initialize and start the whole GUI by invoking initGUI.
(see Initial Threads for more info)
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
public class Main implements Runnable, KeyListener {
public static void main(String[] args) {
SwingUtilities.invokeLater(Main::initGUI);
}
private static void initGUI() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JLabel("Press SPACE key for printing primes"));
frame.pack();
frame.setLocationRelativeTo(null); // center on screen
frame.setVisible(true);
Main main = new Main();
frame.addKeyListener(main);
Thread thread = new Thread(main);
thread.start();
}
private boolean spaceKeyPressed;
private boolean isPrime(int n) {
for (int i = 2; i < n; i++) {
if (n % i == 0)
return false;
}
return true;
}
#Override
public void run() {
for (int n = 2; /**/; n++) {
while (!spaceKeyPressed) {
synchronized (this) {
try {
wait(); // waits until notify()
} catch (InterruptedException e) {
// do nothing
}
}
}
if (isPrime(n)) {
System.out.println(n);
}
}
}
#Override
public void keyTyped(KeyEvent e) {
// do nothing
}
#Override
public synchronized void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
spaceKeyPressed = true;
notifyAll(); // cause wait() to finish
}
}
#Override
public synchronized void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
spaceKeyPressed = false;
notifyAll(); // cause wait() to finish
}
}
}
So, the answer is - it's complicated. It covers broad topics such as concurrency (in general), GUI development, best practices with the specific API (Swing) which are better covered in more detail by reading through the various tutorials (and experimenting)
Concurrency
Creating a GUI With JFC/Swing
Concurrency in Swing
Worker Threads and SwingWorker
How to Use Actions
How to Use Key Bindings
The example presents two ways to execute the "loop" (which is presented in the doInBackground method of the CalculateWorker class).
You can press and hold the JButton or press and hold the [kbd]Space[kbd] bar, both will cause the "main loop" to run, updating the JTextArea with the results...
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
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;
private CalculateWorker worker;
public TestPane() {
setLayout(new BorderLayout());
ta = new JTextArea(20, 20);
ta.setEditable(false);
add(new JScrollPane(ta));
worker = new CalculateWorker(ta);
JButton btn = new JButton("Press");
btn.getModel().addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
System.out.println("...isRunning = " + worker.isRunning());
if (!worker.isRunning()) {
return;
}
System.out.println("...isPressed = " + btn.getModel().isPressed());
System.out.println("...isPaused = " + worker.isPaused());
if (btn.getModel().isPressed()) {
worker.pause(false);
} else {
worker.pause(true);
}
}
});
add(btn, BorderLayout.SOUTH);
worker.execute();
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, false), "Space.released");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, true), "Space.pressed");
am.put("Space.released", new CalculateAction(false, worker));
am.put("Space.pressed", new CalculateAction(true, worker));
}
public class CalculateWorker extends SwingWorker<List<String>, String> {
private AtomicBoolean run = new AtomicBoolean(true);
private AtomicBoolean paused = new AtomicBoolean(false);
private ReentrantLock pausedLocked = new ReentrantLock();
private Condition pausedCondition = pausedLocked.newCondition();
private JTextArea ta;
public CalculateWorker(JTextArea ta) {
this.ta = ta;
pause(true);
}
public void stop() {
run.set(false);
pausedLocked.lock();
pausedCondition.signalAll();
pausedLocked.unlock();
}
public void pause(boolean pause) {
paused.set(pause);
pausedLocked.lock();
pausedCondition.signalAll();
pausedLocked.unlock();
}
public boolean isPaused() {
return paused.get();
}
public boolean isRunning() {
return run.get();
}
#Override
protected List<String> doInBackground() throws Exception {
List<String> values = new ArrayList<>(256);
long value = 0;
System.out.println("!! Start running");
while (run.get()) {
while (paused.get()) {
System.out.println("!! I'm paused");
pausedLocked.lock();
try {
pausedCondition.await();
} finally {
pausedLocked.unlock();
}
}
System.out.println("!! Start loop");
while (!paused.get() && run.get()) {
value++;
values.add(Long.toString(value));
publish(Long.toString(value));
Thread.sleep(5);
}
System.out.println("!! Main loop over");
}
System.out.println("!! Run is over");
return values;
}
#Override
protected void process(List<String> chunks) {
for (String value : chunks) {
ta.append(value);
ta.append("\n");
}
ta.setCaretPosition(ta.getText().length());
}
}
public class CalculateAction extends AbstractAction {
private boolean start;
private CalculateWorker worker;
public CalculateAction(boolean start, CalculateWorker worker) {
putValue(NAME, "Calculate");
this.start = start;
this.worker = worker;
}
#Override
public void actionPerformed(ActionEvent e) {
worker.pause(start);
}
}
}
}
Is there a simpler solution?
Of course, I always go for the most difficult, hard to understand solutions first (sarcasm)
While it "might" be possible to reduce the complexity, the example presents a number of "best practice" concepts which you would do well to learn and understand.
The solution could also be done differently depending on the API used, so, it's the "simplest" solution for the specific API choice.
I wanted to do it from the console!
Java can't do that - it's console support is rudimentary at best and doesn't support a concept of "key pressed/released" actions (since it's running in a single thread, it would be impossible for it to do otherwise).
There "are" solutions you might try, but they would require a third party library linked to native binaries to implement, which would (possibly) reduce the number of platforms it would run on

Why does this swing app hang?

The program hangs after trying to construct another instance of itself when I wait for the reference returned by the constructor to be set.
If I click on the button, the program will hang.
edit: removed silly second wait loop.
edit 2: change true to false when calling constructor. program seems to work now.
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import javax.swing.*;
public class Problem extends JPanel {
public Problem(boolean wait) {
frame=new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
if(wait) try {
System.out.println("calling invoke and wait");
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
Problem.this.run();
}
});
} catch(InvocationTargetException|InterruptedException e) {
throw new RuntimeException(e);
}
else {
System.out.println("calling invoke later");
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Problem.this.run();
}
});
}
}
public String title() {
return "title";
}
public void addContent() {
JButton button=new JButton("click");
add(button,BorderLayout.CENTER);
button.addActionListener(new ActionListener() {
#Override public void actionPerformed(ActionEvent arg0) {
Runnable runnable=new Runnable() {
#Override public void run() {
System.out.println("before new "+Thread.currentThread());
problem=new Problem(false);
System.out.println("after new "+Thread.currentThread());
}
};
new Thread(runnable).start();
System.out.println("before first wait "+Thread.currentThread());
while (problem==null)
;
}
});
}
void run() {
frame.setTitle(title());
frame.getContentPane().add(this,BorderLayout.CENTER);
addContent();
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
new Problem(false);
}
Problem problem;
public final JFrame frame;
private static final long serialVersionUID=1;
}
p1 = problem instance from main function
State 1: Creation - p1 is initialized. p1.problem is null
State 2: User clicks button. Eventually, in another thread, p1.problem is created. However, p1.problem.problem had never been initialized and won't be until the user clicks a button, which will never happen. I'm not sure what you're trying to do but it seems like your program is hanging on while (problem.problem==null);

Query on creating separate thread in java?

Below is the compiled program replica of actual problem code,
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
public class Dummy {
public static boolean getUserCheck(int size, boolean Check) {
if (Check) {
int ret = JOptionPane.showConfirmDialog(null, size + " entries, Yes or no?",
"Warning", 0);
if (ret > 0) {
System.out.println("User said No: " + ret);
return false;
} else if (ret <= 0) {
System.out.println("user said Yes: " + ret);
return true;
}
}
return true;
}
public static void workerMethod1() {
System.out.println("am worker method 1");
}
public static void workerMethod2() {
System.out.println("am worker method 2");
}
public static void main(String[] args) {
System.out.println("mainthread code line 1");
int size = 13;
boolean thresholdBreach = true;
if (getUserCheck(size, thresholdBreach)) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
workerMethod1();
}
});
SwingUtilities.invokeLater(new Runnable() {
public void run() {
workerMethod2();
}
});
}
System.out.println("mainthread code line 2");
System.out.println("mainthread code line 3");
}
}
where i would like to run the if{} block in main() on separate thread. Because these 2 lines,
System.out.println("mainthread code line 2");
System.out.println("mainthread code line 3");
need not wait for completion of if(){} block
Another problem is, experts recommend to run confirm-dialog methods on event thread.
int ret = JOptionPane.showConfirmDialog(null, size + " entries, Yes or no?",
"Warning", 0);
Please help me!!!!
JOptionPane is a Swing method and should be called on the EDT, the Event Dispatch Thread, and only on this thread, and so it suggests that all your code above should be on the EDT, and that most of your SwingUtilities.invokeLater(new Runnable() calls are completely unnecessary. The only necessary ones will be the main one, where you launch your Swing GUI code, and any areas where Swing calls need to be made from within background threads. Again, if any of the above code is being made within background threads, then the JOptionPane should not be in that thread.
For more specific information in this or any other answer, please provide more specific information in your question. Let's end all confusion. The best way to get us to fully and quickly understand your problem would be if you were to to create and post a minimal example program, a small but complete program that only has necessary code to demonstrate your problem, that we can copy, paste, compile and run without modification.
I have a sneaking suspicion that a decent refactoring along MVC lines could solve most of your problems. Your code is very linear with its lines of code that must follow one another and its if blocks, and it is also tightly coupled with your GUI, two red flags for me. Perhaps better would be less linear code, more event and state-driven code, code where your background code interacts with the GUI via observer notification, and where the background code likewise responds to state changes in the GUI from control notification.
Your control needs two SwingWorkers, one to get the row count and the other to get the rest of the data if the user decides to do so. I'd add a PropertyChangeListener to the first SwingWorker to be notified when the row count data is ready, and then once it is, present it to the view for the user to select whether or not to proceed. If he decides to proceed, I'd then call the 2nd SwingWorker to get the main body of the data.
For example, a rough sketch of what I'm talking about:
import java.awt.Dialog.ModalityType;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import javax.swing.*;
#SuppressWarnings("serial")
public class SwingWorkerFooView extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = 300;
private JProgressBar progressBar;
private JDialog dialog;
public SwingWorkerFooView() {
add(new JButton(new ButtonAction("Foo", this)));
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
public boolean showOptionGetAllData(int numberOfRows) {
String message = "Number of rows = " + numberOfRows + ". Get all of the data?";
String title = "Get All Of Data?";
int optionType = JOptionPane.YES_NO_OPTION;
int result = JOptionPane.showConfirmDialog(this, message, title, optionType);
return result == JOptionPane.YES_OPTION;
}
public void showProgressBarDialog() {
progressBar = new JProgressBar();
progressBar.setIndeterminate(true);
Window window = SwingUtilities.getWindowAncestor(this);
dialog = new JDialog(window, "Hang on", ModalityType.APPLICATION_MODAL);
JPanel panel = new JPanel();
panel.add(progressBar);
dialog.add(panel);
dialog.pack();
dialog.setLocationRelativeTo(this);
dialog.setVisible(true);
}
public void closeProgressBarDialog() {
dialog.dispose();
}
private static void createAndShowGui() {
JFrame frame = new JFrame("SwingWorkerFoo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new SwingWorkerFooView());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
#SuppressWarnings("serial")
class ButtonAction extends AbstractAction {
Workers workers = new Workers();
private SwingWorker<Integer, Void> firstWorker;
private SwingWorker<List<String>, Void> secondWorker;
private SwingWorkerFooView mainGui;
public ButtonAction(String name, SwingWorkerFooView mainGui) {
super(name);
this.mainGui = mainGui;
}
#Override
public void actionPerformed(ActionEvent e) {
firstWorker = workers.createFirstWorker();
firstWorker.addPropertyChangeListener(new FirstPropertyChangeListener());
firstWorker.execute();
mainGui.showProgressBarDialog();
}
private class FirstPropertyChangeListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
mainGui.closeProgressBarDialog();
try {
int numberOfRows = firstWorker.get();
boolean getAllData = mainGui.showOptionGetAllData(numberOfRows);
if (getAllData) {
secondWorker = workers.createSecondWorker();
secondWorker.addPropertyChangeListener(new SecondPropertyChangeListener());
secondWorker.execute();
mainGui.showProgressBarDialog();
} else {
// user decided not to get all data
workers.cleanUp();
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
private class SecondPropertyChangeListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
mainGui.closeProgressBarDialog();
try {
List<String> finalData = secondWorker.get();
// display finalData in the GUI
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
}
class Workers {
// database object that may be shared by two SwingWorkers
private Object someDataBaseVariable;
private Random random = new Random(); // just for simulation purposes
private class FirstWorker extends SwingWorker<Integer, Void> {
#Override
protected Integer doInBackground() throws Exception {
// The Thread.sleep(...) is not going to be in final production code
// it's just to simulate a long running task
Thread.sleep(4000);
// here we create our database object and check how many rows there are
int rows = random.nextInt(10 + 10); // this is just for demonstration purposes only
// here we create any objects that must be shared by both SwingWorkers
// and they will be saved in a field of Workers
someDataBaseVariable = "Fubar";
return rows;
}
}
private class SecondWorker extends SwingWorker<List<String>, Void> {
#Override
protected List<String> doInBackground() throws Exception {
// The Thread.sleep(...) is not going to be in final production code
// it's just to simulate a long running task
Thread.sleep(4000);
List<String> myList = new ArrayList<>();
// here we go through the database filling the myList collection
return myList;
}
}
public SwingWorker<Integer, Void> createFirstWorker() {
return new FirstWorker();
}
public void cleanUp() {
// TODO clean up any resources and database stuff that will not be used.
}
public SwingWorker<List<String>, Void> createSecondWorker() {
return new SecondWorker();
}
}
The key to all of this is to not to think in a linear console program way but rather to use observer design pattern, i.e., listeners of some sort to check for change of state of both the GUI and the model.
It's essentially:
create worker
add observer to worker (property change listener)
execute worker
show progress bar dialog or notify user in some way that worker is executing.
The listener will be notified when the worker is done, and then you can query the worker (here via the get() method call) as to its end result.
Then the progress dialog can be closed
And the view can display the result or get additional information from the user.
Yes; SwingUtilities.invokeLater() simply places your runnable on the AWT event queue to be processed later, and it is safe to do so at any time.

Using a thread loop to update a JFrame

ive done some extensive searching on using threads in a loop and whilst I understand the concept how how seperate threads work, I still cant seem to grasp how to implement it in my simple application.
My application consists of a form with a text box. This textbox needs to be updated once ever iteration of a loop. It starts with the press of a button but the loop should also finish with the press of a stop button. Ive used a boolean value to track if its been pressed.
Here is my form code:
package threadtester;
public class MainForm extends javax.swing.JFrame {
public MainForm() {
initComponents();
}
private void RunButtonActionPerformed(java.awt.event.ActionEvent evt) {
ThreadTester.setRunnable(true);
ThreadTester example = new ThreadTester(2,this);
example.run();
}
private void StopButtonActionPerformed(java.awt.event.ActionEvent evt) {
ThreadTester.setRunnable(false);
}
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new MainForm().setVisible(true);
}
});
}
public void setTextBox(String myString){
MainTextbox.setText(myString);
}
}
As you can see I have a button that is pressed. When the button is pressed this executes the code thats in a different class called ThreadTester. Here is the code for that class:
package threadtester;
import java.util.logging.Level;
import java.util.logging.Logger;
public class ThreadTester implements Runnable
{
int thisThread;
MainForm myMainForm;
private static boolean runnable;
// constructor
public ThreadTester (int number,MainForm mainForm)
{
thisThread = number;
myMainForm = mainForm;
}
public void run ()
{
for (int i =0;i< 20; i++) {
if(runnable==false){
break;
}
System.out.println("I'm in thread " + thisThread + " line " + i);
myMainForm.setTextBox(i + "counter");
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
Logger.getLogger(ThreadTester.class.getName()).log(Level.SEVERE, null, ex);
}
} }
public static void setRunnable(Boolean myValue){
runnable = myValue;
}
public static void main(String[] args) {
MainForm.main(args);
}
}
as you can see the loop has been created on a seperate thread... but the textbox only updates after the loop has finished. Now as far as im aware in my MainForm I created a seperate thread to run the loop on, so I dont understand why its not running? Any guidence would be much appreciated, ive tried looking at examples on stack exchange but I cant seem to get them to fit into my implemntation.
With the recommendation suggested by Tassos my run method now looks like this:
public void run ()
{
for (int i =0;i< 20; i++) {
if(runnable==false){
break;
}
System.out.println("I'm in thread " + thisThread + " line " + i);
final String var = i + "counter";
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
myMainForm.setTextBox(var);
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
Logger.getLogger(ThreadTester.class.getName()).log(Level.SEVERE, null, ex);
}
} }
In order for Tassos' answer to work, you actually have to create an new thread, which you did not do. Simply calling
ThreadTester example = new ThreadTester(2,this);
example.run();
is not enough, sice that just calls the run method from EDT. You need to do the following:
Thread t = new Thread(new ThreadTester(2,this));
t.start();
Please refer to Defining and Starting a Thread.
Also, you want modify the same field from two different threads (runnable), which is a bug. You should read more about java concurrency.
Change this line
myMainForm.setTextBox(i + "counter");
into
final String var = i + "counter";
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
myMainForm.setTextBox(var);
}
});
}
Why? Because you can't do UI work in non-UI threads.
The problem is that you are blocking the EDT (Event Dispatching Thread), preventing the UI to refresh until your loop is finished.
The solutions to these issues is always the same, use a Swing Timer or use a SwingWorker.
Here is an example of the usage of a SwingWorker:
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class TestSwingWorker {
private JTextField progressTextField;
protected void initUI() {
final JFrame frame = new JFrame();
frame.setTitle(TestSwingWorker.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton button = new JButton("Clik me to start work");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
doWork();
}
});
progressTextField = new JTextField(25);
progressTextField.setEditable(false);
frame.add(progressTextField, BorderLayout.NORTH);
frame.add(button, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
}
protected void doWork() {
SwingWorker<Void, Integer> worker = new SwingWorker<Void, Integer>() {
#Override
protected Void doInBackground() throws Exception {
// Here not in the EDT
for (int i = 0; i < 100; i++) {
// Simulates work
Thread.sleep(10);
publish(i); // published values are passed to the #process(List) method
}
return null;
}
#Override
protected void process(List<Integer> chunks) {
// chunks are values retrieved from #publish()
// Here we are on the EDT and can safely update the UI
progressTextField.setText(chunks.get(chunks.size() - 1).toString());
}
#Override
protected void done() {
// Invoked when the SwingWorker has finished
// We are on the EDT, we can safely update the UI
progressTextField.setText("Done");
}
};
worker.execute();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestSwingWorker().initUI();
}
});
}
}

Make method that returns when JButton is pressed

I need to make a method that returns only when a JButton is pressed. I have a custom JButton class
public class MyButton extends JButton {
public void waitForPress() {
//returns only when user presses this button
}
}
and I want to implement waitForPress. Basically, the method should only return when the user presses the button with their mouse. I have achieved similar behavior for JTextField (to return only when user presses Space):
public void waitForTriggerKey() {
final CountDownLatch latch = new CountDownLatch(1);
KeyEventDispatcher dispatcher = new KeyEventDispatcher() {
public boolean dispatchKeyEvent(KeyEvent e) {
if (e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() == KeyEvent.VK_SPACE) {
System.out.println("presed!");
latch.countDown();
}
return false;
}
};
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(dispatcher);
try {
//current thread waits here until countDown() is called (see a few lines above)
latch.await();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(dispatcher);
}
but I would like to do the same thing with JButton.
In advance: Please, if you wish to comment saying that this is not a good idea and that one should simply wait for actionPerformed event on a JButton and then do some action, please realize I already know that and have a good reason for doing what I'm asking here. Please try to only help with what I've asked. Thanks!!
In advance: Please, also realize that implementing actionPerformed also will not directly solve the problem. Because the code will progress even without the button being pressed. I need the program to stop, and only return when the button has been pressed. Here is a terrible solution if I were to use actionPerformed:
public class MyButton extends JButton implements ActionPerformed {
private boolean keepGoing = true;
public MyButton(String s) {
super(s);
addActionListener(this);
}
public void waitForPress() {
while(keepGoing);
return;
}
public void actionPerformed(ActionEvent e) {
keepGoing = false;
}
}
For what it's worth, here is how you can do it with wait() and notify() but yet I feel that there is a deeper problem here. I would not consider this as a satisfying solution:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class TestBlockingButton {
boolean clicked = false;
private Object toNotify;
private void initUI() {
JFrame frame = new JFrame(TestBlockingButton.class.getSimpleName());
JButton button = new JButton("Click me");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
clicked = true;
if (toNotify != null) {
synchronized (TestBlockingButton.this) {
toNotify.notify();
}
}
}
});
frame.add(button);
frame.setSize(400, 400);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public void waitForProcess() {
toNotify = this;
while (!clicked) {
synchronized (this) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
System.out.println("continuing work");
}
public static void main(String[] args) {
final TestBlockingButton test = new TestBlockingButton();
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
test.initUI();
}
});
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
pool.execute(new Runnable() {
#Override
public void run() {
System.out.println("I was doing something and now I will wait for button click");
test.waitForProcess();
System.out.println("User has now cliked the button and I can continue my work");
}
});
}
}
As you asked for an implementation with a mutex, here's what it would be like.
I'm using an ActionListener though, but there's no busy wait in it. If that isn't what you desire, you atleast saw what Burkhard meant ;)
public class MyButton extends JButton implements ActionListener
{
private Semaphore sem = new Semaphore(1);
public MyButton(String text) throws InterruptedException
{
super(text);
addActionListener(this);
sem.acquire();
}
#Override
public void actionPerformed(ActionEvent e) {
sem.release();
}
public void waitForPress() throws InterruptedException {
sem.acquire();
//do your stuff
sem.acquire();
//or just
//waitForPress()
//if you dont want it to end.
}
public static void main(String[] args) throws InterruptedException {
JFrame frame = new JFrame();
MyButton m = new MyButton("test");
frame.add(m);
frame.pack();
frame.setVisible(true);
m.waitForPress();
//another time, if you only want it to block twice
m.waitForPress();
}
}
But I don't think this is a clean approach, but it doesn't consume CPU-time like a while(isStatementTrue)-implementation.
An important thing here is: you're blocking the main thread with m.waitForPress() but as you wrote you're quite experienced and you know how to handle that.

Categories