问题
How do I make a JPanel move while a button is held down and stop when the button is released. I have tried using thread.start() with a Runnable and ways like that. I always run in to errors. Can anyone help me though?
回答1:
There are a number of important considerations that you need to take into consideration.
- Buttons aren't designed to work this way. They are designed to trigger and action event when they are clicked (pressed and released), so you can't use the normal action API. Lucky for us, there are other ways to determine the state of buttons. This example uses a
ChangeListener
on theButtonModel
and takes actions based on the state of the model. - Components are normally under the control of layout managers. This means in order to be able to move the component, we need to turn this off (also known as
null
orabsolute
layouts). Normally, I would discourage this, but this is the only way this will work. However. Once you remove the layout manager, you become responsible for ensuring the component(s) are properly positioned and sized...this is not work to be taken lightly. More context of what you're trying to achieve would produce better answers - While the button is "pressed", we need a way to determine which way to move the component. This example uses a simple
enum
to determine which direction to move the component. You could just as easily use ax/yDelta
and modify the componentsx/y
position directly. Either should work fine. - Swing is a single thread environment. That is, all interactions and modifications to the UI are expected to be executed within the context of the Event Dispatching Thread. But any actions that block the EDT will prevent the UI from begin update or any new events from begin processed. This means, in order to move the component, we can't simply use a
while-loop
, because it will never end (no new events will be processed). Instead, this example uses ajavax.swing.Timer
, which waits in the background and raises anActionEvent
on each tick within the context of the EDT. The a tick occurs, we modify the location of the panel
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.ButtonModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class MovePane {
public static void main(String[] args) {
new MovePane();
}
public MovePane() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public enum Direction {
None, Up, Down, Left, Right;
}
public class TestPane extends JPanel {
private JPanel mobby;
private Timer moveTimer;
private Direction moveDirection = Direction.None;
public TestPane() {
mobby = new JPanel();
mobby.setBackground(Color.RED);
mobby.setSize(50, 50);;
setLayout(new BorderLayout());
JPanel pool = new JPanel(null);
pool.add(mobby);
add(pool);
JPanel buttons = new JPanel(new GridBagLayout());
JButton up = new JButton("Up");
JButton dwn = new JButton("Down");
JButton lft = new JButton("Left");
JButton rgt = new JButton("Right");
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridx = 1;
gbc.gridy = 0;
buttons.add(up, gbc);
gbc.gridx = 1;
gbc.gridy = 2;
buttons.add(dwn, gbc);
gbc.gridx = 0;
gbc.gridy = 1;
buttons.add(lft, gbc);
gbc.gridx = 2;
gbc.gridy = 1;
buttons.add(rgt, gbc);
add(buttons, BorderLayout.SOUTH);
up.getModel().addChangeListener(new ChangeHandler(Direction.Up));
dwn.getModel().addChangeListener(new ChangeHandler(Direction.Down));
lft.getModel().addChangeListener(new ChangeHandler(Direction.Left));
rgt.getModel().addChangeListener(new ChangeHandler(Direction.Right));
moveTimer = new Timer(40, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Container parent = mobby.getParent();
Rectangle bounds = mobby.getBounds();
switch (moveDirection) {
case Up:
bounds.y--;
break;
case Down:
bounds.y++;
break;
case Left:
bounds.x--;
break;
case Right:
bounds.x++;
break;
}
if (bounds.x < 0) {
bounds.x = 0;
} else if (bounds.x + bounds.width > parent.getWidth()) {
bounds.x = parent.getWidth() - bounds.width;
}
if (bounds.y < 0) {
bounds.y = 0;
} else if (bounds.y + bounds.height > parent.getHeight()) {
bounds.y = parent.getHeight() - bounds.height;
}
mobby.setBounds(bounds);
}
});
moveTimer.setInitialDelay(0);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
public class ChangeHandler implements ChangeListener {
private Direction direction;
public ChangeHandler(Direction direction) {
this.direction = direction;
}
@Override
public void stateChanged(ChangeEvent e) {
ButtonModel b = (ButtonModel) e.getSource();
if (b.isPressed()) {
moveDirection = direction;
moveTimer.start();
} else {
moveTimer.stop();
}
}
}
}
}
You might like to have a read through Concurrency in Swing for more details...
Updated based on input from the OP
Use key strokes instead of buttons is surprisingly the same approach. You have a start action and a end action, you just need to figure out how to apply those states.
It is highly recommended that you use Key Bindings over KeyListener
. The main reason is KeyListener
suffers from focus issues, which the key bindings API has the ability to over come or control.
The basic premise is, you want to register a key action on key press and key release. This is relatively easy to accomplish with the key bindings API.
Caveat: This example will only allow for a single direction at a time. If you press, for example, Up and Down, the down action will win out. This is because I'm using a enum
for the direction. You can easily change this by using a xDelta
and yDelta
value instead, which would allow you to modify the vertical and horizontal directions simultaneously...but can't do everything for you ;)
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.ButtonModel;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class MovePane {
public static void main(String[] args) {
new MovePane();
}
public MovePane() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public enum Direction {
None, Up, Down, Left, Right;
}
public class TestPane extends JPanel {
private JPanel mobby;
private Timer moveTimer;
private Direction moveDirection = Direction.None;
public TestPane() {
mobby = new JPanel();
mobby.setBackground(Color.RED);
mobby.setSize(50, 50);;
setLayout(new BorderLayout());
JPanel pool = new JPanel(null);
pool.add(mobby);
add(pool);
moveTimer = new Timer(40, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Container parent = mobby.getParent();
Rectangle bounds = mobby.getBounds();
switch (moveDirection) {
case Up:
bounds.y--;
break;
case Down:
bounds.y++;
break;
case Left:
bounds.x--;
break;
case Right:
bounds.x++;
break;
}
if (bounds.x < 0) {
bounds.x = 0;
} else if (bounds.x + bounds.width > parent.getWidth()) {
bounds.x = parent.getWidth() - bounds.width;
}
if (bounds.y < 0) {
bounds.y = 0;
} else if (bounds.y + bounds.height > parent.getHeight()) {
bounds.y = parent.getHeight() - bounds.height;
}
mobby.setBounds(bounds);
}
});
moveTimer.setInitialDelay(0);
InputMap im = pool.getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = pool.getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "UpPressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "UpReleased");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "DownPressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "DownReleased");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "LeftPressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), "LeftReleased");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "RightPressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "RightReleased");
KeyUpAction keyUpAction = new KeyUpAction();
am.put("UpReleased", keyUpAction);
am.put("DownReleased", keyUpAction);
am.put("LeftReleased", keyUpAction);
am.put("RightReleased", keyUpAction);
am.put("UpPressed", new MoveAction(Direction.Up));
am.put("DownPressed", new MoveAction(Direction.Down));
am.put("LeftPressed", new MoveAction(Direction.Left));
am.put("RightPressed", new MoveAction(Direction.Right));
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
public class KeyUpAction extends AbstractAction {
@Override
public void actionPerformed(ActionEvent e) {
moveTimer.stop();
moveDirection = Direction.None;
}
}
public class MoveAction extends AbstractAction {
private Direction direction;
public MoveAction(Direction direction) {
this.direction = direction;
}
@Override
public void actionPerformed(ActionEvent e) {
moveDirection = direction;
moveTimer.start();
}
}
}
}
回答2:
Use SwingTimer for moving the panel and add mouseListener
to the panel and override mousePressed
and mouseReleased
methods.
UPDATE:
From Comments:
The OP said I want to move the panel with
Keyboard buttons
.
You didn't mentioned Keyboard in the question, you just said buttons, anyway, Take this tutorial on How to use Key Bindings, it'll help you for now and future.
Come back if you had a question, I will post an example for moving the panel, but right now, I am not posting it because I am sure you'll not read the tutorial, you'll just copy my example and leave reading the tuts.
回答3:
Something like this might do what you want to do:
Have a class which contains
private JPanel movingJPanel = new JPanel(); // Declare this however
public void paint(Graphics g) {
//draw background first
Point drawAt;
syncronised (sync) {
drawAt = this.drawAt
}
Dimension size = movingJPanel.getPreferredSize();
Graphics paintWith = g.create(movingJPanel);
movingJPanel.paint(paintWith);
}
private Point moveFrom = new Point(0, 0);
private Point moveTo = new Point(100, 100);
private Point drawAt = new Point(0, 0);
private int steps = 35;
private int step = 0;
private long timeBetweenSteps = 50L;
private Object sync = new Object();
private boolean moving = false;
private Thread thread = new Thread(new Runnable() {
public void run() {
while (!Thread.interrupted()) {
synchronized(sync) {
if (moving && step < steps) {
step++;
drawAt = new Point((moveTo.x - moveFrom.x) * step / steps,
(moveTo.y - moveFrom.y) * step / steps)
drawMovingPanelIn.repaint();
sync.wait(timeBetweenSteps);
}
}
}
}
});
public void start() {
synchronized(sync) {
moving = true;
sync.notify();
}
}
public void start() {
synchronized(sync) {
moving = false;
}
}
public void reset() {
syncronized(sync) {
steps = 0;
}
}
and a constructor which contains
thread.start();
Now call the start()
and stop()
methods from the mousePressed and mouseRelaesed methods of a MouseListener that you add to a button.
来源:https://stackoverflow.com/questions/18219829/java-making-objects-move-while-buttons-held