为什么我的变量呈指数增长?

时间:2014-11-09 19:33:17

标签: java swing

每当我按下按钮时,我都会有一段移动矩形的代码。它设置为每次按下键时x或y坐标减少或增加1个像素。然而,我注意到如果我连续多次按下相同的键,1个像素以某种方式复合自身,堆栈可能是一个更好的词,并且呈指数增长。代码中有什么东西你可以看到为什么会这样做?

public class drawingComponent extends JComponent implements KeyListener
{

    Rectangle   hello   = new Rectangle(300, 100, 50, 50);

    public void paintComponent (Graphics g)
    {
        Graphics2D g2 = (Graphics2D)g;
        g2.setColor(new Color(255, 25, 0));
        g2.setFont(new Font("monospace", Font.BOLD + Font.ITALIC, 30));
        g2.drawString("nothing yet", 300, 320);
        g2.fill(hello);
        setFocusable(true);
        requestFocus();
        addKeyListener(this);

    }

    @Override
    public void keyPressed (KeyEvent e)
    {

        if (e.getKeyCode() == KeyEvent.VK_W)
        {
            hello.y = hello.y - 1;
            hello.setLocation(hello.x, hello.y);
            repaint();
            System.out.println(hello.y);
        }
        if (e.getKeyCode() == KeyEvent.VK_S)
        {
            hello.y = hello.y + 1;
            hello.setLocation(hello.x, hello.y);
            repaint();

        }
        if (e.getKeyCode() == KeyEvent.VK_A)
        {
            hello.x = hello.x - 1;
            hello.setLocation(hello.x, hello.y);
            repaint();

        }
        if (e.getKeyCode() == KeyEvent.VK_D)
        {
            hello.x = hello.x + 1;
            hello.setLocation(hello.x, hello.y);
            repaint();

        }

    }

    @Override
    public void keyReleased (KeyEvent e)
    {

    }

    @Override
    public void keyTyped (KeyEvent e)
    {
        // TODO Auto-generated method stub

    }
}

2 个答案:

答案 0 :(得分:13)

您在组件的每次重绘时添加KeyListener

public void paintComponent(Graphics g){
    //..
    addKeyListener(this); // adds another KeyListener
}

如果repaint();被调用,KeyListener的每次通话都会添加另一个keyPressed来完成他的工作。这意味着,您首先有one个侦听器,然后是two,然后是four,然后是sixteen,依此类推。

不要这样做。在构造函数中添加一次侦听器。

构造函数可以如下所示:

public drawingComponent() {
    // init other stuff
    addKeyListener(this);
}

答案 1 :(得分:4)

您正试图通过使用可能会或可能不会在其他系统或平台上运行的黑客和变通办法来克服API的固有问题。

不要在paint方法中更改组件的状态,绘画应该绘制当前状态,没有别的。通过在paint方法中请求焦点,您可能会触发另一个重绘请求,从而导致恶性循环,从而消耗CPU周期。

核心问题是(如汤姆所强调的),每次调用KeyListener时,您都会添加另一个paint。绘画会发生很多,通常是在你不知情或要求的情况下。

不应该解决这个问题,而应该使用旨在修复它的API,即密钥绑定API。此API将允许您定义触发键事件所需的焦点级别,以下示例模仿KeyListener的默认行为(因为我不知道您的其他要求),但有一个允许的选项要成为焦点的组件。

一些提示......

  • 在进行自定义绘画之前始终调用super.paintComponent,这很容易被遗忘,并可能导致一些严重的奇怪图形故障。
  • paintComponent不需要public,您永远不希望任何人打电话给它。
  • 您可能希望阅读Code Conventions for the Java TM Programming Language,这样可以让人们更轻松地阅读您的代码并让您阅读其他人

作为一个简单的例子......

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
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.InputMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new DrawingComponent());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class DrawingComponent extends JComponent {

        Rectangle hello = new Rectangle(300, 100, 50, 50);

        public DrawingComponent() {
            setFocusable(true);
            InputMap im = getInputMap();

            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0), "Move.up");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0), "Move.down");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0), "Move.left");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0), "Move.right");

            ActionMap am = getActionMap();
            am.put("Move.up", new DeltaAction(0, -1));
            am.put("Move.down", new DeltaAction(0, 1));
            am.put("Move.left", new DeltaAction(-1, 0));
            am.put("Move.right", new DeltaAction(1, 0));

            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    requestFocusInWindow();
                }
            });
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(400, 400);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D) g;
            g2.setColor(new Color(255, 25, 0));
            g2.setFont(new Font("monospace", Font.BOLD + Font.ITALIC, 30));
            g2.drawString("nothing yet", 300, 320);
            g2.fill(hello);
        }

        public class DeltaAction extends AbstractAction {

            private int xDelta;
            private int yDelta;

            public DeltaAction(int xDelta, int yDelta) {
                this.xDelta = xDelta;
                this.yDelta = yDelta;
            }

            @Override
            public void actionPerformed(ActionEvent e) {
                hello.x += xDelta;
                hello.y += yDelta;
                repaint();
            }

        }

    }

}

“但我为什么要使用密钥绑定API”我听到你问?除了为您提供更多的灵活性和可配置性(包括设置触发事件所需的焦点级别),您还可以更改更容易触发事件的键,或者拥有多组键 - 也可以考虑箭头键,它可以与Swing API的其他部分一起使用,包括按钮。

单个Action可以应用于JMenuItemJButton和密钥绑定,无需额外编码......

有关详细信息,请参阅How to Use Key Bindings