【问题标题】:Java last added button allows agent.move, rest dontJava最后添加的按钮允许agent.move,其余的不允许
【发布时间】:2013-09-23 21:58:34
【问题描述】:

我是 Java 新手,一直致力于创建代码以获取使用钥匙移动的汽车的小图片。我的问题是当我向面板添加超过 1 个按钮时。我发布的代码中按钮的功能没什么,只是打印“button [i] clicked”消息。目的是让他们读取文件并根据文件中的数据更新汽车的位置。这应该是我的强化学习项目中的一部分。我认为这将是一个学习 java 的好机会,因为这个“图形包”对于项目来说不是必需的,只是一个“不错的”补充。代码在这里:

package graphics;

public class Board extends JPanel implements ActionListener {

private Timer timer;
private Agent agent;

private String button = "button.png";
private Image image;


protected JButton b1;
protected JButton b2;
protected JButton b3;

public Board() {

    //Keylistener added for the agent to respond to arrow keys

 addKeyListener(new TAdapter());
     agent = new Agent();
    timer = new Timer(10, this); //10ms timer calls action performed
    timer.start();     

            //This part for the button.
    ImageIcon i = new ImageIcon(this.getClass().getResource(button));
    image = i.getImage();

    b1 = new JButton("1", i);
    b1.setVerticalTextPosition(AbstractButton.CENTER);
    b1.setHorizontalTextPosition(AbstractButton.LEADING); 
    b1.setActionCommand("Active1");



    b2 = new JButton("2", i);
    b2.setVerticalTextPosition(AbstractButton.CENTER);
    b2.setHorizontalTextPosition(AbstractButton.LEADING); 
    b2.setActionCommand("Active2");



    b3 = new JButton("3", i);
    b3.setVerticalTextPosition(AbstractButton.CENTER);
    b3.setHorizontalTextPosition(AbstractButton.LEADING);
    b3.setActionCommand("Active3");



    b1.addActionListener(this); 
    b2.addActionListener(this);
    b3.addActionListener(this);

     add(b1);          add(b2);  add(b3);          

    setFocusable(true);
    setBackground(Color.BLACK);
    setDoubleBuffered(true);


  }


public void paint(Graphics g) {
    super.paint(g);

    Graphics2D g2d = (Graphics2D)g;

    //Transformations for the agent to be painted based upon its position and orientation

    AffineTransform trans = new AffineTransform();

    trans.rotate(Math.toRadians(agent.getTh()), agent.getX()+64, agent.getY()+64);  
    trans.translate(agent.getX(), agent.getY());

    g2d.drawImage(agent.getImage(), trans, this); // Draws agent with said transformations

    Toolkit.getDefaultToolkit().sync();
    g.dispose();
}


public void actionPerformed(ActionEvent ae) {

    b1.setEnabled(true);
    b2.setEnabled(true);
    b3.setEnabled(true);

    if (ae.getActionCommand()=="Active1") {
        b1.setEnabled(false);
        b2.setEnabled(true);
        b3.setEnabled(true);
       System.out.println("Clicked 1");
       agent.reset();
    } 



    if(ae.getActionCommand()=="Active2") {
        b1.setEnabled(true);
        b2.setEnabled(false);
        b3.setEnabled(true);
        System.out.println("Clicked 2");
        agent.reset();
    }

    if (ae.getActionCommand()=="Active3") {
        b1.setEnabled(true);
        b2.setEnabled(true);
        b3.setEnabled(false);
        System.out.println("Clicked 3");
        agent.reset();


    }

    agent.move();
    repaint();

}

private class TAdapter extends KeyAdapter {

    public void keyReleased(KeyEvent e) {
        agent.keyReleased(e);
    }

    public void keyPressed(KeyEvent e) {
        agent.keyPressed(e);
    }
}

}

现在,问题是这样的。如果我单击按钮 1 或按钮 2,其他按钮将被禁用,并显示“单击 1(或 2)”,这很好。但是 agent.move() 和 repaint() 没有被调用。当我按下按键时,汽车不动。如果我然后单击按钮 3,则其他两个按钮将被禁用,并且汽车会随着钥匙移动。

