I have a class that implements MouseListener (JPanel). When I click on the panel something happens. What I want is some kind of while-loop that loops as long as left mousebutton is pressed down.
#Override
public void mousePressed(MouseEvent e) {
while(e.isPressedDownD) { // <--
//DO SOMETHING
}
}
This obviously doesn't work, but I hope you understand what I'm trying to achieve.
The whole class for those that are interested:
package control;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import model.GridModel;
import view.GUIView;
public class MapListener implements MouseListener{
private GridModel model;
private GUIView view;
private int posX;
private int posY;
public MapListener(GridModel model, GUIView view) {
this.model = model;
this.view = view;
}
#Override
public void mouseClicked(MouseEvent e) {
posX = e.getX();
posY = e.getY();
model.setMouseAtX(posX);
model.setMouseAtY(posY);
view.paintTile();
System.out.println("X: " + posX + " Y: " + posY);
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent arg0) {
}
#Override
public void mousePressed(MouseEvent e) {
while(e.getModifiers() == MouseEvent.MOUSE_PRESSED) { //Obviously doesn't work
//DO SOMETHING
}
}
#Override
public void mouseReleased(MouseEvent arg0) {
}
}
As pointed out by other answers, the place to do your work is not in the mouse event listener methods.
Also there is no explicit "mouse pressed" notion in MouseEvent, so you must track that yourself. I have provided an example of how to do this. Also note the MouseEvent.BUTTON1 references, as this is just to track the state of the left mouse button.
This is where you must start to learn about concurrency. For that reason, I've added in a synchronized method as you need to be aware that funny things happen when multiple threads access properties at the same time, and synchronized is a mechanism for keeping this sane. Consider it further reading beyond the scope of this example.
Untested, but this should work:
volatile private boolean mouseDown = false;
public void mousePressed(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
mouseDown = true;
initThread();
}
}
public void mouseReleased(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
mouseDown = false;
}
}
volatile private boolean isRunning = false;
private synchronized boolean checkAndMark() {
if (isRunning) return false;
isRunning = true;
return true;
}
private void initThread() {
if (checkAndMark()) {
new Thread() {
public void run() {
do {
//do something
} while (mouseDown);
isRunning = false;
}
}.start();
}
}
Why do so many of these answers wrongly assert that there is no explicit "mouse pressed" notion in MouseEvent?
Although the other commenters are correct that the OP should not be doing all that stuff in an event handler, there are other situations in which querying the button state in a mouse listener is useful. In those cases, you actually CAN determine the button down state. For example:
#Override
public void mouseExited(MouseEvent event) // Or any other mouse event handler...
{
int buttonsDownMask = MouseEvent.BUTTON1_DOWN_MASK
| MouseEvent.BUTTON2_DOWN_MASK
| MouseEvent.BUTTON3_DOWN_MASK; // Or whichever buttons you care about...
if ( (event.getModifiersEx() & buttonsDownMask) != 0 )
System.out.println("Hey! Some button is pressed!");
}
Notice in particular the use of the MouseEvent.getModifiersEx() method, along with MouseEvent.BUTTON1_DOWN_MASK and friends.
You could create a new Thread containing your while loop.
You start that Thread when the mouse button is pressed. You stop it when the mouse button is released.
You shouldn't be doing that in an event handler as no more events will be processed until the event handler exits.
What you want to achieve can be done with a separate worker thread. Create the thread from the mousePressed listener, do whatever you want to do in the thread (this should contain the while loop) and make the thread exit when the mouse is released (your mouseReleased listener should notify the thread).
for example
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ClickListener extends MouseAdapter implements ActionListener {
private final static int clickInterval = (Integer) Toolkit.getDefaultToolkit().getDesktopProperty("awt.multiClickInterval");
private MouseEvent lastEvent;
private Timer timer;
public ClickListener() {
this(clickInterval);
}
public ClickListener(int delay) {
timer = new Timer(delay, this);
}
#Override
public void mouseClicked(MouseEvent e) {
/*if (e.getClickCount() > 2) {
return;
}
lastEvent = e;
if (timer.isRunning()) {
timer.stop();
doubleClick(lastEvent);
} else {
timer.restart();
}*/
if (timer.isRunning() && !e.isConsumed() && e.getClickCount() > 1) {
System.out.println("double");
timer.stop();
} else {
timer.restart();
}
}
#Override
public void actionPerformed(ActionEvent e) {
timer.stop();
singleClick(lastEvent);
}
public void singleClick(MouseEvent e) {
}
public void doubleClick(MouseEvent e) {
}
public static void main(String[] args) {
JFrame frame = new JFrame("Double Click Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.addMouseListener(new ClickListener() {
#Override
public void singleClick(MouseEvent e) {
System.out.println("single");
}
#Override
public void doubleClick(MouseEvent e) {
System.out.println("double");
}
});
frame.setPreferredSize(new Dimension(200, 200));
frame.pack();
frame.setVisible(true);
}
}
Related
When a user clicks the red 'X' button of a JFrame, how do I detect whether the JFrame is open or closed? I have a swing timer where the JFrame keeps updating it's label until the user closes down the JFrame.
int delay = 1000; //milliseconds
final Timer timer = new Timer(delay, null);
timer.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
tempLabel.setVisible(true);
String tmp = "test";
tempLabel.setText("Temperature : " + tmp);
// timer.stop();
}
});
timer.start();
You have to implement either the WindowStateListener or the WindowListener. If you use the WindowListener it could look like this:
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import javax.swing.Timer;
public class Foo implements WindowListener {
private Timer timer;
public static void main(String args[]){
initTimerComponent();
}
private void initTimerComponent() {
int delay = 1000; //milliseconds
timer = new Timer(delay, null);
timer.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
tempLabel.setVisible(true);
String tmp = "test";
tempLabel.setText("Temperature : " + tmp);
}
});
timer.start();
}
#Override
public void windowOpened(WindowEvent e) { }
#Override
public void windowClosing(WindowEvent e) {
timer.stop();
}
#Override
public void windowClosed(WindowEvent e) { }
#Override
public void windowIconified(WindowEvent e) { }
#Override
public void windowDeiconified(WindowEvent e) { }
#Override
public void windowActivated(WindowEvent e) { }
#Override
public void windowDeactivated(WindowEvent e) { }
}
You have to implement them all as WindowListener is an interface and the first concrete class implementing an interface is forced to implement all its abstract methods. But you actually need just one method.
Use this method
public void windowClosing(WindowEvent e) {
timer.stop();
}
to stop your timer as soon as the window is closing after the user clicked the red X.
Answer
addWindowListener(new WindowAdapter() {
//for closing
#Override
public void windowClosing(WindowEvent e) {
JOptionPane.showMessageDialog(null, "Closing");
}
//for closed
#Override
public void windowClosed(WindowEvent e) {
}
});
I am trying to do something when one of the arrow keys are pressed using the KeyListener in my JPanel class. Here is my code:
public class TestPanel extends JPanel implements KeyListener{
public TestPanel(){
this.addKeyListener(this);
this.setFocusable(true);
this.requestFocusInWindow();
}
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
System.out.println("Right");
}
if (e.getKeyCode() == KeyEvent.VK_LEFT) {
System.out.println("Left");
}
}
public void keyTyped(KeyEvent e) {}
public void keyReleased(KeyEvent e) {}
}
My main method adds a new instance of this panel to a frame and displays it. Do I need to add the keylistener to the JFrame? In my case, this would be difficult and inefficient, so I would like to make it work with this JPanel if possible. Anyone know what I am doing wrong?
EDIT: Key Bindings code that does not work either:
public class GamePanel extends JPanel implements ActionListener{
//Constructor
public GamePanel(){
setupKeyBinding();
this.setFocusable(true);
this.requestFocusInWindow();
}
private void setupKeyBinding() {
int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
InputMap inMap = getInputMap(condition);
ActionMap actMap = getActionMap();
inMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "Left");
actMap.put("Left", new leftAction());
}
private class leftAction extends AbstractAction {
public void actionPerformed(ActionEvent e) {
System.out.println("test");
}
}
public void actionPerformed(ActionEvent e) {
//some other game info
}
}
Can someone tell me why this doesnt work either? (my second action listener is for other stuff needed for my game)
If you search this problem, you'll see that it is asked and has been solved many times.
KeyListeners need to be on the focused component to work. One solution is to give your component the focus after first making it focusable.
Better by a long shot however is to use Key Bindings. Google the tutorial on this.
Please have a look at my answer to this question for more on this, including many of the gory details.
For reference, I've create an example using your approach; while it works, it also suggests a focus problem elsewhere in your code. Key Bindings avoid this, as shown here.
Addendum: Here's my working key binding.
private static class TestPanel extends JPanel {
private static final String LEFT = "Left";
private Action left = new AbstractAction(LEFT) {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println(LEFT);
}
};
private static final String RIGHT = "Right";
private Action right = new AbstractAction(RIGHT) {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println(RIGHT);
}
};
public TestPanel() {
this.getInputMap().put(
KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), LEFT);
this.getActionMap().put(LEFT, left);
this.getInputMap().put(
KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), RIGHT);
this.getActionMap().put(RIGHT, right);
}
}
Original SSCCE:
import java.awt.EventQueue;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* #see https://stackoverflow.com/a/16531380/230513
*/
public class Test {
private void display() {
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new TestPanel());
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static class TestPanel extends JPanel implements KeyListener {
public TestPanel() {
this.addKeyListener(this);
this.setFocusable(true);
this.requestFocusInWindow();
}
#Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
System.out.println("Right");
}
if (e.getKeyCode() == KeyEvent.VK_LEFT) {
System.out.println("Left");
}
}
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Test().display();
}
});
}
}
For receives key events on JPanel you must set focus:
setFocusable(true);
requestFocus();
the JPanel now has focus, so it receives key events
I had to do two things: I added comp.setFocusable(true); to the component comp that listens to key events, and I added comp.requestFocus(); to each action which caused comp to lose the focus.
So I have a label and I want to do the next, as default it starts on red background color, when i do first click (mousePressed) i change the background color to Green.
Now, i want it to turn back to Red once i press for 2nd time once i press it again.
Something like if it is red, turn green.
And if it is green, turn red.
However i don't manage to get it right... I tried something like this:
Object o = evt.getSource();
boolean checkGreen = false;
if (o.equals(lblSI)) {
lblSI.setBackground(Color.GREEN);
checkGreen = true;
}
if (o.equals(lblSI) && checkGreen == true) {
lblSI.setBackground(Color.RED);
}
But it obviously doesnt work since I first turn it green, then red, its an instant change, cant find the right code...
You can use an else to take a different action. However, the state of the green color needs to be part of the object fields, not defined in the action method (as it would be reset to false for each action then).
It might also be more clear if you separate the check for the source and the check for color selection.
... object definition ...
boolean isGreen = false;
... action listener...
Object o = evt.getSource();
if (o.equals(lblSI)) {
if (isGreen) {
lblSI.setBackground(Color.RED);
} else {
lblSI.setBackground(Color.GREEN);
}
isGreen = !isGreen;
}
Adding a complete example that instead sets the foreground color as background color will not work on all platforms.
public class RedGreen implements Runnable {
private JButton press;
#Override
public void run() {
JFrame frame = new JFrame("RedGreen");
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
press = new JButton("Press");
press.addActionListener(new ActionListener() {
boolean isGreen = false;
#Override
public void actionPerformed(ActionEvent e) {
if (isGreen) {
press.setForeground(Color.RED);
} else {
press.setForeground(Color.GREEN);
}
isGreen = !isGreen;
}
});
frame.getContentPane().add(press);
frame.pack();
frame.setVisible(true);
}
public static void main(String...args) throws Exception {
SwingUtilities.invokeAndWait(new RedGreen());
}
}
Here we go for a pretty clean way of doing it. I hope this answers your question.
public class MyLabel extends JLabel implements MouseListener {
private boolean isRed;
private boolean isGreen;
private boolean first_time = true;
public MyLabel(String name) {
super(name);
this.isRed = true;
this.isGreen = false;
this.setOpaque(true);
this.addMouseListener(this);
this.setBackground(Color.red);
}
public void setRed(boolean val) {
isRed = val;
}
public void setGreen(boolean val) {
isGreen = val;
}
public boolean getRed() {
return isRed;
}
public boolean getGreen() {
return isGreen;
}
#Override
public void mouseClicked(MouseEvent e) {
}
#Override
public void mousePressed(MouseEvent e) {
if (first_time) {
first_time = false;
this.setGreen(true);
this.setRed(false);
}
if (getRed()) {
this.setBackground(Color.red);
this.setGreen(true);
this.setRed(false);
}
else if (getGreen()) {
this.setBackground(Color.green);
this.setGreen(false);
this.setRed(true);
}
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
}
And the next:
public class TestFrame extends JFrame {
public TestFrame() {
JLabel label = new MyLabel("Name of Label");
this.add(label);
this.setVisible(true);
pack();
}
}
And lastly the main method:
public class Main {
public static void main(String[] args) {
new TestFrame();
}
}
If you have any questions about what I'm doing feel free to ask me. This is how I learnt myself how to code better by asking questions. All the best with the coding :)
I search the forum and see this codes:
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
System.out.println(" and it's a double click!");
wasDoubleClick = true;
} else {
Integer timerinterval = (Integer) Toolkit.getDefaultToolkit().getDesktopProperty(
"awt.multiClickInterval");
timer = new Timer(timerinterval.intValue(), new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if (wasDoubleClick) {
wasDoubleClick = false; // reset flag
} else {
System.out.println(" and it's a simple click!");
}
}
});
timer.setRepeats(false);
timer.start();
}
}
but the code runs incorrectly(Sometime it prints out " and it's a single click!" 2 times . It should print out " and it's a double click!"). Can anybody show me why? or can you give me some better ways to do this?
Thank you!
Sometime it prints out " and it's a single click!" 2 times . It should print out " and it's a double click!").
That is normal. A double click only happens if you click twice within the specified time interval. So sometimes if you don't click fast enough you will get two single clicks in a row.
Integer timerinterval = (Integer) Toolkit.getDefaultToolkit().getDesktopProperty("awt.multiClickInterval");
The above line of code determines how fast the double click must be.
For what its worth here is some code I have used to do the same thing. Don't know if its any better or worse than the code you have:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class ClickListener extends MouseAdapter implements ActionListener
{
private final static int clickInterval = (Integer)Toolkit.getDefaultToolkit().
getDesktopProperty("awt.multiClickInterval");
MouseEvent lastEvent;
Timer timer;
public ClickListener()
{
this(clickInterval);
}
public ClickListener(int delay)
{
timer = new Timer( delay, this);
}
public void mouseClicked (MouseEvent e)
{
if (e.getClickCount() > 2) return;
lastEvent = e;
if (timer.isRunning())
{
timer.stop();
doubleClick( lastEvent );
}
else
{
timer.restart();
}
}
public void actionPerformed(ActionEvent e)
{
timer.stop();
singleClick( lastEvent );
}
public void singleClick(MouseEvent e) {}
public void doubleClick(MouseEvent e) {}
public static void main(String[] args)
{
JFrame frame = new JFrame( "Double Click Test" );
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.addMouseListener( new ClickListener()
{
public void singleClick(MouseEvent e)
{
System.out.println("single");
}
public void doubleClick(MouseEvent e)
{
System.out.println("double");
}
});
frame.setSize(200, 200);
frame.setVisible(true);
}
}
public void mouseClicked(MouseEvent evt) {
if (evt.getButton()==MouseEvent.BUTTON1){
leftClick = true; clickCount = 0;
if(evt.getClickCount() == 2) doubleClick=true;
Integer timerinterval = (Integer)Toolkit.getDefaultToolkit().getDesktopProperty("awt.multiClickInterval");
timer = new Timer(timerinterval, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if(doubleClick){
System.out.println("double click.");
sb = new StringBuffer();
sb.append("Double Click");
clickCount++;
if(clickCount == 2){
clickCount=0;
doubleClick = false;
}
} else {
sb = new StringBuffer();
sb.append("Left Mouse");
System.out.println("single click.");
}
}
});
timer.setRepeats(false);
timer.start();
if(evt.getID()==MouseEvent.MOUSE_RELEASED) timer.stop();
}
I'm trying to create a subclass of JButton or AbstractButton that would call specified .actionPerformed as long as the mouse is held down on the button.
So far I was thinking of extending JButton, adding a mouse listener on creation (inside constructor) and calling actionPerformed while the mouse is down. So far i came up with that but I was wondwering if I was on the right track and if so, how to correctly implement the "held down" logic.
package components;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JButton;
public class HoldButton extends JButton {
private class HeldDownMouseListener implements MouseListener {
private boolean mouseIsHeldDown;
private HoldButton button;
private long millis;
public HeldDownMouseListener(HoldButton button, long millis) {
this.button = button;
this.millis = millis;
}
#Override
public void mouseClicked(MouseEvent arg0) { }
#Override
public void mouseEntered(MouseEvent arg0) { }
#Override
public void mouseExited(MouseEvent arg0) { }
#Override
public void mousePressed(MouseEvent arg0) {
mouseIsHeldDown = true;
// This should be run in a sub thread?
// while (mouseIsHeldDown) {
// button.fireActionPerformed(new ActionEvent(button, ActionEvent.ACTION_PERFORMED, "heldDown"));
// try {
// Thread.sleep(millis);
// } catch (InterruptedException e) {
// e.printStackTrace();
// continue;
// }
// }
}
#Override
public void mouseReleased(MouseEvent arg0) {
mouseIsHeldDown = false;
}
}
public HoldButton() {
addHeldDownMouseListener();
}
public HoldButton(Icon icon) {
super(icon);
addHeldDownMouseListener();
}
public HoldButton(String text) {
super(text);
addHeldDownMouseListener();
}
public HoldButton(Action a) {
super(a);
addHeldDownMouseListener();
}
private void addHeldDownMouseListener() {
addMouseListener(new HeldDownMouseListener(this, 300));
}
}
Thanks a lot for your time.
edit: Choosing the Timer method I came up with a working implementation:
package components;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JButton;
public class HoldButton extends JButton {
private static final long serialVersionUID = 1L;
public static final long CLICK_LAG = 300;
public static final long INITIAL_FIRE_DELAY = 500;
public static final double FIRE_DELAY_STEP_MULTIPLIER = 25;
public static final long MIN_FIRE_DELAY = 100;
private class HeldDownMouseListener implements MouseListener {
private class HeldDownCheckerTask extends TimerTask {
private HeldDownMouseListener listener;
public HeldDownCheckerTask(HeldDownMouseListener listener) {
this.listener = listener;
}
#Override
public void run() {
long delay = INITIAL_FIRE_DELAY;
while (listener.isMouseHeldDownOnButton()) {
listener.fireMouseHeldDown();
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (delay > MIN_FIRE_DELAY) {
final long decrease = Math.round(FIRE_DELAY_STEP_MULTIPLIER * Math.pow(INITIAL_FIRE_DELAY / delay, 2));
delay = Math.max(delay - decrease, MIN_FIRE_DELAY);
}
}
}
}
private boolean mouseIsHeldDown;
private boolean mouseIsOnButton;
private HoldButton button;
private Timer timer;
public HeldDownMouseListener(HoldButton button) {
this.button = button;
}
public boolean isMouseHeldDownOnButton() {
return mouseIsHeldDown && mouseIsOnButton;
}
private void cancelTimer() {
if (timer != null) {
timer.cancel();
timer = null;
}
}
private void fireMouseHeldDown() {
button.fireActionPerformed(new ActionEvent(button, ActionEvent.ACTION_PERFORMED, "heldDown"));
}
#Override
public void mouseClicked(MouseEvent arg0) {
cancelTimer();
}
#Override
public void mouseEntered(MouseEvent arg0) {
mouseIsOnButton = true;
}
#Override
public void mouseExited(MouseEvent arg0) {
mouseIsOnButton = false;
}
#Override
public void mousePressed(MouseEvent arg0) {
cancelTimer();
mouseIsHeldDown = true;
timer = new Timer();
timer.schedule(new HeldDownCheckerTask(this), CLICK_LAG);
}
#Override
public void mouseReleased(MouseEvent arg0) {
mouseIsHeldDown = false;
}
}
public HoldButton() {
addHeldDownMouseListener();
}
public HoldButton(Icon icon) {
super(icon);
addHeldDownMouseListener();
}
public HoldButton(String text) {
super(text);
addHeldDownMouseListener();
}
public HoldButton(Action a) {
super(a);
addHeldDownMouseListener();
}
private void addHeldDownMouseListener() {
addMouseListener(new HeldDownMouseListener(this));
}
}
When the mouse is pressed you could start a timer that invokes your action repeatedly at the interval you need until the button is let go. Then you can stop the timer. By submitting it to a timer, you can submit it to a new thread and not do the thread management yourself.
At least one good practice is to do the 'work', in this case you actionPerformed-loop in a runnable via the SwingUtilies.invokeLater(). That way it gets offloaded to the event threadpool/queue and you're not blocking the awt main thread so you're not blocking your GUI.