I'm trying to write a game in java3d on Linux and for that I need a proper KeyListener.
Did anyone of you know how to do it? I'm currently using following code, I found somewhere on the net. It's working pretty good, holding down just one key, but as soon, as I press more than one (like space and w) it will do unexpected things...
public class RepeatingReleasedEventsFixer implements AWTEventListener {
private final HashMap<Integer, ReleasedAction> _map = new HashMap<Integer, ReleasedAction>();
public void install() {
Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK);
}
public void remove() {
Toolkit.getDefaultToolkit().removeAWTEventListener(this);
}
#Override
public void eventDispatched(AWTEvent event) {
assert event instanceof KeyEvent : "Shall only listen to KeyEvents, so no other events shall come here";
assert assertEDT(); // REMEMBER THAT THIS IS SINGLE THREADED, so no need for synch.
// ?: Is this one of our synthetic RELEASED events?
if (event instanceof Reposted) {
// -> Yes, so we shalln't process it again.
return;
}
// ?: KEY_TYPED event? (We're only interested in KEY_PRESSED and KEY_RELEASED).
if (event.getID() == KeyEvent.KEY_TYPED) {
// -> Yes, TYPED, don't process.
return;
}
final KeyEvent keyEvent = (KeyEvent) event;
// ?: Is this already consumed?
// (Note how events are passed on to all AWTEventListeners even though a previous one consumed it)
if (keyEvent.isConsumed()) {
return;
}
// ?: Is this RELEASED? (the problem we're trying to fix!)
if (keyEvent.getID() == KeyEvent.KEY_RELEASED) {
// -> Yes, so stick in wait
/**
* Really just wait until "immediately", as the point is that the subsequent PRESSED shall already have been
* posted on the event queue, and shall thus be the direct next event no matter which events are posted
* afterwards. The code with the ReleasedAction handles if the Timer thread actually fires the action due to
* lags, by cancelling the action itself upon the PRESSED.
*/
final Timer timer = new Timer(2, null);
ReleasedAction action = new ReleasedAction(keyEvent, timer);
timer.addActionListener(action);
timer.start();
_map.put(Integer.valueOf(keyEvent.getKeyCode()), action);
// Consume the original
keyEvent.consume();
}
else if (keyEvent.getID() == KeyEvent.KEY_PRESSED) {
// Remember that this is single threaded (EDT), so we can't have races.
ReleasedAction action = _map.remove(Integer.valueOf(keyEvent.getKeyCode()));
// ?: Do we have a corresponding RELEASED waiting?
if (action != null) {
// -> Yes, so dump it
action.cancel();
}
// System.out.println("PRESSED: [" + keyEvent + "]");
}
else {
throw new AssertionError("All IDs should be covered.");
}
}
/**
* The ActionListener that posts the RELEASED {#link RepostedKeyEvent} if the {#link Timer} times out (and hence the
* repeat-action was over).
*/
private class ReleasedAction implements ActionListener {
private final KeyEvent _originalKeyEvent;
private Timer _timer;
ReleasedAction(KeyEvent originalReleased, Timer timer) {
_timer = timer;
_originalKeyEvent = originalReleased;
}
void cancel() {
assert assertEDT();
_timer.stop();
_timer = null;
_map.remove(Integer.valueOf(_originalKeyEvent.getKeyCode()));
}
#Override
public void actionPerformed(#SuppressWarnings ("unused") ActionEvent e) {
assert assertEDT();
// ?: Are we already cancelled?
// (Judging by Timer and TimerQueue code, we can theoretically be raced to be posted onto EDT by TimerQueue,
// due to some lag, unfair scheduling)
if (_timer == null) {
// -> Yes, so don't post the new RELEASED event.
return;
}
// Stop Timer and clean.
cancel();
// Creating new KeyEvent (we've consumed the original).
KeyEvent newEvent = new RepostedKeyEvent((Component) _originalKeyEvent.getSource(),
_originalKeyEvent.getID(), _originalKeyEvent.getWhen(), _originalKeyEvent.getModifiers(),
_originalKeyEvent.getKeyCode(), _originalKeyEvent.getKeyChar(), _originalKeyEvent.getKeyLocation());
// Posting to EventQueue.
Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(newEvent);
// System.out.println("Posted synthetic RELEASED [" + newEvent + "].");
}
}
/**
* Marker interface that denotes that the {#link KeyEvent} in question is reposted from some
* {#link AWTEventListener}, including this. It denotes that the event shall not be "hack processed" by this class
* again. (The problem is that it is not possible to state "inject this event from this point in the pipeline" - one
* have to inject it to the event queue directly, thus it will come through this {#link AWTEventListener} too.
*/
public interface Reposted {
// marker
}
/**
* Dead simple extension of {#link KeyEvent} that implements {#link Reposted}.
*/
public static class RepostedKeyEvent extends KeyEvent implements Reposted {
public RepostedKeyEvent(#SuppressWarnings ("hiding") Component source, #SuppressWarnings ("hiding") int id,
long when, int modifiers, int keyCode, char keyChar, int keyLocation) {
super(source, id, when, modifiers, keyCode, keyChar, keyLocation);
}
}
private static boolean assertEDT() {
if (!EventQueue.isDispatchThread()) {
throw new AssertionError("Not EDT, but [" + Thread.currentThread() + "].");
}
return true;
}
}
I can't be the only one who still runs into this - meanwhile 15 y.o. - problem and don't want to use timers...
EDIT: What this code is doing is fix the known problem on any Linux distri, where you add a simple KeyListener, which handles keyDowns, but invokes keyReleased Event repeatedly. To clearify my problem here a simple example
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
public class Test5 extends JFrame{
public Test5() {
addKeyListener(new KeyListener() {
boolean keydown = false;
#Override
public void keyTyped(KeyEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void keyReleased(KeyEvent arg0) {
keydown = false;
System.out.println("keyup");
}
#Override
public void keyPressed(KeyEvent arg0) {
if (keydown){
System.out.println("key is down");
} else {
System.out.println("key not down");
}
keydown = true;
}
});
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(400, 400);
setVisible(true);
//new RepeatingReleasedEventsFixer().install(); // This line will fix it for one key pressed
}
public static void main(String[] args) {
new Test5();
}
}
The output without the line being commented out:
key not down
keyup
key not down
keyup
key not down
keyup
key not down
keyup
key not down
keyup
otherwise:
key not down
key is down
key is down
key is down
key is down
key is down
key is down
key is down
key is down
key is down
keyup
Btw. How come, that it's not beeing fixed by now?
EDIT:
I tried the KeyBindings, as suggested, where it comes to these problems:
public class Test5 extends JFrame{
long timestamp = 0;
public Test5() {
((JComponent)getComponent(0)).getInputMap().put(KeyStroke.getKeyStroke('a'), "a");
((JComponent)getComponent(0)).getActionMap().put("a", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("time: "+(System.currentTimeMillis()-timestamp));
timestamp = System.currentTimeMillis();
}
});
((JComponent)getComponent(0)).getInputMap().put(KeyStroke.getKeyStroke('s'), "s");
((JComponent)getComponent(0)).getActionMap().put("s", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent arg0) {
System.out.println("s");
}
});
((JComponent)getComponent(0)).getInputMap().put(KeyStroke.getKeyStroke('d'), "d");
((JComponent)getComponent(0)).getActionMap().put("d", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent arg0) {
System.out.println("d");
}
});
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(400, 400);
setVisible(true);
new RepeatingReleasedEventsFixer().install(); // This line will fix it for one key pressed
}
/**
* #param args
*/
public static void main(String[] args) {
new Test5();
}
Holding down "a" will give me following output:
time: 4171
time: 501
time: 30
time: 30
time: 30
Where the second time is the actual problem. It takes about 470ms too long.
Holding down "s" and then somewhne pressing "d" will give me that output:
s
s
s
s
d
d
d
d
d
So I can't process two actions as the same time, so I can't use KeyBindings
This is not an answer, it is a long comment with a picture and some explanations.
I used your Test5 (without RepeatingReleasedEventsFixer) to hold down a and measure the time responses. The output is of the form
time: t1
time: t2
time: t3
time: t3
time: t3
...
t1 is meaningless since it depends on the current time and has nothing to do with response time (you also seem to ignore it).
t2 is the time it takes for the OS to realize that you're holding the key for repeated input.
t3 is the "sample time" of the held key, or a discretization of the input.
I'm using Windows where I have the following control panel options:
Repeat delay allows me to set t2 between ~257 (short) and ~1050 (long).
Repeat rate allows me to set t3 between ~407 (slow) and ~37 (fast).
For Linux, you'll have to consult someone / somewhere on how to change these values if you don't already know how to.
As for using multiple keys, see this question and answer and the excellent link within (especially the "Motion With Multiple Keys Pressed" section). It's a short tutorial and analysis of key bindings and key listeners, similar to the one I sent you to on this site.
Key bindings will always be preferred over key listeners unless maybe there is some very low level thing you want to do.
After days of researching and putting stuff together, I ended up writing my own Listener combined with a KeyEventDispatcher, here is the code for someone running into the same problem. It can and should be optimized, but is working for now:
Klass to test if a specific key is pressed:
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.event.KeyEvent;
import java.util.HashMap;
public class IsKeyPressed {
private static boolean wPressed = false;
private HashMap<Integer, Boolean> keys = new HashMap<Integer, Boolean>();
public IsKeyPressed() {
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {
#Override
public boolean dispatchKeyEvent(KeyEvent ke) {
synchronized (IsKeyPressed.class) {
switch (ke.getID()) {
case KeyEvent.KEY_PRESSED:
keys.put(ke.getKeyCode(), true);
break;
case KeyEvent.KEY_RELEASED:
keys.put(ke.getKeyCode(), false);
break;
}
return false;
}
}
});
}
public static boolean isWPressed() {
synchronized (IsKeyPressed.class) {
return wPressed;
}
}
public boolean isPressed(int keyCode){
synchronized (IsKeyPressed.class) {
if (keys == null)
return false;
if (keys.get(keyCode) == null)
return false;
return keys.get(keyCode);
}
}
}
Abstract class, thats beeing used for the actions.
public abstract class KeyActionListener {
protected int keyCode;
public KeyActionListener(int keyCode) {
this.keyCode = keyCode;
}
public void setKeyCode(int keyCode){
this.keyCode = keyCode;
}
public int getKeyCode(){
return this.keyCode;
}
public abstract void onKeyDown();
public abstract void onKeyUp();
public abstract void onKeyHolding();
}
Start listening to the keys and run the actions.
import java.util.ArrayList;
import java.util.HashMap;
public class KeyThread extends Thread{
private int sleep = 3;
ArrayList<KeyActionListener> listener = new ArrayList<KeyActionListener>();
IsKeyPressed isPressed = new IsKeyPressed();
HashMap<KeyActionListener, Boolean> pressed = new HashMap<KeyActionListener, Boolean>();
public KeyThread() {
this.start();
}
public void run() {
while (true){
for (int i = 0; i < listener.size(); i++) {
KeyActionListener curListener = listener.get(i);
if (isPressed.isPressed(curListener.getKeyCode()) && !pressed.get(curListener)){
curListener.onKeyDown();
pressed.put(curListener, true);
} else if(!isPressed.isPressed(curListener.getKeyCode()) && pressed.get(curListener)) {
curListener.onKeyUp();
pressed.put(curListener, false);
}
if(isPressed.isPressed(curListener.getKeyCode())){
curListener.onKeyHolding();
}
try{
Thread.sleep(sleep);
} catch(InterruptedException e){
}
}
}
}
public void addKeyActionListener(KeyActionListener l){
listener.add(l);
pressed.put(l, false);
}
}
Related
I'm trying to stop a timer inside the the ActionListener. Below is the code of what i'm trying to do. I'm tring to stop the timer i created when a certain condition is met inside the actionPerformed method. timer.stop() does not work , the compiler does not let me do that.
Any help . suggestion , advice would be really helpful.
public class ToggleAnnotationsAction extends IdentifiedMultiAction {
//This status indicates if the Toggle action has been completed
/**
* Defines the toggling direction of a <code>ToggleAnnotationAction</code> instance.
*/
public static enum Direction {FORWARD, BACKWARD};
private Direction myDir;
/**
* Create an action with the direction presets given by the provided <code>Enum</code>.
*
* #param dir An <code>Enum</code> defined in this class which maps to the correct direction of toggling
* #see behaviors.multiact.IdentifiedMultiAction#IdentifiedMultiAction(Enum)
*/
public ToggleAnnotationsAction(Direction dir) {
super(dir);
this.myDir = dir;
}
/**
* Performs the toggling, moving the audio position to the next/previous annotation.
*
* Afterward sends an update to all <code>UpdatingActions<code>.
*
* Since the waveform display autonomously decides when to paint itself, this action may not result in an instant visual change.
*
* <p>Prints warnings if an appropriate Annotation could not be found, despite the action being enabled.
*
* #param e The <code>ActionEvent</code> provided by the trigger
*/
public void actionPerformed(ActionEvent e) {
//Reset Status to 0
status =0;
Annotation ann = findAnnotation(myDir, CurAudio.getMaster().framesToMillis(CurAudio.getAudioProgress()));
if(ann == null) {
System.err.println("It should not have been possible to call " + getClass().getName() + ". Could not find matching annotation");
}
else {
final long approxFrame = CurAudio.getMaster().millisToFrames(ann.getTime());
final long curFrame = CurAudio.getAudioProgress();
if(approxFrame < 0 || approxFrame > CurAudio.getMaster().durationInFrames() - 1) {
GiveMessage.errorMessage("The annotation I am toggling to isn't in range.\nPlease check annotation file for errors.");
return;
}
Timer timer = new Timer(10, new ActionListener() {
private long panFrame = curFrame;
private long endFrame = approxFrame;
public void actionPerformed(ActionEvent evt) {
if(myDir == Direction.FORWARD){
if (panFrame >= endFrame) {
//How do i Stop my timer here ?
return;
}
CurAudio.setAudioProgressWithoutUpdatingActions(panFrame);
panFrame += 4000;
}
else if(myDir == Direction.BACKWARD){
if (panFrame <= endFrame) {
// How do i Stop my timer here ?
return;
}
CurAudio.setAudioProgressWithoutUpdatingActions(panFrame);
panFrame -= 4000;
}
}
}
);
timer.start();
}
MyFrame.getInstance().requestFocusInWindow();
}
/**
* A forward (backward) <code>ToggleAnnotationsAction</code> should be enabled only when audio is open, not playing, and when there is an annotation following (preceding) the current position.
*/
#Override
public void update() {
if(CurAudio.audioOpen()) {
if(CurAudio.getPlayer().getStatus() == PrecisionPlayer.Status.PLAYING) {
setEnabled(false);
}
else {
double curTimeMillis = CurAudio.getMaster().framesToMillis(CurAudio.getAudioProgress());
if(findAnnotation(myDir, curTimeMillis) != null) {
setEnabled(true);
}
else {
setEnabled(false);
}
}
}
else {
setEnabled(false);
}
}
/**
* Finds the next/previous <code>Annotation</code> relative to a certain audio position in milliseconds.
*
* #param dir The direction of movement
* #param curTimeMillis The present time in milliseconds
*
* #return In principle, the <code>Annotation</code> after/before <code>curTimeMillis</code>
*/
private Annotation findAnnotation(Direction dir, double curTimeMillis) {
Annotation[] anns = AnnotationDisplay.getAnnotationsInOrder();
if(myDir == Direction.FORWARD) {
for(int i = 0; i < anns.length; i++) {
if(anns[i].getTime() - curTimeMillis > 1) {
return anns[i];
}
}
}
else {
for(int i = anns.length - 1; i >= 0; i--) {
if(curTimeMillis - anns[i].getTime() > 1) {
return anns[i];
}
}
}
return null;
}
}
Thanks in Advance
Krishnan
Also possible:
final Timer timer = new Timer(10, null);
timer.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
(as in the question, except that you can refer to timer here)
}
});
Or, use the event object to get the source (and cast it, boo):
final Timer timer = new Timer(10, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
((Timer)evt.getSource()).stop();
}
});
Or, keep the timer in an instance variable and you can reference it from your handler or have the handler call a method on your class which could stop/start it.
Fun problem.
You have to make Timer final to access it in the anonymous ActionListener you want to stop it in. But the anonymous ActionListener still won't compile because the Timer hasn't been initialized yet.
Note that a Timer may not be your best choice here. But to make this work as is, I'd wrap the Timer in an inner class.
In your ToggleAnnotationsAction.actionPerformed() method add a line like:
MyTimer timer = new MyTimer();
timer.start();
Then a class like this could be used, replacing my simple ActionListener code with your Timer's ActionListener code:
private class MyTimer implements ActionListener{
private Timer timer;
private MyTimer(){
timer = new Timer(10, this);
}
public void start(){
timer.start();
}
public void stop(){
timer.stop();
}
public void actionPerformed(ActionEvent e){
if(isTimeToStop()){
stop();
}
}
public boolean isTimeToStop(){
return true;
}
}
I'm trying to make a 'delete' button that deletes either a) a single character in a text-area if pressed and released in quick succession, or b) all of the text if pressed and held down for more than 2 seconds without release.
Is this possible in Java?
In order to be able to detect long key presses from the keyboard input, you need to understand and use 2 concepts:
1. KeyListener.
2. How to get current time.
Once you understand both, just compare the times between keyPressed and keyReleased and call the proper delete action.
Alternativly to a Swing-Timer (watch here for example) you could use a simple SwingWorker to realize the delay. In general you should not execute a delay, i.e. by Thread.sleep(1000), on the Swing EDT, since this would block the gui (for further information ...). Furthermore you should use a MouseListener to capture other informations that you need (stop the timer when mouse is released or exits the buttona area). Here is a very short example:
public class JButtonTest extends JFrame {
public static void main(String[] args) {
JButtonTest x = new JButtonTest();
JButton button = new JButton("Delete");
button.addMouseListener(new MouseAdapter() {
private static final long DELTA = 2000;
private SwingWorker<Void, Void> waitingWorker;
private Long timer;
#Override
public void mousePressed(MouseEvent e) {
timer = System.currentTimeMillis();
System.out.println("delete single char");//DO single delete here
if (waitingWorker != null && !waitingWorker.isDone())
waitingWorker.cancel(true);
waitingWorker = new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() throws Exception {
Thread.sleep(DELTA);
return null;
}
#Override
protected void done() {
if (timer != null && System.currentTimeMillis() >= timer + DELTA)
System.out.println("delete all text");//DO text delete here
}
};
waitingWorker.execute();
}
#Override
public void mouseReleased(MouseEvent e) {
timer = null;
}
#Override
public void mouseExited(MouseEvent e) {
timer = null;
}
});
x.add(button);
x.setSize(100, 100);
x.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
x.setVisible(true);
}
}
So this is my code:
System.out.println("System Exiting...");
long current = System.currentTimeMillis();
long disired = current + 4000;
boolean done = false;
while (!done)
{
current = System.currentTimeMillis();
if (current == disired)
{
done = true;
System.exit(0);
}
}
My problem is that the print statement doesn't run, well it does run, but it runs at the same time as the exit statement, so you don't see it
[EDIT] Ok, so i just ran this code in its own file(with nothing else), and it works as i want, it prints "system Exiting..." it waits 4 seconds, and the code exits.
so it has to be something to do with the fact that i have this code inside an event listener
Your if condition is much too restrictive since your code will almost never get the times to be exactly equal, but the change needed is very simple:
Change
// hitting this exactly is like finding the proverbial needle
// in the haystack -- almost impossible to do.
if (current == disired)
to
// this is guaranteed to work.
// note if this is in English, you'll want to change disired to desired
if (current >= disired)
Having said this, your while (true) loop is not a good thing to do as it will needlessly tie up the CPU with empty cycles. Instead use some type of event notification or call-back system like ChangeListener or a PropertyChangeListener or a Timer.
You state:
yes it is within a swing GUI
You're calling a long while (true) block of code on the Swing event thread, rendering this thread ineffective. Since the event thread is responsible for all Swing graphics and user interactions, this effectively freezes your GUI until the while loop completes. The solution is obvious: 1) use a Swing Timer for your delay, not a while true loop (this is the callback mechanism that I mentioned in my original answer), and 2) in the future, please give us this important relevant information with the original question since it changes the entire nature of the question.
e.g.
// caveat: code not tested
System.out.println("System Exiting...");
int delay = 4 * 1000;
new Timer(delay, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
System.out. println("Exited");
System.exit(0);
}
}).start();
e.g.,
import java.awt.Component;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class TestDelayedExit extends JPanel {
private static final int GAP = 100;
public TestDelayedExit() {
add(new JButton(new DisposeAction("Exit", KeyEvent.VK_X)));
setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP));
}
private static void createAndShowGui() {
JFrame frame = new JFrame("TestDelayedExit");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new TestDelayedExit());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
private class DisposeAction extends AbstractAction {
private int count = 4;
private Timer timer;
public DisposeAction(String name, int mnemonic) {
super(name);
putValue(MNEMONIC_KEY, mnemonic); // for alt-key combo
}
#Override
public void actionPerformed(ActionEvent e) {
if (timer != null && timer.isRunning()) {
return;
}
final Component c = (Component) e.getSource();
int timerDelay = 1000;
putValue(NAME, String.valueOf(count));
timer = new Timer(timerDelay, new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (count == 0) {
((Timer) e.getSource()).stop();
// this will not work for JMenuItems, and for that
// you would need to get the pop up window's parent component
Window win = SwingUtilities.getWindowAncestor(c);
if (win != null) {
win.dispose();
}
} else {
count--;
putValue(NAME, String.valueOf(count));
}
}
});
timer.start();
}
}
}
Basically, I'm learning Java and I'm trying to make a simple game in Netbeans using JavaFX. Right now I have a window set up with a rectangle drawn in it. (I have a canvas set up to draw on.) I have made a player class and global class, but I need to know how to read key inputs. I was taught by a friend who is REALLY good with Java, so the info he gives me is good. I read up on KeyEvent, but I have no clue how to implement it.
Any help is greatly appreciated.
I had the same question some weeks ago. The problem was about how to check at any time if a key is being held down or not. There are various solutions, I solved it by using a bitset:
package game.input;
import java.util.BitSet;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
public class Input {
/**
* Bitset which registers if any {#link KeyCode} keeps being pressed or if it is released.
*/
private BitSet keyboardBitSet = new BitSet();
// -------------------------------------------------
// default key codes
// will vary when you let the user customize the key codes or when you add support for a 2nd player
// -------------------------------------------------
private KeyCode upKey = KeyCode.UP;
private KeyCode downKey = KeyCode.DOWN;
private KeyCode leftKey = KeyCode.LEFT;
private KeyCode rightKey = KeyCode.RIGHT;
private KeyCode primaryWeaponKey = KeyCode.SPACE;
private KeyCode secondaryWeaponKey = KeyCode.CONTROL;
Scene scene;
public Input( Scene scene) {
this.scene = scene;
}
public void addListeners() {
scene.addEventFilter(KeyEvent.KEY_PRESSED, keyPressedEventHandler);
scene.addEventFilter(KeyEvent.KEY_RELEASED, keyReleasedEventHandler);
}
public void removeListeners() {
scene.removeEventFilter(KeyEvent.KEY_PRESSED, keyPressedEventHandler);
scene.removeEventFilter(KeyEvent.KEY_RELEASED, keyReleasedEventHandler);
}
/**
* "Key Pressed" handler for all input events: register pressed key in the bitset
*/
private EventHandler<KeyEvent> keyPressedEventHandler = new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
// register key down
keyboardBitSet.set(event.getCode().ordinal(), true);
}
};
/**
* "Key Released" handler for all input events: unregister released key in the bitset
*/
private EventHandler<KeyEvent> keyReleasedEventHandler = new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
// register key up
keyboardBitSet.set(event.getCode().ordinal(), false);
}
};
// -------------------------------------------------
// Evaluate bitset of pressed keys and return the player input.
// If direction and its opposite direction are pressed simultaneously, then the direction isn't handled.
// -------------------------------------------------
public boolean isMoveUp() {
return keyboardBitSet.get( upKey.ordinal()) && !keyboardBitSet.get( downKey.ordinal());
}
public boolean isMoveDown() {
return keyboardBitSet.get( downKey.ordinal()) && !keyboardBitSet.get( upKey.ordinal());
}
public boolean isMoveLeft() {
return keyboardBitSet.get( leftKey.ordinal()) && !keyboardBitSet.get( rightKey.ordinal());
}
public boolean isMoveRight() {
return keyboardBitSet.get( rightKey.ordinal()) && !keyboardBitSet.get( leftKey.ordinal());
}
public boolean isFirePrimaryWeapon() {
return keyboardBitSet.get( primaryWeaponKey.ordinal());
}
public boolean isFireSecondaryWeapon() {
return keyboardBitSet.get( secondaryWeaponKey.ordinal());
}
}
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.