I understand that it is important to use the Event Dispatch Thread for any changes to the interface in Java. However, I have no idea how I can manipulate these events to stop/continue/start. I want to refrain from moving on to the next line of main() (after the ones which put the Runnable in the EventQueue) until a certain key is pressed.
I put together an example for clarity. What I'd like to do here is spawn the JFrame, allow the user to move the box around with the arrow keys and then press Enter to cease the box-shifting operations, and ONLY then make the calculation at the end of main() and cause the answer to appear. I should be able to get 400, 500, 600, etc. As it is, the calculation is made immediately after the JFrame appears, so the answer is always 300.
I carved out a spot for whatever action should be bound to Enter; it's underneath the declarations for the actions bound to the arrow keys.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class EndTheShifter extends JFrame
{
private Color ourRectColor = new Color(28,222,144);
private int ourRectWidth = 50;
private int ourRectHeight = 50;
protected static Point ourRecLocation = new Point(100,100);
// Rectangle object can paint itself
public class Rectangle
{
protected void paint(Graphics2D g2d)
{
g2d.setColor(ourRectColor);
g2d.fillRect(ourRecLocation.x, ourRecLocation.y, ourRectWidth, ourRectHeight);
}
} // Rectangle class
// OurRectangle can create a Rectangle and call paint() on it
public class OurRectangle extends JPanel
{
private Rectangle capableRectangle;
public OurRectangle()
{
capableRectangle = new Rectangle();
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g.create();
capableRectangle.paint(g2d);
g2d.dispose();
}
} // OurRectangle class
KeyStroke pressRight = KeyStroke.getKeyStroke("RIGHT");
KeyStroke pressLeft = KeyStroke.getKeyStroke("LEFT");
KeyStroke pressUp = KeyStroke.getKeyStroke("UP");
KeyStroke pressDown = KeyStroke.getKeyStroke("DOWN");
KeyStroke pressEnter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0);
OurRectangle recToWorkWith = new OurRectangle();
// Create InputMap and ActionMap
InputMap inputMap = recToWorkWith.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = recToWorkWith.getActionMap();
// Mapping Shortcut
protected void setTheAction(KeyStroke a, String b, Action c)
{
inputMap.put(a,b);
actionMap.put(b,c);
}
// Constructor!!!
public EndTheShifter()
{
add(recToWorkWith);
Action rightAction = new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
if(ourRecLocation.x != 600)
ourRecLocation.x += 50;
else
ourRecLocation.x = 100;
recToWorkWith.repaint();
}
};
Action leftAction = new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
if(ourRecLocation.x != 100)
ourRecLocation.x -= 50;
else
ourRecLocation.x = 600;
recToWorkWith.repaint();
}
};
Action downAction = new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
if(ourRecLocation.y != 600)
ourRecLocation.y += 50;
else
ourRecLocation.y = 100;
recToWorkWith.repaint();
}
};
Action upAction = new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
if(ourRecLocation.y != 100)
ourRecLocation.y -= 50;
else
ourRecLocation.y = 600;
recToWorkWith.repaint();
}
};
/*
Action enterAction = new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
}
}
setTheAction(pressEnter,"enterAction",enterAction);
*/
setTheAction(pressRight,"rightAction",rightAction);
setTheAction(pressLeft,"leftAction",leftAction);
setTheAction(pressDown,"downAction",downAction);
setTheAction(pressUp,"upAction",upAction);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(800,800);
setVisible(true);
}
// Main kicks things off by putting all of the above
// in the Event Dispatch thread
// On an enter press, I want the last line of main() to run
public static void main(String[] argv)
{
EventQueue.invokeLater(
new Runnable()
{
#Override
public void run()
{
new EndTheShifter();
}
});
// What I want to trigger only on Enter
System.out.println(ourRecLocation.x + 2*ourRecLocation.y);
}
} // EndTheShifter, our outermost class
and ONLY then make the calculation at the end of main()
That is not the way a GUI works.
The main() method is only used to display the frame.
Once the frame is visible the EDT is started and the frame sits there waiting for user events to be generated.
Your application code then responds to these user events.
I understand that it is important to use the Event Dispatch Thread for any changes to the interface in Java.
All code invoked in a listener does execute on the EDT. So the code in your Action does execute on the EDT. You don't need to do anything special.
What I want to trigger only on Enter
Then that logic should be contained in the Enter Action.
I would like to support what camickr said; there is likely a better way to achieve what you are trying to do. That said, if you really want to make your main method wait until the enter key is pressed, here's how:
First, at the top of your file, define an object to use as a synchronization lock like so:
public static final Object LOCK = new Object();
Then, in your main method, before your println statement, put the following code:
synchronized (LOCK) {
LOCK.wait();
}
What this does is it waits until the LOCK object's monitor lock is not being used by any thread (very simplified explanation, read more here), and then it makes the current thread (in this case, the thread that started your main method) wait indefinitely.
Next, add a throws declaration to the method header on your main method:
public static void main(String[] argv) throws InterruptedException
This tells the compiler that your code could throw an InterruptedException, which would happen if your thread was interrupted while it was waiting.
Finally, anywhere in your EndTheShifter constructor, put the following code:
synchronized (LOCK) {
LOCK.notify();
}
This again waits until the LOCK object's monitor lock becomes available, and it then "notifies" all threads waiting on the LOCK object that they may continue. In this case, it will make our main thread continue and execute the println.
Related
I have my GUI set up so that if the button b1 is pressed:
public class CubeCalc {
static int next = 0;
public static void MakeTitlePage()
{
final JFrame window = new JFrame("Cubic Feet Calculator"); //Creates Frame
JButton b1 = new JButton("Start");
b1.setBackground(Color.decode("#5A20DF"));
b1.setForeground(Color.WHITE);
/*b1.setLayout(new GridBagLayout());*/
b1.setPreferredSize(new Dimension(150,50));
b1.addActionListener(new ActionListener() { // action when button is pressed
int pressCount=0;
#Override
public void actionPerformed(ActionEvent e) {
window.dispose();
next = 1;
}
});
then it will dispose of the title page and and next will equal one, and on the Event Dispatch Thread, it creates a new page that does other things:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() { // launch frame on the Event Dispatch Thread
#Override
public void run() {
MakeTitlePage();
if (next==1)
{
MakeCalcPage();
}
System.out.println(next);
}
});
}
The problem is that the variable next remains equal to zero even though I have changed it in the method MakeTitlePage(). How do I change the variable across all the methods, and not just that one?
I think you might have misunderstood how the event dispatch thread works. When you add a listener to a component then you are telling Swing to listen for certain events and invoke the associated listener on the event dispatch thread. If you are using the static variable next to communicate between threads then, firstly, that's not the way to do it and, secondly, you are communicating to the same thread anyway.
If you want the button to close the current window and open a new one then you should do that directly in the actionPerformed method:
public void actionPerformed(ActionEvent e) {
window.setVisible(false);
showCalculationFrame();
}
What I am trying to do is an Appled which throws 2 threads, each running a counter which increases itself via an infinite loop
I then use a while(true) in the Applet's paint() method, which continuously paints the counters, the problem is that I have also 2 buttons, each intended to stop each thread, but the infinite loop in the paint() method doesn't let me neither click none of them nor close the Applet's window nor anything
Here a screenshot followed by the code
btw I'm certain the problem is the paint() loop as if I disable the loop I can interact with the buttons, but the counters are obviously not updated, and weird thing is that I put the mouse cursor over the buttons to show it took the form like when you want to resize a windows but the imprpant didn't capture it :/
http://i.imgur.com/PJnDI4u.png
public class MainApplet extends Applet implements ActionListener {
private static final long serialVersionUID = -2500043816999861110L;
private Font fuente;
private Button bUno, bDos;
private HiloContador hUno, hDos;
public void init() {
setBackground(Color.LIGHT_GRAY);
fuente = new Font("Verdana",Font.BOLD,26);
bUno = new Button("Parar");
bUno.addActionListener(this);
bDos = new Button("Parar");
bDos.addActionListener(this);
bUno.setSize(40,20);
add(bUno);
bDos.setSize(40,20);
add(bDos);
hUno = new HiloContador(20);
hUno.start();
hDos = new HiloContador(40);
hDos.start();
}
#SuppressWarnings({ "deprecation", "static-access" })
public void actionPerformed(ActionEvent e) {
if(e.getSource().equals(bUno)){
hUno.parar();
bUno.setLabel("1 parado");
}else if (e.getSource().equals(bDos)){
hDos.parar();
bDos.setLabel("2 parado");
}
}
public void paint(Graphics g) {
while (true){
g.clearRect(1,1,getSize().width,getSize().height); //dibuja la ventana
g.setFont(fuente);
g.drawString(hUno.getContador()+"",40,60);
g.drawString(hDos.getContador()+"",100,60);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
in case it helps anyone, solved deleting the infinite loop and adding this method
Timer timer = new Timer();
timer.schedule( new TimerTask() {
public void run() {
repaint();}
}, 0, 1000);
So I have a GUI which acts kind of like a console. I want the user to enter text into a JTextField and press enter. I've used key bindings to make a callback for when the user presses enter.
Now I want to create a method called waitForInput() which waits for the user to enter something and returns it. What I'm trying is below. But it results in a java.lang.IllegalMonitorStateException when notify() is called in the callback function.
public class MainWindow{
private JFrame mainWindow;
private JTextArea textEntry;
private String inputStringMonitor = ""; // lock/user input value
private Boolean stringReady = false; //flag for wait while loop
public MainWindow(){
mainWindow = new JFrame("console");
textEntry = new JTextArea();
// set up key bindings
InputAction = new UserInputAction();
textEntry.getInputMap().put( KeyStroke.getKeyStroke( "ENTER" ),"EnterAction" );
textEntry.getActionMap().put( "EnterAction", InputAction);
//configure window
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWindow.setMinimumSize(new Dimension(800,675));
mainWindow.getContentPane().add(textEntry);
mainWindow.pack();
mainWindow.setVisible(true);
}
// callback action when user presses enter
public class UserInputAction extends AbstractAction
{
public void actionPerformed( ActionEvent bp )
{
System.out.println( "enter pressed" );
textEntry.setText("> ");
textEntry.setCaretPosition(2);
synchronized(inputStringMonitor){
stringReady = true;
inputStringMonitor = textEntry.getText();
inputStringMonitor.notify(); //causes exception
}
}
}
public String waitForInput() throws InterruptedException {
String retval = "";
synchronized(inputStringMonitor){
stringReady = false;
System.out.println("waiting");
while(!stringReady){
inputStringMonitor.wait();
}
retval = inputStringMonitor;
}
return retval;
}
}
I think that I have an idea of what you're trying to do, and if so, I feel that I have a better solution. Correct me if I'm wrong, but I think that you want to create a GUI text entry window that other programs can use, and that notifies other programs when text as been entered. If so, then a better solution is to use a tool that is already present within Swing GUI components -- PropertyChangeSupport. If you want to listen for changes in a String's state, then make the String a "bound" property, one that notifies the GUI if its state ever changes by firing the Swing innate property change method. This way outside classes can register as listeners and be notified of this change.
For instance, the class below extends JPanel, partly because this will give the class a SwingPropertyChangeSupport object as well as add/remove PropertyChangeListener methods, but if you don't want to extend the Swing component, you can easily roll your own by adding your own SwingPropertyChangeSupport object as well as add/remove PropertyChangeListener methods to your class.
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class CallBackGui extends JPanel {
// public constant for the propertyName
public static final String TEXT_ENTRY = "text entry";
private static final int ROWS = 20;
private static final int COLUMNS = 40;
private static final String CARET_MARKER = "> ";
private JTextArea textEntryArea = new JTextArea(ROWS, COLUMNS);
private String enteredText = ""; // "bound" property
public CallBackGui() {
textEntryArea.setText(CARET_MARKER);
textEntryArea.setWrapStyleWord(true);
textEntryArea.setLineWrap(true);
JScrollPane scrollPane = new JScrollPane(textEntryArea);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
add(scrollPane);
int condition = WHEN_FOCUSED;
InputMap inputMap = textEntryArea.getInputMap(condition);
ActionMap actionMap = textEntryArea.getActionMap();
KeyStroke enterKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
inputMap.put(enterKeyStroke, TEXT_ENTRY);
actionMap.put(TEXT_ENTRY, new TextEntryAction());
}
public String getEnteredText() {
return enteredText;
}
// or can make this private if you wish it to not be changed by outside forces
public void setEnteredText(String enteredText) {
String oldValue = this.enteredText;
String newValue = enteredText;
this.enteredText = enteredText; // change our bound property here
// notify listeners here
firePropertyChange(TEXT_ENTRY, oldValue, newValue);
}
// used by Key Bindings
private class TextEntryAction extends AbstractAction {
#Override
public void actionPerformed(ActionEvent e) {
// call method to set bound poperty
setEnteredText(textEntryArea.getText().substring(CARET_MARKER.length()));
textEntryArea.setText(CARET_MARKER);
}
}
}
Then any outside class that has a reference to the displayed CallBackGui can register a property change listener onto this object and get notification. A very (overly) simple example:
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class TestCallBackGui {
private static void createAndShowGui() {
CallBackGui callBackGui = new CallBackGui();
JFrame frame = new JFrame("CallBackGui");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(callBackGui);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
// add our PropertyChangeListener
callBackGui.addPropertyChangeListener(CallBackGui.TEXT_ENTRY, new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
System.out.println("Text Entered:");
// result held by newValue
System.out.println(evt.getNewValue());
// or can call callBackGui.getEnteredText()
}
});
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
The benefit -- avoidance of all low-level wait/notify/synchronized code, especially the use of this type of code on the Swing event thread, and instead use of safer higher level constructs. Also, since the Swing component actually uses a SwingPropertyChangeSupport object, all call backs will be made on the Swing event thread, an important point if the listening program is also a Swing GUI.
Okay. So here's a solution thanks to Titus
1) Using synchronized() in the callback blocks the EDT, so that's bad. Instead use the invokeLater() to notify asynchronously.
// thread to branch off in order to notify
Runnable doNotify = new Runnable() {
public void run() {
synchronized(inputStringMonitor){
userString = textEntry.getText();
inputStringMonitor.notify();
}
}
};
// callback function
public void actionPerformed( ActionEvent bp )
{
System.out.println( "enter pressed" );
textEntry.setText("> ");
textEntry.setCaretPosition(2);
SwingUtilities.invokeLater(doNotify);
}
2) Assigning to inputStringMonitor re-initializes the lock and messes things up. Instead use a dedicated lock, and a separate string for storing the actual data.
I want to cause the "main thread" (the thread started which runs main()) to do some work from the actionPerformed() method of a button's ActionListener, but I do not know how to achieve this.
A little more context:
I am currently programming a 2D game using Swing (a flavour of Tetris).
When the application starts, a window opens which displays the main menu of the game.
The user is presented several possibilities, one of them is to start the game by pushing a "Start" button, which causes the game panel to be displayed and triggers the main loop of the game.
To be able to switch between the two panels (that of the main menu and that of the game), I am using a CardLayout manager, then I can display one panel by calling show().
The idea is that I would like my start button to have a listener that looks like this:
public class StartListener implements ActionListener {
StartListener() {}
public void actionPerformed(ActionEvent e) {
displayGamePanel();
startGame();
}
}
but this does not work because actionPerformed() is called from the event-dispatch thread, so the call to startGame() (which triggers the main loop: game logic update + repaint() call at each frame) blocks the whole thread.
The way I am handling this right now is that actionPerformed() just changes a boolean flag value: public void actionPerformed(ActionEvent e) {
startPushed = true;
}
which is then eventually checked by the main thread:
while (true) {
while (!g.startPushed) {
try {
Thread.sleep(100);
} catch (Exception e) {}
}
g.startPushed = false;
g.startGame();
}
But I find this solution to be very inelegant.
I have read the Concurrency in Swing lesson but I am still confused (should I implement a Worker Thread – isn't that a little overkill?). I haven't done any actual multithreading work yet so I am a little lost.
Isn't there a way to tell the main thread (which would be sleeping indefinitely, waiting for a user action) "ok, wake up now and do this (display the game panel and start the game)"?.
Thanks for your help.
EDIT:
Just to be clear, this is what my game loop looks like:
long lastLoopTime = System.currentTimeMillis();
long dTime;
int delay = 10;
while (running) {
// compute the time that has gone since the last frame
dTime = System.currentTimeMillis() - lastLoopTime;
lastLoopTime = System.currentTimeMillis();
// UPDATE STATE
updateState(dTime);
//...
// UPDATE GRAPHICS
// thread-safe: repaint() will run on the EDT
frame.repaint()
// Pause for a bit
try {
Thread.sleep(delay);
} catch (Exception e) {}
}
This doesn't make sense:
but this does not work because actionPerformed() is called from the event-dispatch thread, so the call to startGame() (which triggers the main loop: game logic update + repaint() call at each frame) blocks the whole thread.
Since your game loop should not block the EDT. Are you using a Swing Timer or a background thread for your game loop? If not, do so.
Regarding:
while (true) {
while (!g.startPushed) {
try {
Thread.sleep(100);
} catch (Exception e) {}
}
g.startPushed = false;
g.startGame();
}
Don't do this either, but instead use listeners for this sort of thing.
e.g.,
import java.awt.CardLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class GameState extends JPanel {
private CardLayout cardlayout = new CardLayout();
private GamePanel gamePanel = new GamePanel();
private StartPanel startpanel = new StartPanel(this, gamePanel);
public GameState() {
setLayout(cardlayout);
add(startpanel, StartPanel.DISPLAY_STRING);
add(gamePanel, GamePanel.DISPLAY_STRING);
}
public void showComponent(String displayString) {
cardlayout.show(this, displayString);
}
private static void createAndShowGui() {
GameState mainPanel = new GameState();
JFrame frame = new JFrame("GameState");
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 StartPanel extends JPanel {
public static final String DISPLAY_STRING = "Start Panel";
public StartPanel(final GameState gameState, final GamePanel gamePanel) {
add(new JButton(new AbstractAction("Start") {
#Override
public void actionPerformed(ActionEvent e) {
gameState.showComponent(GamePanel.DISPLAY_STRING);
gamePanel.startAnimation();
}
}));
}
}
class GamePanel extends JPanel {
public static final String DISPLAY_STRING = "Game Panel";
private static final int PREF_W = 500;
private static final int PREF_H = 400;
private static final int RECT_WIDTH = 10;
private int x;
private int y;
public void startAnimation() {
x = 0;
y = 0;
int timerDelay = 10;
new Timer(timerDelay , new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
x++;
y++;
repaint();
}
}).start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.fillRect(x, y, RECT_WIDTH, RECT_WIDTH);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
}
you should be using a SwingWorker this will execute the code in doInBackground() in a background thread and the code in done() in the EDT after doInBackground() stops
The easiest way: use a CountDownLatch. You set it to 1, make it available in the Swing code by any means appropriate, and in the main thread you await it.
You can consider showing a modal dialog with the game panel using SwingUtilities.invokeAndWait() so that when the dialog is closed the control returns back to main thread.
You can make all code except the EDT run on single thread execution service and then just post runnables whenever you need some code executed.
How can I update the JProgressBar.setValue(int) from another thread?
My secondary goal is do it in the least amount of classes possible.
Here is the code I have right now:
// Part of the main class....
pp.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent event){
new Thread(new Task(sd.getValue())).start();
}
});
public class Task implements Runnable {
int val;
public Task(int value){
this.val = value;
}
#Override
public void run() {
for (int i = 0; i <= value; i++){ // Progressively increment variable i
pbar.setValue(i); // Set value
pbar.repaint(); // Refresh graphics
try{Thread.sleep(50);} // Sleep 50 milliseconds
catch (InterruptedException err){}
}
}
}
pp is a JButton and starts the new thread when the JButton is clicked.
pbar is the JProgressBar object from the Main class.
How can I update its value?(progress)
The code above in run() cannot see the pbar.
Always obey swing's rule
Once a Swing component has been realized, all code that might affect or depend on the state of that component should be executed in the event-dispatching thread.
What you can do is to create an observer that will update your progress bar -such as
- in this instance you want to show progress of data being loaded on click of a button.
DemoHelper class implements Observable and sends updates to all observers on when certain percent of data is loaded.
Progress bar is updated via public void update(Observable o, Object arg) {
class PopulateAction implements ActionListener, Observer {
JTable tableToRefresh;
JProgressBar progressBar;
JButton sourceButton;
DemoHelper helper;
public PopulateAction(JTable tableToRefresh, JProgressBar progressBarToUpdate) {
this.tableToRefresh = tableToRefresh;
this.progressBar = progressBarToUpdate;
}
public void actionPerformed(ActionEvent e) {
helper = DemoHelper.getDemoHelper();
helper.addObserver(this);
sourceButton = ((JButton) e.getSource());
sourceButton.setEnabled(false);
helper.insertData();
}
public void update(Observable o, Object arg) {
progressBar.setValue(helper.getPercentage());
}
}
Shameless plug: this is from source from my demo project
Feel free to browse for more details.
You shouldn't do any Swing stuff outside of the event dispatch thread. To access this, you need to create a Runnable with your code in run, and then pass that off to SwingUtilities.invokeNow() or SwingUtilities.invokeLater(). The problem is that we need a delay in your JProgressBar checking to avoid jamming up the Swing thread. To do this, we'll need a Timer which will call invokeNow or later in its own Runnable. Have a look at http://www.javapractices.com/topic/TopicAction.do?Id=160 for more details.
There is need not to call pbra.repaint explicitly.
Update JProgressBar shall be done through GUI dispatch thread.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Remember to make pbar final variable.
pbar.setValue(i);
}
});