2017-03-16 105 views
0

我的目标是绘制矩形并使用Observer模式从左至右平滑地移动它。带观察者模式的Java Swing多线程管理

我有一个Model类,它是Observable放置矩形的坐标,Display类是Observer,每次模型中的坐标改变时执行重绘。

模型中的坐标变化是在SwingWorker中的while循环中进行的:在每次迭代中,我将x坐标增加1,然后休眠100 ms,然后通知观察者(显示器)哪个任务是执行重绘。正如你所看到的那样,在EDT上调用repaint()方法就像它被建议做的那样。

问题是移动大约一秒后不平滑,重绘频率改变,看起来矩形越来越少重新绘制。

这里是模型类:

import java.util.Observable; 
import java.awt.EventQueue; 
import javax.swing.SwingWorker; 

public class Model extends Observable{ 
    int xCoordinate; 

    Model(Display d){ 
     SwingWorker<Void,Void> sw = new SwingWorker<Void,Void>(){ 

      @Override 
      protected Void doInBackground() { 
       while(xCoordinate<600){ 
        xCoordinate ++; 

        try { 
         Thread.sleep(100); 
        } catch (InterruptedException ex) {} 
        setChanged(); 
        notifyObservers(xCoordinate); 
       } 
       return null; 
      } 
     }; 
     addObserver(d); 
     sw.execute(); 
    } 

    public static void main(String[] a){ 
     EventQueue.invokeLater(new Runnable(){ 
      @Override 
      public void run(){ 
       Display d = new Display(); 
       Model m = new Model(d); 
       d.model = m; 
      } 
     }); 
    } 
} 

这里是Display类:

import java.awt.Color; 
import java.awt.EventQueue; 
import java.awt.Graphics; 
import java.util.Observable; 
import java.util.Observer; 
import javax.swing.JFrame; 
import javax.swing.JPanel; 

public class Display extends JFrame implements Observer{ 
    Model model; 
    int xCoordinate; 

    Display(){ 
     getContentPane().add(new JPanel(){ 
      @Override 
      public void paintComponent(Graphics g){ 
       super.paintComponent(g); 
       g.setColor(Color.RED); 
       g.fillRect(xCoordinate, 1, 50, 50); 
      } 
     }); 
     setSize(600, 600); 
     setVisible(true); 
     setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
    } 

    @Override 
    /* arg is the updated xCoordinate*/ 
    public void update(Observable o, Object arg) { 
     xCoordinate = (Integer)arg; 
     EventQueue.invokeLater(new Runnable(){ 
      @Override 
      public void run() { 
       repaint(); 
      } 

     }); 
    } 
} 

我尝试过其他方法,例如在利用显示计时器,但没有奏效无论是。 SwingWorker可能在这里没有用,因为在SwingWorker线程上进行的计算很容易(增加1),但是我将需要它来执行我打算在我的项目(池游戏)上进行的繁重计算。

我也尝试通过查看两次重新绘制之间的时间(在Display中)和两次递增之间的时间(模型中)来调试,并且它的预期时间约为100 ms。

在此先感谢

+0

你可以改变的睡眠时间?尝试10毫秒,它会移动更顺利。 – ahoxha

+0

那么它仍然不流畅(至少在我的电脑上)。我试了10和500毫秒。 – Alsvartr

+0

我尝试了一个使用'javax.swing.Timer'的例子,但将延迟设置为100毫秒,它不能平稳移动。以下是示例:http://www.java2s.com/Tutorial/Java/0240__Swing/Timerbasedanimation.htm。 – ahoxha

回答

1

好了,所以作为初步测试,我开始与一个Swing Timer ...

