2017-03-16 83 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