2012-09-30 42 views
2

我试图制作一个运行动画的应用程序。要做到这一点,我有一个Jframe,它包含我的动画运行的Jpanel的子类。这里是我的两个类:paintComponent()和actionPerformed()对JPanel iplements不同步ActionListener

首先,这里是我的驱动程序类:

import javax.swing.*; 

public class Life { 
    public static void main(String[] args){ 
     JFrame game = new JFrame("Life"); 

     game.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     game.setSize(500, 500); 

     MainPanel mainPanel = new MainPanel(); 
     game.setContentPane(mainPanel); 


     game.setVisible(true); 

    } 
} 

其次,这里是我的JPanel的子类:

import javax.swing.*; 
import java.awt.*; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 

public class MainPanel extends JPanel implements ActionListener{ 
    int i = 0; 
    int j = 0; 
    public MainPanel(){ 
     super(); 
    } 

    public void paintComponent(Graphics g){ 
     j++; 
     g.drawLine(10,10, 20 + i, 20 + i); 
     Timer t = new Timer(1000, this); 
     t.start(); 
    } 

    @Override 
    public void actionPerformed(ActionEvent actionEvent) { 
     i++; 
     repaint(); 
    } 
} 

请注意,我每次递增变量actionPreformed是调用,每次调用paintComponent时调用变量j。发生的事情是,我开始比j大得多,并且paintComponent绘制的线条似乎以更快更快的速度增长。

这里是我的问题:

  • 为什么会出现这种情况?
  • 我该如何同步事物,以便每1000毫秒重新绘制一行?
  • 鉴于我想要做的是,我的方法是否错误?我应该以不同的方式做事吗?

在此先感谢。

回答

4

不要从paintComponent方法中启动摆动计时器。

这种方法应该做你的绘画,只有你的绘画,而不是绘画。它应该绝对包含程序逻辑。理解你对这种方法的控制非常有限,因为你无法预测何时或是否会被调用,调用的频率。你甚至不能自己打电话,或保证当你建议它被称为通过repaint(),它实际上会被称为。

此外,此方法必须快速,尽可能快,因为任何会减慢速度的方法,无论是创建对象还是读入文件都会降低GUI的响应速度,这是您希望看到的最后一件事。

解决方案是将程序逻辑从该方法中分离出来,并转换为更好的方法,如构造函数。重复的代码应该在Swing Timer中。

编辑:
幽州:

我只是做了该测试的事情了。还有一个问题:如果paintComponent或paintComponent中的工作所依赖的某个线程花费的时间超过1000毫秒(或任何它)来执行其工作,会发生什么情况?我能想到的唯一情况是让paintComponent画出动画的进度,而不是等待动画到达下一步(如果这有意义的话)。思考?

你应该永远具有的paintComponent码取几毫秒长,甚至10的。如果存在类似情况的风险,那么在后台线程和BufferedImage中执行绘图,然后在Swing事件线程中使用Graphics#drawImage(...)方法在paintComponent方法中显示BufferedImage。

+0

哦哇。我甚至没有注意到我已经做到了。哇。 – David

+0

在构造函数中启动计时器似乎效果更好。这听起来像是正确的做法吗? – David

+0

@大卫:是的,绝对! –

3

一些小的补充@ HFOE的必不可少的见解:

  • 公共start()方法是确保您的看法开始前完全构建的便捷方式。
  • 字段有明确的default values,他们should be private
  • Swing GUI对象应该在event dispatch thread上构建和操作只有
  • 覆盖getPreferredSize()pack()封闭Window

修改后的代码:

import java.awt.Dimension; 
import java.awt.EventQueue; 
import java.awt.Graphics; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import javax.swing.*; 

public class Life { 

    public static void main(String[] args) { 
     EventQueue.invokeLater(new Runnable() { 

      private final JTabbedPane jtp = new JTabbedPane(); 

      @Override 
      public void run() { 
       JFrame game = new JFrame("Life"); 
       game.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
       MainPanel mainPanel = new MainPanel(); 
       game.setContentPane(mainPanel); 
       game.pack(); 
       game.setVisible(true); 
       mainPanel.start(); 
      } 
     }); 
    } 

    private static class MainPanel extends JPanel implements ActionListener { 

     private Timer t = new Timer(100, this); 
     private int i; 
     private int j; 

     @Override 
     public void paintComponent(Graphics g) { 
      g.drawLine(10, 10, 20 + i, 20 + i); 
     } 

     public void start() { 
      t.start(); 
     } 

     @Override 
     public void actionPerformed(ActionEvent actionEvent) { 
      i++; 
      repaint(); 
     } 

     @Override 
     public Dimension getPreferredSize() { 
      return new Dimension(500, 500); 
     } 
    } 
} 
+0

1+一如既往,伟大的建议! –

1

一个Timer由默认值继续运行。只有当你致电setRepeats(false)它会停止。

所以下面几行

Timer t = new Timer(1000, this); 
t.start(); 

paintComponent方法意味着一些重画后,你将有许多Timer实例运行,解释为什么i增加更快了,然后j

解决方案当然是将Timer移动到paintComponent方法之外,并坚持一个Timer实例。

而且讲话(这不表示,受别人,不会再重复他们非常有用的提醒):

  • 不调用super方法
  • 你不应该让从不覆盖paintComponent方法ActionListener接口。只需使用ActionListener内部
+0

感谢您的回复。 两个问题: 1)为什么我不应该暴露ActionListner接口? 2)如何在内部使用ActionListener? – David

+0

@David 1)这是一个实现细节。你的类不是用作'ActionListener',所以不需要公开它2)例如通过使用匿名类或内部类 – Robin

相关问题