import java.awt.Color; 
import java.awt.Dimension; 
import java.awt.EventQueue; 
import java.awt.Graphics; 
import java.awt.Graphics2D; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import java.util.Observable; 
import java.util.Observer; 
import javax.swing.JFrame; 
import javax.swing.JPanel; 
import javax.swing.Timer; 
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(); 
       } 

       Model model = new Model(); 

       JFrame frame = new JFrame("Testing"); 
       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
       frame.add(new TestPane(model)); 
       frame.pack(); 
       frame.setLocationRelativeTo(null); 
       frame.setVisible(true); 
       Timer timer = new Timer(40, new ActionListener() { 
                  @Override 
                  public void actionPerformed(ActionEvent e) { 
                   model.update(); 
                  } 
                 }); 
       timer.setInitialDelay(1000); 
       timer.start(); 
      } 

     }); 
    } 

    public class Model extends Observable { 

     private int xCoordinate; 

     public void update() { 
      xCoordinate++; 
      setChanged(); 
      notifyObservers(xCoordinate); 
     } 

     public int getXCoordinate() { 
      return xCoordinate; 
     } 

    } 

    public class TestPane extends JPanel implements Observer { 

     private Model model; 

     public TestPane(Model model) { 
      this.model = model; 
      model.addObserver(this); 
     } 

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

     @Override 
     protected void paintComponent(Graphics g) { 
      super.paintComponent(g); 
      Graphics2D g2d = (Graphics2D) g.create(); 
      g.setColor(Color.RED); 
      g.fillRect(model.getXCoordinate(), 1, 50, 50); 
      g2d.dispose(); 
     } 

     @Override 
     public void update(Observable o, Object arg) { 
      System.out.println(arg); 
      repaint(); 
     } 

    } 

} 

我发现的东西,你永远不会调用setChangedObservable,这我的测试过程中,这意味着它永远不会叫Observer小号

我也做了一个测试用SwingWorker ...

import java.awt.Color; 
import java.awt.Dimension; 
import java.awt.EventQueue; 
import java.awt.Graphics; 
import java.awt.Graphics2D; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import java.util.Observable; 
import java.util.Observer; 
import javax.swing.JFrame; 
import javax.swing.JPanel; 
import javax.swing.SwingWorker; 
import javax.swing.Timer; 
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(); 
       } 

       Model model = new Model(); 

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

       SwingWorker worker = new SwingWorker() { 
        @Override 
        protected Object doInBackground() throws Exception { 
         Thread.sleep(1000); 
         while (true) { 
          model.update(); 
         } 
        } 
       }; 
       worker.execute(); 
      } 

     }); 
    } 

    public class Model extends Observable { 

     private int xCoordinate; 

     public synchronized void update() { 
      xCoordinate++; 
      setChanged(); 
      notifyObservers(xCoordinate); 
     } 

     public synchronized int getXCoordinate() { 
      return xCoordinate; 
     } 

    } 

    public class TestPane extends JPanel implements Observer { 

     private Model model; 

     public TestPane(Model model) { 
      this.model = model; 
      model.addObserver(this); 
     } 

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

     @Override 
     protected void paintComponent(Graphics g) { 
      super.paintComponent(g); 
      Graphics2D g2d = (Graphics2D) g.create(); 
      g.setColor(Color.RED); 
      g.fillRect(model.getXCoordinate(), 1, 50, 50); 
      g2d.dispose(); 
     } 

     @Override 
     public void update(Observable o, Object arg) { 
      System.out.println(arg); 
      repaint(); 
     } 

    } 

} 

由于线程同步问题,我同步对方法的访问以确保值在更新之间不发生更改。因为您使用的是Observer,实际上可以将“新状态”传递给Observer,因此它们在使用模型时不依赖于该模型的价值。

好了,这样长期和短期的它,你需要调用setChangedObservable一旦它被更新,使notifyObservers实际上将调用Observer小号

正如有人无疑指出,这种方法患有在时间上的不准确性,由于其性质,Swing TimerThread.sleep仅保证“至少”时间并且可以在每次更新之间进行验证。

如果您有一个可变长度的操作,这也会影响更新之间的时间。相反,你应该计算你花了执行您的操作,减去要等待的时间量的时候,你会再使用这种延迟计算多久你想帧之间的“等待”。你也应该使用System.nanoTimeSystem.currentTimeMillis,因为它不会从同一系统时钟同步问题遭受

+0

好,谢谢您帮助,我想我明白你的意思,但是当我将Thread.sleep(100)放入SwingWorker的while(true)循环(在第二个示例中)时,我的计算机上的动画仍然不流畅。所以问题来自Thread.sleep()的不准确性?我该如何处理它?因为即使用你的答案来计算我想要“等待”多久,我也不得不使用Thread.sleep吗? – Alsvartr

+0

不,我想说这只是运行在10fps(1000/100)(和同步,这是慢)的事实,我会考虑减少延迟(40是25fps,16是60fps)和看看它有什么不同。取决于你想要做什么,基于时间的方法可能更合适。也就是说,给定一定的时间,将对象移动一定的距离,如果正确完成,该算法可以“放下”框架,这可以给出更好的移动幻觉 – MadProgrammer