Java 使对象在按住按钮时移动
如何让 JPanel 在按住按钮时移动并在释放按钮时停止.我尝试过使用带有 Runnable 的 thread.start() 和类似的方法.我总是遇到错误.有谁能帮帮我吗?
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?
推荐答案
您需要考虑许多重要的考虑因素.
There are a number of important considerations that you need to take into consideration.
- 按钮并非设计为以这种方式工作.它们旨在在单击(按下和释放)时触发和操作事件,因此您不能使用正常的操作 API.幸运的是,还有其他方法可以确定按钮的状态.此示例在
ButtonModel
上使用ChangeListener
,并根据模型的状态执行操作. - 组件通常受布局管理器的控制.这意味着为了能够移动组件,我们需要将其关闭(也称为
null
或absolute
布局).通常,我不鼓励这样做,但这是唯一可行的方法.然而.删除布局管理器后,您将负责确保组件正确定位和调整大小……这不能掉以轻心.更多关于您想要达到的目标的背景信息会产生更好的答案 - 当按钮被按下"时,我们需要一种方法来确定移动组件的方式.此示例使用一个简单的
enum
来确定移动组件的方向.您可以轻松地使用x/yDelta
并直接修改组件的x/y
位置.两者都应该可以正常工作. - Swing 是一个单线程环境.也就是说,对 UI 的所有交互和修改都应该在 Event Dispatching Thread 的上下文中执行.但是任何阻止 EDT 的操作都会阻止 UI 开始更新或任何新事件开始处理.这意味着,为了移动组件,我们不能简单地使用
while-loop
,因为它永远不会结束(不会处理新事件).相反,此示例使用javax.swing.Timer
,它在后台等待并在 EDT 上下文中的每个刻度上引发ActionEvent
.打勾出现,我们修改面板的位置
- 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();
}
}
}
}
}
您可能希望阅读 Concurrency in Swing更多细节...
You might like to have a read through Concurrency in Swing for more details...
使用击键代替按钮出人意料地是相同的方法.你有一个开始动作和一个结束动作,你只需要弄清楚如何应用这些状态.
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.
强烈建议您使用 键绑定通过 KeyListener
.主要原因是 KeyListener
存在焦点问题,键绑定 API 有能力克服或控制.
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.
基本前提是,您要在按键和按键释放时注册一个按键动作.这使用键绑定 API 相对容易实现.
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.
警告:此示例一次只允许一个方向.例如,如果您按下 Up 和 Down,则向下操作将胜出.这是因为我使用 enum
作为方向.您可以通过使用 xDelta
和 yDelta
值轻松更改此设置,这将允许您同时修改垂直和水平方向...但不能为你;)
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();
}
}
}
}
相关文章