如果我以不同的顺序添加按钮 add(b3);添加(b2);添加(b1);然后会发生同样的情况,但这次它的按钮 1 可以正常工作。

【问题讨论】:

    标签: java swing animation keylistener key-bindings


    【解决方案1】:

    问题:

    • 您的主要问题是焦点之一——当 JButton 获得焦点而 JPanel 失去焦点时,JPanel 的 KeyListener 将不起作用,因为 KeyListener 要求被监听的组件具有焦点,也不例外。
    • 一个糟糕的解决方案是强制 JPanel 始终保持焦点。如果您的窗口有 JButtons,这将很糟糕,如果您需要显示 JTextFields、JTextAreas 或其他文本组件,这将是一场灾难。
    • 更好的解决方案是不使用 KeyListener,因为它在 Swing 应用程序中存在很多问题,尤其是上面提到的焦点问题。请改用键绑定。谷歌教程了解这方面的详细信息。
    • 不要使用== 来比较字符串,而是使用equals(...)equalsIgnoreCase(...)。问题是== 检查对象是否相等,字符串 A 是否与字符串 B 是 相同的对象,而您并不关心这一点。您想知道这两个字符串是否以相同的顺序保存相同的字符,这就是这两种方法的用武之地。
    • 不要dispose() JVM 为您提供的 Graphics 对象,因为这可能会弄乱组件边框、子项的绘制,甚至会产生其他副作用。
    • 不要在 JPanel 的 paint(...) 方法中绘制,而是在其 paintComponent(...) 方法中绘制,就像教程告诉您的那样。如果您不小心,在paint(...) 中绘图可能会对组件的边框和子级产生副作用,并且也没有默认双缓冲的好处,这对于平滑动画很重要。 paintComponent(...) 解决了所有这些问题。
    • 说到这一点,您应该谷歌并阅读 Swing 图形教程。您无法编造东西并希望它能起作用,而图形编程将需要一种与您习惯完全不同的方法。
    • 忽略 Andromeda 的线程建议。虽然他的意思很好,但我建议您不要在后台线程中进行绘画。只需在您进行操作时使用您的 Swing Timer 移动汽车。后台线程有其用途,但不是在这里,因为 Timer 可以正常工作。当您有一个长时间运行的进程阻塞调用线程时,您将需要使用后台线程,这是您当前代码中没有的,因此不需要线程“修复”,实际上是一个潜在的如果您在 Swing 事件线程上进行 Swing 调用时不格外小心,则会出现问题。不过,我们的未知数是您的“代理人”在做什么。如果它正在调用长时间运行的代码或其中包含Thread.sleep(...)wait()/notify() 的代码,那么是的,您将需要使用后台线程。
    • 但同样,我们知道这不是您的主要问题,因为您的问题是在添加焦点抓取器(JButtons)之后才开始的。这再次有力地表明您的主要问题不是线程,而是 KeyListener 的使用及其焦点要求。

    例如:

    import java.awt.BasicStroke;
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.RenderingHints;
    import java.awt.Stroke;
    import java.awt.event.*;
    import java.util.EnumMap;
    import javax.swing.*;
    
    @SuppressWarnings("serial")
    public class KeyBindingPanel extends JPanel {
       private static final int PREF_W = 800;
       private static final int PREF_H = PREF_W;
       private static final Stroke THICK_STROKE = new BasicStroke(5f,
             BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
       private static final int OVAL_WIDTH = 30;
       private static final int OVAL_HEIGHT = 30;
       private static final Color OVAL_COLOR = Color.red;
       private static final Color BKGRD_COLOR = Color.black;
       private static final int TIMER_DELAY = 20;
       public static final int STEP = 2;
       private int myX = 0;
       private int myY = 0;
       private JButton[] buttons = new JButton[3];
       private int condition = WHEN_IN_FOCUSED_WINDOW;
       private InputMap inputMap = getInputMap(condition);
       private ActionMap actionMap = getActionMap();
       private EnumMap<Direction, Boolean> directionMap = new EnumMap<Direction, Boolean>(
             Direction.class);
    
       public KeyBindingPanel() {
          for (int i = 0; i < buttons.length; i++) {
             buttons[i] = new JButton(new ButtonAction());
             add(buttons[i]);
          }
          setBackground(BKGRD_COLOR);
    
          for (final Direction direction : Direction.values()) {
             directionMap.put(direction, Boolean.FALSE);
    
             Boolean[] onKeyReleases = { Boolean.TRUE, Boolean.FALSE };
             for (Boolean onKeyRelease : onKeyReleases) {
                KeyStroke keyStroke = KeyStroke.getKeyStroke(
                      direction.getKeyCode(), 0, onKeyRelease);
                inputMap.put(keyStroke, keyStroke.toString());
                actionMap.put(keyStroke.toString(), new DirAction(direction,
                      onKeyRelease));
             }
          }
    
          new Timer(TIMER_DELAY, new GameTimerListener()).start();
       }
    
       @Override
       public Dimension getPreferredSize() {
          return new Dimension(PREF_W, PREF_H);
       }
    
       @Override
       protected void paintComponent(Graphics g) {
          super.paintComponent(g);
    
          Graphics2D g2b = (Graphics2D) g.create();
          g2b.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
          g2b.setStroke(THICK_STROKE);
          g2b.setColor(OVAL_COLOR);
          g2b.drawOval(myX, myY, OVAL_WIDTH, OVAL_HEIGHT);
    
          g2b.dispose(); // since I created this guy
       }
    
       private class GameTimerListener implements ActionListener {
          @Override
          public void actionPerformed(ActionEvent e) {
             for (Direction direction : Direction.values()) {
                if (directionMap.get(direction)) {
                   myX += STEP * direction.getRight();
                   myY += STEP * direction.getDown();
                }
             }
             repaint();
          }
       }
    
       private class DirAction extends AbstractAction {
          private Direction direction;
          private boolean onRelease;
    
          public DirAction(Direction direction, boolean onRelease) {
             this.direction = direction;
             this.onRelease = onRelease;
          }
    
          @Override
          public void actionPerformed(ActionEvent evt) {
             directionMap.put(direction, !onRelease); // it's the opposite!
          }
       }
    
       private class ButtonAction extends AbstractAction {
          public ButtonAction() {
             super("Press Me!");
          }
    
          @Override
          public void actionPerformed(ActionEvent e) {
             JButton thisBtn = (JButton) e.getSource();
             for (JButton btn : buttons) {
                if (btn == thisBtn) {
                   btn.setEnabled(false);
                } else {
                   btn.setEnabled(true);
                }
             }
          }
       }
    
       private static void createAndShowGui() {
          JFrame frame = new JFrame("KeyBindingPanel");
          frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          frame.getContentPane().add(new KeyBindingPanel());
          frame.pack();
          frame.setLocationRelativeTo(null);
          frame.setVisible(true);
       }
    
       public static void main(String[] args) {
          SwingUtilities.invokeLater(new Runnable() {
             public void run() {
                createAndShowGui();
             }
          });
       }
    }
    
    enum Direction {
       UP(KeyEvent.VK_UP, -1, 0), DOWN(KeyEvent.VK_DOWN, 1, 0), LEFT(
             KeyEvent.VK_LEFT, 0, -1), RIGHT(KeyEvent.VK_RIGHT, 0, 1);
    
       private int keyCode;
       private int down;
       private int right;
    
       private Direction(int keyCode, int down, int right) {
          this.keyCode = keyCode;
          this.down = down;
          this.right = right;
       }
    
       public int getKeyCode() {
          return keyCode;
       }
    
       public int getDown() {
          return down;
       }
    
       public int getRight() {
          return right;
       }
    
    }
    

    【讨论】:

      【解决方案2】:

      看来您需要使用线程。您可以将您的课程设为implements Runnable,然后覆盖public void run(){} 方法并在那里进行绘画。

      【讨论】:

      • 在这种情况下,您遇到了线程问题。似乎您的代码在所有代码完成之前都处于阻塞状态。所以,在另一个线程中重新绘制。
      • @NaboditPaudyal:虽然 Andromeda 的意思很好,但在您当前的情况下使用后台线程的建议是错误的,并且会给您的程序增加更多不必要的复杂性和可能的​​错误。继续使用摇摆计时器即可。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-03-14
      • 1970-01-01
      • 2020-10-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多